forge-autonomy 2.3.1__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 (156) hide show
  1. forge/__init__.py +35 -0
  2. forge/__main__.py +20 -0
  3. forge/_version.py +38 -0
  4. forge/action_derivation_canary_closure.py +129 -0
  5. forge/action_derivation_evidence.py +101 -0
  6. forge/action_derivation_qualification.py +71 -0
  7. forge/action_derivation_reattempt.py +113 -0
  8. forge/ai_architect/__init__.py +5 -0
  9. forge/ai_architect/provider_registry.py +82 -0
  10. forge/architecture/__init__.py +8 -0
  11. forge/architecture/advisor.py +37 -0
  12. forge/architecture/presentation.py +21 -0
  13. forge/architecture/workspace.py +169 -0
  14. forge/autonomous_orchestrator.py +201 -0
  15. forge/business/__init__.py +9 -0
  16. forge/business/advisor.py +44 -0
  17. forge/business/ingress.py +186 -0
  18. forge/business/presentation.py +74 -0
  19. forge/business/workspace.py +141 -0
  20. forge/canary.py +134 -0
  21. forge/capabilities/__init__.py +11 -0
  22. forge/capabilities/delegation.py +150 -0
  23. forge/completion/__init__.py +5 -0
  24. forge/completion/assessor.py +53 -0
  25. forge/constitutional/__init__.py +5 -0
  26. forge/constitutional/assessor.py +38 -0
  27. forge/core/__init__.py +5 -0
  28. forge/core/json_store.py +24 -0
  29. forge/decision_evidence/__init__.py +5 -0
  30. forge/decision_evidence/repository.py +68 -0
  31. forge/dispatcher/__init__.py +13 -0
  32. forge/dispatcher/dispatcher.py +194 -0
  33. forge/execution/__init__.py +13 -0
  34. forge/execution/loop.py +346 -0
  35. forge/execution/presentation.py +14 -0
  36. forge/foundation/__init__.py +10 -0
  37. forge/foundation/loader.py +378 -0
  38. forge/generation_two.py +89 -0
  39. forge/generation_two_execution.py +179 -0
  40. forge/governance/__init__.py +26 -0
  41. forge/governance/execution_policy.py +83 -0
  42. forge/governance/profiles.py +93 -0
  43. forge/governance_authority.py +199 -0
  44. forge/intake.py +136 -0
  45. forge/integration/__init__.py +7 -0
  46. forge/integration/coordinator.py +100 -0
  47. forge/integration/repository.py +54 -0
  48. forge/knowledge/__init__.py +6 -0
  49. forge/knowledge/consumer.py +39 -0
  50. forge/knowledge/registry.py +65 -0
  51. forge/lifecycle/__init__.py +17 -0
  52. forge/lifecycle/store.py +445 -0
  53. forge/mission_amendment.py +35 -0
  54. forge/mission_scheduler.py +264 -0
  55. forge/models/__init__.py +561 -0
  56. forge/models/action.py +53 -0
  57. forge/models/action_derivation.py +284 -0
  58. forge/models/agent_policy.py +252 -0
  59. forge/models/ai_architect_provider.py +230 -0
  60. forge/models/ai_architect_session.py +147 -0
  61. forge/models/ai_provider_registry.py +140 -0
  62. forge/models/architecture_mission.py +113 -0
  63. forge/models/architecture_reasoning.py +257 -0
  64. forge/models/architecture_review.py +232 -0
  65. forge/models/codex_runtime_prompt.py +227 -0
  66. forge/models/constitutional_validation.py +109 -0
  67. forge/models/decision_evidence.py +202 -0
  68. forge/models/execution_host.py +289 -0
  69. forge/models/foundation.py +192 -0
  70. forge/models/historical_intent.py +212 -0
  71. forge/models/integration.py +173 -0
  72. forge/models/intent.py +265 -0
  73. forge/models/intent_authoring.py +119 -0
  74. forge/models/mission.py +301 -0
  75. forge/models/mission_candidate.py +108 -0
  76. forge/models/mission_planner.py +219 -0
  77. forge/models/mission_recommendation.py +222 -0
  78. forge/models/phase_completion.py +162 -0
  79. forge/models/planning.py +162 -0
  80. forge/models/producer.py +164 -0
  81. forge/models/prompt_artifact.py +234 -0
  82. forge/models/proposal.py +148 -0
  83. forge/models/runtime_prompt.py +245 -0
  84. forge/models/solution_template.py +64 -0
  85. forge/operator_identity.py +135 -0
  86. forge/planner/__init__.py +28 -0
  87. forge/planner/action_derivation.py +265 -0
  88. forge/planner/engine.py +49 -0
  89. forge/planner/openai_responses.py +660 -0
  90. forge/planner/provider_adapter.py +81 -0
  91. forge/planning/__init__.py +6 -0
  92. forge/planning/loader.py +232 -0
  93. forge/planning/registry.py +38 -0
  94. forge/portfolio_intelligence.py +110 -0
  95. forge/programme_authorization.py +194 -0
  96. forge/prompts/__init__.py +6 -0
  97. forge/prompts/codex_cli.py +121 -0
  98. forge/prompts/generator.py +106 -0
  99. forge/proposals/__init__.py +5 -0
  100. forge/proposals/generator.py +90 -0
  101. forge/provider_security.py +210 -0
  102. forge/qualification/__init__.py +13 -0
  103. forge/qualification/bootstrap_sequence.py +266 -0
  104. forge/qualification/generation_one.py +103 -0
  105. forge/qualification/installed_smoke.py +29 -0
  106. forge/recommendations/__init__.py +5 -0
  107. forge/recommendations/engine.py +113 -0
  108. forge/repository_truth.py +105 -0
  109. forge/review/__init__.py +5 -0
  110. forge/review/engine.py +80 -0
  111. forge/runtime/__init__.py +34 -0
  112. forge/runtime/bootstrap.py +360 -0
  113. forge/runtime/database.py +2191 -0
  114. forge/runtime/evidence.py +355 -0
  115. forge/runtime/execution_context.py +173 -0
  116. forge/runtime/runner.py +402 -0
  117. forge/runtime/service.py +127 -0
  118. forge/scheduler/__init__.py +7 -0
  119. forge/scheduler/adapter.py +245 -0
  120. forge/scheduler/ep_http_adapter.py +231 -0
  121. forge/scheduler/ep_v12.py +182 -0
  122. forge/scheduler/scheduler.py +180 -0
  123. forge/schemas/capability-registry-1.0.schema.json +23 -0
  124. forge/schemas/capability.schema.json +17 -0
  125. forge/schemas/common.schema.json +9 -0
  126. forge/schemas/decision-evidence-1.0.schema.json +20 -0
  127. forge/schemas/engineering-goal-0.5.schema.json +16 -0
  128. forge/schemas/engineering-increment-proposal-0.5.schema.json +20 -0
  129. forge/schemas/engineering-mode.schema.json +8 -0
  130. forge/schemas/engineering-plan-0.5.schema.json +29 -0
  131. forge/schemas/engineering-prompt-artifact-0.7.schema.json +26 -0
  132. forge/schemas/engineering-proposal-0.6.schema.json +75 -0
  133. forge/schemas/evidence-reference-0.5.schema.json +16 -0
  134. forge/schemas/foundation-document.schema.json +26 -0
  135. forge/schemas/governance-profile.schema.json +8 -0
  136. forge/schemas/knowledge-source-0.4.schema.json +23 -0
  137. forge/schemas/knowledge-source.schema.json +18 -0
  138. forge/schemas/mission-candidate-1.0.schema.json +27 -0
  139. forge/schemas/mission-candidate-1.1.schema.json +15 -0
  140. forge/schemas/phase-completion-1.0.schema.json +60 -0
  141. forge/schemas/planning-document-0.5.schema.json +17 -0
  142. forge/schemas/repository-catalog.schema.json +24 -0
  143. forge/schemas/repository.schema.json +18 -0
  144. forge/schemas/solution-template-1.0.schema.json +14 -0
  145. forge/schemas/workspace.schema.json +18 -0
  146. forge/solutions/__init__.py +6 -0
  147. forge/solutions/catalogue.py +54 -0
  148. forge/solutions/generator.py +64 -0
  149. forge/state/__init__.py +19 -0
  150. forge/state/mission_state.py +429 -0
  151. forge/workspace/__init__.py +1 -0
  152. forge_autonomy-2.3.1.dist-info/METADATA +474 -0
  153. forge_autonomy-2.3.1.dist-info/RECORD +156 -0
  154. forge_autonomy-2.3.1.dist-info/WHEEL +5 -0
  155. forge_autonomy-2.3.1.dist-info/entry_points.txt +2 -0
  156. forge_autonomy-2.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,150 @@
