Skip to content

Commit 79e30c3

Browse files
committed
Release Codex package v0.6.0
1 parent 2a03159 commit 79e30c3

17 files changed

Lines changed: 805 additions & 78 deletions

.codex/README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ Current package version:
1212
explicit publish operations.
1313

1414
Current status:
15-
- The package version is `v0.5.0`.
15+
- The package version is `v0.6.0`.
16+
- `v0.6.0` adds verified CLI/desktop custom-role activation guidance, raw
17+
parent/child/hook evidence validation, V2 no-fork enforcement, and
18+
remote-aware fail-closed release checks.
1619
- `v0.5.0` adds strict `.env*` protection, fail-closed manifest ownership and
1720
installer transactions, backward-compatible uninstall parsing, Codex-native
1821
skill QA, and documented path-rule enforcement limitations.
@@ -27,9 +30,10 @@ Current status:
2730
state, and default patch-aware installs.
2831
- `v0.3.0` documents the latest root workflow alignment and the hook parser fix
2932
for current Codex `apply_patch` JSON payloads and legacy raw patch payloads.
30-
- Release validation compares against `codex-vX.Y.Z` tags, plus the legacy
31-
`v0.1.0` initial Codex-port baseline, not upstream Claude release tags
32-
inherited through the pinned source history.
33+
- Release validation compares against origin's `codex-vX.Y.Z` tags through a
34+
read-only remote query, plus the legacy `v0.1.0` initial Codex-port baseline,
35+
not stale local refs or upstream Claude release tags inherited through the
36+
pinned source history.
3337

