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,186 @@
1
+ """Forge-owned Business Governance Ingress for canonical recommendations.
2
+
3
+ This composition service deliberately accepts only records in the canonical
4
+ RecommendationLifecycleStore. It has no Engineering Platform dependency and
5
+ delegates identity allocation to RuntimeDatabase and downstream work to the
6
+ existing MissionRuntimeScheduler.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from hashlib import sha256
13
+ from typing import Protocol
14
+
15
+ from forge.governance import GovernanceRole, ResolvedGovernanceProfile
16
+ from forge.lifecycle import (
17
+ LifecycleError,
18
+ MissionCandidate,
19
+ RecommendationLifecycleStore,
20
+ RecommendationStatus,
21
+ )
22
+ from forge.mission_scheduler import MissionRuntimeScheduler, SchedulerResult
23
+ from forge.runtime import RuntimeDatabase
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class BusinessGovernanceIngressResult:
28
+ recommendation_id: str
29
+ result: str
30
+ business_decision_id: str | None
31
+ business_approval_status: str | None
32
+ architecture_state: str
33
+ mission_candidate_id: str | None
34
+ mission_id: str | None
35
+ mission_runtime_state: str | None
36
+ scheduler_state: str | None
37
+ decision_evidence_references: tuple[str, ...]
38
+
39
+
40
+ class SchedulerHandoff(Protocol):
41
+ """The scheduler's existing handoff surface; ingress owns no submission."""
42
+
43
+ def evaluate(self, mission_id: str) -> SchedulerResult: ...
44
+
45
+
46
+ class BusinessGovernanceIngress:
47
+ """Approve and reconcile one existing recommendation through canonical state."""
48
+
49
+ def __init__(self, lifecycle: RecommendationLifecycleStore, runtime: RuntimeDatabase,
50
+ governance: ResolvedGovernanceProfile, *, scheduler: SchedulerHandoff | None = None) -> None:
51
+ self._lifecycle = lifecycle
52
+ self._runtime = runtime
53
+ self._governance = governance
54
+ self._scheduler = scheduler
55
+
56
+ def approve_recommendation(self, recommendation_id: str, *, actor: str | None,
57
+ occurred_at: str, rationale: str) -> BusinessGovernanceIngressResult:
58
+ if not actor:
59
+ return self._result(recommendation_id, "AUTHORIZATION_REQUIRED", architecture_state="NOT_REACHED")
60
+ allowed = self._governance.role_assignments.get(GovernanceRole.BUSINESS_OWNER, ())
61
+ if actor not in allowed:
62
+ return self._result(recommendation_id, "FORBIDDEN", architecture_state="NOT_REACHED")
63
+ try:
64
+ recommendation = self._lifecycle.resolve_recommendation(recommendation_id)
65
+ except LifecycleError as error:
66
+ result = "AMBIGUOUS" if "ambiguous" in str(error) else "NOT_FOUND"
67
+ return self._result(recommendation_id, result, architecture_state="NOT_REACHED")
68
+ recommendation_id = recommendation.id
69
+ if recommendation.status is RecommendationStatus.RECOMMENDED:
70
+ recommendation = self._lifecycle.transition(
71
+ recommendation_id, RecommendationStatus.BUSINESS_APPROVED, actor=actor,
72
+ occurred_at=occurred_at, rationale=rationale,
73
+ references=(recommendation.decision_evidence_reference,),
74
+ )
75
+ elif recommendation.status not in {
76
+ RecommendationStatus.BUSINESS_APPROVED, RecommendationStatus.ARCHITECTURE_APPROVED,
77
+ RecommendationStatus.MISSION_ALLOCATED,
78
+ }:
79
+ return self._result(recommendation_id, "INVALID_LIFECYCLE_STATE",
80
+ architecture_state=recommendation.status.value)
81
+ if recommendation.status is RecommendationStatus.BUSINESS_APPROVED:
82
+ return self._current(recommendation_id, "WAITING_ARCHITECTURE_APPROVAL",
83
+ architecture_state="WAITING_ARCHITECTURE_APPROVAL")
84
+ return self._reconcile_approved(recommendation_id, occurred_at)
85
+
86
+ def _reconcile_approved(self, recommendation_id: str, occurred_at: str) -> BusinessGovernanceIngressResult:
87
+ allocation = self._lifecycle.allocation_for_recommendation(recommendation_id)
88
+ candidate = self._lifecycle.candidate_for_recommendation(recommendation_id)
89
+ if allocation is None:
90
+ if candidate is None:
91
+ recommendation = self._lifecycle.get_recommendation(recommendation_id)
92
+ candidate = MissionCandidate(
93
+ id="candidate:" + sha256(recommendation_id.encode()).hexdigest()[:20],
94
+ recommendation_id=recommendation_id, title=recommendation.title,
95
+ objective=recommendation.engineering_summary,
96
+ scope=(recommendation.business_summary,),
97
+ acceptance_criteria=(recommendation.engineering_value,),
98
+ architecture_constraints=(recommendation.architectural_value,),
99
+ dependencies=recommendation.dependencies,
100
+ )
101
+ self._lifecycle.create_candidate(candidate)
102
+ allocation = self._lifecycle.allocate(
103
+ candidate.id, actor="forge", occurred_at=occurred_at,
104
+ rationale="Canonical Business and Architecture approvals permit Mission allocation.",
105
+ allocate_mission_id=lambda source, timestamp: self._runtime.allocate_next_mission_id(
106
+ source=f"recommendation:{source}", allocated_at=timestamp),
107
+ )
108
+ self._initialize_runtime(allocation.mission_id, recommendation_id, candidate.id, occurred_at, allocation)
109
+ scheduler_state = "SCHEDULER_ELIGIBLE"
110
+ if self._scheduler is not None:
111
+ scheduler_state = self._scheduler.evaluate(allocation.mission_id).state
112
+ return self._current(recommendation_id, "APPROVED_AND_ACTIVATED",
113
+ architecture_state="ARCHITECTURE_APPROVED", scheduler_state=scheduler_state)
114
+
115
+ def _initialize_runtime(self, mission_id: str, recommendation_id: str, candidate_id: str,
116
+ occurred_at: str, allocation: object) -> None:
117
+ try:
118
+ self._runtime.get_document("mission_state", mission_id)
119
+ runtime_exists = True
120
+ except Exception:
121
+ runtime_exists = False
122
+ # Runtime Decision Evidence has a foreign-key relationship to its
123
+ # Mission. Reserve only the canonical operational record first;
124
+ # the complete initialized graph is written below.
125
+ self._runtime.save_mission_state({
126
+ "mission_id": mission_id, "lifecycle": "ALLOCATED", "status": "ALLOCATED",
127
+ "progress": {"percent_complete": 0}, "resume": {"phase": "governance_activation"},
128
+ "execution_policy": {"mode": "mission_review", "execution_authorized": False},
129
+ })
130
+ if not self._runtime.has_document("decision_evidence", f"{mission_id}-governance-activation-1"):
131
+ self._runtime.record_decision_evidence({
132
+ "id": f"{mission_id}-governance-activation-1", "decision_type": "mission_planning",
133
+ "timestamp": occurred_at, "mission_context": {"artifact_id": mission_id},
134
+ "repository_context": {"artifact_id": "forge-governance"},
135
+ "reasoning_summary": "Allocated canonical Mission is activated from immutable governance evidence.",
136
+ "evidence_references": [{"artifact_id": item} for item in (
137
+ recommendation_id, candidate_id, allocation.business_decision_evidence_id,
138
+ allocation.architecture_decision_evidence_id, allocation.allocation_decision_evidence_id)],
139
+ "alternatives_considered": [{"id": "wait", "reason": "Both required approvals are already recorded."}],
140
+ "chosen_alternative": "activate", "confidence": {"score": 100, "mission_state": {"artifact_id": mission_id}},
141
+ "execution_receipt_references": [],
142
+ })
143
+ if not runtime_exists:
144
+ intent_id, action_id = f"{mission_id}-intent-1", f"{mission_id}-action-1"
145
+ intent = {"id": intent_id, "title": "Approved Mission activation", "objective": "Execute the approved bounded Mission.", "status": "APPROVED"}
146
+ action = {"id": action_id, "intent_id": intent_id, "objective": "Start the approved Mission according to its canonical scope.", "status": "READY", "dependencies": []}
147
+ self._runtime.save_mission_state({
148
+ "mission_id": mission_id, "mission_title": self._lifecycle.get_recommendation(recommendation_id).title,
149
+ "mission_recommendation_status": "MISSION_ALLOCATED", "business_summary": self._lifecycle.get_recommendation(recommendation_id).business_summary,
150
+ "engineering_summary": self._lifecycle.get_recommendation(recommendation_id).engineering_summary,
151
+ "lifecycle": "ACTIVE", "status": "ACTIVE", "progress": {"percent_complete": 0},
152
+ "resume": {"phase": "autonomous_scheduler"}, "execution_policy": {"mode": "mission_review", "execution_authorized": True},
153
+ "governance": {"business_approval": "approved", "architecture_approval": "approved"},
154
+ "engineering_intents": [intent], "engineering_actions": [action],
155
+ "current_engineering_intent": intent, "current_engineering_action": action,
156
+ "runtime_prompts": [{"id": f"scheduler-prompt:{action_id}:1", "action_id": action_id, "intent_id": intent_id, "status": "READY_FOR_ENGINEERING_PLATFORM"}],
157
+ "decision_evidence_ids": [f"{mission_id}-governance-activation-1"], "execution_receipt_references": [],
158
+ })
159
+ self._runtime.record_mission_lifecycle(mission_id, "allocated", occurred_at)
160
+ self._runtime.record_mission_lifecycle(mission_id, "active", occurred_at)
161
+ self._runtime.save_dispatcher_state(status="ACTIVE", mission_sequence=(mission_id,), active_mission_id=mission_id)
162
+ self._runtime.save_planning_state({"planner_version": "business-governance-ingress-1", "current_queue": [mission_id],
163
+ "pending_engineering_actions": [action_id], "blocked_engineering_actions": [],
164
+ "execution_policy": {"mode": "mission_review", "execution_authorized": True},
165
+ "planner_runtime_metadata": {"scheduler_state": "READY"}})
166
+ self._runtime.runtime_evidence().mission_runtime_projection(mission_id)
167
+
168
+ def _current(self, recommendation_id: str, result: str, *, architecture_state: str,
169
+ scheduler_state: str | None = None) -> BusinessGovernanceIngressResult:
170
+ recommendation = self._lifecycle.get_recommendation(recommendation_id)
171
+ history = self._lifecycle.history(recommendation_id)
172
+ decisions = tuple(item.id for item in history)
173
+ business = next((item.id for item in history if item.kind == "business_decision"), None)
174
+ candidate = self._lifecycle.candidate_for_recommendation(recommendation_id)
175
+ allocation = self._lifecycle.allocation_for_recommendation(recommendation_id)
176
+ runtime_state = None
177
+ if allocation is not None:
178
+ runtime_state = self._runtime.get_document("mission_state", allocation.mission_id).get("lifecycle")
179
+ return BusinessGovernanceIngressResult(recommendation_id, result, business,
180
+ "APPROVED" if business else None, architecture_state, None if candidate is None else candidate.id,
181
+ None if allocation is None else allocation.mission_id, runtime_state, scheduler_state, decisions)
182
+
183
+ @staticmethod
184
+ def _result(recommendation_id: str, result: str, *, architecture_state: str) -> BusinessGovernanceIngressResult:
185
+ return BusinessGovernanceIngressResult(recommendation_id, result, None, None, architecture_state,
186
+ None, None, None, None, ())
@@ -0,0 +1,74 @@
1
+ """Minimal deterministic view models for the human-facing Business Workspace."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from forge.models.mission_recommendation import MissionRecommendation
8
+ from forge.models.decision_evidence import DecisionEvidence
9
+ from forge.lifecycle import LifecycleDecisionEvidence, MissionRecommendation as LifecycleMissionRecommendation
10
+
11
+
12
+ def render_mission_recommendation(recommendation: MissionRecommendation) -> dict[str, Any]:
13
+ """Render advisory details without changing recommendation or Portfolio state."""
14
+ return {
15
+ "id": recommendation.id,
16
+ "title": recommendation.title,
17
+ "rationale": recommendation.rationale,
18
+ "business_value": recommendation.business_value,
19
+ "expected_engineering_value": recommendation.expected_engineering_value,
20
+ "risk_if_deferred": recommendation.risk_if_deferred,
21
+ "estimated_engineering_effort": recommendation.estimated_effort.value,
22
+ "confidence": recommendation.confidence.to_dict(),
23
+ "required_disciplines": [item.value for item in recommendation.required_disciplines],
24
+ "missing_disciplines": [item.value for item in recommendation.missing_disciplines],
25
+ "dependencies": recommendation.dependencies.to_dict(),
26
+ "origin": recommendation.origin.value,
27
+ "repository_evidence": [item.to_dict() for item in recommendation.repository_evidence],
28
+ "recommendation_source": recommendation.recommendation_source,
29
+ "decision_evidence_references": list(recommendation.decision_evidence_references),
30
+ "architecture_review_reference": recommendation.architecture_review_id,
31
+ "advisory": True,
32
+ }
33
+
34
+
35
+ def render_business_decision_evidence(evidence: DecisionEvidence) -> dict[str, Any]:
36
+ """Expose recommendation rationale without granting or automating approval."""
37
+ return {"id": evidence.id, "decision_type": evidence.decision_type.value, "decision": evidence.decision,
38
+ "reasoning_summary": evidence.reasoning_summary, "alternatives": [item.to_dict() for item in evidence.alternatives_considered],
39
+ "chosen_alternative": evidence.chosen_alternative, "confidence": evidence.confidence.to_dict(),
40
+ "approval_state": evidence.approval_state.value, "advisory": True}
41
+
42
+
43
+ def render_persisted_mission_recommendation(recommendation: LifecycleMissionRecommendation,
44
+ *, selection_decision_evidence_id: str | None = None) -> dict[str, Any]:
45
+ """Render lifecycle-owned recommendation fields required for Business review."""
46
+ return {
47
+ "id": recommendation.id, "recommendation_set_id": recommendation.recommendation_set_id,
48
+ "rank": recommendation.rank, "title": recommendation.title,
49
+ "mission_origin": recommendation.mission_origin, "business_value": recommendation.business_value,
50
+ "architectural_value": recommendation.architectural_value, "engineering_value": recommendation.engineering_value,
51
+ "confidence": recommendation.confidence, "dependencies": list(recommendation.dependencies),
52
+ "risk_if_deferred": recommendation.risk_if_deferred, "rationale": recommendation.business_summary,
53
+ "decision_evidence_reference": selection_decision_evidence_id or recommendation.decision_evidence_reference,
54
+ "recommendation_status": recommendation.status.value, "approval_status": "NOT_YET_APPROVED",
55
+ "advisory": True,
56
+ }
57
+
58
+
59
+ def render_persisted_recommendation_set(recommendations: tuple[LifecycleMissionRecommendation, ...],
60
+ decision_evidence: LifecycleDecisionEvidence) -> tuple[dict[str, Any], ...]:
61
+ """Render a complete current set for Business review without inferring Mission state."""
62
+ if not recommendations or decision_evidence.decision_type != "MISSION_RECOMMENDATION":
63
+ raise ValueError("a recommendation selection Decision Evidence record is required")
64
+ if decision_evidence.selected_recommendation_id != recommendations[0].id:
65
+ raise ValueError("selection Decision Evidence must identify rank one")
66
+ if decision_evidence.ranked_alternatives != tuple(item.id for item in recommendations):
67
+ raise ValueError("selection Decision Evidence must preserve the ranked alternatives")
68
+ rendered = tuple(
69
+ render_persisted_mission_recommendation(item, selection_decision_evidence_id=decision_evidence.id)
70
+ for item in recommendations
71
+ )
72
+ if sum(item["recommendation_status"] == "RECOMMENDED" for item in rendered) != 1:
73
+ raise ValueError("Business Workspace requires exactly one recommended recommendation")
74
+ return rendered
@@ -0,0 +1,141 @@
1
+ """Local, auditable Business Workspace storage and business-only transitions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sqlite3
7
+ from dataclasses import dataclass, replace
8
+ from pathlib import Path
9
+
10
+ from forge.models.mission_candidate import MissionCandidate, MissionCandidateStatus
11
+
12
+
13
+ class BusinessWorkspaceError(ValueError):
14
+ pass
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class MissionCandidateHistoryEntry:
19
+ revision: int
20
+ event: str
21
+ actor: str
22
+ occurred_at: str
23
+ rationale: str
24
+
25
+
26
+ class BusinessWorkspace:
27
+ """Legacy local workspace storage for offline/tests; not production authority.
28
+
29
+ Supported Forge governance mutations use :meth:`for_runtime`, which is
30
+ bound to the resolved Runtime Instance and canonical evidence repository.
31
+ """
32
+
33
+ @classmethod
34
+ def for_runtime(cls, database: object, repository: object, context: object) -> object:
35
+ """Return the canonical runtime-bound Business approval service.
36
+
37
+ No filesystem path is accepted on this supported production route.
38
+ """
39
+ from forge.governance_authority import CanonicalBusinessWorkspace
40
+ if getattr(repository, "database", None) is not database:
41
+ raise BusinessWorkspaceError("Business governance repository must use the resolved Runtime Instance")
42
+ return CanonicalBusinessWorkspace(repository, context)
43
+
44
+ def __init__(self, path: Path) -> None:
45
+ self._connection = sqlite3.connect(path)
46
+ self._connection.row_factory = sqlite3.Row
47
+ self._connection.executescript("""
48
+ CREATE TABLE IF NOT EXISTS mission_candidate (
49
+ candidate_id TEXT PRIMARY KEY, revision INTEGER NOT NULL, status TEXT NOT NULL, document TEXT NOT NULL
50
+ );
51
+ CREATE TABLE IF NOT EXISTS mission_candidate_history (
52
+ candidate_id TEXT NOT NULL, revision INTEGER NOT NULL, event TEXT NOT NULL, actor TEXT NOT NULL,
53
+ occurred_at TEXT NOT NULL, rationale TEXT NOT NULL, PRIMARY KEY(candidate_id, revision),
54
+ FOREIGN KEY(candidate_id) REFERENCES mission_candidate(candidate_id)
55
+ );
56
+ CREATE TRIGGER IF NOT EXISTS mission_candidate_history_no_update BEFORE UPDATE ON mission_candidate_history
57
+ BEGIN SELECT RAISE(ABORT, 'mission candidate history is append-only'); END;
58
+ CREATE TRIGGER IF NOT EXISTS mission_candidate_history_no_delete BEFORE DELETE ON mission_candidate_history
59
+ BEGIN SELECT RAISE(ABORT, 'mission candidate history is append-only'); END;
60
+ """)
61
+ self._connection.commit()
62
+
63
+ def close(self) -> None:
64
+ self._connection.close()
65
+
66
+ def __enter__(self) -> "BusinessWorkspace":
67
+ return self
68
+
69
+ def __exit__(self, *_: object) -> None:
70
+ self.close()
71
+
72
+ def create(self, candidate: MissionCandidate, *, actor: str, occurred_at: str) -> MissionCandidate:
73
+ if candidate.status is not MissionCandidateStatus.BUSINESS_REVIEW:
74
+ raise BusinessWorkspaceError("new candidates must enter business_review")
75
+ self._write(candidate, 1, "created", actor, occurred_at, candidate.business_rationale, insert=True)
76
+ return self.get(candidate.id)
77
+
78
+ def get(self, candidate_id: str) -> MissionCandidate:
79
+ row = self._connection.execute("SELECT document FROM mission_candidate WHERE candidate_id = ?", (candidate_id,)).fetchone()
80
+ if row is None:
81
+ raise BusinessWorkspaceError(f"unknown mission candidate: {candidate_id}")
82
+ return MissionCandidate.from_dict(json.loads(row["document"]))
83
+
84
+ def list(self, *, status: MissionCandidateStatus | None = None) -> tuple[MissionCandidate, ...]:
85
+ query = "SELECT document FROM mission_candidate" + (" WHERE status = ?" if status else "") + " ORDER BY candidate_id"
86
+ rows = self._connection.execute(query, () if status is None else (status.value,)).fetchall()
87
+ return tuple(MissionCandidate.from_dict(json.loads(row["document"])) for row in rows)
88
+
89
+ def refine(self, candidate_id: str, *, actor: str, occurred_at: str, rationale: str, **changes: object) -> MissionCandidate:
90
+ candidate, revision = self._current(candidate_id)
91
+ if candidate.status is not MissionCandidateStatus.BUSINESS_REVIEW:
92
+ raise BusinessWorkspaceError("only candidates in business_review may be refined")
93
+ allowed = {"title", "summary", "business_objective", "business_value", "priority", "business_rationale", "maturity", "dependencies", "required_disciplines"}
94
+ if not changes or set(changes) - allowed:
95
+ raise BusinessWorkspaceError("business refinement may update only business candidate fields")
96
+ refined = replace(candidate, **changes)
97
+ self._write(refined, revision + 1, "refined", actor, occurred_at, rationale)
98
+ return self.get(candidate_id)
99
+
100
+ def approve_for_architecture(self, candidate_id: str, *, actor: str, occurred_at: str, rationale: str) -> MissionCandidate:
101
+ return self._transition(candidate_id, MissionCandidateStatus.APPROVED_FOR_ARCHITECTURE, "approved_for_architecture", actor, occurred_at, rationale)
102
+
103
+ def reject(self, candidate_id: str, *, actor: str, occurred_at: str, rationale: str) -> MissionCandidate:
104
+ return self._transition(candidate_id, MissionCandidateStatus.REJECTED, "rejected", actor, occurred_at, rationale)
105
+
106
+ def archive(self, candidate_id: str, *, actor: str, occurred_at: str, rationale: str) -> MissionCandidate:
107
+ return self._transition(candidate_id, MissionCandidateStatus.ARCHIVED, "archived", actor, occurred_at, rationale)
108
+
109
+ def history(self, candidate_id: str) -> tuple[MissionCandidateHistoryEntry, ...]:
110
+ self.get(candidate_id)
111
+ rows = self._connection.execute("SELECT revision, event, actor, occurred_at, rationale FROM mission_candidate_history WHERE candidate_id = ? ORDER BY revision", (candidate_id,)).fetchall()
112
+ return tuple(MissionCandidateHistoryEntry(**dict(row)) for row in rows)
113
+
114
+ def _transition(self, candidate_id: str, target: MissionCandidateStatus, event: str, actor: str, occurred_at: str, rationale: str) -> MissionCandidate:
115
+ candidate, revision = self._current(candidate_id)
116
+ if candidate.status is not MissionCandidateStatus.BUSINESS_REVIEW:
117
+ raise BusinessWorkspaceError(f"mission candidate transition {candidate.status.value} -> {target.value} is not permitted")
118
+ if not all((actor, occurred_at, rationale)):
119
+ raise BusinessWorkspaceError("business decision requires actor, time, and rationale")
120
+ self._write(replace(candidate, status=target), revision + 1, event, actor, occurred_at, rationale)
121
+ return self.get(candidate_id)
122
+
123
+ def _current(self, candidate_id: str) -> tuple[MissionCandidate, int]:
124
+ row = self._connection.execute("SELECT revision, document FROM mission_candidate WHERE candidate_id = ?", (candidate_id,)).fetchone()
125
+ if row is None:
126
+ raise BusinessWorkspaceError(f"unknown mission candidate: {candidate_id}")
127
+ return MissionCandidate.from_dict(json.loads(row["document"])), row["revision"]
128
+
129
+ def _write(self, candidate: MissionCandidate, revision: int, event: str, actor: str, occurred_at: str, rationale: str, *, insert: bool = False) -> None:
130
+ if not all((actor, occurred_at, rationale)):
131
+ raise BusinessWorkspaceError("business change requires actor, time, and rationale")
132
+ document = json.dumps(candidate.to_dict(), sort_keys=True, separators=(",", ":"), ensure_ascii=False)
133
+ try:
134
+ with self._connection:
135
+ if insert:
136
+ self._connection.execute("INSERT INTO mission_candidate VALUES (?, ?, ?, ?)", (candidate.id, revision, candidate.status.value, document))
137
+ else:
138
+ self._connection.execute("UPDATE mission_candidate SET revision = ?, status = ?, document = ? WHERE candidate_id = ?", (revision, candidate.status.value, document, candidate.id))
139
+ self._connection.execute("INSERT INTO mission_candidate_history VALUES (?, ?, ?, ?, ?, ?)", (candidate.id, revision, event, actor, occurred_at, rationale))
140
+ except sqlite3.IntegrityError as error:
141
+ raise BusinessWorkspaceError(f"mission candidate already exists: {candidate.id}") from error
forge/canary.py ADDED
@@ -0,0 +1,134 @@
1
+ """Deterministic End-to-End Bootstrap Mission Canary qualification scenario."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from forge.intake import MissionIntake
9
+ from forge.models import (
10
+ CodexCliRuntimePromptRequest, EngineeringAction, EngineeringIntent,
11
+ EngineeringActionStatus, ExecutionHostCompatibility, IntentApproval,
12
+ IntentCategory, IntentReference, IntentStatus, IntentTraceability,
13
+ RepositoryState,
14
+ )
15
+ from forge.models.execution_host import ExecutionEvidenceOutcome
16
+ from forge.models.mission import EngineeringMission, MissionIntentMembership, MissionScope, MissionStatus
17
+ from forge.prompts import CodexCliRuntimePromptRenderer
18
+ from forge.runtime import BootstrapMissionRunner, RuntimeDatabase
19
+ from forge.scheduler import BootstrapMissionScheduler
20
+ from forge.scheduler.adapter import (
21
+ BootstrapExecutionHostAdapter, EngineeringPlatformInboxReceipt, EngineeringPlatformReport,
22
+ EngineeringPlatformReportOutcome, ExecutionHostConfiguration,
23
+ )
24
+ from forge.state import MissionExecutionStatus, MissionStateStore
25
+
26
+
27
+ CANARY_MISSION_ID = "forge-bootstrap-mission-canary"
28
+ CANARY_INTENT_ID = "forge-bootstrap-mission-canary-intent"
29
+ CANARY_ACTION_ID = "forge-bootstrap-mission-canary-action"
30
+ CANARY_CORRELATION_ID = "forge-bootstrap-mission-canary-correlation"
31
+ CANARY_HOST_RUN_ID = "forge-bootstrap-mission-canary-host-run"
32
+
33
+
34
+ def _reference(name: str) -> IntentReference:
35
+ return IntentReference(name, "1", f"local://{name}")
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class CanaryQualificationReport:
40
+ answer: str
41
+ mission_id: str
42
+ intent_id: str
43
+ action_id: str
44
+ runtime_prompt_id: str
45
+ execution_host_run_id: str
46
+ correlation_id: str
47
+ evidence_kinds: tuple[str, ...]
48
+ admission_levels: tuple[str, ...]
49
+
50
+
51
+ class _Configuration:
52
+ def resolve(self, host_id: str) -> ExecutionHostConfiguration:
53
+ return ExecutionHostConfiguration(host_id, "2.4", ("GENESIS",), ("codex_cli", "local_git"),
54
+ "engineering-platform>=1.5.0", "canary://engineering-platform-1.5")
55
+
56
+
57
+ class _Preflight:
58
+ def __init__(self) -> None:
59
+ self.levels: list[str] = []
60
+
61
+ def admit(self, compatibility: ExecutionHostCompatibility, configuration: ExecutionHostConfiguration) -> None:
62
+ if compatibility.execution_host_contract_version != configuration.host_contract_version:
63
+ raise ValueError("Execution Host Preflight Level 1 failed")
64
+ self.levels.append("execution_host_level_1")
65
+ if compatibility.execution_mode not in configuration.supported_execution_modes:
66
+ raise ValueError("Workspace Preflight Level 2 failed")
67
+ self.levels.append("workspace_level_2")
68
+ if not set(compatibility.required_capabilities).issubset(configuration.supported_capabilities):
69
+ raise ValueError("Capability Preflight Level 3 failed")
70
+ self.levels.append("capability_level_3")
71
+
72
+
73
+ class _Inbox:
74
+ def __init__(self) -> None:
75
+ self.request = None
76
+
77
+ def submit(self, request: object) -> EngineeringPlatformInboxReceipt:
78
+ self.request = request
79
+ return EngineeringPlatformInboxReceipt(CANARY_HOST_RUN_ID, "forge-bootstrap-mission-canary-receipt", "engineering-platform-1.5", "2026-08-03T23:30:00Z")
80
+
81
+ def receipt_for(self, correlation_id: str) -> EngineeringPlatformInboxReceipt | None:
82
+ return EngineeringPlatformInboxReceipt(CANARY_HOST_RUN_ID, "forge-bootstrap-mission-canary-receipt", "engineering-platform-1.5", "2026-08-03T23:30:00Z") if self.request and correlation_id == CANARY_CORRELATION_ID else None
83
+
84
+
85
+ class _Reports:
86
+ def __init__(self, inbox: _Inbox) -> None: self._inbox = inbox
87
+
88
+ def report_for(self, run_id: str) -> EngineeringPlatformReport | None:
89
+ request = self._inbox.request
90
+ if run_id != CANARY_HOST_RUN_ID or request is None:
91
+ return None
92
+ return EngineeringPlatformReport(CANARY_HOST_RUN_ID, "forge-bootstrap-mission-canary-report",
93
+ EngineeringPlatformReportOutcome.COMPLETE, "canary-repository-revision", "sha256:" + "c" * 64,
94
+ ("validation:bootstrap-mission-canary",), ("evidence:engineering-platform-1.5",),
95
+ "2026-08-03T23:30:00Z", "2026-08-03T23:31:00Z", "forge-bootstrap-mission-canary-receipt",
96
+ request.execution_host_id, request.correlation_id, request.runtime_prompt_id, request.mission_id,
97
+ request.intent_id, request.intent_revision, request.action_id, execution_duration_ms=60_000)
98
+
99
+
100
+ def run_bootstrap_mission_canary(state_path: Path) -> CanaryQualificationReport:
101
+ """Run the complete adapter pipeline once with deterministic host evidence."""
102
+ reference = _reference("canary-approval")
103
+ intent = EngineeringIntent(CANARY_INTENT_ID, "1", "Bootstrap Mission Canary Intent", "Qualify the pipeline.",
104
+ IntentCategory.IMPLEMENTATION, IntentTraceability((reference,), (reference,), (reference,), (reference,), (reference,)),
105
+ approval=IntentApproval("architecture", "2026-08-03T23:29:00Z", reference), status=IntentStatus.APPROVED)
106
+ mission = EngineeringMission(CANARY_MISSION_ID, "1", "Bootstrap Mission Canary", "Qualify one bounded Mission.",
107
+ MissionScope(("one deterministic execution",), ("continuous execution",)),
108
+ (MissionIntentMembership(1, intent.id, intent.revision),), status=MissionStatus.ACTIVE)
109
+ action = EngineeringAction(1, CANARY_ACTION_ID, intent.id, intent.revision, "Execute the one qualified canary Action.",
110
+ ("repository evidence",), status=EngineeringActionStatus.READY)
111
+ preflight, inbox = _Preflight(), _Inbox()
112
+ adapter = BootstrapExecutionHostAdapter(_Configuration(), preflight, inbox, _Reports(inbox))
113
+ with MissionStateStore(RuntimeDatabase(state_path.parent)) as store:
114
+ intake = MissionIntake(store, lambda: "2026-08-03T23:29:30Z")
115
+ intake.admit(mission, (intent,), (action,))
116
+ def render(_intent: object, active: EngineeringAction):
117
+ return CodexCliRuntimePromptRenderer().render(CodexCliRuntimePromptRequest(
118
+ mission, intent, active, RepositoryState("forge", "canary-repository-revision", "sha256:" + "c" * 64, "2026-08-03T23:29:30Z"),
119
+ ("one action only",), ("bootstrap canary validation",),
120
+ ExecutionHostCompatibility("2.4", "GENESIS", ("codex_cli", "local_git"), "engineering-platform>=1.5.0"),
121
+ ))
122
+ runner = BootstrapMissionRunner(store, BootstrapMissionScheduler(), adapter, render, host_id="engineering-platform-1.5",
123
+ workspace_id="forge", repository_id="forge", clock=lambda: "2026-08-03T23:29:30Z",
124
+ correlation_id_factory=lambda: CANARY_CORRELATION_ID)
125
+ # Intake owns state creation; the Runner advances that same durable state.
126
+ result = runner.run(CANARY_MISSION_ID)
127
+ if result.status is not MissionExecutionStatus.COMPLETED:
128
+ raise ValueError("Bootstrap Mission Canary did not complete")
129
+ request = result.execution_correlation["request"]
130
+ evidence = result.execution_evidence
131
+ assert evidence is not None
132
+ return CanaryQualificationReport("YES", mission.id, intent.id, action.id, request["runtime_prompt"]["id"],
133
+ result.execution_correlation["host_run_id"], request["correlation_id"],
134
+ ("execution_host", "engineering", "repository", "validation", "mission_completion"), tuple(preflight.levels))
@@ -0,0 +1,11 @@
1
+ """Capability registry and delegation framework."""
2
+
3
+ from .delegation import (
4
+ CAPABILITY_REGISTRY_SCHEMA_VERSION, DELEGATION_SCHEMA_VERSION, CapabilityAssessment,
5
+ CapabilityAvailability, CapabilityExecutionMode, CapabilityOwner, CapabilityRegistration,
6
+ CapabilityRegistry, DelegationApprovalState, DelegationRequest, DelegationResultState,
7
+ )
8
+
9
+ __all__ = ["CAPABILITY_REGISTRY_SCHEMA_VERSION", "DELEGATION_SCHEMA_VERSION", "CapabilityAssessment",
10
+ "CapabilityAvailability", "CapabilityExecutionMode", "CapabilityOwner", "CapabilityRegistration",
11
+ "CapabilityRegistry", "DelegationApprovalState", "DelegationRequest", "DelegationResultState"]