diff --git a/.repository-projection.json b/.repository-projection.json index 00106e970..d50d3a106 100644 --- a/.repository-projection.json +++ b/.repository-projection.json @@ -3,11 +3,11 @@ "projection": "deixic-code", "projectionSchemaVersion": 1, "sourceRepository": "dx-corp/mono", - "sourceSha": "f1457cd20a4b86478bcc70b0152230aaa8a17dea", + "sourceSha": "d42a9af9edd2a1c5b5ec9076c68bc30a3b83a83e", "destinationRepository": "dx-corp/code", - "priorProjectedBase": "6a1ee9a7f2d6d82fa7f55c3a1dc7c7d8e95c964f", + "priorProjectedBase": "151f172cbc14fbc955f0f221d0b757910e158152", "definitionDigest": "cb9d429542ebb0a2de9b42a7aad60d9d8696a648ceba47c30f05c0b285ca0db7", "toolDigest": "c9112982c70b80737d8faac67d58b06d55bde0dc297eb1009c1b02296f34f4a9", - "contentDigest": "2ee4dc7a3e288db3224383edce5c9dede750c4e7733a87436b0fb14c221b58c0", + "contentDigest": "81d79f31e8f1483766e9eb0012fe456a12bb8d3b38162bc7fa515a95af275ba0", "publicationEligible": true } diff --git a/scripts/capture-tui.py b/scripts/capture-tui.py index 9593bbfa2..dada1ae15 100644 --- a/scripts/capture-tui.py +++ b/scripts/capture-tui.py @@ -313,13 +313,21 @@ def __init__(self, root, binary, columns, rows, fixture=None): self.config.write_text( 'set -g default-terminal "xterm-256color"\nset -g escape-time 0\nset -g status off\n' ) - self.workspace = root / "release-planner" + self.workspace = root / ("security-policy" if fixture and fixture.scene == "compliance" else "release-planner") self.workspace.mkdir() if fixture: (self.workspace / "README.md").write_text( "# Release checklist\n\nA small workspace for planning a team release.\n\n" "1. Choose a release owner.\n2. Review the changes.\n3. Run the checks.\n4. Publish the release.\n5. Write a short release note.\n" ) + if fixture and fixture.scene == "compliance": + (self.workspace / "SECURITY.md").write_text("# Security policy\n\nWe acknowledge reports within two business days.\n") + (self.workspace / "test_security_policy.py").write_text( + "from pathlib import Path\nimport unittest\n\n" + "class SecurityPolicyTest(unittest.TestCase):\n" + " def test_private_reporting(self):\n self.assertIn('security@acme.example', Path('SECURITY.md').read_text())\n" + " def test_response_commitment(self):\n self.assertIn('two business days', Path('SECURITY.md').read_text())\n" + ) self.env = { "PATH": os.environ.get("PATH", "/usr/bin:/bin"), "HOME": str(root), diff --git a/scripts/test-capture-tui.py b/scripts/test-capture-tui.py index 82044f245..b312ca757 100644 --- a/scripts/test-capture-tui.py +++ b/scripts/test-capture-tui.py @@ -62,6 +62,17 @@ def test_light_scenes_select_real_palette_and_reduced_motion_is_explicit(self): self.assertIn({"text": "/dex motion-off"}, reduced["steps"]) self.assertEqual(reduced["steps"][-1], {"wait": "Dex appreciates the boop"}) + def test_compliance_fixture_refuses_unproven_success(self): + from tui_capture_fixture import CaptureFixture + with CaptureFixture("compliance") as fixture: + base = fixture.environment()["MAESTRO_IDENTITY_URL"] + fixture.turn = 3 + request = urllib.request.Request(base + "/v1/chat/completions", data=json.dumps({"messages": []}).encode(), headers={"Content-Type": "application/json"}) + with self.assertRaises(urllib.error.HTTPError) as raised: + urllib.request.urlopen(request, timeout=3) + self.assertEqual(raised.exception.code, 409) + self.assertIn(b"requires actual passing regression test", raised.exception.read()) + def test_fixture_serves_only_local_scripted_contracts(self): from tui_capture_fixture import CaptureFixture diff --git a/scripts/tui_capture_fixture.py b/scripts/tui_capture_fixture.py index b096f23a0..2cf113021 100644 --- a/scripts/tui_capture_fixture.py +++ b/scripts/tui_capture_fixture.py @@ -33,7 +33,35 @@ def do_POST(self): if self.path in {"/v1/chat/completions", "/v1/responses"}: responses = self.path == "/v1/responses" fixture.turn += 1 - if fixture.scene == "streaming": + if fixture.scene == "compliance": + request = json.loads(request_body) + messages = request.get("input", []) if responses else request.get("messages", []) + results = [str(message.get("content", message.get("output", ""))) for message in messages + if message.get("role") == "tool" or message.get("type") == "function_call_output"] + def tool(name, args): + return {"role": "assistant", "tool_calls": [{"index": 0, "id": f"compliance-{fixture.turn}", "type": "function", "function": {"name": name, "arguments": json.dumps(args)}}]} + if fixture.turn == 1: + delta = tool("read", {"path": "SECURITY.md"}) + finish = "tool_calls" + elif fixture.turn == 2: + if not any("Security policy" in result for result in results): + self.send_error(409, "requires actual security file read") + return + delta = tool("write", {"path": "SECURITY.md", "content": "# Security policy\n\nReport vulnerabilities privately to security@acme.example.\nWe acknowledge reports within two business days.\n"}) + finish = "tool_calls" + elif fixture.turn == 3: + delta = tool("bash", {"command": "python3 -m unittest test_security_policy -v"}) + finish = "tool_calls" + elif fixture.turn == 4: + if not any("OK" in result and "test_private_reporting" in result for result in results): + self.send_error(409, "requires actual passing regression test") + return + delta = {"role": "assistant", "content": "Fixed SECURITY.md after your approval.\n\n- Added a private vulnerability reporting address.\n- Kept the two-business-day response commitment.\n- Read the file in the regression test: 2 tests pass.\n\nThe local change is ready for review. Production publication and the SOC 2 control re-test are still pending."} + finish = "stop" + else: + self.send_error(409, "capture fixture exhausted") + return + elif fixture.scene == "streaming": self.send_response(200) self.send_header("Content-Type", "text/event-stream") self.end_headers() @@ -62,7 +90,7 @@ def do_POST(self): self.wfile.flush() fixture.stopped.wait(20) return - if fixture.scene == "long-conversation": + elif fixture.scene == "long-conversation": delta = { "role": "assistant", "content": "Release review\n\n" diff --git a/test/fixtures/tui-capture/baselines/macos-menlo/compliance-120x40.manifest.json b/test/fixtures/tui-capture/baselines/macos-menlo/compliance-120x40.manifest.json new file mode 100644 index 000000000..9cd19abed --- /dev/null +++ b/test/fixtures/tui-capture/baselines/macos-menlo/compliance-120x40.manifest.json @@ -0,0 +1,39 @@ +{ + "scenario": "compliance", + "scenario_sha256": "41a647918bd724893856779f19a413561b6ac014e9755e20d69a189f6489af54", + "binary": "/Users/jonathanhaas/Documents/Codex/2026-10-05/lo/work/maestro-target/debug/maestro", + "columns": 120, + "rows": 40, + "font": "/System/Library/Fonts/Menlo.ttc", + "font_sha256": "f2d400484a6aace67980730ec0a9ef24ce9e535a2604f118e6fdc2510021b85f", + "font_size": 18, + "terminal_palette": { + "default": "#e6e4e0", + "black": "#1b1c1e", + "red": "#d28b87", + "green": "#a0b69a", + "brown": "#c9b58c", + "blue": "#96a9c7", + "magenta": "#b0a2cb", + "cyan": "#93b6b3", + "white": "#d8d6d2", + "brightblack": "#85868b", + "brightred": "#e3a19b", + "brightgreen": "#b3c8ac", + "brightbrown": "#ddc9a1", + "brightblue": "#acbdd8", + "brightmagenta": "#c3b5de", + "brightcyan": "#a8cbc8", + "brightwhite": "#f4f2ee" + }, + "status": "passed", + "binary_sha256": "8a94378908f17bce6249a022b61b7bc6f367a90b24302b0e1e6b272c03ed0287", + "backend": "local-scripted-fixture", + "binary_version": "deixic-code 0.10.111", + "cursor": [ + 2, + 36 + ], + "identity_requests": 3, + "model_requests": 4 +} diff --git a/test/fixtures/tui-capture/baselines/macos-menlo/compliance-120x40.png b/test/fixtures/tui-capture/baselines/macos-menlo/compliance-120x40.png new file mode 100644 index 000000000..e1643d742 Binary files /dev/null and b/test/fixtures/tui-capture/baselines/macos-menlo/compliance-120x40.png differ diff --git a/test/fixtures/tui-capture/compliance.json b/test/fixtures/tui-capture/compliance.json new file mode 100644 index 000000000..826226c1e --- /dev/null +++ b/test/fixtures/tui-capture/compliance.json @@ -0,0 +1,16 @@ +{ + "name": "compliance", + "steps": [ + {"wait": "(?s)Dex.*GPT-6 Astra.*security-policy"}, + {"text": "Fix SECURITY.md and run its tests."}, + {"wait": "Fix SECURITY.md and run its tests"}, + {"key": "Enter"}, + {"wait": "Action Approval Required"}, + {"key": "Enter"}, + {"wait": "Action Approval Required"}, + {"key": "Enter"}, + {"wait": "2 tests pass"}, + {"absent": "Working|Thinking"}, + {"wait": "Dex ยท finished"} + ] +} diff --git a/vendor/dex-loop/src/lib.rs b/vendor/dex-loop/src/lib.rs index 6fbb2488d..50a144964 100644 --- a/vendor/dex-loop/src/lib.rs +++ b/vendor/dex-loop/src/lib.rs @@ -76,4 +76,4 @@ pub use agent_codemode::{ModelBinding, ModelOperation, OutputBlock}; pub use codemode_output::validate_codemode_blocks; mod grc_context; -pub use grc_context::{GRC_CONTEXT_BYTES, GRC_GRAPH_TOOL_NAME, GRC_RESULT_BYTES}; +pub use grc_context::{GRC_CONTEXT_BYTES, GRC_GRAPH_TOOL_NAME}; diff --git a/vendor/dex-loop/tests/grc_context.rs b/vendor/dex-loop/tests/grc_context.rs index b5324b225..373ffbfdf 100644 --- a/vendor/dex-loop/tests/grc_context.rs +++ b/vendor/dex-loop/tests/grc_context.rs @@ -3,6 +3,7 @@ mod support; use dex_loop::{ Budget, CancellationToken, Exit, GRC_CONTEXT_BYTES, GRC_GRAPH_TOOL_NAME, Message, Outcome, + Output, }; use serde_json::json; use support::{FakeLog, FakeModel, FakeTools, call, engine, read_tool, text}; @@ -40,8 +41,16 @@ async fn grc_parallel_then_successive_reads_execute_once_and_replay_the_same_his .history() .iter() .filter_map(|e| match &e.message { - Message::Tool { name, outcome, .. } if name.as_str() == GRC_GRAPH_TOOL_NAME => { - Some((outcome, e.message.size())) + Message::Tool { + name, + outcome, + output, + .. + } if name.as_str() == GRC_GRAPH_TOOL_NAME => { + let Output::Text(text) = output else { + panic!("the GRC fixture must produce inline text"); + }; + Some((outcome, text.len())) } _ => None, })