3438
Coexistence rules:
3539
- Do not write to `.claude/`.
@@ -57,6 +61,16 @@ Install and release notes:
5761
contents when state is missing or stale.
5862
- Installer success is static package verification, not proof that project
5963
trust, hooks, rules, or config are active in the current Codex session.
64+
- Standalone custom-agent files are validated as supported Codex profiles, but
65+
an exact role name, task path, nickname, or child self-identification is not
66+
accepted as activation proof. Codex `0.144.x` Sol parents select MultiAgent
67+
V2 and may hide the custom-role selector; use the verified `gpt-5.5` V1
68+
fallback or a separately verified user-level V2 workaround. V2 custom roles
69+
require `fork_turns: "none"`, and results remain blocked unless role metadata,
70+
instructions, model, and reasoning effort all match the selected profile.
71+
- `audit.sh smoke-interactive` reports `skipped`; trusted model-running evidence
72+
must be recorded separately or supplied with `--evidence`; task-path-only or
73+
default-role evidence fails closed.
6074
- `audit.sh release` validates `.codex/VERSION`, `CHANGELOG.md`, release tags,
6175
and changed installable files without mutating the checkout.
6276
- Package publishing is `bump -> edit changelog/docs -> check -> commit/push ->

.codex/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.5.0
1+
0.6.0

.codex/audit.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ case "$cmd" in
1515
all)
1616
run validate_manifest.py "$@"
1717
run validate_runtime.py "$@"
18+
run validate_runtime.py --kind skills "$@"
19+
run validate_runtime.py --kind agents "$@"
1820
run validate_hooks.py "$@"
1921
run validate_rules.py "$@"
2022
run validate_install.py "$@"

.codex/docs/VALIDATION.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,29 @@ Optional interactive smoke:
1414

1515
```bash
1616
./.codex/audit.sh smoke-interactive --root "$PWD"
17+
./.codex/audit.sh smoke-interactive --root "$PWD" \
18+
--evidence /path/to/role-activation-evidence.json
1719
```
1820

19-
Interactive smoke can depend on project trust, auth, approvals, and model access, so it is not required for the default acceptance gate.
21+
Without evidence, this command reports `status: skipped`; it does not claim
22+
that a model-running smoke occurred. With evidence, it parses the raw parent
23+
rollout, child rollout, and SubagentStart JSONL and cross-checks role metadata,
24+
instructions, model, effort, child identity, and V2 fork mode against the
25+
authoritative role TOML. Interactive smoke depends on project trust, auth,
26+
approvals, and model access, so it is recorded separately from the default
27+
acceptance gate. A trusted run should verify project skill discovery, strict config,
28+
ordinary-file controls, `.env*` read/write denial, SessionStart/PreToolUse/
29+
PostToolUse/SubagentStart/SubagentStop hooks, configured model routes, footer
30+
items, and authoritative custom-agent type/instruction evidence. MultiAgent V2
31+
custom-role evidence requires `fork_turns: "none"`.
2032

2133
Validator policy:
2234

2335
- Python validators use only the standard library.
2436
- Release validation is non-mutating. It verifies `.codex/VERSION`,
25-
`CHANGELOG.md`, `codex-vX.Y.Z` package tags, and changed installable files,
26-
but it does not create tags, edit files, publish, or bump versions. The
37+
`CHANGELOG.md`, origin's `codex-vX.Y.Z` package tags, and changed installable
38+
files through read-only `git ls-remote`; unavailable remote tag truth fails
39+
closed. It does not create tags, edit files, publish, or bump versions. The
2740
legacy `v0.1.0` tag is accepted only as the initial Codex-port baseline;
2841
inherited upstream Claude tags such as plain `v0.2.0`, `v0.3.0`, and
2942
`v1.0.0` are ignored.

.codex/docs/director-gates.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,21 @@ with internal simulated verdicts. The skill invocation is already authorization
102102
for declared gates that survived review-mode filtering; do not ask a second
103103
confirmation before spawning them.
104104

105+
Before spawning, confirm that the model-visible delegation tool can select the
106+
exact custom role. For MultiAgent V2, set `fork_turns: "none"`; full-history
107+
forks currently reject custom role, model, or reasoning overrides. Treat the
108+
requested task name, agent path, nickname, and child self-identification as
109+
labels rather than activation evidence. Accept the gate result only when
110+
authoritative metadata identifies the requested role, the matching role
111+
instructions loaded, and the configured model and reasoning effort were
112+
applied. Otherwise mark the gate blocked and do not simulate its verdict.
113+
105114
```
106115
# Apply mode check, then:
107116
Spawn `[agent-name]` via Codex subagent delegation:
108117
- Gate: [GATE-ID] (see .codex/docs/director-gates.md)
118+
- Custom role: [agent-name]
119+
- Fork mode: `none` when using MultiAgent V2
109120
- Context: [fields listed under that gate]
110121
- Await the verdict before proceeding.
111122
```

.codex/lib/validate_install.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
".codex/tests/fixtures/claude-existing/.claude/settings.json",
1818
".codex/tests/fixtures/claude-existing/CLAUDE.md",
1919
}
20-
EXPECTED_INSTALLED_FILE_COUNT = 509
20+
EXPECTED_INSTALLED_FILE_COUNT = 510
2121

2222

2323
def run_command(command: list[str], cwd: Path, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:

.codex/lib/validate_release.py

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -110,55 +110,51 @@ def validate_readme_versions(root: Path, version: str) -> list[str]:
110110
return errors
111111

112112

113-
def tag_commit(root: Path, tag: str) -> str | None:
114-
result = run_git(root, "rev-list", "-n", "1", tag)
113+
def origin_release_tags(root: Path) -> list[tuple[tuple[int, int, int], str, str]]:
114+
result = run_git(root, "ls-remote", "--tags", "origin")
115115
if result.returncode != 0:
116-
return None
117-
return result.stdout.strip() or None
116+
detail = result.stderr.strip() or "unknown git ls-remote failure"
117+
raise ValidationError(f"could not query origin release tags: {detail}")
118118

119-
120-
def is_ancestor(root: Path, ancestor: str, descendant: str) -> bool:
121-
result = run_git(root, "merge-base", "--is-ancestor", ancestor, descendant)
122-
return result.returncode == 0
123-
124-
125-
def codex_release_tags(root: Path) -> list[tuple[tuple[int, int, int], str]]:
126-
result = run_git(root, "tag", "--list", f"{CODEX_TAG_PREFIX}[0-9]*.[0-9]*.[0-9]*")
127-
tags: list[tuple[tuple[int, int, int], str]] = []
128-
baseline_commit = tag_commit(root, LEGACY_BASELINE_TAG)
129-
if baseline_commit:
130-
tags.append(((0, 1, 0), LEGACY_BASELINE_TAG))
131-
if result.returncode != 0:
132-
return tags
133-
for tag in result.stdout.splitlines():
134-
raw = tag.removeprefix(CODEX_TAG_PREFIX)
135-
try:
136-
parsed = parse_semver(raw)
137-
except ValidationError:
119+
discovered: dict[str, tuple[tuple[int, int, int], str]] = {}
120+
for line in result.stdout.splitlines():
121+
parts = line.split()
122+
if len(parts) != 2 or not parts[1].startswith("refs/tags/"):
138123
continue
139-
if baseline_commit and parsed == (0, 1, 0):
140-
commit = tag_commit(root, tag)
141-
if commit and commit != baseline_commit:
142-
continue
143-
elif baseline_commit:
144-
commit = tag_commit(root, tag)
145-
if commit and not is_ancestor(root, baseline_commit, commit):
124+
commit, ref = parts
125+
tag = ref.removeprefix("refs/tags/")
126+
peeled = tag.endswith("^{}")
127+
if peeled:
128+
tag = tag[:-3]
129+
if tag == LEGACY_BASELINE_TAG:
130+
parsed = (0, 1, 0)
131+
elif tag.startswith(CODEX_TAG_PREFIX):
132+
try:
133+
parsed = parse_semver(tag.removeprefix(CODEX_TAG_PREFIX))
134+
except ValidationError:
146135
continue
147-
if any(existing == parsed for existing, _ in tags):
136+
else:
148137
continue
149-
tags.append((parsed, tag))
150-
return sorted(tags)
138+
existing = discovered.get(tag)
139+
if existing is None or peeled:
140+
discovered[tag] = (parsed, commit)
141+
return sorted((parsed, tag, commit) for tag, (parsed, commit) in discovered.items())
151142

152143

153-
def changed_paths_since(root: Path, tag: str, watched_paths: list[str]) -> list[str]:
144+
def changed_paths_since(root: Path, base_ref: str, watched_paths: list[str]) -> list[str]:
154145
existing = [path for path in watched_paths if (root / path).exists()]
155146
if not existing:
156147
return []
157-
result = run_git(root, "diff", "--name-only", tag, "--", *existing)
158-
changed = set(result.stdout.splitlines()) if result.returncode == 0 else set()
148+
result = run_git(root, "diff", "--name-only", base_ref, "--", *existing)
149+
if result.returncode != 0:
150+
detail = result.stderr.strip() or "unknown git diff failure"
151+
raise ValidationError(f"could not compare release files against {base_ref}: {detail}")
152+
changed = set(result.stdout.splitlines())
159153
untracked = run_git(root, "ls-files", "--others", "--exclude-standard", "--", *existing)
160-
if untracked.returncode == 0:
161-
changed.update(untracked.stdout.splitlines())
154+
if untracked.returncode != 0:
155+
detail = untracked.stderr.strip() or "unknown git ls-files failure"
156+
raise ValidationError(f"could not inspect untracked release files: {detail}")
157+
changed.update(untracked.stdout.splitlines())
162158
return sorted(path for path in changed if path in set(watched_paths))
163159

164160

@@ -177,17 +173,25 @@ def validate_release(root: Path) -> tuple[list[str], list[str]]:
177173
errors.append(f"CHANGELOG.md is missing a section for v{version}")
178174
errors.extend(validate_readme_versions(root, version))
179175

180-
tags = codex_release_tags(root)
176+
try:
177+
tags = origin_release_tags(root)
178+
except ValidationError as exc:
179+
errors.append(str(exc))
180+
return errors, warnings
181181
if not tags:
182-
warnings.append("no codex-vX.Y.Z git tags or legacy v0.1.0 baseline found; skipping tag-diff version-bump check")
182+
errors.append("origin has no codex-vX.Y.Z release tags or legacy v0.1.0 baseline")
183183
return errors, warnings
184184

185-
latest_tuple, latest_tag = tags[-1]
185+
latest_tuple, latest_tag, latest_commit = tags[-1]
186186
if version_tuple < latest_tuple:
187187
errors.append(f".codex/VERSION {version} is older than latest tag {latest_tag}")
188188
return errors, warnings
189189

190-
changed = changed_paths_since(root, latest_tag, watched_paths)
190+
try:
191+
changed = changed_paths_since(root, latest_commit, watched_paths)
192+
except ValidationError as exc:
193+
errors.append(str(exc))
194+
return errors, warnings
191195
if version_tuple == latest_tuple and changed:
192196
errors.append(
193197
"installable or release files changed since "

.codex/lib/validate_runtime.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,14 @@
227227
"numbered choice set",
228228
)
229229

230+
ROLE_DELEGATION_EVIDENCE_PHRASES = (
231+
"agent_type: default",
232+
"agent_role: null",
233+
'fork_turns: "none"',
234+
"A task name, agent path, nickname",
235+
"do not simulate the specialist verdict",
236+
)
237+
230238
PLAYTEST_FOCUS_CONTRACT = {
231239
"AGENTS.md": (
232240
"user-owned playtest",
@@ -990,6 +998,12 @@ def validate_agent_startup_roster(root: Path, agent_names: set[str]) -> list[str
990998
if len(agent_names) != 49 and "49 coordinated Codex subagents" in text:
991999
errors.append("AGENTS.md: stale 49-agent claim remains for project-local agent set")
9921000

1001+
for phrase in ROLE_DELEGATION_EVIDENCE_PHRASES:
1002+
if phrase not in text:
1003+
errors.append(
1004+
f"AGENTS.md: missing runtime role-delegation evidence boundary {phrase!r}"
1005+
)
1006+
9931007
return errors
9941008

9951009

0 commit comments

Comments
 (0)