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
forge/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Forge's local-only Foundation Model, knowledge, and planning contracts."""
|
|
2
|
+
|
|
3
|
+
from .models import (
|
|
4
|
+
ArchitectureMission,
|
|
5
|
+
Capability,
|
|
6
|
+
EngineeringMode,
|
|
7
|
+
GovernanceProfile,
|
|
8
|
+
KnowledgeSource,
|
|
9
|
+
MissionCandidate,
|
|
10
|
+
Repository,
|
|
11
|
+
RepositoryCatalog,
|
|
12
|
+
Workspace,
|
|
13
|
+
)
|
|
14
|
+
from .foundation import FoundationDocument, FoundationDocumentLoader, ValidationIssue, ValidationReport
|
|
15
|
+
from .planning import EngineeringPlanningDocument, PlanningDocumentLoader, PlanningRegistry, PlanningValidationReport
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ArchitectureMission",
|
|
19
|
+
"Capability",
|
|
20
|
+
"EngineeringMode",
|
|
21
|
+
"GovernanceProfile",
|
|
22
|
+
"KnowledgeSource",
|
|
23
|
+
"MissionCandidate",
|
|
24
|
+
"Repository",
|
|
25
|
+
"RepositoryCatalog",
|
|
26
|
+
"Workspace",
|
|
27
|
+
"FoundationDocument",
|
|
28
|
+
"FoundationDocumentLoader",
|
|
29
|
+
"ValidationIssue",
|
|
30
|
+
"ValidationReport",
|
|
31
|
+
"EngineeringPlanningDocument",
|
|
32
|
+
"PlanningDocumentLoader",
|
|
33
|
+
"PlanningRegistry",
|
|
34
|
+
"PlanningValidationReport",
|
|
35
|
+
]
|
forge/__main__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Read-only command entrypoint for the packaged Forge runtime."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
|
|
6
|
+
from ._version import canonical_version
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main(argv: list[str] | None = None) -> int:
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
prog="forge",
|
|
12
|
+
description="Forge mission planning and evidence interpretation runtime.",
|
|
13
|
+
)
|
|
14
|
+
parser.add_argument("--version", action="version", version=canonical_version())
|
|
15
|
+
parser.parse_args(argv)
|
|
16
|
+
return 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
raise SystemExit(main())
|
forge/_version.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Resolve Forge's canonical version without creating a second allocator."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version as installed_version
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_VERSION = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _source_version() -> str | None:
|
|
14
|
+
"""Read the sole source of authority when this is a source distribution."""
|
|
15
|
+
manifest = Path(__file__).resolve().parents[1] / "product-version.json"
|
|
16
|
+
try:
|
|
17
|
+
value = json.loads(manifest.read_text(encoding="utf-8"))
|
|
18
|
+
except (OSError, json.JSONDecodeError):
|
|
19
|
+
return None
|
|
20
|
+
candidate = value.get("version") if isinstance(value, dict) else None
|
|
21
|
+
return candidate if isinstance(candidate, str) and _VERSION.fullmatch(candidate) else None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def canonical_version() -> str:
|
|
25
|
+
"""Return the source manifest version or installed distribution metadata."""
|
|
26
|
+
source = _source_version()
|
|
27
|
+
if source is not None:
|
|
28
|
+
return source
|
|
29
|
+
try:
|
|
30
|
+
value = installed_version("forge-autonomy")
|
|
31
|
+
except PackageNotFoundError as error:
|
|
32
|
+
raise RuntimeError("Forge version metadata is unavailable") from error
|
|
33
|
+
if _VERSION.fullmatch(value) is None:
|
|
34
|
+
raise RuntimeError("Forge version metadata is not a stable X.Y.Z version")
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__version__ = canonical_version()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Canonical immutable closure for a bounded Action-Derivation qualification canary."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from forge.governance_authority import CanonicalGovernanceRepository, GovernanceCapability
|
|
8
|
+
from forge.operator_identity import OperatorContext
|
|
9
|
+
from forge.runtime.database import _timestamp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_NOT_QUALIFIED = (
|
|
13
|
+
"ACTION_MATERIALIZATION", "AUTONOMOUS_NEXT_MISSION_LOOP", "EP_DISPATCH",
|
|
14
|
+
"EP_RESULT_OBSERVATION", "EXECUTION_ADMISSION",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _digest(value: object) -> str:
|
|
19
|
+
return "sha256:" + sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CanonicalActionDerivationCanaryClosureService:
|
|
23
|
+
"""Close a qualified planning-only canary without changing Mission lifecycle."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, repository: CanonicalGovernanceRepository) -> None:
|
|
26
|
+
self.repository, self.database = repository, repository.database
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def _operator_id(context: OperatorContext) -> str:
|
|
30
|
+
return sha256(context.generated_uid.encode()).hexdigest()[:16]
|
|
31
|
+
|
|
32
|
+
def close(self, *, mission_id: str, successor_attempt_id: str,
|
|
33
|
+
qualification_decision_id: str, operator_context: OperatorContext) -> dict[str, object]:
|
|
34
|
+
if not self.repository.operators.authorize(operator_context):
|
|
35
|
+
raise PermissionError("trusted operator context is required")
|
|
36
|
+
state = self.database.get_document("mission_state", mission_id)
|
|
37
|
+
successor = self.database.get_document("action_derivations", successor_attempt_id)
|
|
38
|
+
required = (
|
|
39
|
+
"predecessor_attempt_id", "authorization_id", "preflight_receipt_id", "main_head",
|
|
40
|
+
"snapshot_digest", "evidence_digest", "effective_contract_digest",
|
|
41
|
+
"provider_configuration", "generation_request_digest", "validation_digest",
|
|
42
|
+
)
|
|
43
|
+
if (state.get("status") != "APPROVED_PLANNABLE" or successor.get("mission_id") != mission_id
|
|
44
|
+
or successor.get("lifecycle") != "VALIDATED" or successor.get("validation_result") != "PASS"
|
|
45
|
+
or successor.get("provider_output_untrusted") is not True
|
|
46
|
+
or successor.get("runtime_action_executed") is not False
|
|
47
|
+
or successor.get("action_materialized") is not False
|
|
48
|
+
or successor.get("engineering_side_effects", 0) != 0
|
|
49
|
+
or any(not isinstance(successor.get(key), str) or not successor[key] for key in required)):
|
|
50
|
+
raise PermissionError("canonical validated non-executing Action-Derivation evidence is incomplete")
|
|
51
|
+
predecessor = self.database.get_document("action_derivations", successor["predecessor_attempt_id"])
|
|
52
|
+
authorization = self.database.get_document(
|
|
53
|
+
"action_derivation_reattempt_authorizations", successor["authorization_id"]
|
|
54
|
+
)
|
|
55
|
+
if (predecessor.get("mission_id") != mission_id or predecessor.get("lifecycle") != "FAILED"
|
|
56
|
+
or authorization.get("successor_attempt_id") != successor_attempt_id
|
|
57
|
+
or authorization.get("predecessor_attempt_id") != successor["predecessor_attempt_id"]
|
|
58
|
+
or any(authorization.get(left) != successor.get(right) for left, right in (
|
|
59
|
+
("planning_snapshot_digest", "snapshot_digest"), ("effective_contract_digest", "effective_contract_digest"),
|
|
60
|
+
("evidence_digest", "evidence_digest"), ("g011_policy_digest", "provider_configuration"),
|
|
61
|
+
("provider_request_digest", "generation_request_digest"), ("main_head", "main_head")))):
|
|
62
|
+
raise PermissionError("canonical successor lineage is stale or conflicting")
|
|
63
|
+
if self.database._connection.execute(
|
|
64
|
+
"SELECT 1 FROM action_derivation_reattempt_consumptions WHERE authorization_id=?",
|
|
65
|
+
(successor["authorization_id"],),
|
|
66
|
+
).fetchone() is None:
|
|
67
|
+
raise PermissionError("validated successor lacks a consumed canonical reattempt authorization")
|
|
68
|
+
receipt = self.database.consumed_token_preflight_receipt(successor["preflight_receipt_id"])
|
|
69
|
+
if any(receipt.get(left) != successor.get(right) for left, right in (
|
|
70
|
+
("mission_id", "mission_id"), ("main_head", "main_head"), ("policy_digest", "provider_configuration"),
|
|
71
|
+
("request_digest", "generation_request_digest"), ("evidence_digest", "evidence_digest"),
|
|
72
|
+
("effective_contract_digest", "effective_contract_digest"),
|
|
73
|
+
)):
|
|
74
|
+
raise PermissionError("canonical token-preflight receipt is stale or conflicting")
|
|
75
|
+
try:
|
|
76
|
+
decision = self.repository.decision(qualification_decision_id)
|
|
77
|
+
except ValueError as error:
|
|
78
|
+
raise PermissionError("post-canary Security qualification is absent") from error
|
|
79
|
+
summary = {
|
|
80
|
+
"mission_id": mission_id, "successor_attempt_id": successor_attempt_id,
|
|
81
|
+
"predecessor_attempt_id": successor["predecessor_attempt_id"],
|
|
82
|
+
"authorization_id": successor["authorization_id"], "main_head": successor["main_head"],
|
|
83
|
+
"snapshot_digest": successor["snapshot_digest"], "evidence_digest": successor["evidence_digest"],
|
|
84
|
+
"effective_contract_digest": successor["effective_contract_digest"],
|
|
85
|
+
"g011_policy_digest": successor["provider_configuration"],
|
|
86
|
+
"request_digest": successor["generation_request_digest"],
|
|
87
|
+
"preflight_receipt_id": successor["preflight_receipt_id"],
|
|
88
|
+
"validation_digest": successor["validation_digest"],
|
|
89
|
+
}
|
|
90
|
+
if (decision.get("subject_id") != successor_attempt_id
|
|
91
|
+
or decision.get("subject_revision") != successor["validation_digest"]
|
|
92
|
+
or decision.get("capability") != GovernanceCapability.SECURITY_APPROVAL.value
|
|
93
|
+
or decision.get("decision") != "approved"
|
|
94
|
+
or decision.get("scope") != ["ACTION_DERIVATION_QUALIFICATION"]
|
|
95
|
+
or decision.get("gates") != ["POST_CANARY_SECURITY_REVIEW"]
|
|
96
|
+
or decision.get("predecessor_digest") != _digest(summary)):
|
|
97
|
+
raise PermissionError("post-canary Security qualification is absent, stale, or conflicting")
|
|
98
|
+
closure_id = f"action-derivation-canary-closure:{mission_id}:{successor_attempt_id}"
|
|
99
|
+
document = {
|
|
100
|
+
"closure_id": closure_id, "mission_id": mission_id,
|
|
101
|
+
"successor_attempt_id": successor_attempt_id,
|
|
102
|
+
"predecessor_attempt_id": successor["predecessor_attempt_id"],
|
|
103
|
+
"qualification_decision_id": qualification_decision_id,
|
|
104
|
+
"qualification_decision_digest": _digest(decision),
|
|
105
|
+
"effective_contract_digest": successor["effective_contract_digest"],
|
|
106
|
+
"evidence_digest": successor["evidence_digest"],
|
|
107
|
+
"g011_policy_digest": successor["provider_configuration"],
|
|
108
|
+
"provider_request_digest": successor["generation_request_digest"],
|
|
109
|
+
"preflight_receipt_id": successor["preflight_receipt_id"],
|
|
110
|
+
"reattempt_authorization_id": successor["authorization_id"],
|
|
111
|
+
"main_head": successor["main_head"], "qualified_capability": "ACTION_DERIVATION",
|
|
112
|
+
"not_qualified_capabilities": list(_NOT_QUALIFIED),
|
|
113
|
+
"installation_id": operator_context.installation_id,
|
|
114
|
+
"operator_id": self._operator_id(operator_context),
|
|
115
|
+
"runtime_id": self.database.metadata["runtime_id"], "closed_at": _timestamp(),
|
|
116
|
+
}
|
|
117
|
+
existing = self.database._connection.execute(
|
|
118
|
+
"SELECT document FROM action_derivation_canary_closures WHERE successor_attempt_id=?",
|
|
119
|
+
(successor_attempt_id,),
|
|
120
|
+
).fetchone()
|
|
121
|
+
if existing is not None:
|
|
122
|
+
persisted = json.loads(existing["document"])
|
|
123
|
+
expected = {key: value for key, value in document.items() if key != "closed_at"}
|
|
124
|
+
actual = {key: value for key, value in persisted.items() if key not in ("closed_at", "digest")}
|
|
125
|
+
if actual != expected:
|
|
126
|
+
raise PermissionError("existing Action-Derivation canary closure is stale or conflicting")
|
|
127
|
+
return persisted
|
|
128
|
+
document["digest"] = _digest(document)
|
|
129
|
+
return self.database.create_action_derivation_canary_closure(document)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Canonical, pre-planning evidence producer for Action Derivation.
|
|
2
|
+
|
|
3
|
+
It deliberately derives only from a Mission's immutable admission contract;
|
|
4
|
+
it neither observes repositories, creates approvals, nor grants execution.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hashlib import sha256
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
from forge.governance_authority import CanonicalGovernanceRepository
|
|
12
|
+
from forge.models.architecture_mission import ArchitectureMission
|
|
13
|
+
from forge.models.intent import IntentReference
|
|
14
|
+
from forge.models.mission_planner import (ApprovedScope, MissionPlannerInput, MissionPlanningState,
|
|
15
|
+
PlanningEvidence, PlanningInputKind)
|
|
16
|
+
from forge.runtime.database import RuntimeDatabase, RuntimeIntegrityError
|
|
17
|
+
from forge.mission_amendment import MissionAmendmentService
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _digest(value: object) -> str:
|
|
21
|
+
return "sha256:" + sha256(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()).hexdigest()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ActionDerivationEvidenceError(ValueError):
|
|
25
|
+
"""The immutable admission contract cannot produce a complete evidence set."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CanonicalActionDerivationEvidenceProducer:
|
|
29
|
+
"""The sole runtime-owned producer for generic pre-planning evidence."""
|
|
30
|
+
|
|
31
|
+
_NO_REPOSITORY_TARGET = "NO_REPOSITORY_TARGET=TRUE"
|
|
32
|
+
_CAPABILITIES = ("action-derivation-planning", "deterministic-action-materialization")
|
|
33
|
+
|
|
34
|
+
def __init__(self, database: RuntimeDatabase, repository: CanonicalGovernanceRepository) -> None:
|
|
35
|
+
if repository.database is not database:
|
|
36
|
+
raise ValueError("evidence producer requires the canonical governance Runtime Database")
|
|
37
|
+
self.database, self.repository = database, repository
|
|
38
|
+
|
|
39
|
+
def produce(self, mission_id: str) -> dict[str, object]:
|
|
40
|
+
state = self.database.get_document("mission_state", mission_id)
|
|
41
|
+
contract, _ = MissionAmendmentService(self.repository).effective_contract(mission_id)
|
|
42
|
+
if not isinstance(contract, dict) or state.get("status") != "APPROVED_PLANNABLE":
|
|
43
|
+
raise ActionDerivationEvidenceError("Mission is not canonically approved/plannable")
|
|
44
|
+
required = ("installation_id", "candidate_id", "subject_revision", "business_decision_id",
|
|
45
|
+
"architecture_decision_id", "planning", "envelope_digest", "write_scope", "mission")
|
|
46
|
+
if any(item not in contract for item in required) or contract["write_scope"] != "NONE":
|
|
47
|
+
raise ActionDerivationEvidenceError("Mission admission contract is incomplete or has non-NONE write scope")
|
|
48
|
+
mission = ArchitectureMission.from_dict(contract["mission"])
|
|
49
|
+
if mission.id != mission_id or mission.candidate_id != contract["candidate_id"]:
|
|
50
|
+
raise RuntimeIntegrityError("Mission admission contract identity mismatch")
|
|
51
|
+
if contract["installation_id"] != self.repository.operators.installation_id():
|
|
52
|
+
raise ActionDerivationEvidenceError("Mission admission contract is cross-installation")
|
|
53
|
+
planning = contract["planning"]
|
|
54
|
+
if not isinstance(planning, dict) or planning.get("provenance_revision") != contract["subject_revision"]:
|
|
55
|
+
raise ActionDerivationEvidenceError("Mission planning evidence revision is stale")
|
|
56
|
+
for decision_id in (contract["business_decision_id"], contract["architecture_decision_id"]):
|
|
57
|
+
decision = self.repository.decision(decision_id)
|
|
58
|
+
if decision["subject_id"] != contract["candidate_id"] or decision["subject_revision"] != contract["subject_revision"]:
|
|
59
|
+
raise ActionDerivationEvidenceError("Mission approval lineage is inconsistent")
|
|
60
|
+
if self._NO_REPOSITORY_TARGET not in mission.engineering_constraints:
|
|
61
|
+
raise ActionDerivationEvidenceError("Mission lacks explicit approved NO_REPOSITORY_TARGET evidence")
|
|
62
|
+
context = {"kind": "NO_REPOSITORY_TARGET", "mission_id": mission_id,
|
|
63
|
+
"installation_id": contract["installation_id"], "candidate_id": contract["candidate_id"],
|
|
64
|
+
"subject_revision": contract["subject_revision"], "write_scope": contract["write_scope"],
|
|
65
|
+
"classification": "planning_only_no_repository_target",
|
|
66
|
+
"provenance": {"envelope_digest": contract["envelope_digest"], "mission_constraint": self._NO_REPOSITORY_TARGET}}
|
|
67
|
+
context["digest"] = _digest(context)
|
|
68
|
+
projection = {"kind": "PRE_PLANNING_CANONICAL_PROJECTION", "mission_id": mission_id,
|
|
69
|
+
"installation_id": contract["installation_id"], "candidate_id": contract["candidate_id"],
|
|
70
|
+
"subject_revision": contract["subject_revision"], "architecture_decision_id": contract["architecture_decision_id"],
|
|
71
|
+
"envelope_digest": contract["envelope_digest"], "planning": planning}
|
|
72
|
+
projection["digest"] = _digest(projection)
|
|
73
|
+
catalogue = {"kind": "RUNTIME_CAPABILITY_CATALOGUE", "mission_id": mission_id,
|
|
74
|
+
"installation_id": contract["installation_id"], "candidate_id": contract["candidate_id"],
|
|
75
|
+
"subject_revision": contract["subject_revision"], "envelope_digest": contract["envelope_digest"],
|
|
76
|
+
"capabilities": list(self._CAPABILITIES), "effective_write_scope": "NONE", "grants_authority": False}
|
|
77
|
+
catalogue["digest"] = _digest(catalogue)
|
|
78
|
+
mission_evidence = {"kind": "MISSION_STATE", "mission_id": mission_id, "digest": _digest(state)}
|
|
79
|
+
value = {"evidence_set_id": "action-derivation-evidence-" + contract["envelope_digest"][7:23],
|
|
80
|
+
"mission_id": mission_id, "installation_id": contract["installation_id"],
|
|
81
|
+
"candidate_id": contract["candidate_id"], "subject_revision": contract["subject_revision"],
|
|
82
|
+
"envelope_digest": contract["envelope_digest"], "mission_state": mission_evidence,
|
|
83
|
+
"repository_context": context, "architecture_review": projection, "capability_catalogue": catalogue}
|
|
84
|
+
value["digest"] = _digest(value)
|
|
85
|
+
return self.database.create_action_derivation_evidence_set(value)
|
|
86
|
+
|
|
87
|
+
def planner_input(self, mission_id: str) -> MissionPlannerInput:
|
|
88
|
+
evidence_set = self.produce(mission_id)
|
|
89
|
+
mission = ArchitectureMission.from_dict(self.database.get_document("mission_state", mission_id)["admission_contract"]["mission"])
|
|
90
|
+
reference = IntentReference("architecture-decision", str(evidence_set["subject_revision"]),
|
|
91
|
+
"runtime://governance/" + str(evidence_set["architecture_review"]["architecture_decision_id"]))
|
|
92
|
+
documents = ((PlanningInputKind.MISSION_STATE, evidence_set["mission_state"]),
|
|
93
|
+
(PlanningInputKind.REPOSITORY_CONTEXT, evidence_set["repository_context"]),
|
|
94
|
+
(PlanningInputKind.ARCHITECTURE_REVIEW, evidence_set["architecture_review"]),
|
|
95
|
+
(PlanningInputKind.CAPABILITY_CATALOGUE, evidence_set["capability_catalogue"]))
|
|
96
|
+
evidence = tuple(PlanningEvidence(kind, str(document["kind"]).lower(), str(evidence_set["subject_revision"]),
|
|
97
|
+
"runtime://action-derivation-evidence/" + str(evidence_set["evidence_set_id"]), str(document["digest"]))
|
|
98
|
+
for kind, document in documents)
|
|
99
|
+
capability = mission.required_capabilities[0]
|
|
100
|
+
scopes = tuple(ApprovedScope(scope, capability, (reference,), (), allow_provider_derivation=True) for scope in mission.scope)
|
|
101
|
+
return MissionPlannerInput(mission, MissionPlanningState(mission.id, int(self.database.get_document("mission_state", mission_id)["revision"])), evidence, scopes)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Canonical post-canary qualification decision for validated derivations."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from forge.governance_authority import CanonicalGovernanceRepository, GovernanceCapability, GovernanceDecision
|
|
8
|
+
from forge.operator_identity import OperatorContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _digest(value: object) -> str:
|
|
12
|
+
return "sha256:" + sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CanonicalActionDerivationQualificationService:
|
|
16
|
+
"""Records no new authority: a typed projection of SECURITY_APPROVAL only."""
|
|
17
|
+
def __init__(self, repository: CanonicalGovernanceRepository) -> None:
|
|
18
|
+
self.repository, self.database = repository, repository.database
|
|
19
|
+
|
|
20
|
+
def qualify(self, *, mission_id: str, successor_attempt_id: str,
|
|
21
|
+
operator_context: OperatorContext, decision_id: str) -> str:
|
|
22
|
+
if not self.repository.operators.authorize(operator_context):
|
|
23
|
+
raise PermissionError("trusted operator context is required")
|
|
24
|
+
state = self.database.get_document("mission_state", mission_id)
|
|
25
|
+
successor = self.database.get_document("action_derivations", successor_attempt_id)
|
|
26
|
+
required = ("predecessor_attempt_id", "authorization_id", "preflight_receipt_id", "main_head", "evidence_digest",
|
|
27
|
+
"effective_contract_digest", "provider_configuration", "generation_request_digest",
|
|
28
|
+
"validation_digest")
|
|
29
|
+
if (state.get("status") != "APPROVED_PLANNABLE" or successor.get("mission_id") != mission_id
|
|
30
|
+
or successor.get("lifecycle") != "VALIDATED" or successor.get("validation_result") != "PASS"
|
|
31
|
+
or successor.get("provider_output_untrusted") is not True
|
|
32
|
+
or successor.get("runtime_action_executed") is not False
|
|
33
|
+
or successor.get("action_materialized") is not False
|
|
34
|
+
or any(not isinstance(successor.get(key), str) or not successor[key] for key in required)):
|
|
35
|
+
raise PermissionError("canonical validated non-executing Action-Derivation evidence is incomplete")
|
|
36
|
+
predecessor = self.database.get_document("action_derivations", successor["predecessor_attempt_id"])
|
|
37
|
+
authorization = self.database.get_document("action_derivation_reattempt_authorizations", successor["authorization_id"])
|
|
38
|
+
if (predecessor.get("lifecycle") != "FAILED"
|
|
39
|
+
or authorization.get("successor_attempt_id") != successor_attempt_id
|
|
40
|
+
or authorization.get("predecessor_attempt_id") != successor["predecessor_attempt_id"]
|
|
41
|
+
or any(authorization.get(left) != successor.get(right) for left, right in (
|
|
42
|
+
("planning_snapshot_digest", "snapshot_digest"), ("g011_policy_digest", "provider_configuration"),
|
|
43
|
+
("evidence_digest", "evidence_digest"), ("effective_contract_digest", "effective_contract_digest"),
|
|
44
|
+
("provider_request_digest", "generation_request_digest"), ("main_head", "main_head")))):
|
|
45
|
+
raise PermissionError("canonical successor lineage is stale or conflicting")
|
|
46
|
+
consumed_authorization = self.database._connection.execute(
|
|
47
|
+
"SELECT 1 FROM action_derivation_reattempt_consumptions WHERE authorization_id = ?",
|
|
48
|
+
(successor["authorization_id"],),
|
|
49
|
+
).fetchone()
|
|
50
|
+
if consumed_authorization is None:
|
|
51
|
+
raise PermissionError("validated successor lacks a consumed canonical reattempt authorization")
|
|
52
|
+
receipt = self.database.consumed_token_preflight_receipt(successor["preflight_receipt_id"])
|
|
53
|
+
if any(receipt.get(left) != successor.get(right) for left, right in (
|
|
54
|
+
("mission_id", "mission_id"), ("main_head", "main_head"), ("policy_digest", "provider_configuration"),
|
|
55
|
+
("request_digest", "generation_request_digest"), ("evidence_digest", "evidence_digest"),
|
|
56
|
+
("effective_contract_digest", "effective_contract_digest"),
|
|
57
|
+
)):
|
|
58
|
+
raise PermissionError("canonical token-preflight receipt is stale or conflicting")
|
|
59
|
+
summary = {"mission_id": mission_id, "successor_attempt_id": successor_attempt_id,
|
|
60
|
+
"predecessor_attempt_id": successor["predecessor_attempt_id"],
|
|
61
|
+
"authorization_id": successor["authorization_id"],
|
|
62
|
+
"main_head": successor["main_head"], "snapshot_digest": successor["snapshot_digest"],
|
|
63
|
+
"evidence_digest": successor["evidence_digest"],
|
|
64
|
+
"effective_contract_digest": successor["effective_contract_digest"],
|
|
65
|
+
"g011_policy_digest": successor["provider_configuration"],
|
|
66
|
+
"request_digest": successor["generation_request_digest"],
|
|
67
|
+
"preflight_receipt_id": successor["preflight_receipt_id"],
|
|
68
|
+
"validation_digest": successor["validation_digest"]}
|
|
69
|
+
return self.repository.record(GovernanceDecision(
|
|
70
|
+
decision_id, successor_attempt_id, successor["validation_digest"], GovernanceCapability.SECURITY_APPROVAL,
|
|
71
|
+
"approved", ("ACTION_DERIVATION_QUALIFICATION",), ("POST_CANARY_SECURITY_REVIEW",), _digest(summary)), operator_context)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Canonical, explicitly authorized successor lineage for Action Derivation.
|
|
2
|
+
|
|
3
|
+
This module allocates no provider work. It records the one durable authority
|
|
4
|
+
needed before a later, separately authorized token preflight may be attempted.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hashlib import sha256
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
from forge.action_derivation_evidence import CanonicalActionDerivationEvidenceProducer
|
|
14
|
+
from forge.governance_authority import CanonicalGovernanceRepository
|
|
15
|
+
from forge.models.action_derivation import PlanningSnapshot
|
|
16
|
+
from forge.operator_identity import OperatorContext
|
|
17
|
+
from forge.planner.openai_responses import _G011PolicySnapshot, _digest
|
|
18
|
+
from forge.planner.provider_adapter import ProviderDerivationRequest
|
|
19
|
+
from forge.runtime.database import RuntimeIntegrityError, _timestamp
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
REQUEST_SEMANTICS_CHANGED = "REQUEST_SEMANTICS_CHANGED"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NewPlanningRequired(RuntimeIntegrityError):
|
|
26
|
+
"""The candidate drifted beyond the narrow reattempt authority."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CanonicalActionDerivationReattemptService:
|
|
30
|
+
"""The sole application path for a governed Action-Derivation successor.
|
|
31
|
+
|
|
32
|
+
It accepts a canonical adapter/configuration rather than caller-provided
|
|
33
|
+
policy, evidence, database, or request digests. Persisting authorization
|
|
34
|
+
never calls a provider and never consumes a preflight receipt.
|
|
35
|
+
"""
|
|
36
|
+
def __init__(self, adapter, governance_repository: CanonicalGovernanceRepository) -> None:
|
|
37
|
+
database = adapter.configuration.policy_service.db
|
|
38
|
+
if governance_repository.database is not database:
|
|
39
|
+
raise PermissionError("reattempt lineage requires the canonical governance RuntimeDatabase")
|
|
40
|
+
self.adapter, self.repository, self.database = adapter, governance_repository, database
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _record_digest(value: dict[str, object]) -> str:
|
|
44
|
+
return "sha256:" + sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
45
|
+
|
|
46
|
+
def authorize_successor(self, *, predecessor_attempt_id: str, operator_context: OperatorContext,
|
|
47
|
+
rationale: str, reason: str = REQUEST_SEMANTICS_CHANGED) -> dict[str, object]:
|
|
48
|
+
if reason != REQUEST_SEMANTICS_CHANGED:
|
|
49
|
+
raise PermissionError("unsupported Action-Derivation reattempt reason")
|
|
50
|
+
if not isinstance(rationale, str) or not rationale.strip() or len(rationale) > 512:
|
|
51
|
+
raise ValueError("a bounded explicit reattempt rationale is required")
|
|
52
|
+
if not self.repository.operators.authorize(operator_context):
|
|
53
|
+
raise PermissionError("trusted operator context is required for a reattempt")
|
|
54
|
+
predecessor = self.database.get_document("action_derivations", predecessor_attempt_id)
|
|
55
|
+
if predecessor.get("lifecycle") != "FAILED":
|
|
56
|
+
raise PermissionError("only a terminal failed Action-Derivation attempt may have a successor")
|
|
57
|
+
required = ("mission_id", "snapshot_digest", "effective_contract_digest", "evidence_digest",
|
|
58
|
+
"provider_configuration", "generation_request_digest", "main_head")
|
|
59
|
+
if any(not isinstance(predecessor.get(field), str) or not predecessor[field] for field in required):
|
|
60
|
+
raise RuntimeIntegrityError("predecessor lacks complete canonical reattempt provenance")
|
|
61
|
+
|
|
62
|
+
mission_id = predecessor["mission_id"]
|
|
63
|
+
producer = CanonicalActionDerivationEvidenceProducer(self.database, self.repository)
|
|
64
|
+
planning_input = producer.planner_input(mission_id)
|
|
65
|
+
snapshot = PlanningSnapshot.from_planner_input(planning_input)
|
|
66
|
+
policy = self.adapter.configuration.current_policy()
|
|
67
|
+
policy_digest = _G011PolicySnapshot.from_policy(policy).digest
|
|
68
|
+
boundary = self.adapter.configuration.preflight_authority.boundary_for(mission_id)
|
|
69
|
+
state = self.database.get_document("mission_state", mission_id)
|
|
70
|
+
if state.get("status") != "APPROVED_PLANNABLE":
|
|
71
|
+
raise NewPlanningRequired("Mission is no longer approved/plannable")
|
|
72
|
+
if any((snapshot.digest != predecessor["snapshot_digest"],
|
|
73
|
+
boundary.effective_contract_digest != predecessor["effective_contract_digest"],
|
|
74
|
+
boundary.evidence_digest != predecessor["evidence_digest"],
|
|
75
|
+
policy_digest != predecessor["provider_configuration"])):
|
|
76
|
+
raise NewPlanningRequired("Mission, planning, effective contract, evidence, or G011 policy drift requires new planning")
|
|
77
|
+
|
|
78
|
+
successor_attempt_id = f"reattempt-{uuid.uuid4()}"
|
|
79
|
+
request = ProviderDerivationRequest(successor_attempt_id, snapshot, policy.provider_id, policy.model)
|
|
80
|
+
request_digest = _digest(self.adapter._body(request, policy))
|
|
81
|
+
if request_digest == predecessor["generation_request_digest"]:
|
|
82
|
+
raise PermissionError("an unchanged request digest is an exact replay, not a successor attempt")
|
|
83
|
+
sequence_row = self.database._connection.execute(
|
|
84
|
+
"SELECT MAX(attempt_sequence) AS sequence FROM action_derivation_reattempt_authorizations WHERE predecessor_attempt_id=?",
|
|
85
|
+
(predecessor_attempt_id,),
|
|
86
|
+
).fetchone()
|
|
87
|
+
if sequence_row is not None and sequence_row["sequence"] is not None:
|
|
88
|
+
# A second child of this predecessor is never silently given a new
|
|
89
|
+
# sequence; its different reason needs separate governance semantics.
|
|
90
|
+
raise PermissionError("a predecessor already has a governed successor attempt")
|
|
91
|
+
operator_id = self.repository._operator_id(operator_context)
|
|
92
|
+
document: dict[str, object] = {
|
|
93
|
+
"authorization_id": f"action-derivation-reattempt-{uuid.uuid4()}",
|
|
94
|
+
"successor_attempt_id": successor_attempt_id,
|
|
95
|
+
"mission_id": mission_id,
|
|
96
|
+
"predecessor_attempt_id": predecessor_attempt_id,
|
|
97
|
+
"predecessor_terminal_state": predecessor["lifecycle"],
|
|
98
|
+
"attempt_sequence": 2,
|
|
99
|
+
"planning_snapshot_digest": snapshot.digest,
|
|
100
|
+
"effective_contract_digest": boundary.effective_contract_digest,
|
|
101
|
+
"evidence_digest": boundary.evidence_digest,
|
|
102
|
+
"g011_policy_digest": policy_digest,
|
|
103
|
+
"provider_request_digest": request_digest,
|
|
104
|
+
"main_head": boundary.main_head,
|
|
105
|
+
"reattempt_reason": reason,
|
|
106
|
+
"rationale": rationale.strip(),
|
|
107
|
+
"authorization_identity": operator_id,
|
|
108
|
+
"installation_id": operator_context.installation_id,
|
|
109
|
+
"created_at": _timestamp(),
|
|
110
|
+
}
|
|
111
|
+
document["digest"] = self._record_digest(document)
|
|
112
|
+
return self.database.create_action_derivation_reattempt_authorization(document)
|
|
113
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Local deterministic registry for AI Architect Provider declarations only."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from forge.models import (
|
|
6
|
+
AIProviderMetadata,
|
|
7
|
+
ProviderQualification,
|
|
8
|
+
ProviderQualificationState,
|
|
9
|
+
ProviderSelection,
|
|
10
|
+
ProviderSelectionRequest,
|
|
11
|
+
ProviderStatus,
|
|
12
|
+
WorkspaceProviderConfiguration,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AIProviderRegistry:
|
|
17
|
+
"""Register and select declared providers without loading or invoking one."""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self._providers: dict[str, AIProviderMetadata] = {}
|
|
21
|
+
self._qualifications: dict[str, ProviderQualification] = {}
|
|
22
|
+
|
|
23
|
+
def register(self, provider: AIProviderMetadata, qualification: ProviderQualification) -> AIProviderMetadata:
|
|
24
|
+
"""Register one repository-qualified declaration, rejecting duplicate ids."""
|
|
25
|
+
if provider.id in self._providers:
|
|
26
|
+
raise ValueError("provider id is already registered")
|
|
27
|
+
if qualification.provider_id != provider.id or qualification.provider_version != provider.version:
|
|
28
|
+
raise ValueError("provider qualification must match provider identity and version")
|
|
29
|
+
if qualification.state is not provider.qualification_state:
|
|
30
|
+
raise ValueError("provider qualification must match provider qualification state")
|
|
31
|
+
self._providers[provider.id] = provider
|
|
32
|
+
self._qualifications[provider.id] = qualification
|
|
33
|
+
return provider
|
|
34
|
+
|
|
35
|
+
def list(self) -> tuple[AIProviderMetadata, ...]:
|
|
36
|
+
"""Return all declarations in stable provider-id and version order."""
|
|
37
|
+
return tuple(sorted(self._providers.values(), key=lambda item: (item.id, item.version)))
|
|
38
|
+
|
|
39
|
+
def qualification(self, provider_id: str) -> ProviderQualification | None:
|
|
40
|
+
"""Return the repository-owned qualification record, if registered."""
|
|
41
|
+
return self._qualifications.get(provider_id)
|
|
42
|
+
|
|
43
|
+
def select(
|
|
44
|
+
self,
|
|
45
|
+
request: ProviderSelectionRequest,
|
|
46
|
+
configuration: WorkspaceProviderConfiguration,
|
|
47
|
+
) -> ProviderSelection:
|
|
48
|
+
"""Select an active qualified declaration by policy, never invoke it."""
|
|
49
|
+
if configuration.workspace_id != request.workspace_id:
|
|
50
|
+
raise ValueError("workspace provider configuration must match selection request")
|
|
51
|
+
qualified = tuple(
|
|
52
|
+
provider
|
|
53
|
+
for provider in self.list()
|
|
54
|
+
if provider.qualification_state is ProviderQualificationState.QUALIFIED
|
|
55
|
+
and provider.status is ProviderStatus.ACTIVE
|
|
56
|
+
and request.capability in provider.capabilities
|
|
57
|
+
and request.reasoning_mode in provider.reasoning_modes
|
|
58
|
+
)
|
|
59
|
+
if not qualified:
|
|
60
|
+
raise LookupError("no qualified provider supports the requested capability and reasoning mode")
|
|
61
|
+
by_id = {provider.id: provider for provider in qualified}
|
|
62
|
+
configured = self._ordered_configured_ids(configuration)
|
|
63
|
+
for provider_id, reason in configured:
|
|
64
|
+
provider = by_id.get(provider_id)
|
|
65
|
+
if provider is not None:
|
|
66
|
+
return ProviderSelection(request, provider, reason)
|
|
67
|
+
return ProviderSelection(request, qualified[0], "stable_provider_id_version_tiebreaker")
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _ordered_configured_ids(configuration: WorkspaceProviderConfiguration) -> tuple[tuple[str, str], ...]:
|
|
71
|
+
candidates = [(provider_id, "workspace_provider_preference") for provider_id in configuration.provider_preferences]
|
|
72
|
+
if configuration.default_provider_id:
|
|
73
|
+
candidates.append((configuration.default_provider_id, "workspace_default_provider"))
|
|
74
|
+
if configuration.fallback_provider_id:
|
|
75
|
+
candidates.append((configuration.fallback_provider_id, "workspace_fallback_provider"))
|
|
76
|
+
seen: set[str] = set()
|
|
77
|
+
ordered: list[tuple[str, str]] = []
|
|
78
|
+
for provider_id, reason in candidates:
|
|
79
|
+
if provider_id not in seen:
|
|
80
|
+
seen.add(provider_id)
|
|
81
|
+
ordered.append((provider_id, reason))
|
|
82
|
+
return tuple(ordered)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Architecture Workspace public contracts."""
|
|
2
|
+
|
|
3
|
+
from .advisor import ArchitectureAdvisor, ArchitectureAdvisorAdvice
|
|
4
|
+
from .presentation import render_architecture_decision_evidence, render_architecture_mission
|
|
5
|
+
from .workspace import ArchitectureMissionHistoryEntry, ArchitectureWorkspace, ArchitectureWorkspaceError
|
|
6
|
+
from forge.governance_authority import ArchitecturePlanningEvidence, CanonicalArchitectureWorkspace, MissionPlanningEvidenceEnvelope
|
|
7
|
+
|
|
8
|
+
__all__ = ["ArchitectureAdvisor", "ArchitectureAdvisorAdvice", "ArchitectureMissionHistoryEntry", "ArchitectureWorkspace", "ArchitectureWorkspaceError", "ArchitecturePlanningEvidence", "CanonicalArchitectureWorkspace", "MissionPlanningEvidenceEnvelope", "render_architecture_decision_evidence", "render_architecture_mission"]
|