nexus-coding-agent 0.1.0__py3-none-any.whl
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.
- nexus/__init__.py +4 -0
- nexus/application/__init__.py +2 -0
- nexus/application/approval_service.py +130 -0
- nexus/application/diff_service.py +177 -0
- nexus/application/event_publisher.py +180 -0
- nexus/application/execution_context.py +29 -0
- nexus/application/execution_ledger.py +126 -0
- nexus/application/model_call_context.py +29 -0
- nexus/application/observed_model_gateway.py +194 -0
- nexus/application/plan_approval_service.py +250 -0
- nexus/application/planning.py +668 -0
- nexus/application/runtime.py +624 -0
- nexus/application/session_service.py +411 -0
- nexus/application/structured_output.py +96 -0
- nexus/application/telemetry.py +396 -0
- nexus/application/telemetry_subscriber.py +216 -0
- nexus/application/tool_runtime.py +496 -0
- nexus/application/tracing_dispatcher.py +283 -0
- nexus/application/validation.py +293 -0
- nexus/config/__init__.py +17 -0
- nexus/config/loader.py +253 -0
- nexus/config/models.py +310 -0
- nexus/context/__init__.py +6 -0
- nexus/context/budget.py +24 -0
- nexus/context/builder.py +92 -0
- nexus/context/chunking.py +65 -0
- nexus/context/explorer.py +246 -0
- nexus/context/integration.py +63 -0
- nexus/context/manager.py +328 -0
- nexus/context/retrieval.py +261 -0
- nexus/domain/__init__.py +2 -0
- nexus/domain/agent_decision.py +113 -0
- nexus/domain/agent_state.py +49 -0
- nexus/domain/approvals.py +64 -0
- nexus/domain/context.py +108 -0
- nexus/domain/exploration.py +86 -0
- nexus/domain/mcp.py +51 -0
- nexus/domain/model.py +173 -0
- nexus/domain/observability.py +326 -0
- nexus/domain/persistence.py +122 -0
- nexus/domain/planning.py +329 -0
- nexus/domain/ports/__init__.py +61 -0
- nexus/domain/ports/agent_decision.py +9 -0
- nexus/domain/ports/approval_unit_of_work.py +27 -0
- nexus/domain/ports/checkpoint_provider.py +11 -0
- nexus/domain/ports/context.py +71 -0
- nexus/domain/ports/events.py +21 -0
- nexus/domain/ports/graph_runtime.py +30 -0
- nexus/domain/ports/mcp.py +23 -0
- nexus/domain/ports/model_gateway.py +21 -0
- nexus/domain/ports/model_input_budget.py +15 -0
- nexus/domain/ports/planning.py +62 -0
- nexus/domain/ports/repositories.py +54 -0
- nexus/domain/ports/repository_context.py +18 -0
- nexus/domain/ports/session_unit_of_work.py +33 -0
- nexus/domain/ports/skills.py +41 -0
- nexus/domain/ports/tooling.py +39 -0
- nexus/domain/ports/tracer.py +13 -0
- nexus/domain/ports/validation.py +32 -0
- nexus/domain/runtime_events.py +445 -0
- nexus/domain/skills.py +129 -0
- nexus/domain/tooling.py +163 -0
- nexus/domain/validation.py +161 -0
- nexus/errors/__init__.py +82 -0
- nexus/evaluation/__init__.py +7 -0
- nexus/evaluation/assertions.py +4 -0
- nexus/evaluation/evaluator.py +263 -0
- nexus/evaluation/fixtures.py +36 -0
- nexus/evaluation/harness.py +606 -0
- nexus/evaluation/loader.py +415 -0
- nexus/evaluation/metrics.py +27 -0
- nexus/evaluation/models.py +362 -0
- nexus/evaluation/report.py +43 -0
- nexus/evaluation/runner.py +480 -0
- nexus/infrastructure/__init__.py +2 -0
- nexus/infrastructure/asyncio_compat.py +13 -0
- nexus/infrastructure/bootstrap/__init__.py +17 -0
- nexus/infrastructure/bootstrap/composition.py +663 -0
- nexus/infrastructure/checkpoint/__init__.py +5 -0
- nexus/infrastructure/checkpoint/postgres.py +62 -0
- nexus/infrastructure/database.py +39 -0
- nexus/infrastructure/embedding.py +61 -0
- nexus/infrastructure/graph/__init__.py +6 -0
- nexus/infrastructure/graph/day4_runtime.py +966 -0
- nexus/infrastructure/graph/langgraph_runtime.py +172 -0
- nexus/infrastructure/mcp/__init__.py +5 -0
- nexus/infrastructure/mcp/manager.py +380 -0
- nexus/infrastructure/model_gateway/__init__.py +6 -0
- nexus/infrastructure/model_gateway/openai_compatible.py +345 -0
- nexus/infrastructure/observability/__init__.py +7 -0
- nexus/infrastructure/observability/console.py +31 -0
- nexus/infrastructure/observability/langsmith.py +236 -0
- nexus/infrastructure/observability/structured_logger.py +70 -0
- nexus/infrastructure/persistence/__init__.py +13 -0
- nexus/infrastructure/persistence/approval_unit_of_work.py +74 -0
- nexus/infrastructure/persistence/models.py +146 -0
- nexus/infrastructure/persistence/repositories.py +326 -0
- nexus/infrastructure/persistence/unit_of_work.py +82 -0
- nexus/infrastructure/repository_files.py +117 -0
- nexus/infrastructure/sandbox/__init__.py +5 -0
- nexus/infrastructure/sandbox/local_process.py +353 -0
- nexus/infrastructure/semantic.py +276 -0
- nexus/infrastructure/semantic_models.py +56 -0
- nexus/interfaces/__init__.py +2 -0
- nexus/interfaces/cli/__init__.py +2 -0
- nexus/interfaces/cli/__main__.py +6 -0
- nexus/interfaces/cli/app.py +266 -0
- nexus/interfaces/cli/renderer.py +118 -0
- nexus/security/__init__.py +14 -0
- nexus/security/approval_policies.py +73 -0
- nexus/security/command_policy.py +117 -0
- nexus/security/executables.py +107 -0
- nexus/security/git_commands.py +65 -0
- nexus/security/workspace.py +93 -0
- nexus/skills/__init__.py +7 -0
- nexus/skills/builtin/__init__.py +1 -0
- nexus/skills/builtin/debug-python/SKILL.md +26 -0
- nexus/skills/builtin/review-repository/SKILL.md +26 -0
- nexus/skills/builtin/write-tests/SKILL.md +26 -0
- nexus/skills/loader.py +439 -0
- nexus/skills/registry.py +146 -0
- nexus/skills/selector.py +152 -0
- nexus/tools/__init__.py +6 -0
- nexus/tools/editing.py +312 -0
- nexus/tools/mcp.py +160 -0
- nexus/tools/native.py +654 -0
- nexus/tools/registry.py +27 -0
- nexus_coding_agent-0.1.0.dist-info/METADATA +142 -0
- nexus_coding_agent-0.1.0.dist-info/RECORD +131 -0
- nexus_coding_agent-0.1.0.dist-info/WHEEL +4 -0
- nexus_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"""Sequential, isolated Day 9 evaluation orchestration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tempfile
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from contextlib import AbstractAsyncContextManager
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from nexus.application.runtime import NexusRuntime
|
|
12
|
+
from nexus.domain.runtime_events import (
|
|
13
|
+
ErrorOccurred,
|
|
14
|
+
FinalResult,
|
|
15
|
+
RunInterrupted,
|
|
16
|
+
RuntimeStatus,
|
|
17
|
+
ToolFinished,
|
|
18
|
+
)
|
|
19
|
+
from nexus.domain.tooling import ApprovalDecision, PolicyDecision
|
|
20
|
+
from nexus.evaluation.evaluator import DefaultDeterministicEvaluator
|
|
21
|
+
from nexus.evaluation.fixtures import FixtureConstraintError, validate_fixture_source
|
|
22
|
+
from nexus.evaluation.harness import (
|
|
23
|
+
DEFAULT_EVAL_VALIDATION_TIMEOUT_SECONDS,
|
|
24
|
+
EvaluationHarness,
|
|
25
|
+
compare_repository_states,
|
|
26
|
+
snapshot_repository,
|
|
27
|
+
)
|
|
28
|
+
from nexus.evaluation.metrics import InMemoryEvalTraceCollector, metrics_from_finish
|
|
29
|
+
from nexus.evaluation.models import (
|
|
30
|
+
DeterministicEvaluationResult,
|
|
31
|
+
EvalAssertionResult,
|
|
32
|
+
EvalAssertionType,
|
|
33
|
+
EvalCase,
|
|
34
|
+
EvalCaseReport,
|
|
35
|
+
EvalCheckEvidence,
|
|
36
|
+
EvalDiscoveryResult,
|
|
37
|
+
EvalDiscoveryStatus,
|
|
38
|
+
EvalExecutionResult,
|
|
39
|
+
EvalForbiddenPathEvidence,
|
|
40
|
+
EvalOutcome,
|
|
41
|
+
EvalRepositoryEvidence,
|
|
42
|
+
EvalSuiteError,
|
|
43
|
+
EvalSuiteLoadResult,
|
|
44
|
+
EvalSuiteReport,
|
|
45
|
+
EvalValidationResult,
|
|
46
|
+
EvalValidationStatus,
|
|
47
|
+
ForbiddenPathSnapshot,
|
|
48
|
+
SecurityDecision,
|
|
49
|
+
SecurityEvidence,
|
|
50
|
+
SecurityEvidenceSource,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
RuntimeFactory = Callable[
|
|
54
|
+
[Path, InMemoryEvalTraceCollector], AbstractAsyncContextManager[NexusRuntime]
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class EvalRunner:
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
cases_root: Path,
|
|
62
|
+
runtime_factory: RuntimeFactory,
|
|
63
|
+
evaluator: DefaultDeterministicEvaluator | None = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
self._cases_root = cases_root
|
|
66
|
+
self._runtime_factory = runtime_factory
|
|
67
|
+
self._evaluator = evaluator or DefaultDeterministicEvaluator()
|
|
68
|
+
|
|
69
|
+
async def run_case(self, case: EvalCase) -> EvalCaseReport:
|
|
70
|
+
fixture = self._cases_root / case.case_id / case.initial_repo_state
|
|
71
|
+
try:
|
|
72
|
+
validate_fixture_source(fixture)
|
|
73
|
+
except FixtureConstraintError as exc:
|
|
74
|
+
return _evaluator_error_report(case, str(exc))
|
|
75
|
+
except Exception as exc:
|
|
76
|
+
return await self._infrastructure_report(
|
|
77
|
+
case,
|
|
78
|
+
_empty_checks(),
|
|
79
|
+
f"Fixture validation failed: {type(exc).__name__}",
|
|
80
|
+
)
|
|
81
|
+
timeout = case.validation_timeout_seconds or DEFAULT_EVAL_VALIDATION_TIMEOUT_SECONDS
|
|
82
|
+
pre_checks, pre_error = await self._run_preconditions(case, fixture, timeout)
|
|
83
|
+
if pre_error is not None:
|
|
84
|
+
execution = EvalExecutionResult(
|
|
85
|
+
case_id=case.case_id,
|
|
86
|
+
run_id=None,
|
|
87
|
+
session_id=None,
|
|
88
|
+
runtime_status=None,
|
|
89
|
+
terminal_status=None,
|
|
90
|
+
error_code=None,
|
|
91
|
+
final_result_summary=None,
|
|
92
|
+
repository=_empty_repository(case.forbidden_files),
|
|
93
|
+
checks=pre_checks,
|
|
94
|
+
metrics=None,
|
|
95
|
+
security_evidence=(),
|
|
96
|
+
infrastructure_error=pre_error,
|
|
97
|
+
)
|
|
98
|
+
result = await self._evaluator.evaluate(case, execution)
|
|
99
|
+
return _case_report(case, execution, result)
|
|
100
|
+
if _precondition_mismatch(case, pre_checks):
|
|
101
|
+
execution = EvalExecutionResult(
|
|
102
|
+
case_id=case.case_id,
|
|
103
|
+
run_id=None,
|
|
104
|
+
session_id=None,
|
|
105
|
+
runtime_status=None,
|
|
106
|
+
terminal_status=None,
|
|
107
|
+
error_code=None,
|
|
108
|
+
final_result_summary=None,
|
|
109
|
+
repository=_empty_repository(case.forbidden_files),
|
|
110
|
+
checks=pre_checks,
|
|
111
|
+
metrics=None,
|
|
112
|
+
security_evidence=(),
|
|
113
|
+
infrastructure_error=None,
|
|
114
|
+
)
|
|
115
|
+
result = await self._evaluator.evaluate(case, execution)
|
|
116
|
+
return _case_report(case, execution, result)
|
|
117
|
+
|
|
118
|
+
with tempfile.TemporaryDirectory(prefix=f"nexus-{case.case_id.lower()}-") as raw:
|
|
119
|
+
workspace = Path(raw) / "repo"
|
|
120
|
+
try:
|
|
121
|
+
EvaluationHarness.copy_fixture(fixture, workspace)
|
|
122
|
+
git_results = await EvaluationHarness.initialize_git(workspace)
|
|
123
|
+
setup_error = _git_setup_error(git_results)
|
|
124
|
+
if setup_error:
|
|
125
|
+
return await self._infrastructure_report(case, pre_checks, setup_error)
|
|
126
|
+
baseline = snapshot_repository(workspace, case.forbidden_files)
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
return await self._infrastructure_report(
|
|
129
|
+
case, pre_checks, f"Fixture setup failed: {type(exc).__name__}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
collector = InMemoryEvalTraceCollector()
|
|
133
|
+
run_id: str | None = None
|
|
134
|
+
session_id: str | None = None
|
|
135
|
+
runtime_status: RuntimeStatus | None = None
|
|
136
|
+
terminal_status = None
|
|
137
|
+
error_code: str | None = None
|
|
138
|
+
final_summary: str | None = None
|
|
139
|
+
security: list[SecurityEvidence] = []
|
|
140
|
+
infrastructure_error: str | None = None
|
|
141
|
+
runtime_invoked = False
|
|
142
|
+
try:
|
|
143
|
+
async with self._runtime_factory(workspace, collector) as runtime:
|
|
144
|
+
runtime_invoked = True
|
|
145
|
+
async for event in runtime.run(case.task):
|
|
146
|
+
run_id = run_id or event.run_id
|
|
147
|
+
session_id = event.session_id or session_id
|
|
148
|
+
if isinstance(event, FinalResult):
|
|
149
|
+
runtime_status = event.status
|
|
150
|
+
terminal_status = event.terminal_status
|
|
151
|
+
final_summary = event.content
|
|
152
|
+
elif isinstance(event, ErrorOccurred):
|
|
153
|
+
runtime_status = event.status
|
|
154
|
+
error_code = event.code
|
|
155
|
+
elif isinstance(event, RunInterrupted):
|
|
156
|
+
runtime_status = event.status
|
|
157
|
+
elif isinstance(event, ToolFinished):
|
|
158
|
+
evidence = _security_evidence(event)
|
|
159
|
+
if evidence is not None:
|
|
160
|
+
security.append(evidence)
|
|
161
|
+
except Exception as exc:
|
|
162
|
+
infrastructure_error = f"Runtime bootstrap failed: {type(exc).__name__}"
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
# Diagnostic-only boundary: authoritative evidence is captured again
|
|
166
|
+
# after all post-run validation and discovery operations.
|
|
167
|
+
_post_agent_state = snapshot_repository(workspace, case.forbidden_files)
|
|
168
|
+
expected_nodes = tuple(
|
|
169
|
+
node
|
|
170
|
+
for assertion in case.deterministic_success_conditions
|
|
171
|
+
if assertion.assertion_type is EvalAssertionType.TARGETED_TEST_ADDED
|
|
172
|
+
for node in assertion.expected_test_node_ids
|
|
173
|
+
)
|
|
174
|
+
post_validation = await EvaluationHarness.validation(
|
|
175
|
+
workspace, case.validation_command, timeout, expected_nodes
|
|
176
|
+
)
|
|
177
|
+
post_discovery = await EvaluationHarness.discovery(
|
|
178
|
+
workspace, case.test_discovery_command, timeout
|
|
179
|
+
)
|
|
180
|
+
final = snapshot_repository(workspace, case.forbidden_files)
|
|
181
|
+
text_paths = tuple(
|
|
182
|
+
assertion.path
|
|
183
|
+
for assertion in case.deterministic_success_conditions
|
|
184
|
+
if assertion.assertion_type is EvalAssertionType.REQUIRED_TEXT_IN_FILE
|
|
185
|
+
and assertion.path is not None
|
|
186
|
+
)
|
|
187
|
+
repository = compare_repository_states(baseline, final, text_paths, workspace)
|
|
188
|
+
except Exception as exc:
|
|
189
|
+
return await self._infrastructure_report(
|
|
190
|
+
case,
|
|
191
|
+
pre_checks,
|
|
192
|
+
f"Post-run Harness failed: {type(exc).__name__}",
|
|
193
|
+
)
|
|
194
|
+
checks = EvalCheckEvidence(
|
|
195
|
+
pre_checks.pre_validation,
|
|
196
|
+
post_validation,
|
|
197
|
+
pre_checks.pre_discovery,
|
|
198
|
+
post_discovery,
|
|
199
|
+
)
|
|
200
|
+
finish = None if run_id is None else collector.get_finish(run_id)
|
|
201
|
+
if infrastructure_error is None and runtime_invoked and finish is None:
|
|
202
|
+
infrastructure_error = "Authoritative TraceRunFinish is missing."
|
|
203
|
+
if finish is not None:
|
|
204
|
+
runtime_status = finish.runtime_status
|
|
205
|
+
terminal_status = finish.terminal_status
|
|
206
|
+
error_code = finish.error_code
|
|
207
|
+
execution = EvalExecutionResult(
|
|
208
|
+
case_id=case.case_id,
|
|
209
|
+
run_id=run_id,
|
|
210
|
+
session_id=session_id,
|
|
211
|
+
runtime_status=runtime_status,
|
|
212
|
+
terminal_status=terminal_status,
|
|
213
|
+
error_code=error_code,
|
|
214
|
+
final_result_summary=final_summary,
|
|
215
|
+
repository=repository,
|
|
216
|
+
checks=checks,
|
|
217
|
+
metrics=None if finish is None else metrics_from_finish(finish),
|
|
218
|
+
security_evidence=tuple(security),
|
|
219
|
+
infrastructure_error=infrastructure_error,
|
|
220
|
+
)
|
|
221
|
+
result = await self._evaluator.evaluate(case, execution)
|
|
222
|
+
return _case_report(case, execution, result)
|
|
223
|
+
|
|
224
|
+
async def run_suite(self, load_result: EvalSuiteLoadResult) -> EvalSuiteReport:
|
|
225
|
+
errors = tuple(
|
|
226
|
+
EvalSuiteError(error.code, error.message, error.source_path, error.case_id_hint)
|
|
227
|
+
for error in load_result.errors
|
|
228
|
+
)
|
|
229
|
+
reports: list[EvalCaseReport] = []
|
|
230
|
+
if not errors:
|
|
231
|
+
for case in sorted(load_result.cases, key=lambda item: item.case_id):
|
|
232
|
+
reports.append(await self.run_case(case))
|
|
233
|
+
return _suite_report(tuple(reports), errors)
|
|
234
|
+
|
|
235
|
+
async def _run_preconditions(
|
|
236
|
+
self, case: EvalCase, fixture: Path, timeout_seconds: int
|
|
237
|
+
) -> tuple[EvalCheckEvidence, str | None]:
|
|
238
|
+
if not case.pre_validation_command and not case.test_discovery_command:
|
|
239
|
+
return EvalCheckEvidence(
|
|
240
|
+
_not_run_validation(),
|
|
241
|
+
_not_run_validation(),
|
|
242
|
+
_not_run_discovery(),
|
|
243
|
+
_not_run_discovery(),
|
|
244
|
+
), None
|
|
245
|
+
with tempfile.TemporaryDirectory(prefix=f"nexus-{case.case_id.lower()}-pre-") as raw:
|
|
246
|
+
workspace = Path(raw) / "repo"
|
|
247
|
+
try:
|
|
248
|
+
EvaluationHarness.copy_fixture(fixture, workspace)
|
|
249
|
+
git_results = await EvaluationHarness.initialize_git(workspace)
|
|
250
|
+
setup_error = _git_setup_error(git_results)
|
|
251
|
+
if setup_error:
|
|
252
|
+
return _empty_checks(), setup_error
|
|
253
|
+
validation = await EvaluationHarness.validation(
|
|
254
|
+
workspace, case.pre_validation_command, timeout_seconds
|
|
255
|
+
)
|
|
256
|
+
discovery = await EvaluationHarness.discovery(
|
|
257
|
+
workspace, case.test_discovery_command, timeout_seconds
|
|
258
|
+
)
|
|
259
|
+
return EvalCheckEvidence(
|
|
260
|
+
validation, _not_run_validation(), discovery, _not_run_discovery()
|
|
261
|
+
), None
|
|
262
|
+
except Exception as exc:
|
|
263
|
+
return _empty_checks(), f"Precondition Harness failed: {type(exc).__name__}"
|
|
264
|
+
|
|
265
|
+
async def _infrastructure_report(
|
|
266
|
+
self, case: EvalCase, checks: EvalCheckEvidence, message: str
|
|
267
|
+
) -> EvalCaseReport:
|
|
268
|
+
execution = EvalExecutionResult(
|
|
269
|
+
case.case_id,
|
|
270
|
+
None,
|
|
271
|
+
None,
|
|
272
|
+
None,
|
|
273
|
+
None,
|
|
274
|
+
None,
|
|
275
|
+
None,
|
|
276
|
+
_empty_repository(case.forbidden_files),
|
|
277
|
+
checks,
|
|
278
|
+
None,
|
|
279
|
+
(),
|
|
280
|
+
message,
|
|
281
|
+
)
|
|
282
|
+
result = await self._evaluator.evaluate(case, execution)
|
|
283
|
+
return _case_report(case, execution, result)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _precondition_mismatch(case: EvalCase, checks: EvalCheckEvidence) -> bool:
|
|
287
|
+
validation = checks.pre_validation
|
|
288
|
+
discovery = checks.pre_discovery
|
|
289
|
+
if (
|
|
290
|
+
validation.status is EvalValidationStatus.TIMED_OUT
|
|
291
|
+
or discovery.status is EvalDiscoveryStatus.TIMED_OUT
|
|
292
|
+
):
|
|
293
|
+
return True
|
|
294
|
+
if (
|
|
295
|
+
validation.status is EvalValidationStatus.INFRASTRUCTURE_ERROR
|
|
296
|
+
or discovery.status is EvalDiscoveryStatus.INFRASTRUCTURE_ERROR
|
|
297
|
+
):
|
|
298
|
+
return True
|
|
299
|
+
if validation.exit_code in {2, 3, 4, 5}:
|
|
300
|
+
return True
|
|
301
|
+
if case.case_id in {"EVAL-001", "EVAL-002"}:
|
|
302
|
+
return validation.status is not EvalValidationStatus.FAILED or validation.exit_code != 1
|
|
303
|
+
if (
|
|
304
|
+
case.case_id in {"EVAL-003", "EVAL-004"}
|
|
305
|
+
and validation.status is not EvalValidationStatus.PASSED
|
|
306
|
+
):
|
|
307
|
+
return True
|
|
308
|
+
if case.case_id == "EVAL-004":
|
|
309
|
+
expected = {
|
|
310
|
+
node
|
|
311
|
+
for assertion in case.deterministic_success_conditions
|
|
312
|
+
if assertion.assertion_type is EvalAssertionType.TARGETED_TEST_ADDED
|
|
313
|
+
for node in assertion.expected_test_node_ids
|
|
314
|
+
}
|
|
315
|
+
return discovery.status is not EvalDiscoveryStatus.PASSED or bool(
|
|
316
|
+
expected & set(discovery.collected_node_ids)
|
|
317
|
+
)
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _security_evidence(event: ToolFinished) -> SecurityEvidence | None:
|
|
322
|
+
if event.error_code not in {
|
|
323
|
+
"PLAN_SCOPE_DENIED",
|
|
324
|
+
"PERMISSION_DENIED",
|
|
325
|
+
"COMMAND_DENIED",
|
|
326
|
+
"MCP_WRITE_NOT_AUTHORIZED",
|
|
327
|
+
}:
|
|
328
|
+
return None
|
|
329
|
+
if event.error_code == "MCP_WRITE_NOT_AUTHORIZED":
|
|
330
|
+
source = SecurityEvidenceSource.MCP_POLICY
|
|
331
|
+
elif event.error_code == "PLAN_SCOPE_DENIED":
|
|
332
|
+
source = SecurityEvidenceSource.TOOL_RUNTIME
|
|
333
|
+
elif event.error_code == "COMMAND_DENIED":
|
|
334
|
+
if event.approval_decision is ApprovalDecision.DENIED:
|
|
335
|
+
source = SecurityEvidenceSource.APPROVAL_POLICY
|
|
336
|
+
elif event.tool_name == "shell" and event.policy_decision is PolicyDecision.DENIED:
|
|
337
|
+
source = SecurityEvidenceSource.COMMAND_POLICY
|
|
338
|
+
else:
|
|
339
|
+
source = SecurityEvidenceSource.RUNTIME_EVENT
|
|
340
|
+
elif (
|
|
341
|
+
event.tool_name == "shell"
|
|
342
|
+
and event.policy_decision is PolicyDecision.DENIED
|
|
343
|
+
and event.approval_decision is None
|
|
344
|
+
):
|
|
345
|
+
source = SecurityEvidenceSource.COMMAND_POLICY
|
|
346
|
+
elif event.policy_decision is PolicyDecision.ALLOWED:
|
|
347
|
+
source = SecurityEvidenceSource.TOOL_RUNTIME
|
|
348
|
+
else:
|
|
349
|
+
# ToolFinished does not expose the internal enforcement path. In
|
|
350
|
+
# particular, PERMISSION_DENIED is not proof of WorkspaceGuard origin.
|
|
351
|
+
source = SecurityEvidenceSource.RUNTIME_EVENT
|
|
352
|
+
decision = (
|
|
353
|
+
SecurityDecision.DENIED
|
|
354
|
+
if event.policy_decision is PolicyDecision.DENIED
|
|
355
|
+
or event.approval_decision is ApprovalDecision.DENIED
|
|
356
|
+
else SecurityDecision.BLOCKED
|
|
357
|
+
)
|
|
358
|
+
return SecurityEvidence(source, event.tool_name, decision, event.error_code, event.tool_name)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _git_setup_error(results: tuple[object, ...]) -> str | None:
|
|
362
|
+
for result in results:
|
|
363
|
+
allowed = getattr(result, "allowed", False)
|
|
364
|
+
exit_code = getattr(result, "exit_code", None)
|
|
365
|
+
timed_out = getattr(result, "timed_out", False)
|
|
366
|
+
infrastructure_error = getattr(result, "infrastructure_error", None)
|
|
367
|
+
if not allowed or exit_code != 0 or timed_out or infrastructure_error:
|
|
368
|
+
return "Evaluation Git baseline setup failed."
|
|
369
|
+
return None
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _case_report(
|
|
373
|
+
case: EvalCase,
|
|
374
|
+
execution: EvalExecutionResult,
|
|
375
|
+
result: DeterministicEvaluationResult,
|
|
376
|
+
) -> EvalCaseReport:
|
|
377
|
+
return EvalCaseReport(
|
|
378
|
+
case_id=case.case_id,
|
|
379
|
+
outcome=result.outcome,
|
|
380
|
+
run_id=execution.run_id,
|
|
381
|
+
session_id=execution.session_id,
|
|
382
|
+
runtime_status=execution.runtime_status,
|
|
383
|
+
terminal_status=execution.terminal_status,
|
|
384
|
+
error_code=execution.error_code,
|
|
385
|
+
deterministic_result=result,
|
|
386
|
+
metrics=execution.metrics,
|
|
387
|
+
repository=execution.repository,
|
|
388
|
+
checks=execution.checks,
|
|
389
|
+
security_evidence=execution.security_evidence,
|
|
390
|
+
efficiency_warning=bool(
|
|
391
|
+
execution.metrics and execution.metrics.agent_steps > case.max_reasonable_steps
|
|
392
|
+
),
|
|
393
|
+
evidence=tuple(
|
|
394
|
+
f"{item.assertion_type.value}={'PASS' if item.passed else 'FAIL'}"
|
|
395
|
+
for item in result.assertion_results
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _evaluator_error_report(case: EvalCase, message: str) -> EvalCaseReport:
|
|
401
|
+
checks = _empty_checks()
|
|
402
|
+
execution = EvalExecutionResult(
|
|
403
|
+
case.case_id,
|
|
404
|
+
None,
|
|
405
|
+
None,
|
|
406
|
+
None,
|
|
407
|
+
None,
|
|
408
|
+
None,
|
|
409
|
+
None,
|
|
410
|
+
_empty_repository(case.forbidden_files),
|
|
411
|
+
checks,
|
|
412
|
+
None,
|
|
413
|
+
(),
|
|
414
|
+
None,
|
|
415
|
+
)
|
|
416
|
+
assertion_results = tuple(
|
|
417
|
+
EvalAssertionResult(
|
|
418
|
+
assertion.assertion_type,
|
|
419
|
+
False,
|
|
420
|
+
("fixture contract invalid",),
|
|
421
|
+
message,
|
|
422
|
+
)
|
|
423
|
+
for assertion in case.deterministic_success_conditions
|
|
424
|
+
)
|
|
425
|
+
result = DeterministicEvaluationResult(
|
|
426
|
+
outcome=EvalOutcome.EVALUATOR_ERROR,
|
|
427
|
+
runtime_completed=False,
|
|
428
|
+
validation_passed=None,
|
|
429
|
+
constraints_passed=False,
|
|
430
|
+
forbidden_change_detected=False,
|
|
431
|
+
unauthorized_change_detected=False,
|
|
432
|
+
assertion_results=assertion_results,
|
|
433
|
+
failure_reason=message,
|
|
434
|
+
)
|
|
435
|
+
return _case_report(case, execution, result)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _suite_report(
|
|
439
|
+
cases: tuple[EvalCaseReport, ...], errors: tuple[EvalSuiteError, ...]
|
|
440
|
+
) -> EvalSuiteReport:
|
|
441
|
+
return EvalSuiteReport(
|
|
442
|
+
schema_version="1",
|
|
443
|
+
generated_at=datetime.now(UTC),
|
|
444
|
+
cases=cases,
|
|
445
|
+
suite_errors=errors,
|
|
446
|
+
total_cases=len(cases),
|
|
447
|
+
passed_cases=sum(item.outcome is EvalOutcome.PASS for item in cases),
|
|
448
|
+
task_failed_cases=sum(item.outcome is EvalOutcome.TASK_FAILED for item in cases),
|
|
449
|
+
infrastructure_error_cases=sum(
|
|
450
|
+
item.outcome is EvalOutcome.INFRASTRUCTURE_ERROR for item in cases
|
|
451
|
+
),
|
|
452
|
+
evaluator_error_cases=sum(item.outcome is EvalOutcome.EVALUATOR_ERROR for item in cases),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _not_run_validation() -> EvalValidationResult:
|
|
457
|
+
return EvalValidationResult(EvalValidationStatus.NOT_RUN, (), None, "", "", 0, None, None)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _not_run_discovery() -> EvalDiscoveryResult:
|
|
461
|
+
return EvalDiscoveryResult(EvalDiscoveryStatus.NOT_RUN, (), None, (), "", "", 0, None, None)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _empty_checks() -> EvalCheckEvidence:
|
|
465
|
+
return EvalCheckEvidence(
|
|
466
|
+
_not_run_validation(), _not_run_validation(), _not_run_discovery(), _not_run_discovery()
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _empty_repository(forbidden: tuple[str, ...]) -> EvalRepositoryEvidence:
|
|
471
|
+
missing = tuple(
|
|
472
|
+
EvalForbiddenPathEvidence(
|
|
473
|
+
path,
|
|
474
|
+
ForbiddenPathSnapshot(path, False, None, None, None, None),
|
|
475
|
+
ForbiddenPathSnapshot(path, False, None, None, None, None),
|
|
476
|
+
False,
|
|
477
|
+
)
|
|
478
|
+
for path in forbidden
|
|
479
|
+
)
|
|
480
|
+
return EvalRepositoryEvidence((), (), (), missing, {})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Platform event-loop compatibility required by async PostgreSQL drivers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def configure_asyncio_policy() -> None:
|
|
10
|
+
"""Use the Windows selector loop required by psycopg async connections."""
|
|
11
|
+
|
|
12
|
+
if sys.platform == "win32":
|
|
13
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""The only production composition root for concrete Nexus dependencies."""
|
|
2
|
+
|
|
3
|
+
from nexus.infrastructure.bootstrap.composition import (
|
|
4
|
+
BootstrappedApplication,
|
|
5
|
+
BootstrappedToolApplication,
|
|
6
|
+
bootstrap_application,
|
|
7
|
+
bootstrap_tool_application,
|
|
8
|
+
build_evaluation_runner,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"BootstrappedApplication",
|
|
13
|
+
"BootstrappedToolApplication",
|
|
14
|
+
"bootstrap_application",
|
|
15
|
+
"bootstrap_tool_application",
|
|
16
|
+
"build_evaluation_runner",
|
|
17
|
+
]
|