Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ef5f736
chore(codex): enforce native replacement mode
johannhartmann Jul 20, 2026
294c036
chore(codex): register native replacement hooks
johannhartmann Jul 20, 2026
57a399e
chore(codex): activate architecture replacement turns
johannhartmann Jul 20, 2026
bace23c
chore(codex): block incomplete replacement turns
johannhartmann Jul 20, 2026
7fb3dcb
chore(codex): add native replacement policy
johannhartmann Jul 20, 2026
3374ebc
chore(codex): add replacement git inspection
johannhartmann Jul 20, 2026
c8266ec
chore(codex): enforce complete native replacements
johannhartmann Jul 20, 2026
3068e3b
docs(performance): define native replacement contracts
johannhartmann Jul 20, 2026
462595c
docs(performance): add replacement contract template
johannhartmann Jul 20, 2026
5de0c99
ci: enforce native replacement policy
johannhartmann Jul 20, 2026
7cdb954
docs(architecture): require complete native replacements
johannhartmann Jul 20, 2026
9c0b094
perf(ratchet): require complete architecture tranches
johannhartmann Jul 20, 2026
8a3d679
chore(codex): mark policy scripts executable
johannhartmann Jul 20, 2026
4187c68
fix(codex): allow replacement contracts without slow paths
johannhartmann Jul 20, 2026
46cec51
docs(performance): permit zero semantic slow paths
johannhartmann Jul 20, 2026
2966d3f
chore(codex): preserve policy executable mode
johannhartmann Jul 20, 2026
b3a7e57
docs(performance): default replacement contracts to no exceptions
johannhartmann Jul 20, 2026
aea403d
fix(codex): catch every explicit fallback removal
johannhartmann Jul 20, 2026
1cf00c8
chore(codex): preserve prompt-hook executable mode
johannhartmann Jul 20, 2026
0ec71b0
docs(performance): document broad replacement trigger
johannhartmann Jul 20, 2026
a2dcc17
fix(codex): recognize plural legacy routes
johannhartmann Jul 20, 2026
0b71e9e
chore(codex): preserve prompt-hook executable bit
johannhartmann Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#:schema https://developers.openai.com/codex/config-schema.json

# Repository-local instructions for Codex. These complement AGENTS.md; they do
# not select a model or relax command permissions.
developer_instructions = """
PHRUST CRANELIFT PRODUCTION-REPLACEMENT CONTRACT

For performance and execution-engine work, externally observable PHP 8.5
behavior is the compatibility boundary. Internal compatibility with a retired
VM, runtime helper ABI, generic binder, interpreter route, fallback route, or
wrapper API is not a product requirement.

When the user asks to remove, replace, retire, cut over, eliminate, or make a
legacy execution route unreachable, treat the task as a production architecture
replacement rather than a compatibility migration or a local micro-optimization.
The final change must contain one auditable JSON contract under
`docs/performance/native-replacement-contracts/` and must pass:

`python3 scripts/verify/native_replacement_guard.py --require-contract --diff-policy`

For such a task, completion requires the named old route to be deleted or
production-unreachable in the same final change. Do not finish with old and new
production routes coexisting. Do not introduce or retain a production adapter,
wrapper, bridge, dual dispatch, shadow implementation, renamed legacy helper,
feature-gated old route, generic catch-all recovery, or preparation-only
refactor as a substitute for the requested replacement.

Prefer the smallest COMPLETE vertical replacement, not the smallest diff.
Temporary internal breakage while editing is acceptable; only the final branch
state must build and pass its correctness and performance gates.

Preserve PHP-visible output, diagnostics, exit status, side-effect order,
references, Copy-on-Write behavior, exceptions, warnings, visibility, magic
methods, hooks, destructors, generators, fibers, and request semantics. Genuine
PHP-semantic slow paths are allowed only when they are runtime-dynamic by PHP
semantics, are named in the replacement contract, and do not re-enter a retired
engine route.

Before editing an architecture replacement, inventory the exact symbols, paths,
and production call edges to remove. After editing, search the final tree and
prove those targets absent, prove that no new engine fallback category was
introduced, run the required correctness/application gates, and show movement
in clean wall time plus structural counters. Renaming or wrapping the old route
is a failed result.
"""

[features]
hooks = true
29 changes: 29 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"description": "Enforce complete Cranelift production replacements instead of wrapper-based migrations.",
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/native_replacement_prompt.py\"",
"timeout": 10,
"statusMessage": "Applying the native replacement contract"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/native_replacement_stop.py\"",
"timeout": 180,
"statusMessage": "Checking the native replacement contract"
}
]
}
]
}
}
216 changes: 216 additions & 0 deletions .codex/hooks/native_replacement_prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""Activate strict replacement mode for legacy-route removal prompts.

Codex sends one JSON event on stdin. For matching prompts this hook persists a
small per-session marker under the Git directory and injects additional
developer context. The Stop hook consumes that marker and refuses to finish
until the repository replacement guard passes.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import os
import re
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


