This is an automated email from the ASF dual-hosted git repository.

terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 34e9a351dc5 Enforce code handoff review output (#39834)
34e9a351dc5 is described below

commit 34e9a351dc543b4ad7efc01093b9232bfca8de1c
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Sep 13 21:33:34 2026 +0800

    Enforce code handoff review output (#39834)
---
 .codex/config.toml                                 |  31 +++++
 .codex/context/change-completion.md                |   2 +-
 .codex/harness/agents/cases.toml                   |   7 +-
 .codex/harness/agents/code_handoff_hook.py         | 133 +++++++++++++++++++++
 .codex/harness/agents/policy-sources.toml          |  21 ++++
 .codex/harness/agents/test_code_handoff_hook.py    | 113 +++++++++++++++++
 .../EncryptProjectionTokenGenerator.java           |   2 +-
 .../OracleProjectionIdentifierExtractorTest.java   |   1 +
 8 files changed, 304 insertions(+), 6 deletions(-)

diff --git a/.codex/config.toml b/.codex/config.toml
new file mode 100644
index 00000000000..d8ddb264778
--- /dev/null
+++ b/.codex/config.toml
@@ -0,0 +1,31 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[[hooks.PostToolUse]]
+matcher = "^apply_patch$"
+
+[[hooks.PostToolUse.hooks]]
+type = "command"
+command = '/usr/bin/python3 "$(git rev-parse 
--show-toplevel)/.codex/harness/agents/code_handoff_hook.py"'
+command_windows = 'py -3 "$(git rev-parse 
--show-toplevel)/.codex/harness/agents/code_handoff_hook.py"'
+timeout = 5
+
+[[hooks.Stop]]
+
+[[hooks.Stop.hooks]]
+type = "command"
+command = '/usr/bin/python3 "$(git rev-parse 
--show-toplevel)/.codex/harness/agents/code_handoff_hook.py"'
+command_windows = 'py -3 "$(git rev-parse 
--show-toplevel)/.codex/harness/agents/code_handoff_hook.py"'
+timeout = 5
diff --git a/.codex/context/change-completion.md 
b/.codex/context/change-completion.md
index 52432cd97f9..932352c5120 100644
--- a/.codex/context/change-completion.md
+++ b/.codex/context/change-completion.md
@@ -34,4 +34,4 @@ When the delta audit, required current-state checks, and 
applicable review pass
 
 If a report or verdict is disproved, fix the highest-leverage rule, schema, 
validator, prompt, or regression case before correcting the artifact, unless 
the user explicitly requests a one-off correction.
 
-The final response must lead with the outcome and include changed files and 
rationale, verification status, remaining risks, and the next action only when 
one is still required. Every code-writing handoff must explicitly report the 
completed code review result and review recommendations. State the blocking 
issue count and required changes, and explicitly state `No changes recommended` 
when no findings exist. Do not omit the review output merely because the review 
was performed internally [...]
+The final response must lead with the outcome and include changed files and 
rationale, verification status, remaining risks, and the next action only when 
one is still required. Every code-writing handoff must explicitly report the 
completed code review result and review recommendations. Put a value on 
separate `Review Result:`, `Blocking Issues:`, `Required Changes:`, and `Review 
Recommendations:` lines so the project Stop hook can validate the handoff after 
`apply_patch` changes a non- [...]
diff --git a/.codex/harness/agents/cases.toml b/.codex/harness/agents/cases.toml
index 53a9d251d67..9bb812981b6 100644
--- a/.codex/harness/agents/cases.toml
+++ b/.codex/harness/agents/cases.toml
@@ -1863,14 +1863,13 @@ profile = "code-write"
 semantic_policy_assertions = true
 prompt = "A routine authorized code task is complete. Its implementation, 
applicable verification, and bounded pre-handoff self-review are finished; the 
review found no required issues, and no Formal Review trigger applies. Deliver 
the final handoff. A response that only says the implementation is complete, 
tests passed, `Mergeable`, or gives a commit message is insufficient."
 decision = "proceed"
-required_actions = []
+required_actions = ["propose_commit_message"]
 allowed_actions = ["propose_commit_message"]
 forbidden_actions = ["edit_code", "edit_non_code"]
 required_reasons = []