1
+ """Deterministic capability assessment and delegated-work records.
2
+
3
+ The registry describes available execution capabilities. It never invokes a
4
+ provider: Forge retains the Mission and records only the bounded Engineering
5
+ Action it asks another provider to complete.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import asdict, dataclass
11
+ from enum import Enum
12
+ from typing import Mapping
13
+
14
+
15
+ CAPABILITY_REGISTRY_SCHEMA_VERSION = "1.0"
16
+ DELEGATION_SCHEMA_VERSION = "1.0"
17
+
18
+
19
+ class CapabilityAvailability(str, Enum):
20
+ AVAILABLE = "available"
21
+ UNAVAILABLE = "unavailable"
22
+
23
+
24
+ class CapabilityOwner(str, Enum):
25
+ FORGE = "internal_forge"
26
+ HUMAN = "human"
27
+ EXTERNAL_FORGE_RUNNER = "external_forge_runner"
28
+ EXTERNAL_AI_AGENT = "external_ai_agent"
29
+ EXTERNAL_APPLICATION = "external_application"
30
+ PROFESSIONAL_SERVICE = "professional_service"
31
+
32
+
33
+ class CapabilityExecutionMode(str, Enum):
34
+ INTERNAL = "internal"
35
+ DELEGATED = "delegated"
36
+
37
+
38
+ class DelegationApprovalState(str, Enum):
39
+ NOT_REQUIRED = "not_required"
40
+ PENDING = "pending"
41
+ APPROVED = "approved"
42
+ REJECTED = "rejected"
43
+
44
+
45
+ class DelegationResultState(str, Enum):
46
+ PENDING = "pending"
47
+ RECEIVED = "received"
48
+ ACCEPTED = "accepted"
49
+ REJECTED = "rejected"
50
+ ADDITIONAL_WORK_REQUIRED = "additional_work_required"
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class CapabilityRegistration:
55
+ id: str
56
+ name: str
57
+ owner: CapabilityOwner
58
+ availability: CapabilityAvailability
59
+ execution_mode: CapabilityExecutionMode
60
+ preferred_provider: CapabilityOwner
61
+ trust_level: str
62
+ approval_required: bool = False
63
+ schema_version: str = CAPABILITY_REGISTRY_SCHEMA_VERSION
64
+
65
+ def __post_init__(self) -> None:
66
+ if self.schema_version != CAPABILITY_REGISTRY_SCHEMA_VERSION or not all((self.id, self.name, self.trust_level)):
67
+ raise ValueError("capability registration requires supported version, identity, name, and trust level")
68
+ if self.execution_mode is CapabilityExecutionMode.INTERNAL and self.owner is not CapabilityOwner.FORGE:
69
+ raise ValueError("internal execution must be owned by Forge")
70
+
71
+ def to_dict(self) -> dict[str, object]:
72
+ document = asdict(self)
73
+ for name in ("owner", "availability", "execution_mode", "preferred_provider"):
74
+ document[name] = document[name].value # type: ignore[union-attr]
75
+ return document
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class CapabilityAssessment:
80
+ capability_id: str
81
+ available: bool
82
+ execution_mode: CapabilityExecutionMode
83
+ selected_provider: CapabilityOwner
84
+ alternatives_considered: tuple[CapabilityOwner, ...]
85
+ rationale: str
86
+ confidence: int
87
+ approval_required: bool
88
+
89
+ def to_dict(self) -> dict[str, object]:
90
+ return {"capability_id": self.capability_id, "available": self.available,
91
+ "execution_mode": self.execution_mode.value, "selected_provider": self.selected_provider.value,
92
+ "alternatives_considered": [item.value for item in self.alternatives_considered],
93
+ "rationale": self.rationale, "confidence": self.confidence,
94
+ "approval_required": self.approval_required}
95
+
96
+
97
+ class CapabilityRegistry:
98
+ """Immutable, deterministically ordered capability configuration."""
99
+
100
+ def __init__(self, registrations: tuple[CapabilityRegistration, ...]) -> None:
101
+ identifiers = [item.id for item in registrations]
102
+ if not registrations or len(identifiers) != len(set(identifiers)):
103
+ raise ValueError("capability registry requires uniquely identified registrations")
104
+ self._registrations = {item.id: item for item in registrations}
105
+
106
+ def assess(self, capability_id: str) -> CapabilityAssessment:
107
+ registration = self._registrations.get(capability_id)
108
+ if registration is None:
109
+ raise ValueError("required capability is not registered")
110
+ available = registration.availability is CapabilityAvailability.AVAILABLE and registration.execution_mode is CapabilityExecutionMode.INTERNAL
111
+ alternatives = tuple(sorted((owner for owner in CapabilityOwner if owner is not registration.preferred_provider), key=lambda item: item.value))
112
+ return CapabilityAssessment(
113
+ capability_id, available, registration.execution_mode,
114
+ CapabilityOwner.FORGE if available else registration.preferred_provider,
115
+ alternatives,
116
+ "internal capability available" if available else "required capability is unavailable for internal execution",
117
+ 100 if available else 90, registration.approval_required,
118
+ )
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class DelegationRequest:
123
+ id: str
124
+ mission_id: str
125
+ action_id: str
126
+ capability_id: str
127
+ provider: CapabilityOwner
128
+ reason: str
129
+ decision_evidence: tuple[str, ...]
130
+ requested_at: str
131
+ approval_state: DelegationApprovalState
132
+ alternatives_considered: tuple[CapabilityOwner, ...]
133
+ confidence: int
134
+ result_state: DelegationResultState = DelegationResultState.PENDING
135
+ verification: Mapping[str, object] | None = None
136
+ schema_version: str = DELEGATION_SCHEMA_VERSION
137
+
138
+ def __post_init__(self) -> None:
139
+ if self.schema_version != DELEGATION_SCHEMA_VERSION or not all((self.id, self.mission_id, self.action_id, self.capability_id, self.reason, self.requested_at)):
140
+ raise ValueError("delegation request requires complete identity, rationale, and timestamp")
141
+ if not 0 <= self.confidence <= 100:
142
+ raise ValueError("delegation confidence must be between 0 and 100")
143
+
144
+ def to_dict(self) -> dict[str, object]:
145
+ document = asdict(self)
146
+ document["provider"] = self.provider.value
147
+ document["approval_state"] = self.approval_state.value
148
+ document["result_state"] = self.result_state.value
149
+ document["alternatives_considered"] = [item.value for item in self.alternatives_considered]
150
+ return document
@@ -0,0 +1,5 @@
1
+ """Deterministic, local-only phase completion assessment."""
2
+
3
+ from .assessor import PhaseCompletionAssessor
4
+
5
+ __all__ = ["PhaseCompletionAssessor"]
@@ -0,0 +1,53 @@
1
+ """Pure deterministic assessment of declared Phase Completion 1.0 evidence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+
7
+ from forge.models import (
8
+ AssessmentFinding,
9
+ AssessmentStatus,
10
+ CompletionEvidence,
11
+ CriterionOutcome,
12
+ EngineeringPhase,
13
+ PhaseAssessment,
14
+ )
15
+
16
+
17
+ class PhaseCompletionAssessor:
18
+ """Assess declared evidence only; never fetch, invent, or mutate evidence."""
19
+
20
+ def assess(self, phase: EngineeringPhase, evidence: Iterable[CompletionEvidence]) -> PhaseAssessment:
21
+ declared = tuple(evidence)
22
+ criterion_ids = {criterion.id for criterion in phase.criteria}
23
+ evidence_by_criterion: dict[str, list[CompletionEvidence]] = {}
24
+ findings: list[AssessmentFinding] = []
25
+ for item in declared:
26
+ if item.criterion_id not in criterion_ids:
27
+ findings.append(AssessmentFinding(item.criterion_id, "UNKNOWN_CRITERION", "Evidence references no declared phase criterion."))
28
+ continue
29
+ evidence_by_criterion.setdefault(item.criterion_id, []).append(item)
30
+
31
+ has_criterion_evidence = bool(evidence_by_criterion)
32
+ for criterion in phase.criteria:
33
+ if not criterion.required:
34
+ continue
35
+ criterion_evidence = evidence_by_criterion.get(criterion.id, [])
36
+ if not criterion_evidence:
37
+ findings.append(AssessmentFinding(criterion.id, "MISSING_EVIDENCE", "Required criterion has no declared evidence."))
38
+ elif any(item.outcome is CriterionOutcome.FAIL for item in criterion_evidence):
39
+ findings.append(AssessmentFinding(criterion.id, "FAILED_CRITERION", "Required criterion has declared failing evidence."))
40
+ elif not any(item.outcome is CriterionOutcome.PASS for item in criterion_evidence):
41
+ findings.append(AssessmentFinding(criterion.id, "UNRESOLVED_CRITERION", "Required criterion has no passing evidence."))
42
+
43
+ ordered_findings = tuple(sorted(findings))
44
+ required_unresolved = any(finding.code != "UNKNOWN_CRITERION" for finding in ordered_findings)
45
+ if not has_criterion_evidence:
46
+ status = AssessmentStatus.NOT_STARTED
47
+ elif required_unresolved or any(finding.code == "UNKNOWN_CRITERION" for finding in ordered_findings):
48
+ status = AssessmentStatus.IN_PROGRESS
49
+ elif phase.completion_declaration is None:
50
+ status = AssessmentStatus.READY
51
+ else:
52
+ status = AssessmentStatus.COMPLETE
53
+ return PhaseAssessment(phase.id, status, ordered_findings)
@@ -0,0 +1,5 @@
1
+ """Pure Constitutional Validation assessment capability."""
2
+
3
+ from .assessor import ConstitutionalAssessor
4
+
5
+ __all__ = ["ConstitutionalAssessor"]
@@ -0,0 +1,38 @@
1
+ """Pure deterministic assessment of declared Constitutional Validation input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+
7
+ from forge.models import (
8
+ ConstitutionalAssessment,
9
+ ConstitutionalAssessmentStatus,
10
+ ConstitutionalFinding,
11
+ ConstitutionalFindingSeverity,
12
+ ConstitutionalRule,
13
+ )
14
+
15
+
16
+ class ConstitutionalAssessor:
17
+ """Assess supplied architectural findings; never retrieve, enforce, or mutate."""
18
+
19
+ def assess(
20
+ self,
21
+ subject_id: str,
22
+ applicable_rules: Iterable[ConstitutionalRule],
23
+ findings: Iterable[ConstitutionalFinding],
24
+ ) -> ConstitutionalAssessment:
25
+ """Derive an assessment with fixed severity precedence and stable ordering."""
26
+ rules = tuple(sorted(applicable_rules))
27
+ ordered_findings = tuple(sorted(findings))
28
+ if not rules:
29
+ if ordered_findings:
30
+ raise ValueError("constitutional findings require at least one applicable rule")
31
+ status = ConstitutionalAssessmentStatus.NOT_APPLICABLE
32
+ elif any(finding.severity is ConstitutionalFindingSeverity.VIOLATION for finding in ordered_findings):
33
+ status = ConstitutionalAssessmentStatus.VIOLATION
34
+ elif ordered_findings:
35
+ status = ConstitutionalAssessmentStatus.WARNING
36
+ else:
37
+ status = ConstitutionalAssessmentStatus.PASS
38
+ return ConstitutionalAssessment(subject_id, rules, status, ordered_findings)
forge/core/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Core local persistence primitives."""
2
+
3
+ from .json_store import JsonStore
4
+
5
+ __all__ = ["JsonStore"]
@@ -0,0 +1,24 @@
1
+ """Deterministic, human-readable local JSON persistence for foundation data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any, Mapping
8
+
9
+
10
+ class JsonStore:
11
+ """Persist a single document locally without database or network dependencies."""
12
+
13
+ def __init__(self, path: Path) -> None:
14
+ self.path = path
15
+
16
+ def save(self, document: Mapping[str, Any]) -> None:
17
+ self.path.parent.mkdir(parents=True, exist_ok=True)
18
+ self.path.write_text(
19
+ json.dumps(document, indent=2, sort_keys=True, ensure_ascii=False) + "\n",
20
+ encoding="utf-8",
21
+ )
22
+
23
+ def load(self) -> dict[str, Any]:
24
+ return json.loads(self.path.read_text(encoding="utf-8"))
@@ -0,0 +1,5 @@
1
+ """Decision Evidence public repository contracts."""
2
+
3
+ from .repository import DecisionEvidenceRepository, DecisionEvidenceRepositoryError
4
+
5
+ __all__ = ["DecisionEvidenceRepository", "DecisionEvidenceRepositoryError"]
@@ -0,0 +1,68 @@
1
+ """Append-only local Repository Truth store for immutable Decision Evidence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sqlite3
7
+ from pathlib import Path
8
+ from typing import Callable
9
+
10
+ from forge.models.decision_evidence import DecisionEvidence, DecisionReference
11
+
12
+
13
+ class DecisionEvidenceRepositoryError(ValueError):
14
+ pass
15
+
16
+
17
+ class DecisionEvidenceRepository:
18
+ """Persists one immutable document per decision and rejects unresolved references."""
19
+
20
+ def __init__(self, path: Path, *, reference_resolver: Callable[[DecisionReference], bool]) -> None:
21
+ path.parent.mkdir(parents=True, exist_ok=True)
22
+ self._connection = sqlite3.connect(path)
23
+ self._connection.row_factory = sqlite3.Row
24
+ self._reference_resolver = reference_resolver
25
+ self._connection.executescript("""
26
+ CREATE TABLE IF NOT EXISTS decision_evidence (
27
+ decision_id TEXT PRIMARY KEY, content_digest TEXT NOT NULL UNIQUE, document TEXT NOT NULL
28
+ );
29
+ CREATE TRIGGER IF NOT EXISTS decision_evidence_no_update BEFORE UPDATE ON decision_evidence
30
+ BEGIN SELECT RAISE(ABORT, 'decision evidence is immutable'); END;
31
+ CREATE TRIGGER IF NOT EXISTS decision_evidence_no_delete BEFORE DELETE ON decision_evidence
32
+ BEGIN SELECT RAISE(ABORT, 'decision evidence is immutable'); END;
33
+ """)
34
+ self._connection.commit()
35
+
36
+ def close(self) -> None:
37
+ self._connection.close()
38
+
39
+ def __enter__(self) -> "DecisionEvidenceRepository":
40
+ return self
41
+
42
+ def __exit__(self, *_: object) -> None:
43
+ self.close()
44
+
45
+ def append(self, evidence: DecisionEvidence) -> DecisionEvidence:
46
+ references = (evidence.repository_context, evidence.mission_context, evidence.repository_maturity_reference,
47
+ *evidence.evidence_references, *evidence.execution_evidence_references,
48
+ evidence.confidence.repository_truth, evidence.confidence.architecture_review,
49
+ evidence.confidence.execution_evidence, evidence.confidence.mission_state)
50
+ if not all(self._reference_resolver(reference) for reference in references):
51
+ raise DecisionEvidenceRepositoryError("all Decision Evidence references must resolve to Repository Truth")
52
+ try:
53
+ with self._connection:
54
+ self._connection.execute("INSERT INTO decision_evidence VALUES (?, ?, ?)",
55
+ (evidence.id, evidence.content_digest, json.dumps(evidence.to_dict(), sort_keys=True, separators=(",", ":"), ensure_ascii=False)))
56
+ except sqlite3.IntegrityError as error:
57
+ raise DecisionEvidenceRepositoryError("decision evidence id or immutable content already exists") from error
58
+ return evidence
59
+
60
+ def get(self, decision_id: str) -> DecisionEvidence:
61
+ row = self._connection.execute("SELECT document FROM decision_evidence WHERE decision_id = ?", (decision_id,)).fetchone()
62
+ if row is None:
63
+ raise DecisionEvidenceRepositoryError(f"unknown decision evidence: {decision_id}")
64
+ return DecisionEvidence.from_dict(json.loads(row["document"]))
65
+
66
+ def list(self) -> tuple[DecisionEvidence, ...]:
67
+ rows = self._connection.execute("SELECT document FROM decision_evidence ORDER BY decision_id").fetchall()
68
+ return tuple(DecisionEvidence.from_dict(json.loads(row["document"])) for row in rows)
@@ -0,0 +1,13 @@
1
+ """Approved Mission Queue and deterministic Mission Dispatcher."""
2
+
3
+ from .dispatcher import (
4
+ BOOTSTRAP_MISSION_SEQUENCE,
5
+ ApprovedMissionQueue,
6
+ DispatcherStatus,
7
+ MissionDispatcher,
8
+ MissionDispatcherError,
9
+ MissionDispatcherStore,
10
+ MissionDispatchRecord,
11
+ )
12
+
13
+ __all__ = ["BOOTSTRAP_MISSION_SEQUENCE", "ApprovedMissionQueue", "DispatcherStatus", "MissionDispatcher", "MissionDispatcherError", "MissionDispatcherStore", "MissionDispatchRecord"]
@@ -0,0 +1,194 @@
1
+ """Persistent, FIFO dispatch of Architecture-approved Missions only.
2
+
3
+ This boundary activates Mission work. It does not create Missions, plan
4
+ Intents or Actions, render prompts, invoke an Execution Host, or execute.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from enum import Enum
11
+ import sqlite3
12
+ from pathlib import Path
13
+ from typing import Callable, Protocol
14
+
15
+ from forge.architecture import ArchitectureWorkspace
16
+ from forge.intake import MissionIntake
17
+ from forge.models.architecture_mission import ArchitectureMission
18
+ from forge.state import MissionExecutionStatus, MissionStateStore
19
+
20
+ # Historical portfolio identifier set retained for the bootstrap regression
21
+ # harness only. It does not affect operational queue ordering.
22
+ BOOTSTRAP_MISSION_SEQUENCE = ("MISSION-0001", "MISSION-0002", "MISSION-0003", "MISSION-0004", "MISSION-0005")
23
+
24
+
25
+ class DispatcherStatus(str, Enum):
26
+ PENDING = "PENDING"
27
+ ACTIVE = "ACTIVE"
28
+ COMPLETED = "COMPLETED"
29
+ BLOCKED = "BLOCKED"
30
+ FAILED = "FAILED"
31
+ ARCHIVED = "ARCHIVED"
32
+
33
+
34
+ class MissionDispatcherError(ValueError):
35
+ pass
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class MissionDispatchRecord:
40
+ mission_id: str
41
+ status: DispatcherStatus
42
+ sequence: int
43
+ occurred_at: str
44
+
45
+
46
+ class CompletionHook(Protocol):
47
+ def __call__(self, mission_id: str) -> None: ...
48
+
49
+
50
+ class ApprovedMissionQueue:
51
+ """A read-only, governance-filtered queue; recommendations never enter it."""
52
+
53
+ def __init__(self, workspace: ArchitectureWorkspace) -> None:
54
+ self._workspace = workspace
55
+
56
+ def missions(self) -> tuple[ArchitectureMission, ...]:
57
+ return self._workspace.approved_for_engineering()
58
+
59
+
60
+ class MissionDispatcherStore:
61
+ """Local durable dispatcher lifecycle with a database-enforced one-active invariant."""
62
+
63
+ def __init__(self, path: Path) -> None:
64
+ self._connection = sqlite3.connect(path)
65
+ self._connection.row_factory = sqlite3.Row
66
+ self._connection.executescript("""
67
+ CREATE TABLE IF NOT EXISTS mission_dispatch (
68
+ mission_id TEXT PRIMARY KEY, status TEXT NOT NULL, sequence INTEGER NOT NULL, occurred_at TEXT NOT NULL
69
+ );
70
+ CREATE UNIQUE INDEX IF NOT EXISTS one_active_mission ON mission_dispatch(status) WHERE status = 'ACTIVE';
71
+ """)
72
+ self._connection.commit()
73
+
74
+ def close(self) -> None:
75
+ self._connection.close()
76
+
77
+ def active(self) -> MissionDispatchRecord | None:
78
+ row = self._connection.execute("SELECT * FROM mission_dispatch WHERE status = 'ACTIVE'").fetchone()
79
+ return None if row is None else self._record(row)
80
+
81
+ def get(self, mission_id: str) -> MissionDispatchRecord | None:
82
+ row = self._connection.execute("SELECT * FROM mission_dispatch WHERE mission_id = ?", (mission_id,)).fetchone()
83
+ return None if row is None else self._record(row)
84
+
85
+ def records(self) -> tuple[MissionDispatchRecord, ...]:
86
+ return tuple(self._record(row) for row in self._connection.execute("SELECT * FROM mission_dispatch ORDER BY sequence, mission_id"))
87
+
88
+ def activate(self, mission_id: str, *, occurred_at: str) -> MissionDispatchRecord:
89
+ if self.active() is not None:
90
+ raise MissionDispatcherError("only one Mission may be active")
91
+ existing = self.get(mission_id)
92
+ if existing and existing.status not in {DispatcherStatus.PENDING, DispatcherStatus.BLOCKED, DispatcherStatus.FAILED}:
93
+ raise MissionDispatcherError("Mission dispatch lifecycle cannot be activated")
94
+ sequence = existing.sequence if existing else len(self.records()) + 1
95
+ try:
96
+ with self._connection:
97
+ if existing:
98
+ self._connection.execute("UPDATE mission_dispatch SET status = ?, occurred_at = ? WHERE mission_id = ?", (DispatcherStatus.ACTIVE.value, occurred_at, mission_id))
99
+ else:
100
+ self._connection.execute("INSERT INTO mission_dispatch VALUES (?, ?, ?, ?)", (mission_id, DispatcherStatus.ACTIVE.value, sequence, occurred_at))
101
+ except sqlite3.IntegrityError as error:
102
+ raise MissionDispatcherError("only one Mission may be active") from error
103
+ return self.get(mission_id) # type: ignore[return-value]
104
+
105
+ def pending(self, mission_id: str, *, occurred_at: str) -> MissionDispatchRecord:
106
+ if self.get(mission_id) is not None:
107
+ raise MissionDispatcherError("Mission already has a dispatch record")
108
+ with self._connection:
109
+ self._connection.execute(
110
+ "INSERT INTO mission_dispatch VALUES (?, ?, ?, ?)",
111
+ (mission_id, DispatcherStatus.PENDING.value, len(self.records()) + 1, occurred_at),
112
+ )
113
+ return self.get(mission_id) # type: ignore[return-value]
114
+
115
+ def transition(self, mission_id: str, status: DispatcherStatus, *, occurred_at: str) -> MissionDispatchRecord:
116
+ current = self.get(mission_id)
117
+ if current is None or current.status is not DispatcherStatus.ACTIVE:
118
+ raise MissionDispatcherError("only the active Mission may change terminal dispatch state")
119
+ if status not in {DispatcherStatus.COMPLETED, DispatcherStatus.BLOCKED, DispatcherStatus.FAILED, DispatcherStatus.ARCHIVED}:
120
+ raise MissionDispatcherError("dispatch transition must be terminal or explicitly blocked")
121
+ with self._connection:
122
+ self._connection.execute("UPDATE mission_dispatch SET status = ?, occurred_at = ? WHERE mission_id = ?", (status.value, occurred_at, mission_id))
123
+ return self.get(mission_id) # type: ignore[return-value]
124
+
125
+ @staticmethod
126
+ def _record(row: sqlite3.Row) -> MissionDispatchRecord:
127
+ return MissionDispatchRecord(row["mission_id"], DispatcherStatus(row["status"]), row["sequence"], row["occurred_at"])
128
+
129
+
130
+ class MissionDispatcher:
131
+ """Activate one approved Mission, then wait for its independently verified completion."""
132
+
133
+ def __init__(self, queue: ApprovedMissionQueue, intake: MissionIntake, states: MissionStateStore, store: MissionDispatcherStore, *, clock: Callable[[], str], architecture_review: CompletionHook | None = None, recommendations: CompletionHook | None = None) -> None:
134
+ self._queue, self._intake, self._states, self._store = queue, intake, states, store
135
+ self._clock, self._architecture_review, self._recommendations = clock, architecture_review, recommendations
136
+
137
+ @property
138
+ def is_idle(self) -> bool:
139
+ return self._store.active() is None and not any(item.id not in {record.mission_id for record in self._store.records()} for item in self._queue.missions())
140
+
141
+ def dispatch(self) -> MissionDispatchRecord | None:
142
+ active = self.resume()
143
+ if active:
144
+ return active
145
+ if any(item.status in {DispatcherStatus.BLOCKED, DispatcherStatus.FAILED} for item in self._store.records()):
146
+ return None
147
+ completed = {item.mission_id for item in self._store.records() if item.status in {DispatcherStatus.COMPLETED, DispatcherStatus.ARCHIVED}}
148
+ mission = next((item for item in self._queue.missions() if item.id not in completed), None)
149
+ if mission is None:
150
+ return None
151
+ if self._store.get(mission.id) is None:
152
+ self._store.pending(mission.id, occurred_at=self._clock())
153
+ self._intake.admit_approved_mission(mission)
154
+ return self._store.activate(mission.id, occurred_at=self._clock())
155
+
156
+ def resume(self) -> MissionDispatchRecord | None:
157
+ active = self._store.active()
158
+ if active is None:
159
+ return None
160
+ state = self._states.get(active.mission_id)
161
+ if state.status in {MissionExecutionStatus.COMPLETED, MissionExecutionStatus.ARCHIVED}:
162
+ raise MissionDispatcherError("active dispatcher record has terminal Mission State")
163
+ return active
164
+
165
+ def complete(self, mission_id: str) -> MissionDispatchRecord | None:
166
+ state = self._states.get(mission_id)
167
+ if state.status is not MissionExecutionStatus.COMPLETED:
168
+ raise MissionDispatcherError("Mission completion requires verified completed Mission State")
169
+ evidence = getattr(state, "execution_evidence", None)
170
+ if evidence is not None and (not evidence.get("receipt_id") or evidence.get("outcome") != "complete"):
171
+ raise MissionDispatcherError("Mission completion requires host-issued complete execution evidence")
172
+ self._store.transition(mission_id, DispatcherStatus.COMPLETED, occurred_at=self._clock())
173
+ if self._architecture_review:
174
+ self._architecture_review(mission_id)
175
+ if self._recommendations:
176
+ self._recommendations(mission_id)
177
+ return self.dispatch()
178
+
179
+ def hold(self, mission_id: str, status: MissionExecutionStatus) -> MissionDispatchRecord:
180
+ """Reflect a loop-owned deterministic pause without advancing the queue."""
181
+ if status not in {MissionExecutionStatus.BLOCKED, MissionExecutionStatus.FAILED}:
182
+ raise MissionDispatcherError("dispatcher hold requires blocked or failed Mission State")
183
+ state = self._states.get(mission_id)
184
+ if state.status is not status:
185
+ raise MissionDispatcherError("dispatcher hold requires matching durable Mission State")
186
+ dispatch_status = DispatcherStatus.BLOCKED if status is MissionExecutionStatus.BLOCKED else DispatcherStatus.FAILED
187
+ return self._store.transition(mission_id, dispatch_status, occurred_at=self._clock())
188
+
189
+ def recover(self, mission_id: str) -> MissionDispatchRecord:
190
+ """Reactivate an explicitly recovered Mission; it does not plan or execute it."""
191
+ state = self._states.get(mission_id)
192
+ if state.status not in {MissionExecutionStatus.READY, MissionExecutionStatus.ACTIVE}:
193
+ raise MissionDispatcherError("dispatcher recovery requires a recovered Mission State")
194
+ return self._store.activate(mission_id, occurred_at=self._clock())
@@ -0,0 +1,13 @@
1
+ """Autonomous, single-Mission execution orchestration."""
2
+
3
+ from .loop import (
4
+ ExecutionLoop,
5
+ ExecutionLoopError,
6
+ ExecutionLoopObservability,
7
+ RecoveryAuthorization,
8
+ )
9
+ from .presentation import render_execution_decision_evidence_reference
10
+ from forge.governance import ApprovalRecord, ExecutionPolicy, ExecutionPolicyKind, PauseBoundary
11
+
12
+ __all__ = ["ApprovalRecord", "ExecutionLoop", "ExecutionLoopError", "ExecutionLoopObservability",
13
+ "ExecutionPolicy", "ExecutionPolicyKind", "PauseBoundary", "RecoveryAuthorization", "render_execution_decision_evidence_reference"]