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.
Files changed (131) hide show
  1. nexus/__init__.py +4 -0
  2. nexus/application/__init__.py +2 -0
  3. nexus/application/approval_service.py +130 -0
  4. nexus/application/diff_service.py +177 -0
  5. nexus/application/event_publisher.py +180 -0
  6. nexus/application/execution_context.py +29 -0
  7. nexus/application/execution_ledger.py +126 -0
  8. nexus/application/model_call_context.py +29 -0
  9. nexus/application/observed_model_gateway.py +194 -0
  10. nexus/application/plan_approval_service.py +250 -0
  11. nexus/application/planning.py +668 -0
  12. nexus/application/runtime.py +624 -0
  13. nexus/application/session_service.py +411 -0
  14. nexus/application/structured_output.py +96 -0
  15. nexus/application/telemetry.py +396 -0
  16. nexus/application/telemetry_subscriber.py +216 -0
  17. nexus/application/tool_runtime.py +496 -0
  18. nexus/application/tracing_dispatcher.py +283 -0
  19. nexus/application/validation.py +293 -0
  20. nexus/config/__init__.py +17 -0
  21. nexus/config/loader.py +253 -0
  22. nexus/config/models.py +310 -0
  23. nexus/context/__init__.py +6 -0
  24. nexus/context/budget.py +24 -0
  25. nexus/context/builder.py +92 -0
  26. nexus/context/chunking.py +65 -0
  27. nexus/context/explorer.py +246 -0
  28. nexus/context/integration.py +63 -0
  29. nexus/context/manager.py +328 -0
  30. nexus/context/retrieval.py +261 -0
  31. nexus/domain/__init__.py +2 -0
  32. nexus/domain/agent_decision.py +113 -0
  33. nexus/domain/agent_state.py +49 -0
  34. nexus/domain/approvals.py +64 -0
  35. nexus/domain/context.py +108 -0
  36. nexus/domain/exploration.py +86 -0
  37. nexus/domain/mcp.py +51 -0
  38. nexus/domain/model.py +173 -0
  39. nexus/domain/observability.py +326 -0
  40. nexus/domain/persistence.py +122 -0
  41. nexus/domain/planning.py +329 -0
  42. nexus/domain/ports/__init__.py +61 -0
  43. nexus/domain/ports/agent_decision.py +9 -0
  44. nexus/domain/ports/approval_unit_of_work.py +27 -0
  45. nexus/domain/ports/checkpoint_provider.py +11 -0
  46. nexus/domain/ports/context.py +71 -0
  47. nexus/domain/ports/events.py +21 -0
  48. nexus/domain/ports/graph_runtime.py +30 -0
  49. nexus/domain/ports/mcp.py +23 -0
  50. nexus/domain/ports/model_gateway.py +21 -0
  51. nexus/domain/ports/model_input_budget.py +15 -0
  52. nexus/domain/ports/planning.py +62 -0
  53. nexus/domain/ports/repositories.py +54 -0
  54. nexus/domain/ports/repository_context.py +18 -0
  55. nexus/domain/ports/session_unit_of_work.py +33 -0
  56. nexus/domain/ports/skills.py +41 -0
  57. nexus/domain/ports/tooling.py +39 -0
  58. nexus/domain/ports/tracer.py +13 -0
  59. nexus/domain/ports/validation.py +32 -0
  60. nexus/domain/runtime_events.py +445 -0
  61. nexus/domain/skills.py +129 -0
  62. nexus/domain/tooling.py +163 -0
  63. nexus/domain/validation.py +161 -0
  64. nexus/errors/__init__.py +82 -0
  65. nexus/evaluation/__init__.py +7 -0
  66. nexus/evaluation/assertions.py +4 -0
  67. nexus/evaluation/evaluator.py +263 -0
  68. nexus/evaluation/fixtures.py +36 -0
  69. nexus/evaluation/harness.py +606 -0
  70. nexus/evaluation/loader.py +415 -0
  71. nexus/evaluation/metrics.py +27 -0
  72. nexus/evaluation/models.py +362 -0
  73. nexus/evaluation/report.py +43 -0
  74. nexus/evaluation/runner.py +480 -0
  75. nexus/infrastructure/__init__.py +2 -0
  76. nexus/infrastructure/asyncio_compat.py +13 -0
  77. nexus/infrastructure/bootstrap/__init__.py +17 -0
  78. nexus/infrastructure/bootstrap/composition.py +663 -0
  79. nexus/infrastructure/checkpoint/__init__.py +5 -0
  80. nexus/infrastructure/checkpoint/postgres.py +62 -0
  81. nexus/infrastructure/database.py +39 -0
  82. nexus/infrastructure/embedding.py +61 -0
  83. nexus/infrastructure/graph/__init__.py +6 -0
  84. nexus/infrastructure/graph/day4_runtime.py +966 -0
  85. nexus/infrastructure/graph/langgraph_runtime.py +172 -0
  86. nexus/infrastructure/mcp/__init__.py +5 -0
  87. nexus/infrastructure/mcp/manager.py +380 -0
  88. nexus/infrastructure/model_gateway/__init__.py +6 -0
  89. nexus/infrastructure/model_gateway/openai_compatible.py +345 -0
  90. nexus/infrastructure/observability/__init__.py +7 -0
  91. nexus/infrastructure/observability/console.py +31 -0
  92. nexus/infrastructure/observability/langsmith.py +236 -0
  93. nexus/infrastructure/observability/structured_logger.py +70 -0
  94. nexus/infrastructure/persistence/__init__.py +13 -0
  95. nexus/infrastructure/persistence/approval_unit_of_work.py +74 -0
  96. nexus/infrastructure/persistence/models.py +146 -0
  97. nexus/infrastructure/persistence/repositories.py +326 -0
  98. nexus/infrastructure/persistence/unit_of_work.py +82 -0
  99. nexus/infrastructure/repository_files.py +117 -0
  100. nexus/infrastructure/sandbox/__init__.py +5 -0
  101. nexus/infrastructure/sandbox/local_process.py +353 -0
  102. nexus/infrastructure/semantic.py +276 -0
  103. nexus/infrastructure/semantic_models.py +56 -0
  104. nexus/interfaces/__init__.py +2 -0
  105. nexus/interfaces/cli/__init__.py +2 -0
  106. nexus/interfaces/cli/__main__.py +6 -0
  107. nexus/interfaces/cli/app.py +266 -0
  108. nexus/interfaces/cli/renderer.py +118 -0
  109. nexus/security/__init__.py +14 -0
  110. nexus/security/approval_policies.py +73 -0
  111. nexus/security/command_policy.py +117 -0
  112. nexus/security/executables.py +107 -0
  113. nexus/security/git_commands.py +65 -0
  114. nexus/security/workspace.py +93 -0
  115. nexus/skills/__init__.py +7 -0
  116. nexus/skills/builtin/__init__.py +1 -0
  117. nexus/skills/builtin/debug-python/SKILL.md +26 -0
  118. nexus/skills/builtin/review-repository/SKILL.md +26 -0
  119. nexus/skills/builtin/write-tests/SKILL.md +26 -0
  120. nexus/skills/loader.py +439 -0
  121. nexus/skills/registry.py +146 -0
  122. nexus/skills/selector.py +152 -0
  123. nexus/tools/__init__.py +6 -0
  124. nexus/tools/editing.py +312 -0
  125. nexus/tools/mcp.py +160 -0
  126. nexus/tools/native.py +654 -0
  127. nexus/tools/registry.py +27 -0
  128. nexus_coding_agent-0.1.0.dist-info/METADATA +142 -0
  129. nexus_coding_agent-0.1.0.dist-info/RECORD +131 -0
  130. nexus_coding_agent-0.1.0.dist-info/WHEEL +4 -0
  131. nexus_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