OPT_OUT = re.compile(r"\[native-replacement:(?:off|disable)\]", re.IGNORECASE)
EXPLICIT = re.compile(
r"\[(?:native|architecture)-replacement(?::on)?\]|"
r"production[ _-]architecture[ _-]replacement|"
r"cranelift[ _-]only[ _-]cutover",
re.IGNORECASE,
)
REPLACEMENT = re.compile(
r"\b(?:remove|delete|eliminate|replace|retire|cut[ _-]?over|shut[ _-]?off|"
r"make\s+.+?unreachable|entfern(?:e|en|t)|ersetz(?:e|en|t)|"
r"eliminier(?:e|en|t)|abschalt(?:en|et)|abl(?:ö|oe)s(?:en|e|t)|"
r"vollst(?:ä|ae)ndig)\b",
re.IGNORECASE,
)
LEGACY_ROUTE = re.compile(
r"\b(?:fallbacks?|wrappers?|adapters?|bridges?|safe[ _-]?paths?|safepaths?|legacy|"
r"old[ _-]?(?:routes?|paths?|apis?)|"
r"alte[nr]?[ _-]?(?:routen?|pfade?|strecken?)|"
r"interpreters?|generic[ _-]?(?:binders?|runtimes?|helpers?|routes?)|"
r"dual[ _-]?(?:dispatch|routes?|paths?)|shadow[ _-]?(?:routes?|paths?))\b",
re.IGNORECASE,
)


def read_event() -> dict[str, Any]:
try:
document = json.load(sys.stdin)
except (json.JSONDecodeError, OSError) as error:
raise SystemExit(f"invalid Codex hook input: {error}") from error
if not isinstance(document, dict):
raise SystemExit("invalid Codex hook input: expected an object")
return document


def repository_root(cwd: str | None) -> Path | None:
start = Path(cwd or os.getcwd())
result = subprocess.run(
["git", "-C", str(start), "rev-parse", "--show-toplevel"],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0 or not result.stdout.strip():
return None
return Path(result.stdout.strip()).resolve()


def state_directory(root: Path) -> Path:
result = subprocess.run(
["git", "-C", str(root), "rev-parse", "--git-path", "codex-native-replacement"],
text=True,
capture_output=True,
check=True,
)
path = Path(result.stdout.strip())
return path if path.is_absolute() else root / path


def state_path(root: Path, session_id: str) -> Path:
digest = hashlib.sha256(session_id.encode("utf-8")).hexdigest()
return state_directory(root) / f"{digest}.json"


def prompt_requests_replacement(prompt: str) -> bool:
if OPT_OUT.search(prompt):
return False
if EXPLICIT.search(prompt):
return True
return bool(REPLACEMENT.search(prompt) and LEGACY_ROUTE.search(prompt))


def write_state(path: Path, document: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
fd, temporary = tempfile.mkstemp(prefix=path.name, dir=path.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(document, handle, indent=2, sort_keys=True)
handle.write("\n")
os.replace(temporary, path)
finally:
try:
os.unlink(temporary)
except FileNotFoundError:
pass


def additional_context() -> str:
return """PHRUST NATIVE REPLACEMENT MODE IS ACTIVE FOR THIS TURN.

This is a production architecture replacement, not a compatibility migration.
Before changing code, create one concrete JSON contract under
`docs/performance/native-replacement-contracts/` naming the legacy symbols,
paths, and call edges that this turn will remove. The final production tree may
not contain an adapter, wrapper, bridge, dual route, shadow implementation,
renamed legacy helper, feature-gated old path, or generic engine fallback that
recreates the removed route. Internal compatibility with that route is not a
requirement; externally observable PHP 8.5 behavior is.

Implement the smallest COMPLETE vertical replacement. The turn is incomplete
while old and new production routes coexist or while the change only prepares a
later cutover. Genuine PHP-semantic slow paths must be explicitly named and
reasoned in the contract and must not re-enter the retired engine route.

Before finishing, run:
`python3 scripts/verify/native_replacement_guard.py --require-contract --diff-policy`
Then run every correctness, application, and performance command listed by the
contract. Do not claim completion until the guard passes and the named old route
is absent from the final production source."""


def activate(event: dict[str, Any]) -> dict[str, Any] | None:
prompt = str(event.get("prompt") or "")
session_id = str(event.get("session_id") or "")
root = repository_root(str(event.get("cwd") or ""))
if not session_id or root is None:
return None

path = state_path(root, session_id)
if not prompt_requests_replacement(prompt):
path.unlink(missing_ok=True)
return None

write_state(
path,
{
"schema_version": 1,
"active": True,
"session_id": session_id,
"turn_id": event.get("turn_id"),
"repository_root": str(root),
"prompt_sha256": hashlib.sha256(prompt.encode("utf-8")).hexdigest(),
"activated_at": datetime.now(timezone.utc).isoformat(),
},
)
return {
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": additional_context(),
}
}


def self_test() -> int:
positives = (
"[native-replacement] remove the old runtime bridge",
"Replace the Cranelift fallback route completely",
"Die alte native Wrapper-Strecke vollständig entfernen",
"Cut over the execution architecture and eliminate the generic binder",
"Eliminiere endlich alle Fallbacks",
"Remove the wrappers around the old routes",
)
negatives = (
"Fix a parser diagnostic",
"Document the existing native fallback counter",
"[native-replacement:off] replace the Cranelift fallback route",
)
failures = 0
for prompt in positives:
if not prompt_requests_replacement(prompt):
print(f"[FAIL] replacement prompt not detected: {prompt}")
failures += 1
for prompt in negatives:
if prompt_requests_replacement(prompt):
print(f"[FAIL] ordinary prompt misclassified: {prompt}")
failures += 1
if "smallest COMPLETE vertical replacement" not in additional_context():
print("[FAIL] replacement context lost the completion rule")
failures += 1
if failures:
return 1
print("[ok] native replacement prompt hook")
return 0


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--self-test", action="store_true")
return parser.parse_args()


def main() -> int:
args = parse_args()
if args.self_test:
return self_test()
output = activate(read_event())
if output is not None:
print(json.dumps(output))
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading