|
|
@@ -13,6 +13,7 @@ from agent.orchestration import ArtifactRef
|
|
|
|
|
|
from script_build_host.domain.digests import Sha256Digest
|
|
|
from script_build_host.domain.errors import ScriptBuildError
|
|
|
+from script_build_host.domain.goal_coverage import MAX_DIRECTION_GOALS
|
|
|
|
|
|
_CONTROL_URI_PREFIX = "script-build://"
|
|
|
_ARTIFACT_URI_PREFIX = "script-build://artifact-versions/"
|
|
|
@@ -250,6 +251,7 @@ class ScriptTaskContractV1:
|
|
|
output_schema: str
|
|
|
criteria: tuple[ScriptCriterion, ...]
|
|
|
budget: ScriptTaskBudget
|
|
|
+ goal_ids: tuple[str, ...]
|
|
|
supersedes_decision_ids: tuple[str, ...] = ()
|
|
|
candidate_closure_decision_refs: tuple[AcceptedDecisionRef, ...] = ()
|
|
|
adopted_decision_ids: tuple[str, ...] = ()
|
|
|
@@ -270,6 +272,15 @@ class ScriptTaskContractV1:
|
|
|
"TASK_CONTRACT_INVALID", f"criteria must contain between 1 and {MAX_CRITERIA} items"
|
|
|
)
|
|
|
_require_unique((item.criterion_id for item in self.criteria), "criterion IDs")
|
|
|
+ if len(self.goal_ids) > MAX_DIRECTION_GOALS:
|
|
|
+ raise TaskContractError(
|
|
|
+ "GOAL_SCOPE_INVALID",
|
|
|
+ f"goal_ids may contain at most {MAX_DIRECTION_GOALS} values",
|
|
|
+ )
|
|
|
+ if any(not item.strip() for item in self.goal_ids) or len(set(self.goal_ids)) != len(
|
|
|
+ self.goal_ids
|
|
|
+ ):
|
|
|
+ raise TaskContractError("GOAL_SCOPE_INVALID", "goal_ids must be nonblank and unique")
|
|
|
for item in self.write_scope:
|
|
|
_require_control_uri(item, "write_scope")
|
|
|
_require_unique(self.write_scope, "write_scope")
|
|
|
@@ -417,6 +428,7 @@ class ScriptTaskContractV1:
|
|
|
"output_schema": self.output_schema,
|
|
|
"criteria": [item.to_payload() for item in self.criteria],
|
|
|
"budget": self.budget.to_payload(),
|
|
|
+ "goal_ids": list(self.goal_ids),
|
|
|
"supersedes_decision_ids": list(self.supersedes_decision_ids),
|
|
|
"candidate_closure_decision_refs": [
|
|
|
item.to_payload() for item in self.candidate_closure_decision_refs
|
|
|
@@ -443,6 +455,8 @@ class ScriptTaskContractV1:
|
|
|
budget = value.get("budget")
|
|
|
if not isinstance(budget, Mapping):
|
|
|
raise TaskContractError("TASK_CONTRACT_INVALID", "task budget must be an object")
|
|
|
+ if "goal_ids" not in value:
|
|
|
+ raise TaskContractError("GOAL_SCOPE_INVALID", "goal_ids is required")
|
|
|
return cls(
|
|
|
task_kind=_enum_value(ScriptTaskKind, value.get("task_kind"), "task_kind"),
|
|
|
scope_ref=str(value.get("scope_ref", "")),
|
|
|
@@ -455,6 +469,7 @@ class ScriptTaskContractV1:
|
|
|
output_schema=str(value.get("output_schema", "")),
|
|
|
criteria=_criteria(value.get("criteria")),
|
|
|
budget=ScriptTaskBudget.from_payload(budget),
|
|
|
+ goal_ids=_strings(value.get("goal_ids"), "goal_ids"),
|
|
|
supersedes_decision_ids=_strings(
|
|
|
value.get("supersedes_decision_ids"), "supersedes_decision_ids"
|
|
|
),
|