nexus/domain/skills.py ADDED
@@ -0,0 +1,129 @@
1
+ """Frozen Day 7 Skill values."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+ from enum import StrEnum
8
+ from pathlib import PurePosixPath
9
+
10
+ _SKILL_ID = re.compile(r"^[a-z][a-z0-9-]{0,63}$")
11
+ _SEMVER_CORE = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$")
12
+
13
+
14
+ class SkillSource(StrEnum):
15
+ REPOSITORY = "repository"
16
+ USER_GLOBAL = "user_global"
17
+ BUILTIN = "builtin"
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class SkillLocation:
22
+ source: SkillSource
23
+ relative_path: str
24
+
25
+ def __post_init__(self) -> None:
26
+ if not isinstance(self.source, SkillSource) or not isinstance(self.relative_path, str):
27
+ raise ValueError("Skill location fields are invalid.")
28
+ path = PurePosixPath(self.relative_path)
29
+ if (
30
+ path.is_absolute()
31
+ or len(path.parts) != 2
32
+ or path.parts[-1] != "SKILL.md"
33
+ or not _SKILL_ID.fullmatch(path.parts[0])
34
+ or self.relative_path != path.as_posix()
35
+ ):
36
+ raise ValueError("Skill location must use <skill-id>/SKILL.md.")
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class SkillMetadata:
41
+ skill_id: str
42
+ name: str
43
+ description: str
44
+ usage_scenario: str
45
+ source: SkillSource
46
+ priority: int
47
+ version: str
48
+ location: SkillLocation
49
+
50
+ def __post_init__(self) -> None:
51
+ if not isinstance(self.skill_id, str) or not _SKILL_ID.fullmatch(self.skill_id):
52
+ raise ValueError("Skill ID is invalid.")
53
+ for field_name, value, maximum in (
54
+ ("name", self.name, 100),
55
+ ("description", self.description, 500),
56
+ ("usage_scenario", self.usage_scenario, 500),
57
+ ):
58
+ if not isinstance(value, str):
59
+ raise ValueError(f"Skill {field_name} is invalid.")
60
+ normalized = value.strip()
61
+ if not normalized or len(normalized) > maximum:
62
+ raise ValueError(f"Skill {field_name} is invalid.")
63
+ object.__setattr__(self, field_name, normalized)
64
+ if (
65
+ isinstance(self.priority, bool)
66
+ or not isinstance(self.priority, int)
67
+ or not 0 <= self.priority <= 100
68
+ ):
69
+ raise ValueError("Skill priority must be an integer from 0 through 100.")
70
+ if not isinstance(self.version, str) or not _SEMVER_CORE.fullmatch(self.version):
71
+ raise ValueError("Skill version must use SemVer core form.")
72
+ if not isinstance(self.location, SkillLocation) or not isinstance(
73
+ self.source, SkillSource
74
+ ):
75
+ raise ValueError("Skill source and location are invalid.")
76
+ if self.location.source is not self.source:
77
+ raise ValueError("Skill source and location source must match.")
78
+ if PurePosixPath(self.location.relative_path).parts[0] != self.skill_id:
79
+ raise ValueError("Skill directory and ID must match.")
80
+
81
+
82
+ @dataclass(frozen=True, slots=True)
83
+ class SelectedSkill:
84
+ metadata: SkillMetadata
85
+ body: str
86
+ selected_reason: str
87
+
88
+ def __post_init__(self) -> None:
89
+ if (
90
+ not isinstance(self.body, str)
91
+ or not self.body.strip()
92
+ or "\r" in self.body
93
+ or not self.body.endswith("\n")
94
+ or self.body.endswith("\n\n")
95
+ ):
96
+ raise ValueError("Selected Skill body is not normalized.")
97
+ normalized_reason = _selection_summary(self.selected_reason)
98
+ object.__setattr__(self, "selected_reason", normalized_reason)
99
+
100
+
101
+ @dataclass(frozen=True, slots=True)
102
+ class SkillSelectionResult:
103
+ selected_skill_ids: tuple[str, ...]
104
+ selection_reason_summary: str
105
+
106
+ def __post_init__(self) -> None:
107
+ if isinstance(self.selected_skill_ids, str):
108
+ raise ValueError("Selected Skill IDs must be a sequence of identities.")
109
+ skill_ids = tuple(self.selected_skill_ids)
110
+ if len(skill_ids) > 2 or len(skill_ids) != len(set(skill_ids)) or any(
111
+ not isinstance(skill_id, str) or not _SKILL_ID.fullmatch(skill_id)
112
+ for skill_id in skill_ids
113
+ ):
114
+ raise ValueError("Selected Skill IDs must be valid and unique.")
115
+ object.__setattr__(self, "selected_skill_ids", skill_ids)
116
+ object.__setattr__(
117
+ self,
118
+ "selection_reason_summary",
119
+ _selection_summary(self.selection_reason_summary),
120
+ )
121
+
122
+
123
+ def _selection_summary(value: str) -> str:
124
+ if not isinstance(value, str):
125
+ raise ValueError("Skill selection summary is invalid.")
126
+ normalized = value.strip()
127
+ if not normalized or len(normalized) > 1000:
128
+ raise ValueError("Skill selection summary is invalid.")
129
+ return normalized
@@ -0,0 +1,163 @@
1
+ """Nexus-owned Day 3 tool, policy, and sandbox values."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import StrEnum
7
+ from uuid import UUID
8
+
9
+ JsonObject = dict[str, object]
10
+
11
+
12
+ class RiskLevel(StrEnum):
13
+ SAFE = "SAFE"
14
+ WRITE = "WRITE"
15
+ DANGEROUS = "DANGEROUS"
16
+
17
+
18
+ class ApprovalDecision(StrEnum):
19
+ PENDING = "PENDING"
20
+ APPROVED = "APPROVED"
21
+ DENIED = "DENIED"
22
+
23
+
24
+ class PolicyDecision(StrEnum):
25
+ ALLOWED = "ALLOWED"
26
+ DENIED = "DENIED"
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class ToolInvocation:
31
+ invocation_id: str
32
+ tool_name: str
33
+ arguments: JsonObject
34
+ run_id: str
35
+ session_id: str
36
+
37
+ def __post_init__(self) -> None:
38
+ _validate_uuid(self.invocation_id, "invocation_id")
39
+ _validate_uuid(self.run_id, "run_id")
40
+ _validate_uuid(self.session_id, "session_id")
41
+ if not self.tool_name:
42
+ raise ValueError("tool_name must not be empty.")
43
+ if any(not isinstance(key, str) for key in self.arguments):
44
+ raise ValueError("Tool argument keys must be strings.")
45
+ if not _is_json_value(self.arguments):
46
+ raise ValueError("Tool arguments must contain only JSON-compatible values.")
47
+ object.__setattr__(self, "arguments", dict(self.arguments))
48
+
49
+
50
+ @dataclass(frozen=True, slots=True)
51
+ class ToolError:
52
+ code: str
53
+ message: str
54
+ retryable: bool
55
+
56
+ def __post_init__(self) -> None:
57
+ if not self.code or not self.message:
58
+ raise ValueError("ToolError code and message must not be empty.")
59
+
60
+
61
+ @dataclass(frozen=True, slots=True)
62
+ class ToolResult:
63
+ invocation_id: str
64
+ tool_name: str
65
+ success: bool
66
+ output: JsonObject | None
67
+ error: ToolError | None
68
+ risk_level: RiskLevel
69
+ policy_decision: PolicyDecision
70
+ approval_decision: ApprovalDecision | None
71
+ duration_ms: int
72
+
73
+ def __post_init__(self) -> None:
74
+ _validate_uuid(self.invocation_id, "invocation_id")
75
+ if not self.tool_name:
76
+ raise ValueError("tool_name must not be empty.")
77
+ if self.duration_ms < 0:
78
+ raise ValueError("duration_ms must not be negative.")
79
+ if self.success and self.error is not None:
80
+ raise ValueError("A successful ToolResult cannot contain an error.")
81
+ if self.success and self.policy_decision is not PolicyDecision.ALLOWED:
82
+ raise ValueError("A successful ToolResult must be allowed by policy.")
83
+ if not self.success and self.error is None:
84
+ raise ValueError("A failed ToolResult must contain a ToolError.")
85
+ if self.output is not None and not _is_json_value(self.output):
86
+ raise ValueError("Tool output must contain only JSON-compatible values.")
87
+ if self.output is not None:
88
+ object.__setattr__(self, "output", dict(self.output))
89
+
90
+
91
+ @dataclass(frozen=True, slots=True)
92
+ class SandboxRequest:
93
+ operation: str
94
+ argv: list[str]
95
+ cwd: str
96
+ timeout_seconds: float
97
+
98
+ def __post_init__(self) -> None:
99
+ if not self.operation:
100
+ raise ValueError("Sandbox operation must not be empty.")
101
+ if not self.argv or any(
102
+ not isinstance(item, str) or not item for item in self.argv
103
+ ):
104
+ raise ValueError("Sandbox argv must contain non-empty strings.")
105
+ if not self.cwd:
106
+ raise ValueError("Sandbox cwd must not be empty.")
107
+ if not 0 < self.timeout_seconds <= 300.0:
108
+ raise ValueError("Sandbox timeout must be in (0, 300].")
109
+ object.__setattr__(self, "argv", list(self.argv))
110
+
111
+
112
+ @dataclass(frozen=True, slots=True)
113
+ class SandboxResult:
114
+ argv: list[str]
115
+ cwd: str
116
+ risk_level: RiskLevel
117
+ policy_decision: PolicyDecision
118
+ exit_code: int | None
119
+ stdout: str
120
+ stderr: str
121
+ duration_ms: int
122
+ timed_out: bool
123
+ output_truncated: bool
124
+ error: ToolError | None
125
+
126
+ def __post_init__(self) -> None:
127
+ if self.duration_ms < 0:
128
+ raise ValueError("duration_ms must not be negative.")
129
+ if self.policy_decision is PolicyDecision.DENIED and self.exit_code is not None:
130
+ raise ValueError("A denied SandboxResult cannot have an exit code.")
131
+ if self.timed_out and self.exit_code is not None:
132
+ raise ValueError("A timed-out SandboxResult cannot have an exit code.")
133
+ object.__setattr__(self, "argv", list(self.argv))
134
+
135
+
136
+ def risk_rank(risk: RiskLevel) -> int:
137
+ return {
138
+ RiskLevel.SAFE: 0,
139
+ RiskLevel.WRITE: 1,
140
+ RiskLevel.DANGEROUS: 2,
141
+ }[risk]
142
+
143
+
144
+ def _validate_uuid(value: str, field_name: str) -> None:
145
+ try:
146
+ parsed = UUID(value)
147
+ except ValueError as exc:
148
+ raise ValueError(f"{field_name} must be a UUID string.") from exc
149
+ if str(parsed) != value:
150
+ raise ValueError(f"{field_name} must use canonical UUID text.")
151
+
152
+
153
+ def _is_json_value(value: object) -> bool:
154
+ if value is None or isinstance(value, (str, int, float, bool)):
155
+ return True
156
+ if isinstance(value, list):
157
+ return all(_is_json_value(item) for item in value)
158
+ if isinstance(value, dict):
159
+ return all(
160
+ isinstance(key, str) and _is_json_value(item)
161
+ for key, item in value.items()
162
+ )
163
+ return False
@@ -0,0 +1,161 @@
1
+ """Day 4 validation values and deterministic repairability rules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import StrEnum
7
+ from uuid import UUID
8
+
9
+ from nexus.domain.tooling import JsonObject, ToolResult
10
+
11
+
12
+ class ValidationStatus(StrEnum):
13
+ PASS = "PASS"
14
+ FAIL = "FAIL"
15
+ UNKNOWN = "UNKNOWN"
16
+
17
+
18
+ class ValidationConfidence(StrEnum):
19
+ HIGH = "HIGH"
20
+ MEDIUM = "MEDIUM"
21
+ LOW = "LOW"
22
+
23
+
24
+ class ValidationCheckKind(StrEnum):
25
+ TEST = "TEST"
26
+ BUILD = "BUILD"
27
+ LINT = "LINT"
28
+ TYPE_CHECK = "TYPE_CHECK"
29
+ REPOSITORY_COMMAND = "REPOSITORY_COMMAND"
30
+ GENERATED_TARGETED_TEST = "GENERATED_TARGETED_TEST"
31
+ BASIC_EXECUTION = "BASIC_EXECUTION"
32
+ DIFF_INSPECTION = "DIFF_INSPECTION"
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class ValidationCheck:
37
+ check_id: str
38
+ sequence: int
39
+ kind: ValidationCheckKind
40
+ tool_name: str
41
+ arguments: JsonObject
42
+ selection_reason: str
43
+ required: bool
44
+
45
+ def __post_init__(self) -> None:
46
+ _validate_uuid(self.check_id, "check_id")
47
+ if self.sequence < 1 or not self.tool_name or not self.selection_reason.strip():
48
+ raise ValueError("ValidationCheck identity and description are invalid.")
49
+ if not _is_json_value(self.arguments):
50
+ raise ValueError("ValidationCheck arguments must be JSON-compatible.")
51
+ object.__setattr__(self, "arguments", dict(self.arguments))
52
+
53
+
54
+ @dataclass(frozen=True, slots=True)
55
+ class ValidationCheckResult:
56
+ check: ValidationCheck
57
+ status: ValidationStatus
58
+ tool_result: ToolResult | None
59
+ evidence_summary: str
60
+
61
+ def __post_init__(self) -> None:
62
+ if not self.evidence_summary.strip():
63
+ raise ValueError("Validation evidence_summary must not be empty.")
64
+ if self.tool_result is not None and (
65
+ self.tool_result.invocation_id != self.check.check_id
66
+ or self.tool_result.tool_name != self.check.tool_name
67
+ ):
68
+ raise ValueError("Validation Tool evidence has mismatched identity.")
69
+
70
+
71
+ @dataclass(frozen=True, slots=True)
72
+ class ValidationPlan:
73
+ checks: tuple[ValidationCheck, ...]
74
+
75
+ def __post_init__(self) -> None:
76
+ checks = tuple(self.checks)
77
+ if [check.sequence for check in checks] != list(range(1, len(checks) + 1)):
78
+ raise ValueError("ValidationCheck sequence must be contiguous from one.")
79
+ if len({check.check_id for check in checks}) != len(checks):
80
+ raise ValueError("ValidationCheck IDs must be unique.")
81
+ object.__setattr__(self, "checks", checks)
82
+
83
+
84
+ @dataclass(frozen=True, slots=True)
85
+ class ValidationResult:
86
+ selected_checks: tuple[ValidationCheck, ...]
87
+ executed_checks: tuple[ValidationCheckResult, ...]
88
+ status: ValidationStatus
89
+ confidence: ValidationConfidence
90
+ repairable: bool
91
+ repair_count: int
92
+ summary: str
93
+
94
+ def __post_init__(self) -> None:
95
+ object.__setattr__(self, "selected_checks", tuple(self.selected_checks))
96
+ object.__setattr__(self, "executed_checks", tuple(self.executed_checks))
97
+ if self.repair_count < 0 or not self.summary.strip():
98
+ raise ValueError("ValidationResult count and summary are invalid.")
99
+ if self.status is not ValidationStatus.FAIL and self.repairable:
100
+ raise ValueError("Only a failed ValidationResult may be repairable.")
101
+ if self.repairable != aggregate_repairable(self.status, self.executed_checks):
102
+ raise ValueError("ValidationResult repairable does not match frozen aggregation.")
103
+
104
+
105
+ _REPAIRABLE_KINDS = {
106
+ ValidationCheckKind.TEST,
107
+ ValidationCheckKind.BUILD,
108
+ ValidationCheckKind.LINT,
109
+ ValidationCheckKind.TYPE_CHECK,
110
+ ValidationCheckKind.GENERATED_TARGETED_TEST,
111
+ ValidationCheckKind.BASIC_EXECUTION,
112
+ }
113
+
114
+
115
+ def is_mechanically_repairable(result: ValidationCheckResult) -> bool:
116
+ tool_result = result.tool_result
117
+ return (
118
+ result.status is ValidationStatus.FAIL
119
+ and result.check.kind in _REPAIRABLE_KINDS
120
+ and tool_result is not None
121
+ and tool_result.error is not None
122
+ and tool_result.error.code == "COMMAND_EXIT_NONZERO"
123
+ )
124
+
125
+
126
+ def aggregate_repairable(
127
+ status: ValidationStatus,
128
+ results: tuple[ValidationCheckResult, ...],
129
+ ) -> bool:
130
+ if status is not ValidationStatus.FAIL:
131
+ return False
132
+ required_failures = tuple(
133
+ result
134
+ for result in results
135
+ if result.check.required and result.status is ValidationStatus.FAIL
136
+ )
137
+ return bool(required_failures) and all(
138
+ is_mechanically_repairable(result) for result in required_failures
139
+ )
140
+
141
+
142
+ def _validate_uuid(value: str, field_name: str) -> None:
143
+ try:
144
+ parsed = UUID(value)
145
+ except (AttributeError, TypeError, ValueError) as exc:
146
+ raise ValueError(f"{field_name} must be a canonical UUID string.") from exc
147
+ if str(parsed) != value:
148
+ raise ValueError(f"{field_name} must be a canonical UUID string.")
149
+
150
+
151
+ def _is_json_value(value: object) -> bool:
152
+ if value is None or isinstance(value, (str, int, float, bool)):
153
+ return True
154
+ if isinstance(value, list):
155
+ return all(_is_json_value(item) for item in value)
156
+ if isinstance(value, dict):
157
+ return all(
158
+ isinstance(key, str) and _is_json_value(item)
159
+ for key, item in value.items()
160
+ )
161
+ return False
@@ -0,0 +1,82 @@
1
+ """Nexus-owned error types exposed across stable boundaries."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class NexusError(Exception):
7
+ """Base class for safe, structured Nexus failures."""
8
+
9
+ default_code = "NEXUS_ERROR"
10
+ default_retryable = False
11
+
12
+ def __init__(
13
+ self,
14
+ message: str,
15
+ *,
16
+ code: str | None = None,
17
+ retryable: bool | None = None,
18
+ ) -> None:
19
+ super().__init__(message)
20
+ self.code = code or self.default_code
21
+ self.retryable = self.default_retryable if retryable is None else retryable
22
+
23
+
24
+ class ConfigurationError(NexusError):
25
+ """The resolved Nexus configuration is missing or invalid."""
26
+
27
+ default_code = "CONFIGURATION_ERROR"
28
+
29
+
30
+ class ModelError(NexusError):
31
+ """A model operation failed behind the ModelGateway boundary."""
32
+
33
+ default_code = "MODEL_ERROR"
34
+
35
+
36
+ class MCPError(NexusError):
37
+ """An MCP connection, discovery, or call failed behind the adapter boundary."""
38
+
39
+ default_code = "MCP_ERROR"
40
+
41
+
42
+ class SessionError(NexusError):
43
+ """A session operation failed a Nexus business rule or persistence boundary."""
44
+
45
+ default_code = "SESSION_ERROR"
46
+
47
+
48
+ class ToolExecutionError(NexusError):
49
+ """A tool operation failed behind the structured Tool boundary."""
50
+
51
+ default_code = "TOOL_EXECUTION_ERROR"
52
+
53
+
54
+ class ContextError(NexusError):
55
+ """Repository exploration or bounded context construction failed."""
56
+
57
+ default_code = "CONTEXT_ERROR"
58
+
59
+
60
+ class ValidationError(NexusError):
61
+ """Validation planning or execution could not produce truthful evidence."""
62
+
63
+ default_code = "VALIDATION_ERROR"
64
+
65
+
66
+ class PermissionDeniedError(NexusError):
67
+ """A centralized security policy denied an operation."""
68
+
69
+ default_code = "PERMISSION_DENIED"
70
+
71
+
72
+ __all__ = [
73
+ "ConfigurationError",
74
+ "ContextError",
75
+ "MCPError",
76
+ "ModelError",
77
+ "NexusError",
78
+ "PermissionDeniedError",
79
+ "SessionError",
80
+ "ToolExecutionError",
81
+ "ValidationError",
82
+ ]
@@ -0,0 +1,7 @@
1
+ """Nexus-owned deterministic evaluation subsystem."""
2
+
3
+ from nexus.evaluation.evaluator import DefaultDeterministicEvaluator
4
+ from nexus.evaluation.loader import EvalSuiteLoader
5
+ from nexus.evaluation.models import * # noqa: F403
6
+
7
+ __all__ = ["DefaultDeterministicEvaluator", "EvalSuiteLoader"]
@@ -0,0 +1,4 @@
1
+ """Assertion semantics consume only the typed evidence bundle.
2
+
3
+ Day 9 intentionally defines no second public assertion-engine interface.
4
+ """