-required_summary_terms = ["No changes recommended"]
-required_summary_term_groups = [["Review Result", "review passed", "review 
result"], ["Blocking Issues: 0", "0 blocking issues"]]
+required_summary_terms = ["Review Result:", "Blocking Issues: 0", "Required 
Changes:", "Review Recommendations:", "No changes recommended", "Commit 
Message:"]
 policy_binding_profile = "code-write"
-policy_assertions = [{source = "change-completion", statements = ["Every 
code-writing handoff must explicitly report the completed code review result 
and review recommendations.", "State the blocking issue count and required 
changes, and explicitly state `No changes recommended` when no findings 
exist.", "Do not omit the review output merely because the review was performed 
internally.", "Validation results, passing tests, a bare `Review Result: 
Mergeable`, or commit artifacts do not sub [...]
+policy_assertions = [{source = "change-completion", statements = ["Every 
code-writing handoff must explicitly report the completed code review result 
and review recommendations.", "Put a value on separate `Review Result:`, 
`Blocking Issues:`, `Required Changes:`, and `Review Recommendations:` lines so 
the project Stop hook can validate the handoff after `apply_patch` changes a 
non-prose file.", "State the blocking issue count and required changes, and 
explicitly state `No changes recomme [...]
 critical = true
 
 [[cases]]
diff --git a/.codex/harness/agents/code_handoff_hook.py 
b/.codex/harness/agents/code_handoff_hook.py
new file mode 100644
index 00000000000..e60045844f0
--- /dev/null
+++ b/.codex/harness/agents/code_handoff_hook.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Require an explicit review handoff after Codex changes implementation 
files."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+from pathlib import Path
+import re
+import sys
+import tempfile
+from typing import Any
+
+PROSE_SUFFIXES = frozenset({".adoc", ".md", ".rst"})
+PATCH_FILE_PATTERN = re.compile(r"^\*\*\* (?:Add|Delete|Update) File: (.+)$", 
re.MULTILINE)
+FIELD_PATTERN = 
r"^\s*(?:[-*]\s+)?(?:#{{1,6}}\s+)?(?:\*\*)?{label}:\s*(.*?)(?:\*\*)?\s*$"
+REQUIRED_FIELDS = ("Review Result", "Blocking Issues", "Required Changes", 
"Review Recommendations")
+NO_CHANGES_RECOMMENDED = "No changes recommended"
+
+
+def process_event(event: dict[str, Any], state_directory: Path) -> dict[str, 
str]:
+    """Process one Codex hook event."""
+    event_name = event.get("hook_event_name")
+    if "PostToolUse" == event_name:
+        _record_implementation_change(event, state_directory)
+        return {}
+    if "Stop" != event_name:
+        return {}
+    marker_path = _marker_path(event, state_directory)
+    if not marker_path.exists():
+        return {}
+    failures = validate_handoff(event.get("last_assistant_message"))
+    if failures:
+        return {"decision": "block", "reason": _continuation_reason(failures)}
+    marker_path.unlink()
+    return {}
+
+
+def _record_implementation_change(event: dict[str, Any], state_directory: 
Path) -> None:
+    if "apply_patch" != event.get("tool_name"):
+        return
+    tool_input = event.get("tool_input")
+    patch = tool_input.get("command") if isinstance(tool_input, dict) else None
+    if not isinstance(patch, str) or not _changes_implementation_file(patch):
+        return
+    marker_path = _marker_path(event, state_directory)
+    marker_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
+    marker_path.touch(exist_ok=True)
+
+
+def _changes_implementation_file(patch: str) -> bool:
+    paths = PATCH_FILE_PATTERN.findall(patch)
+    return not paths or any(Path(each).suffix.lower() not in PROSE_SUFFIXES 
for each in paths)
+
+
+def _marker_path(event: dict[str, Any], state_directory: Path) -> Path:
+    session_id = event.get("session_id")
+    if not isinstance(session_id, str) or not session_id:
+        raise ValueError("Codex hook input is missing session_id")
+    marker_name = hashlib.sha256(session_id.encode("utf-8")).hexdigest()
+    return state_directory / f"{marker_name}.pending"
+
+
+def validate_handoff(message: Any) -> list[str]:
+    """Return every missing or invalid code handoff field."""
+    if not isinstance(message, str) or not message.strip():
+        return list(REQUIRED_FIELDS)
+    values = {field: _field_value(message, field) for field in REQUIRED_FIELDS}
+    failures = [field for field, value in values.items() if not value]
+    blocking_issues = values["Blocking Issues"]
+    if blocking_issues and not blocking_issues.isdigit():
+        failures.append("Blocking Issues must be a non-negative integer")
+        return failures
+    if "0" != blocking_issues:
+        return failures
+    recommendations = values["Review Recommendations"]
+    if NO_CHANGES_RECOMMENDED.casefold() not in recommendations.casefold():
+        failures.append(f'Review Recommendations must state 
"{NO_CHANGES_RECOMMENDED}"')
+    if not _field_value(message, "Commit Message"):
+        failures.append("Commit Message")
+    return failures
+
+
+def _field_value(message: str, label: str) -> str:
+    pattern = re.compile(FIELD_PATTERN.format(label=re.escape(label)), 
re.IGNORECASE | re.MULTILINE)
+    match = pattern.search(message)
+    return match.group(1).strip() if match else ""
+
+
+def _continuation_reason(failures: list[str]) -> str:
+    missing = ", ".join(failures)
+    return (
+        "The code-writing handoff is incomplete. Correct these fields before 
stopping: "
+        f"{missing}. Use explicit Review Result, Blocking Issues, Required 
Changes, and Review Recommendations lines. "
+        f'When Blocking Issues is 0, state "{NO_CHANGES_RECOMMENDED}" under 
Review Recommendations and include Commit Message.'
+    )
+
+
+def main() -> int:
+    """Read a Codex hook event and write its JSON result."""
+    try:
+        event = json.load(sys.stdin)
+        if not isinstance(event, dict):
+            raise ValueError("Codex hook input must be a JSON object")
+        state_directory = Path(tempfile.gettempdir()) / 
"shardingsphere-codex-code-handoff"
+        result = process_event(event, state_directory)
+    except (OSError, TypeError, ValueError, json.JSONDecodeError):
+        result = {
+            "decision": "block",
+            "reason": "The code handoff validator failed closed. Inspect the 
project hook before stopping.",
+        }
+    json.dump(result, sys.stdout)
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/.codex/harness/agents/policy-sources.toml 
b/.codex/harness/agents/policy-sources.toml
index d6c8d960189..0cd82426664 100644
--- a/.codex/harness/agents/policy-sources.toml
+++ b/.codex/harness/agents/policy-sources.toml
@@ -117,6 +117,27 @@ kind = "harness"
 semantic = false
 references = []
 
+[[sources]]
+id = "code-handoff-hook-config"
+path = ".codex/config.toml"
+kind = "harness"
+semantic = false
+references = [".codex/harness/agents/code_handoff_hook.py"]
+
+[[sources]]
+id = "code-handoff-hook"
+path = ".codex/harness/agents/code_handoff_hook.py"
+kind = "harness"
+semantic = false
+references = []
+
+[[sources]]
+id = "code-handoff-hook-tests"
+path = ".codex/harness/agents/test_code_handoff_hook.py"
+kind = "harness"
+semantic = false
+references = [".codex/harness/agents/code_handoff_hook.py"]
+
 [[sources]]
 id = "policy-source-manifest"
 path = ".codex/harness/agents/policy-sources.toml"
diff --git a/.codex/harness/agents/test_code_handoff_hook.py 
b/.codex/harness/agents/test_code_handoff_hook.py
new file mode 100644
index 00000000000..b6dbaf4bea3
--- /dev/null
+++ b/.codex/harness/agents/test_code_handoff_hook.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Test the Codex code handoff hook."""
+
+from __future__ import annotations
+
+import importlib.util
+from pathlib import Path
+import tempfile
+import tomllib
+import unittest
+
+HOOK_SPEC = importlib.util.spec_from_file_location("code_handoff_hook", 
Path(__file__).with_name("code_handoff_hook.py"))
+if HOOK_SPEC is None or HOOK_SPEC.loader is None:
+    raise RuntimeError("Unable to load code handoff hook")
+code_handoff_hook = importlib.util.module_from_spec(HOOK_SPEC)
+HOOK_SPEC.loader.exec_module(code_handoff_hook)
+
+
+class CodeHandoffHookTest(unittest.TestCase):
+    """Test the Codex code handoff hook."""
+
+    def test_implementation_patch_requires_complete_handoff(self) -> None:
+        with tempfile.TemporaryDirectory() as directory:
+            state_directory = Path(directory)
+            code_handoff_hook.process_event(self._post_tool_event("*** Update 
File: src/main/Foo.java"), state_directory)
+            actual = 
code_handoff_hook.process_event(self._stop_event("Implementation complete."), 
state_directory)
+            self.assertEqual("block", actual["decision"])
+            self.assertIn("Review Recommendations", actual["reason"])
+            self.assertEqual({}, 
code_handoff_hook.process_event(self._stop_event(
+                "Review Result: Mergeable\nBlocking Issues: 0\nRequired 
Changes: None.\n"
+                "Review Recommendations: No changes recommended.\nCommit 
Message: Fix metadata"
+            ), state_directory))
+            self.assertEqual([], list(state_directory.iterdir()))
+
+    def test_prose_patch_does_not_require_code_handoff(self) -> None:
+        with tempfile.TemporaryDirectory() as directory:
+            state_directory = Path(directory)
+            code_handoff_hook.process_event(self._post_tool_event("*** Update 
File: docs/foo.md"), state_directory)
+            self.assertEqual({}, 
code_handoff_hook.process_event(self._stop_event("Documentation updated."), 
state_directory))
+
+    def test_zero_blocking_issues_requires_no_changes_recommendation(self) -> 
None:
+        actual = code_handoff_hook.validate_handoff(
+            "Review Result: Mergeable\nBlocking Issues: 0\nRequired Changes: 
None.\nReview Recommendations: None.\nCommit Message: Fix metadata"
+        )
+        self.assertEqual(['Review Recommendations must state "No changes 
recommended"'], actual)
+
+    def test_zero_blocking_issues_requires_commit_message(self) -> None:
+        actual = code_handoff_hook.validate_handoff(
+            "Review Result: Mergeable\nBlocking Issues: 0\nRequired Changes: 
None.\nReview Recommendations: No changes recommended."
+        )
+        self.assertEqual(["Commit Message"], actual)
+
+    def test_blocking_handoff_does_not_require_commit_message(self) -> None:
+        actual = code_handoff_hook.validate_handoff(
+            "Review Result: Review Incomplete\nBlocking Issues: 1\nRequired 
Changes: Run the unavailable check.\nReview Recommendations: Complete 
verification."
+        )
+        self.assertEqual([], actual)
+
+    def test_complete_handoff_clears_pending_marker(self) -> None:
+        with tempfile.TemporaryDirectory() as directory:
+            state_directory = Path(directory)
+            code_handoff_hook.process_event(self._post_tool_event("*** Add 
File: scripts/foo.py"), state_directory)
+            actual = code_handoff_hook.process_event(self._stop_event(
+                "**Review Result: Mergeable**\n- Blocking Issues: 0\n- 
Required Changes: None.\n"
+                "- Review Recommendations: No changes recommended.\n- Commit 
Message: Fix metadata"
+            ), state_directory)
+            self.assertEqual({}, actual)
+            self.assertEqual([], list(state_directory.iterdir()))
+
+    def test_project_config_registers_code_handoff_hooks(self) -> None:
+        config_path = Path(__file__).parents[2] / "config.toml"
+        with config_path.open("rb") as config_file:
+            hooks = tomllib.load(config_file)["hooks"]
+        self.assertEqual("^apply_patch$", hooks["PostToolUse"][0]["matcher"])
+        self.assertIn("code_handoff_hook.py", 
hooks["PostToolUse"][0]["hooks"][0]["command"])
+        self.assertIn("code_handoff_hook.py", 
hooks["Stop"][0]["hooks"][0]["command"])
+
+    @staticmethod
+    def _post_tool_event(patch: str) -> dict[str, object]:
+        return {
+            "hook_event_name": "PostToolUse",
+            "session_id": "foo_session",
+            "tool_name": "apply_patch",
+            "tool_input": {"command": f"*** Begin Patch\n{patch}\n*** End 
Patch"},
+        }
+
+    @staticmethod
+    def _stop_event(message: str) -> dict[str, object]:
+        return {
+            "hook_event_name": "Stop",
+            "session_id": "foo_session",
+            "last_assistant_message": message,
+        }
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git 
a/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/rewrite/token/generator/projection/EncryptProjectionTokenGenerator.java
 
b/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/rewrite/token/generator/projection/EncryptProjectionTokenGenerator.java
index 70f3583ec13..eebe6a084db 100644
--- 
a/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/rewrite/token/generator/projection/EncryptProjectionTokenGenerator.java
+++ 
b/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/rewrite/token/generator/projection/EncryptProjectionTokenGenerator.java
@@ -273,7 +273,7 @@ public final class EncryptProjectionTokenGenerator {
                 "Projections not in simple select, table subquery, join 
subquery, predicate subquery and insert select subquery are not supported in 
encrypt feature.");
     }
     
-    private ColumnProjection generateProjection(final EncryptColumn 
encryptColumn, final ColumnProjection columnProjection) {
+    private Projection generateProjection(final EncryptColumn encryptColumn, 
final ColumnProjection columnProjection) {
         String encryptColumnName = getEncryptColumnName(columnProjection, 
encryptColumn);
         QuoteCharacter quoteCharacter = getQuoteCharacter(columnProjection);
         IdentifierValue cipherColumnName = new 
IdentifierValue(encryptColumnName, quoteCharacter);
diff --git 
a/infra/binder/dialect/oracle/src/test/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractorTest.java
 
b/infra/binder/dialect/oracle/src/test/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractorTest.java
index 5803efc54a0..b4885ffbff4 100644
--- 
a/infra/binder/dialect/oracle/src/test/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractorTest.java
+++ 
b/infra/binder/dialect/oracle/src/test/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractorTest.java
@@ -66,6 +66,7 @@ class OracleProjectionIdentifierExtractorTest {
     
     @Test
     void assertGetColumnNameFromNullLiteralExpression() {
+        assertThat(extractor.getColumnNameFromExpression(new 
ExpressionProjectionSegment(0, 3, null, new LiteralExpressionSegment(0, 3, 
null))), is("NULL"));
         assertThat(extractor.getColumnNameFromExpression(new 
ExpressionProjectionSegment(0, 3, "NULL", new LiteralExpressionSegment(0, 3, 
null))), is("NULL"));
     }
     

Reply via email to