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.
- forge/__init__.py +35 -0
- forge/__main__.py +20 -0
- forge/_version.py +38 -0
- forge/action_derivation_canary_closure.py +129 -0
- forge/action_derivation_evidence.py +101 -0
- forge/action_derivation_qualification.py +71 -0
- forge/action_derivation_reattempt.py +113 -0
- forge/ai_architect/__init__.py +5 -0
- forge/ai_architect/provider_registry.py +82 -0
- forge/architecture/__init__.py +8 -0
- forge/architecture/advisor.py +37 -0
- forge/architecture/presentation.py +21 -0
- forge/architecture/workspace.py +169 -0
- forge/autonomous_orchestrator.py +201 -0
- forge/business/__init__.py +9 -0
- forge/business/advisor.py +44 -0
- forge/business/ingress.py +186 -0
- forge/business/presentation.py +74 -0
- forge/business/workspace.py +141 -0
- forge/canary.py +134 -0
- forge/capabilities/__init__.py +11 -0
- forge/capabilities/delegation.py +150 -0
- forge/completion/__init__.py +5 -0
- forge/completion/assessor.py +53 -0
- forge/constitutional/__init__.py +5 -0
- forge/constitutional/assessor.py +38 -0
- forge/core/__init__.py +5 -0
- forge/core/json_store.py +24 -0
- forge/decision_evidence/__init__.py +5 -0
- forge/decision_evidence/repository.py +68 -0
- forge/dispatcher/__init__.py +13 -0
- forge/dispatcher/dispatcher.py +194 -0
- forge/execution/__init__.py +13 -0
- forge/execution/loop.py +346 -0
- forge/execution/presentation.py +14 -0
- forge/foundation/__init__.py +10 -0
- forge/foundation/loader.py +378 -0
- forge/generation_two.py +89 -0
- forge/generation_two_execution.py +179 -0
- forge/governance/__init__.py +26 -0
- forge/governance/execution_policy.py +83 -0
- forge/governance/profiles.py +93 -0
- forge/governance_authority.py +199 -0
- forge/intake.py +136 -0
- forge/integration/__init__.py +7 -0
- forge/integration/coordinator.py +100 -0
- forge/integration/repository.py +54 -0
- forge/knowledge/__init__.py +6 -0
- forge/knowledge/consumer.py +39 -0
- forge/knowledge/registry.py +65 -0
- forge/lifecycle/__init__.py +17 -0
- forge/lifecycle/store.py +445 -0
- forge/mission_amendment.py +35 -0
- forge/mission_scheduler.py +264 -0
- forge/models/__init__.py +561 -0
- forge/models/action.py +53 -0
- forge/models/action_derivation.py +284 -0
- forge/models/agent_policy.py +252 -0
- forge/models/ai_architect_provider.py +230 -0
- forge/models/ai_architect_session.py +147 -0
- forge/models/ai_provider_registry.py +140 -0
- forge/models/architecture_mission.py +113 -0
- forge/models/architecture_reasoning.py +257 -0
- forge/models/architecture_review.py +232 -0
- forge/models/codex_runtime_prompt.py +227 -0
- forge/models/constitutional_validation.py +109 -0
- forge/models/decision_evidence.py +202 -0
- forge/models/execution_host.py +289 -0
- forge/models/foundation.py +192 -0
- forge/models/historical_intent.py +212 -0
- forge/models/integration.py +173 -0
- forge/models/intent.py +265 -0
- forge/models/intent_authoring.py +119 -0
- forge/models/mission.py +301 -0
- forge/models/mission_candidate.py +108 -0
- forge/models/mission_planner.py +219 -0
- forge/models/mission_recommendation.py +222 -0
- forge/models/phase_completion.py +162 -0
- forge/models/planning.py +162 -0
- forge/models/producer.py +164 -0
- forge/models/prompt_artifact.py +234 -0
- forge/models/proposal.py +148 -0
- forge/models/runtime_prompt.py +245 -0
- forge/models/solution_template.py +64 -0
- forge/operator_identity.py +135 -0
- forge/planner/__init__.py +28 -0
- forge/planner/action_derivation.py +265 -0
- forge/planner/engine.py +49 -0
- forge/planner/openai_responses.py +660 -0
- forge/planner/provider_adapter.py +81 -0
- forge/planning/__init__.py +6 -0
- forge/planning/loader.py +232 -0
- forge/planning/registry.py +38 -0
- forge/portfolio_intelligence.py +110 -0
- forge/programme_authorization.py +194 -0
- forge/prompts/__init__.py +6 -0
- forge/prompts/codex_cli.py +121 -0
- forge/prompts/generator.py +106 -0
- forge/proposals/__init__.py +5 -0
- forge/proposals/generator.py +90 -0
- forge/provider_security.py +210 -0
- forge/qualification/__init__.py +13 -0
- forge/qualification/bootstrap_sequence.py +266 -0
- forge/qualification/generation_one.py +103 -0
- forge/qualification/installed_smoke.py +29 -0
- forge/recommendations/__init__.py +5 -0
- forge/recommendations/engine.py +113 -0
- forge/repository_truth.py +105 -0
- forge/review/__init__.py +5 -0
- forge/review/engine.py +80 -0
- forge/runtime/__init__.py +34 -0
- forge/runtime/bootstrap.py +360 -0
- forge/runtime/database.py +2191 -0
- forge/runtime/evidence.py +355 -0
- forge/runtime/execution_context.py +173 -0
- forge/runtime/runner.py +402 -0
- forge/runtime/service.py +127 -0
- forge/scheduler/__init__.py +7 -0
- forge/scheduler/adapter.py +245 -0
- forge/scheduler/ep_http_adapter.py +231 -0
- forge/scheduler/ep_v12.py +182 -0
- forge/scheduler/scheduler.py +180 -0
- forge/schemas/capability-registry-1.0.schema.json +23 -0
- forge/schemas/capability.schema.json +17 -0
- forge/schemas/common.schema.json +9 -0
- forge/schemas/decision-evidence-1.0.schema.json +20 -0
- forge/schemas/engineering-goal-0.5.schema.json +16 -0
- forge/schemas/engineering-increment-proposal-0.5.schema.json +20 -0
- forge/schemas/engineering-mode.schema.json +8 -0
- forge/schemas/engineering-plan-0.5.schema.json +29 -0
- forge/schemas/engineering-prompt-artifact-0.7.schema.json +26 -0
- forge/schemas/engineering-proposal-0.6.schema.json +75 -0
- forge/schemas/evidence-reference-0.5.schema.json +16 -0
- forge/schemas/foundation-document.schema.json +26 -0
- forge/schemas/governance-profile.schema.json +8 -0
- forge/schemas/knowledge-source-0.4.schema.json +23 -0
- forge/schemas/knowledge-source.schema.json +18 -0
- forge/schemas/mission-candidate-1.0.schema.json +27 -0
- forge/schemas/mission-candidate-1.1.schema.json +15 -0
- forge/schemas/phase-completion-1.0.schema.json +60 -0
- forge/schemas/planning-document-0.5.schema.json +17 -0
- forge/schemas/repository-catalog.schema.json +24 -0
- forge/schemas/repository.schema.json +18 -0
- forge/schemas/solution-template-1.0.schema.json +14 -0
- forge/schemas/workspace.schema.json +18 -0
- forge/solutions/__init__.py +6 -0
- forge/solutions/catalogue.py +54 -0
- forge/solutions/generator.py +64 -0
- forge/state/__init__.py +19 -0
- forge/state/mission_state.py +429 -0
- forge/workspace/__init__.py +1 -0
- forge_autonomy-2.3.1.dist-info/METADATA +474 -0
- forge_autonomy-2.3.1.dist-info/RECORD +156 -0
- forge_autonomy-2.3.1.dist-info/WHEEL +5 -0
- forge_autonomy-2.3.1.dist-info/entry_points.txt +2 -0
- forge_autonomy-2.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Immutable, non-executing Architecture Advisor contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from forge.models.architecture_mission import ArchitectureMission
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class ArchitectureAdvisorAdvice:
|
|
12
|
+
mission_id: str
|
|
13
|
+
technical_feasibility: str
|
|
14
|
+
architecture_consistency: str
|
|
15
|
+
implementation_boundaries: tuple[str, ...]
|
|
16
|
+
dependency_findings: tuple[str, ...]
|
|
17
|
+
risk_findings: tuple[str, ...]
|
|
18
|
+
repository_consistency: str
|
|
19
|
+
capability_reuse: tuple[str, ...]
|
|
20
|
+
governance_compliance: str
|
|
21
|
+
advisory: bool = True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ArchitectureAdvisor:
|
|
25
|
+
"""Produces advice only; it cannot approve, plan, execute, or operate a repository."""
|
|
26
|
+
|
|
27
|
+
def advise(self, mission: ArchitectureMission) -> ArchitectureAdvisorAdvice:
|
|
28
|
+
readiness = "ready for engineering approval" if mission.is_engineering_ready() else "requires architectural refinement before engineering approval"
|
|
29
|
+
boundaries = mission.engineering_constraints or ("Define explicit engineering constraints.",)
|
|
30
|
+
dependencies = tuple(f"Review dependency: {item}" for item in mission.dependencies) or ("Identify dependencies.",)
|
|
31
|
+
risks = tuple(f"Review risk: {item}" for item in mission.risks) or ("Identify technical risks.",)
|
|
32
|
+
capabilities = tuple(f"Assess reuse of capability: {item}" for item in mission.required_capabilities) or ("Assess existing capability reuse.",)
|
|
33
|
+
return ArchitectureAdvisorAdvice(
|
|
34
|
+
mission.id, readiness, "Preserve the approved business objective and Mission boundaries.", boundaries,
|
|
35
|
+
dependencies, risks, "Repository truth must be reviewed by a human; this contract performs no repository access.",
|
|
36
|
+
capabilities, "Architecture approval remains a Platform Architect decision.",
|
|
37
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Minimal deterministic Architecture Workspace detail view."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from forge.models.architecture_mission import ArchitectureMission
|
|
8
|
+
from forge.models.decision_evidence import DecisionEvidence
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def render_architecture_mission(mission: ArchitectureMission) -> dict[str, Any]:
|
|
12
|
+
return {**mission.to_dict(), "advisory_available": True, "execution_available": False, "repository_mutation_available": False}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def render_architecture_decision_evidence(evidence: DecisionEvidence) -> dict[str, Any]:
|
|
16
|
+
"""Expose architecture rationale and traceability without an approval action."""
|
|
17
|
+
return {"id": evidence.id, "decision_type": evidence.decision_type.value, "decision": evidence.decision,
|
|
18
|
+
"reasoning_summary": evidence.reasoning_summary, "required_disciplines": list(evidence.required_disciplines),
|
|
19
|
+
"architecture_constraints": list(evidence.architecture_constraints),
|
|
20
|
+
"traceability": [item.to_dict() for item in evidence.evidence_references], "confidence": evidence.confidence.to_dict(),
|
|
21
|
+
"approval_state": evidence.approval_state.value, "execution_available": False}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Local Architecture Workspace governance and approval persistence."""
|
|
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.architecture_mission import ArchitectureMission, ArchitectureMissionStatus
|
|
11
|
+
from forge.models.mission_candidate import MissionCandidate, MissionCandidateStatus
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ArchitectureWorkspaceError(ValueError):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class ArchitectureMissionHistoryEntry:
|
|
20
|
+
revision: int
|
|
21
|
+
event: str
|
|
22
|
+
actor: str
|
|
23
|
+
occurred_at: str
|
|
24
|
+
rationale: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ArchitectureWorkspace:
|
|
28
|
+
"""Legacy local workspace storage for offline/tests; not production authority.
|
|
29
|
+
|
|
30
|
+
Supported Forge governance mutations use :meth:`for_runtime`, which is
|
|
31
|
+
bound to the resolved Runtime Instance and canonical evidence repository.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def for_runtime(cls, database: object, repository: object, context: object) -> object:
|
|
36
|
+
"""Return the canonical runtime-bound Architecture approval service."""
|
|
37
|
+
from forge.governance_authority import CanonicalArchitectureWorkspace
|
|
38
|
+
if getattr(repository, "database", None) is not database:
|
|
39
|
+
raise ArchitectureWorkspaceError("Architecture governance repository must use the resolved Runtime Instance")
|
|
40
|
+
return CanonicalArchitectureWorkspace(repository, context)
|
|
41
|
+
|
|
42
|
+
def __init__(self, path: Path) -> None:
|
|
43
|
+
self._connection = sqlite3.connect(path)
|
|
44
|
+
self._connection.row_factory = sqlite3.Row
|
|
45
|
+
self._connection.executescript("""
|
|
46
|
+
CREATE TABLE IF NOT EXISTS architecture_mission (
|
|
47
|
+
mission_id TEXT PRIMARY KEY, candidate_id TEXT UNIQUE NOT NULL, revision INTEGER NOT NULL, status TEXT NOT NULL, document TEXT NOT NULL
|
|
48
|
+
);
|
|
49
|
+
CREATE TABLE IF NOT EXISTS architecture_mission_history (
|
|
50
|
+
mission_id TEXT NOT NULL, revision INTEGER NOT NULL, event TEXT NOT NULL, actor TEXT NOT NULL,
|
|
51
|
+
occurred_at TEXT NOT NULL, rationale TEXT NOT NULL, PRIMARY KEY(mission_id, revision),
|
|
52
|
+
FOREIGN KEY(mission_id) REFERENCES architecture_mission(mission_id)
|
|
53
|
+
);
|
|
54
|
+
CREATE TRIGGER IF NOT EXISTS architecture_mission_history_no_update BEFORE UPDATE ON architecture_mission_history
|
|
55
|
+
BEGIN SELECT RAISE(ABORT, 'architecture mission history is append-only'); END;
|
|
56
|
+
CREATE TRIGGER IF NOT EXISTS architecture_mission_history_no_delete BEFORE DELETE ON architecture_mission_history
|
|
57
|
+
BEGIN SELECT RAISE(ABORT, 'architecture mission history is append-only'); END;
|
|
58
|
+
""")
|
|
59
|
+
self._connection.commit()
|
|
60
|
+
|
|
61
|
+
def close(self) -> None:
|
|
62
|
+
self._connection.close()
|
|
63
|
+
|
|
64
|
+
def __enter__(self) -> "ArchitectureWorkspace":
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def __exit__(self, *_: object) -> None:
|
|
68
|
+
self.close()
|
|
69
|
+
|
|
70
|
+
def admit(self, candidate: MissionCandidate, *, actor: str, occurred_at: str, rationale: str) -> ArchitectureMission:
|
|
71
|
+
if candidate.status is not MissionCandidateStatus.APPROVED_FOR_ARCHITECTURE:
|
|
72
|
+
raise ArchitectureWorkspaceError("only business-approved candidates may enter architecture review")
|
|
73
|
+
mission = ArchitectureMission.from_candidate(candidate)
|
|
74
|
+
self._write(mission, 1, "admitted_from_business", actor, occurred_at, rationale, insert=True)
|
|
75
|
+
return self.get(mission.id)
|
|
76
|
+
|
|
77
|
+
create = admit
|
|
78
|
+
|
|
79
|
+
def get(self, mission_id: str) -> ArchitectureMission:
|
|
80
|
+
row = self._connection.execute("SELECT document FROM architecture_mission WHERE mission_id = ?", (mission_id,)).fetchone()
|
|
81
|
+
if row is None:
|
|
82
|
+
raise ArchitectureWorkspaceError(f"unknown architecture mission: {mission_id}")
|
|
83
|
+
return ArchitectureMission.from_dict(json.loads(row["document"]))
|
|
84
|
+
|
|
85
|
+
def list(self, *, status: ArchitectureMissionStatus | None = None) -> tuple[ArchitectureMission, ...]:
|
|
86
|
+
query = "SELECT document FROM architecture_mission" + (" WHERE status = ?" if status else "") + " ORDER BY mission_id"
|
|
87
|
+
rows = self._connection.execute(query, () if status is None else (status.value,)).fetchall()
|
|
88
|
+
return tuple(ArchitectureMission.from_dict(json.loads(row["document"])) for row in rows)
|
|
89
|
+
|
|
90
|
+
def approved_for_engineering(self) -> tuple[ArchitectureMission, ...]:
|
|
91
|
+
"""Return the governed queue source in deterministic approval order."""
|
|
92
|
+
rows = self._connection.execute(
|
|
93
|
+
"""
|
|
94
|
+
SELECT mission.document
|
|
95
|
+
FROM architecture_mission AS mission
|
|
96
|
+
JOIN architecture_mission_history AS history
|
|
97
|
+
ON history.mission_id = mission.mission_id
|
|
98
|
+
AND history.event = 'approved_for_engineering'
|
|
99
|
+
WHERE mission.status = ?
|
|
100
|
+
ORDER BY history.occurred_at, mission.mission_id
|
|
101
|
+
""",
|
|
102
|
+
(ArchitectureMissionStatus.APPROVED_FOR_ENGINEERING.value,),
|
|
103
|
+
).fetchall()
|
|
104
|
+
return tuple(ArchitectureMission.from_dict(json.loads(row["document"])) for row in rows)
|
|
105
|
+
|
|
106
|
+
def refine(self, mission_id: str, *, actor: str, occurred_at: str, rationale: str, **changes: object) -> ArchitectureMission:
|
|
107
|
+
mission, revision = self._current(mission_id)
|
|
108
|
+
if mission.status is not ArchitectureMissionStatus.ARCHITECTURE_REVIEW:
|
|
109
|
+
raise ArchitectureWorkspaceError("only Missions in architecture_review may be refined")
|
|
110
|
+
allowed = {"scope", "engineering_constraints", "acceptance_criteria", "technical_assumptions", "dependencies", "required_capabilities", "required_disciplines", "risks"}
|
|
111
|
+
if not changes or set(changes) - allowed:
|
|
112
|
+
raise ArchitectureWorkspaceError("architecture refinement may update only engineering governance fields")
|
|
113
|
+
refined = replace(mission, **changes)
|
|
114
|
+
self._write(refined, revision + 1, "refined", actor, occurred_at, rationale)
|
|
115
|
+
return self.get(mission_id)
|
|
116
|
+
|
|
117
|
+
def approve_for_engineering(self, mission_id: str, *, actor: str, occurred_at: str, rationale: str) -> ArchitectureMission:
|
|
118
|
+
mission, revision = self._current(mission_id)
|
|
119
|
+
if mission.status is not ArchitectureMissionStatus.ARCHITECTURE_REVIEW:
|
|
120
|
+
raise ArchitectureWorkspaceError(f"architecture mission transition {mission.status.value} -> approved_for_engineering is not permitted")
|
|
121
|
+
if not mission.is_engineering_ready():
|
|
122
|
+
raise ArchitectureWorkspaceError("engineering approval requires a complete architectural refinement")
|
|
123
|
+
return self._transition(mission, revision, ArchitectureMissionStatus.APPROVED_FOR_ENGINEERING, "approved_for_engineering", actor, occurred_at, rationale)
|
|
124
|
+
|
|
125
|
+
def return_to_business(self, mission_id: str, *, actor: str, occurred_at: str, rationale: str) -> ArchitectureMission:
|
|
126
|
+
return self._terminal_transition(mission_id, ArchitectureMissionStatus.RETURNED_TO_BUSINESS, "returned_to_business", actor, occurred_at, rationale)
|
|
127
|
+
|
|
128
|
+
def reject(self, mission_id: str, *, actor: str, occurred_at: str, rationale: str) -> ArchitectureMission:
|
|
129
|
+
return self._terminal_transition(mission_id, ArchitectureMissionStatus.REJECTED, "rejected", actor, occurred_at, rationale)
|
|
130
|
+
|
|
131
|
+
def archive(self, mission_id: str, *, actor: str, occurred_at: str, rationale: str) -> ArchitectureMission:
|
|
132
|
+
return self._terminal_transition(mission_id, ArchitectureMissionStatus.ARCHIVED, "archived", actor, occurred_at, rationale)
|
|
133
|
+
|
|
134
|
+
def history(self, mission_id: str) -> tuple[ArchitectureMissionHistoryEntry, ...]:
|
|
135
|
+
self.get(mission_id)
|
|
136
|
+
rows = self._connection.execute("SELECT revision, event, actor, occurred_at, rationale FROM architecture_mission_history WHERE mission_id = ? ORDER BY revision", (mission_id,)).fetchall()
|
|
137
|
+
return tuple(ArchitectureMissionHistoryEntry(**dict(row)) for row in rows)
|
|
138
|
+
|
|
139
|
+
def _terminal_transition(self, mission_id: str, target: ArchitectureMissionStatus, event: str, actor: str, occurred_at: str, rationale: str) -> ArchitectureMission:
|
|
140
|
+
mission, revision = self._current(mission_id)
|
|
141
|
+
if mission.status is not ArchitectureMissionStatus.ARCHITECTURE_REVIEW:
|
|
142
|
+
raise ArchitectureWorkspaceError(f"architecture mission transition {mission.status.value} -> {target.value} is not permitted")
|
|
143
|
+
return self._transition(mission, revision, target, event, actor, occurred_at, rationale)
|
|
144
|
+
|
|
145
|
+
def _transition(self, mission: ArchitectureMission, revision: int, target: ArchitectureMissionStatus, event: str, actor: str, occurred_at: str, rationale: str) -> ArchitectureMission:
|
|
146
|
+
if not all((actor, occurred_at, rationale)):
|
|
147
|
+
raise ArchitectureWorkspaceError("architecture decision requires actor, time, and rationale")
|
|
148
|
+
self._write(replace(mission, status=target), revision + 1, event, actor, occurred_at, rationale)
|
|
149
|
+
return self.get(mission.id)
|
|
150
|
+
|
|
151
|
+
def _current(self, mission_id: str) -> tuple[ArchitectureMission, int]:
|
|
152
|
+
row = self._connection.execute("SELECT revision, document FROM architecture_mission WHERE mission_id = ?", (mission_id,)).fetchone()
|
|
153
|
+
if row is None:
|
|
154
|
+
raise ArchitectureWorkspaceError(f"unknown architecture mission: {mission_id}")
|
|
155
|
+
return ArchitectureMission.from_dict(json.loads(row["document"])), row["revision"]
|
|
156
|
+
|
|
157
|
+
def _write(self, mission: ArchitectureMission, revision: int, event: str, actor: str, occurred_at: str, rationale: str, *, insert: bool = False) -> None:
|
|
158
|
+
if not all((actor, occurred_at, rationale)):
|
|
159
|
+
raise ArchitectureWorkspaceError("architecture change requires actor, time, and rationale")
|
|
160
|
+
document = json.dumps(mission.to_dict(), sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
|
161
|
+
try:
|
|
162
|
+
with self._connection:
|
|
163
|
+
if insert:
|
|
164
|
+
self._connection.execute("INSERT INTO architecture_mission VALUES (?, ?, ?, ?, ?)", (mission.id, mission.candidate_id, revision, mission.status.value, document))
|
|
165
|
+
else:
|
|
166
|
+
self._connection.execute("UPDATE architecture_mission SET revision = ?, status = ?, document = ? WHERE mission_id = ?", (revision, mission.status.value, document, mission.id))
|
|
167
|
+
self._connection.execute("INSERT INTO architecture_mission_history VALUES (?, ?, ?, ?, ?, ?)", (mission.id, revision, event, actor, occurred_at, rationale))
|
|
168
|
+
except sqlite3.IntegrityError as error:
|
|
169
|
+
raise ArchitectureWorkspaceError(f"architecture mission already exists: {mission.id}") from error
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Durable, fail-closed orchestration of one approved active Mission.
|
|
2
|
+
|
|
3
|
+
This composition boundary deliberately does not invoke an Execution Host. It
|
|
4
|
+
selects one bounded action, records the hand-off, and accepts only a complete
|
|
5
|
+
host-issued receipt before continuing. All durable state belongs to the
|
|
6
|
+
canonical :class:`RuntimeDatabase`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from hashlib import sha256
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from forge.runtime import RuntimeDatabase, RuntimeDatabaseError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AutonomousOrchestrationError(RuntimeDatabaseError):
|
|
19
|
+
"""A Mission cannot be autonomously advanced without human intervention."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class ExecutionReceipt:
|
|
24
|
+
"""The minimal immutable identity Forge retains from an Execution Host."""
|
|
25
|
+
|
|
26
|
+
receipt_id: str
|
|
27
|
+
action_id: str
|
|
28
|
+
execution_host: str
|
|
29
|
+
execution_run_id: str
|
|
30
|
+
engineering_report_id: str
|
|
31
|
+
correlation_identity: str
|
|
32
|
+
executed_at: str
|
|
33
|
+
outcome: str
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
if self.outcome not in {"complete", "blocked", "failed"}:
|
|
37
|
+
raise ValueError("execution receipt outcome is unsupported")
|
|
38
|
+
if not all(getattr(self, field) for field in self.__dataclass_fields__):
|
|
39
|
+
raise ValueError("execution receipt requires complete host-issued identity")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class OrchestrationResult:
|
|
44
|
+
mission_id: str
|
|
45
|
+
state: str
|
|
46
|
+
iteration_number: int
|
|
47
|
+
current_intent_id: str | None
|
|
48
|
+
current_action_id: str | None
|
|
49
|
+
runtime_prompt_id: str | None
|
|
50
|
+
decision_evidence_id: str | None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AutonomousMissionOrchestrator:
|
|
54
|
+
"""Advance exactly one approved Mission through sequential host receipts."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, runtime: RuntimeDatabase, *, timestamp: str) -> None:
|
|
57
|
+
self._runtime = runtime
|
|
58
|
+
self._timestamp = timestamp
|
|
59
|
+
|
|
60
|
+
def advance(self, mission_id: str, receipt: ExecutionReceipt | None = None) -> OrchestrationResult:
|
|
61
|
+
state = self._runtime.get_document("mission_state", mission_id)
|
|
62
|
+
self._assert_active_mission(mission_id)
|
|
63
|
+
try:
|
|
64
|
+
self._assert_governance(state)
|
|
65
|
+
except AutonomousOrchestrationError as error:
|
|
66
|
+
state["orchestration_state"] = ("WAITING_BUSINESS_APPROVAL" if "Business" in str(error)
|
|
67
|
+
else "WAITING_ARCHITECTURE_APPROVAL")
|
|
68
|
+
state["status"] = "WAITING"
|
|
69
|
+
self._save(state)
|
|
70
|
+
return self._result(state)
|
|
71
|
+
if state.get("lifecycle") == "COMPLETE":
|
|
72
|
+
return self._result(state)
|
|
73
|
+
if state.get("orchestration_state") in {
|
|
74
|
+
"WAITING_EXTERNAL_CAPABILITY", "WAITING_BUSINESS_APPROVAL",
|
|
75
|
+
"WAITING_ARCHITECTURE_APPROVAL", "WAITING_EXECUTION_FAILURE",
|
|
76
|
+
}:
|
|
77
|
+
return self._result(state)
|
|
78
|
+
if receipt is not None:
|
|
79
|
+
state = self._apply_receipt(state, receipt)
|
|
80
|
+
if state.get("lifecycle") == "COMPLETE" or state.get("orchestration_state") == "WAITING_EXECUTION_FAILURE":
|
|
81
|
+
return self._result(state)
|
|
82
|
+
return self._dispatch_next(state)
|
|
83
|
+
|
|
84
|
+
def _assert_active_mission(self, mission_id: str) -> None:
|
|
85
|
+
row = self._runtime._connection.execute(
|
|
86
|
+
"SELECT status, active_mission_id FROM dispatcher_state WHERE singleton = 1"
|
|
87
|
+
).fetchone()
|
|
88
|
+
if row is None or row["status"] != "ACTIVE" or row["active_mission_id"] != mission_id:
|
|
89
|
+
raise AutonomousOrchestrationError("autonomous orchestration requires exactly one active Mission")
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _assert_governance(state: dict[str, Any]) -> None:
|
|
93
|
+
governance = state.get("governance", {})
|
|
94
|
+
if governance.get("business_approval") != "approved":
|
|
95
|
+
raise AutonomousOrchestrationError("Business approval is not valid for the next dispatch")
|
|
96
|
+
if governance.get("architecture_approval") != "approved":
|
|
97
|
+
raise AutonomousOrchestrationError("Architecture approval is not valid for the next dispatch")
|
|
98
|
+
|
|
99
|
+
def _apply_receipt(self, state: dict[str, Any], receipt: ExecutionReceipt) -> dict[str, Any]:
|
|
100
|
+
current = state.get("current_engineering_action") or {}
|
|
101
|
+
if current.get("id") != receipt.action_id:
|
|
102
|
+
raise AutonomousOrchestrationError("execution receipt does not match the current Engineering Action")
|
|
103
|
+
if self._runtime.has_execution_receipt(receipt.receipt_id):
|
|
104
|
+
return state
|
|
105
|
+
self._runtime.record_execution_receipt(
|
|
106
|
+
receipt_id=receipt.receipt_id, mission_id=state["mission_id"], execution_host=receipt.execution_host,
|
|
107
|
+
execution_run_id=receipt.execution_run_id, engineering_report_id=receipt.engineering_report_id,
|
|
108
|
+
correlation_identity=receipt.correlation_identity, executed_at=receipt.executed_at, outcome=receipt.outcome,
|
|
109
|
+
)
|
|
110
|
+
action_status = {"complete": "COMPLETED", "blocked": "BLOCKED", "failed": "FAILED"}[receipt.outcome]
|
|
111
|
+
for action in state["engineering_actions"]:
|
|
112
|
+
if action["id"] == receipt.action_id:
|
|
113
|
+
action["status"] = action_status
|
|
114
|
+
references = list(state.get("execution_receipt_references", []))
|
|
115
|
+
references.append({"artifact_id": receipt.receipt_id, "kind": "execution_receipt"})
|
|
116
|
+
state["execution_receipt_references"] = references
|
|
117
|
+
state.setdefault("iteration_history", []).append({
|
|
118
|
+
"iteration": state.get("iteration_number", 0), "action_id": receipt.action_id,
|
|
119
|
+
"receipt_id": receipt.receipt_id, "outcome": receipt.outcome,
|
|
120
|
+
})
|
|
121
|
+
if receipt.outcome != "complete":
|
|
122
|
+
state["orchestration_state"] = "WAITING_EXECUTION_FAILURE"
|
|
123
|
+
state["status"] = "BLOCKED" if receipt.outcome == "blocked" else "FAILED"
|
|
124
|
+
self._save(state)
|
|
125
|
+
return state
|
|
126
|
+
self._complete_intents(state)
|
|
127
|
+
if all(action["status"] == "COMPLETED" for action in state["engineering_actions"]):
|
|
128
|
+
state.update({"lifecycle": "COMPLETE", "status": "COMPLETE", "orchestration_state": "COMPLETE",
|
|
129
|
+
"current_engineering_action": None, "current_engineering_intent": None,
|
|
130
|
+
"runtime_prompts": [], "progress": self._progress(state)})
|
|
131
|
+
self._runtime.save_dispatcher_state(status="IDLE", mission_sequence=(state["mission_id"],))
|
|
132
|
+
self._save(state)
|
|
133
|
+
self._runtime.record_mission_lifecycle(state["mission_id"], "complete", self._timestamp)
|
|
134
|
+
return state
|
|
135
|
+
self._save(state)
|
|
136
|
+
return state
|
|
137
|
+
|
|
138
|
+
def _dispatch_next(self, state: dict[str, Any]) -> OrchestrationResult:
|
|
139
|
+
actions = {action["id"]: action for action in state.get("engineering_actions", [])}
|
|
140
|
+
candidates = [action for action in actions.values() if action.get("status") in {"READY", "ACTIVE"}
|
|
141
|
+
and all(actions.get(dependency, {}).get("status") == "COMPLETED" for dependency in action.get("dependencies", []))]
|
|
142
|
+
if len(candidates) != 1:
|
|
143
|
+
raise AutonomousOrchestrationError("autonomous orchestration requires exactly one executable Engineering Action")
|
|
144
|
+
action = candidates[0]
|
|
145
|
+
action["status"] = "ACTIVE"
|
|
146
|
+
intent = next((item for item in state["engineering_intents"] if item["id"] == action["intent_id"]), None)
|
|
147
|
+
if intent is None:
|
|
148
|
+
raise AutonomousOrchestrationError("Engineering Action has no persisted Engineering Intent")
|
|
149
|
+
iteration = int(state.get("iteration_number", 0)) + 1
|
|
150
|
+
prompt_id = "runtime-prompt:" + sha256(f"{state['mission_id']}:{action['id']}:{iteration}".encode()).hexdigest()
|
|
151
|
+
decision_id = f"{state['mission_id']}-orchestration-decision-{iteration}"
|
|
152
|
+
state.update({
|
|
153
|
+
"iteration_number": iteration, "current_iteration": iteration, "current_engineering_action": action,
|
|
154
|
+
"current_engineering_intent": intent, "runtime_prompts": [{"id": prompt_id, "action_id": action["id"],
|
|
155
|
+
"intent_id": intent["id"], "status": "READY_FOR_ENGINEERING_PLATFORM"}],
|
|
156
|
+
"orchestration_state": "WAITING_FOR_EXECUTION", "status": "ACTIVE", "lifecycle": "ACTIVE",
|
|
157
|
+
"decision_evidence_ids": list(state.get("decision_evidence_ids", [])) + [decision_id], "progress": self._progress(state),
|
|
158
|
+
})
|
|
159
|
+
self._save(state)
|
|
160
|
+
self._runtime.record_decision_evidence({
|
|
161
|
+
"id": decision_id, "decision_type": "engineering_action_selection", "timestamp": self._timestamp,
|
|
162
|
+
"mission_context": {"artifact_id": state["mission_id"]}, "repository_context": {"artifact_id": "forge-repository-truth"},
|
|
163
|
+
"reasoning_summary": "Exactly one approved, dependency-satisfied action was selected for the next bounded execution.",
|
|
164
|
+
"evidence_references": [{"artifact_id": "runtime:mission_state"}],
|
|
165
|
+
"alternatives_considered": [{"id": "sequential", "reason": "Selected: preserves one-action execution and receipt correlation."}],
|
|
166
|
+
"chosen_alternative": "sequential", "confidence": {"score": 100, "mission_state": {"artifact_id": state["mission_id"]}},
|
|
167
|
+
"execution_receipt_references": [],
|
|
168
|
+
})
|
|
169
|
+
return self._result(state)
|
|
170
|
+
|
|
171
|
+
def _save(self, state: dict[str, Any]) -> None:
|
|
172
|
+
self._runtime.save_mission_state(state)
|
|
173
|
+
remaining = [action["id"] for action in state["engineering_actions"] if action["status"] != "COMPLETED"]
|
|
174
|
+
complete = state.get("lifecycle") == "COMPLETE"
|
|
175
|
+
self._runtime.save_planning_state({"planner_version": "autonomous-mission-orchestrator-1", "current_queue": [] if complete else [state["mission_id"]],
|
|
176
|
+
"pending_engineering_actions": [] if complete else remaining, "blocked_engineering_actions": [action["id"] for action in state["engineering_actions"] if action["status"] in {"BLOCKED", "FAILED"}],
|
|
177
|
+
"execution_policy": state["execution_policy"], "planner_runtime_metadata": {"iteration_number": state.get("iteration_number", 0)}})
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def _complete_intents(state: dict[str, Any]) -> None:
|
|
181
|
+
for intent in state["engineering_intents"]:
|
|
182
|
+
relevant = [action for action in state["engineering_actions"] if action["intent_id"] == intent["id"]]
|
|
183
|
+
if relevant and all(action["status"] == "COMPLETED" for action in relevant):
|
|
184
|
+
intent["status"] = "COMPLETED"
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _progress(state: dict[str, Any]) -> dict[str, int]:
|
|
188
|
+
actions = state["engineering_actions"]
|
|
189
|
+
completed = sum(action["status"] == "COMPLETED" for action in actions)
|
|
190
|
+
return {"percent_complete": (completed * 100) // len(actions), "completed_engineering_actions": completed,
|
|
191
|
+
"remaining_engineering_actions": len(actions) - completed}
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _result(state: dict[str, Any]) -> OrchestrationResult:
|
|
195
|
+
action = state.get("current_engineering_action") or {}
|
|
196
|
+
intent = state.get("current_engineering_intent") or {}
|
|
197
|
+
prompt = next(iter(state.get("runtime_prompts", [])), {})
|
|
198
|
+
decisions = state.get("decision_evidence_ids", [])
|
|
199
|
+
return OrchestrationResult(state["mission_id"], state.get("orchestration_state", state["status"]),
|
|
200
|
+
int(state.get("iteration_number", 0)), intent.get("id"), action.get("id"), prompt.get("id"),
|
|
201
|
+
decisions[-1] if decisions else None)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Business Workspace public contracts."""
|
|
2
|
+
|
|
3
|
+
from .advisor import BusinessAdvisor, BusinessAdvisorAdvice
|
|
4
|
+
from .presentation import render_business_decision_evidence, render_mission_recommendation, render_persisted_mission_recommendation, render_persisted_recommendation_set
|
|
5
|
+
from .workspace import BusinessWorkspace, BusinessWorkspaceError, MissionCandidateHistoryEntry
|
|
6
|
+
from forge.governance_authority import CanonicalBusinessWorkspace
|
|
7
|
+
from .ingress import BusinessGovernanceIngress, BusinessGovernanceIngressResult
|
|
8
|
+
|
|
9
|
+
__all__ = ["BusinessAdvisor", "BusinessAdvisorAdvice", "BusinessWorkspace", "BusinessWorkspaceError", "CanonicalBusinessWorkspace", "MissionCandidateHistoryEntry", "BusinessGovernanceIngress", "BusinessGovernanceIngressResult", "render_business_decision_evidence", "render_mission_recommendation", "render_persisted_mission_recommendation", "render_persisted_recommendation_set"]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Non-executing Business Advisor contract for Mission Candidate refinement."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from forge.models.mission_candidate import MissionCandidate, MissionCandidateMaturity
|
|
8
|
+
from forge.models.mission_recommendation import RequiredDiscipline
|
|
9
|
+
from forge.models.solution_template import SolutionTemplate
|
|
10
|
+
from forge.solutions.generator import ADVISOR_QUESTIONS, BusinessAdvisorAnswers
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class BusinessAdvisorAdvice:
|
|
15
|
+
candidate_id: str
|
|
16
|
+
missing_information: tuple[str, ...]
|
|
17
|
+
missing_disciplines: tuple[RequiredDiscipline, ...]
|
|
18
|
+
assumption_challenges: tuple[str, ...]
|
|
19
|
+
business_impact_assessment: str
|
|
20
|
+
advisory: bool = True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BusinessAdvisor:
|
|
24
|
+
"""Produces business advice only; it has no state, approval, Mission, or repository authority."""
|
|
25
|
+
|
|
26
|
+
def advise(self, candidate: MissionCandidate) -> BusinessAdvisorAdvice:
|
|
27
|
+
missing = []
|
|
28
|
+
if candidate.maturity is not MissionCandidateMaturity.READY_FOR_ARCHITECTURE:
|
|
29
|
+
missing.append("candidate maturity has not reached ready_for_architecture")
|
|
30
|
+
if len(candidate.business_objective.split()) < 3:
|
|
31
|
+
missing.append("business objective needs a concrete outcome")
|
|
32
|
+
if len(candidate.dependencies) == 0:
|
|
33
|
+
missing.append("dependencies need explicit confirmation, including none when applicable")
|
|
34
|
+
disciplines = tuple(item for item in candidate.required_disciplines if item in {RequiredDiscipline.BUSINESS, RequiredDiscipline.COMMERCIAL, RequiredDiscipline.MARKET_RESEARCH, RequiredDiscipline.LEGAL, RequiredDiscipline.PRIVACY})
|
|
35
|
+
challenges = ("Validate the stated business value against the target portfolio outcome.", "Confirm the priority against other Mission Candidates.")
|
|
36
|
+
impact = f"Priority {candidate.priority}/100 with {candidate.confidence.value} confidence; review evidence before approval."
|
|
37
|
+
return BusinessAdvisorAdvice(candidate.id, tuple(missing), disciplines, challenges, impact)
|
|
38
|
+
|
|
39
|
+
def advise_template(self, template: SolutionTemplate, answers: BusinessAdvisorAnswers) -> BusinessAdvisorAdvice:
|
|
40
|
+
"""Guide a selected template without generating, approving, or persisting a Mission."""
|
|
41
|
+
missing = tuple(f"answer required: {question}" for question in answers.unanswered)
|
|
42
|
+
challenges = tuple(f"Confirm {question.replace('_', ' ')} for {template.name}." for question in ADVISOR_QUESTIONS)
|
|
43
|
+
impact = f"{template.name} is advisory; the Business Owner decides whether to create and refine its drafts."
|
|
44
|
+
return BusinessAdvisorAdvice(template.reference, missing, template.engineering_disciplines, challenges, impact)
|