agenticdome-python-sdk 1.2.33__py3-none-any.whl → 1.2.34__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.
- {agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/METADATA +16 -2
- {agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/RECORD +11 -11
- agenticdome_sdk/catalog_snapshot.json +3 -3
- agenticdome_sdk/connect.py +65 -26
- agenticdome_sdk/copilot_ir.py +9 -3
- agenticdome_sdk/onboarding_cli.py +404 -70
- {agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/WHEEL +0 -0
- {agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/entry_points.txt +0 -0
- {agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/licenses/LICENSE +0 -0
- {agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/licenses/NOTICE +0 -0
- {agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/top_level.txt +0 -0
{agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agenticdome-python-sdk
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.34
|
|
4
4
|
Summary: Official Python SDK for AgenticDome security guardrails, DLP, tool authorization, and multi-agent delegation enforcement.
|
|
5
5
|
Author: AgenticDome
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -218,7 +218,10 @@ agenticdome openclaw verify --output .agenticdome/verification.json
|
|
|
218
218
|
AgenticDome plugin, explicit consent and the exact certified typed hooks.
|
|
219
219
|
`agenticdome plan` returns source-free, tenant-bound Integration Copilot
|
|
220
220
|
guidance for review. `openclaw verify` adds workspace-test and assigned-tenant
|
|
221
|
-
policy evidence.
|
|
221
|
+
policy evidence. If the same deployable workload also uses MCP, this command
|
|
222
|
+
includes the MCP transport rehearsal and both `mcp_verification` and
|
|
223
|
+
`openclaw_verification` in one report. Both sections must be ready before the
|
|
224
|
+
report is imported; `mcp verify` alone is not a combined-workload proof. The
|
|
222
225
|
customer Control Panel separately confirms tenant binding and retained
|
|
223
226
|
telemetry; neither command silently modifies customer source.
|
|
224
227
|
|
|
@@ -228,6 +231,17 @@ languages and candidate boundaries can be discovered locally; business
|
|
|
228
231
|
purpose, sensitive actions and managed-versus-sovereign deployment remain
|
|
229
232
|
explicit customer decisions and are not guessed from source code.
|
|
230
233
|
|
|
234
|
+
`agenticdome init` creates local configuration without contacting Copilot.
|
|
235
|
+
`inspect` and `plan` send source-free structural metadata to the assigned
|
|
236
|
+
tenant sidecar. A selection spanning several deployable packages is analyzed
|
|
237
|
+
as bounded, sequential workload units; successful units are checkpointed
|
|
238
|
+
locally so a retry does not resubmit them. The report lists analyzed parts
|
|
239
|
+
and any result-list caps. It does not claim that calls or guard placement
|
|
240
|
+
across separate workload units were proven; review those boundaries before
|
|
241
|
+
production. If the selection exceeds the 24-unit safety limit, select the
|
|
242
|
+
application or package that actually executes agent actions. No unexamined
|
|
243
|
+
unit is silently omitted or reported as protected.
|
|
244
|
+
|
|
231
245
|
Use **Customer Control Panel → Activate Action Firewall → Core Config** to
|
|
232
246
|
choose the Developer Integration path and import the two evidence files. This
|
|
233
247
|
path is for SDK, MCP, framework or custom-code integrations; Microsoft
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
agenticdome_python_sdk-1.2.
|
|
2
|
-
agenticdome_python_sdk-1.2.
|
|
1
|
+
agenticdome_python_sdk-1.2.34.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
2
|
+
agenticdome_python_sdk-1.2.34.dist-info/licenses/NOTICE,sha256=obvKP6kV1Ny5R8_INFOpf0X5um9U2X43LGwQFobO9kg,550
|
|
3
3
|
agenticdome_sdk/__init__.py,sha256=UY__0ppEM4MlsTQoLh8alZ7AoMjtegGKe4j_Sj7CpNk,903
|
|
4
4
|
agenticdome_sdk/_framework_firewall.py,sha256=W3gy7WsK50scE7ypJrmVJhHUOBsr0zAWQ85rghqaJk4,31963
|
|
5
5
|
agenticdome_sdk/_mode.py,sha256=XvOgh5sW-FG8S39V483h3uLiFh8wSt14OmYXz_6eDO8,1283
|
|
@@ -7,11 +7,11 @@ agenticdome_sdk/agno.py,sha256=L3yfjTBHW47xzT67a1DsjtwmvrAoYBkbxADpsr85Cp0,60766
|
|
|
7
7
|
agenticdome_sdk/attestation.py,sha256=ij4cMff7nzLaIS0W02d2WURFcyKe72bcUlSz-dYkaLE,10310
|
|
8
8
|
agenticdome_sdk/autogen.py,sha256=KP4qlIcsNXB-4ZT9D7Slc1xWEeWXRIWkc5HGW0U6k2c,29018
|
|
9
9
|
agenticdome_sdk/aws_bedrock.py,sha256=2zD9j7xi0jna0sMeMf-hKZdc3Y4RPwzj6tMVOUGgIDQ,69581
|
|
10
|
-
agenticdome_sdk/catalog_snapshot.json,sha256=
|
|
10
|
+
agenticdome_sdk/catalog_snapshot.json,sha256=aOJMqz8FpPF-q1ynpM2xsEk-O-tMyUCPE5Y51dygtLQ,951
|
|
11
11
|
agenticdome_sdk/claude.py,sha256=XeY9AUu61SYcJjQVz_AtL_WaN6X4KqP5nprEbbr4Q6A,17456
|
|
12
12
|
agenticdome_sdk/client.py,sha256=2uVKZR1uXDBIvwOS-lpNPCAXl9K5vroPqsPNNYhBWeg,69473
|
|
13
|
-
agenticdome_sdk/connect.py,sha256=
|
|
14
|
-
agenticdome_sdk/copilot_ir.py,sha256=
|
|
13
|
+
agenticdome_sdk/connect.py,sha256=MY30wXYA6OaZhs__E67KcFXEy7mZMBxMSV06BnlBjRg,18766
|
|
14
|
+
agenticdome_sdk/copilot_ir.py,sha256=zT13tOX8xu11yrOtIPMRFVud8CQ81Gu3WxsXxyQ4XT0,17928
|
|
15
15
|
agenticdome_sdk/copilot_ir_collector.cjs,sha256=oxLY8ByONo7Es4YjYiJuRz_A7IKL65Sy486UXnu-4Rs,9681
|
|
16
16
|
agenticdome_sdk/crewai.py,sha256=sSJQ49O7-8kqOAD2_jrsMtMOmBOcJmFkeOv7S3-ayMs,52490
|
|
17
17
|
agenticdome_sdk/demo.py,sha256=dyfqt2N_cJoyUWKnjPYuQxuw2hb2DrCNUf1jQKeYYoc,8350
|
|
@@ -27,15 +27,15 @@ agenticdome_sdk/mcp_rehearsal_server.py,sha256=cox4XqUTL8d0gaDzqxDZcEALkaz8otjOj
|
|
|
27
27
|
agenticdome_sdk/mcp_verification.py,sha256=VqQS5SpPVFI3G7qCwrT90JmG8vFE6pRJEB7PViHjKqw,14235
|
|
28
28
|
agenticdome_sdk/microsoft_agent_framework.py,sha256=GfIIuk0n-VhsZaTyoB7P6ilpz9bFLD95TWuX0rFSWeU,67219
|
|
29
29
|
agenticdome_sdk/microsoft_ai_foundry.py,sha256=YlO968s1dbAYcaEqhqufL92NFR66GuTtngnCv7-1mYc,56911
|
|
30
|
-
agenticdome_sdk/onboarding_cli.py,sha256=
|
|
30
|
+
agenticdome_sdk/onboarding_cli.py,sha256=yPPlNRPCjuDNpeJpL-0fJK3RZTOZvII8d-qFHJgTILM,120597
|
|
31
31
|
agenticdome_sdk/openai_agents.py,sha256=nNM-YcsUDcE23sGv1buLudcYFz0NLju2Mwiw_wrVaJg,57326
|
|
32
32
|
agenticdome_sdk/outcomes.py,sha256=jadWDzQaZXrHNvpPme8lQY0jihYLCoM3s4CihAq0JRo,2507
|
|
33
33
|
agenticdome_sdk/proof.py,sha256=ECKc14xSE0V1HHmcpZjfVz8-Q2H_fooah3BfdblFV8M,2878
|
|
34
34
|
agenticdome_sdk/pydantic.py,sha256=WKb8X0Fgydfid-ep8E-M8Yslz2pWcCP6w7n_aBxt1rI,54961
|
|
35
35
|
agenticdome_sdk/simulation.py,sha256=UCbZy3u9AzfxQCfY38SCV3CxGxhinV9EA4aHMPlK4mc,8426
|
|
36
36
|
agenticdome_sdk/smolagents.py,sha256=wjMA1HVAQ-9wRNM38Jr9wlCpKMhF-9vxtTk6R-kwQhs,19094
|
|
37
|
-
agenticdome_python_sdk-1.2.
|
|
38
|
-
agenticdome_python_sdk-1.2.
|
|
39
|
-
agenticdome_python_sdk-1.2.
|
|
40
|
-
agenticdome_python_sdk-1.2.
|
|
41
|
-
agenticdome_python_sdk-1.2.
|
|
37
|
+
agenticdome_python_sdk-1.2.34.dist-info/METADATA,sha256=hOOXfVmbkCRG1AKVWBUcSqHa1i6G92XEomK47iBKSiU,140120
|
|
38
|
+
agenticdome_python_sdk-1.2.34.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
39
|
+
agenticdome_python_sdk-1.2.34.dist-info/entry_points.txt,sha256=cRfCphkr5gLrkGIzXJEtL50HdHLajoZLbZtcMLE8NoY,113
|
|
40
|
+
agenticdome_python_sdk-1.2.34.dist-info/top_level.txt,sha256=LZbbjXco5mqDrGwlTJpIKI5Sp6R_njzHHjExxAs9c3s,16
|
|
41
|
+
agenticdome_python_sdk-1.2.34.dist-info/RECORD,,
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"schema": "agenticdome.catalog-snapshot.v1",
|
|
3
3
|
"catalog_schema": "agenticdome.hook-catalog.v1",
|
|
4
4
|
"verified_at": "2026-09-26",
|
|
5
|
-
"python_sdk_version": "1.2.
|
|
5
|
+
"python_sdk_version": "1.2.34",
|
|
6
6
|
"published_packages": {
|
|
7
7
|
"agenticdome-python-sdk": {
|
|
8
8
|
"registry": "pypi",
|
|
9
|
-
"version": "1.2.
|
|
10
|
-
"url": "https://pypi.org/project/agenticdome-python-sdk/1.2.
|
|
9
|
+
"version": "1.2.34",
|
|
10
|
+
"url": "https://pypi.org/project/agenticdome-python-sdk/1.2.34/"
|
|
11
11
|
},
|
|
12
12
|
"agenticdome-sdk": {
|
|
13
13
|
"registry": "npm",
|
agenticdome_sdk/connect.py
CHANGED
|
@@ -17,9 +17,6 @@ from pathlib import Path
|
|
|
17
17
|
from typing import Any, Dict, Optional
|
|
18
18
|
|
|
19
19
|
from .attestation import ensure_attestation_key
|
|
20
|
-
from .hook_catalog import CATALOG_SCHEMA, CATALOG_VERIFIED_AT, catalog_digest
|
|
21
|
-
|
|
22
|
-
|
|
23
20
|
SCOPES = ["onboarding:read", "metadata:write", "evidence:write", "workload:write", "repository:prepare"]
|
|
24
21
|
|
|
25
22
|
|
|
@@ -124,7 +121,10 @@ def _approve_metadata(ir: Dict[str, Any], assume_yes: bool) -> None:
|
|
|
124
121
|
|
|
125
122
|
def run_connect(root: Path, args: Any) -> Dict[str, Any]:
|
|
126
123
|
# Import lazily to avoid a module cycle and keep the public client purely orchestration code.
|
|
127
|
-
from .onboarding_cli import
|
|
124
|
+
from .onboarding_cli import (
|
|
125
|
+
_active_copilot_catalog_binding, _copilot_semantic_analysis,
|
|
126
|
+
_ensure_workload_id, _installed_sdk_version, _scope_gap_message, inspect_repository,
|
|
127
|
+
)
|
|
128
128
|
|
|
129
129
|
portal = str(args.portal).rstrip("/")
|
|
130
130
|
if not portal.startswith("https://") and not getattr(args, "allow_insecure_http", False):
|
|
@@ -155,12 +155,23 @@ def run_connect(root: Path, args: Any) -> Dict[str, Any]:
|
|
|
155
155
|
if not session.get("runtime") or not session.get("copilot"):
|
|
156
156
|
raise SystemExit("Authentication succeeded, but no eligible runtime is assigned. The saved portal session can be resumed after an administrator enables an assignment policy.")
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
local_workload_id = _ensure_workload_id(root)
|
|
159
|
+
report = inspect_repository(root, remote_analysis=False)
|
|
160
|
+
if report.get("scope", {}).get("complete") is not True:
|
|
161
|
+
raise SystemExit(_scope_gap_message(report))
|
|
159
162
|
ir = report["copilot_ir"]
|
|
160
163
|
_approve_metadata(ir, bool(args.yes))
|
|
161
164
|
private_pem, public_pem = ensure_attestation_key(root / ".agenticdome")
|
|
162
165
|
del private_pem
|
|
163
|
-
workload_uuid = str(uuid.uuid5(uuid.NAMESPACE_URL, session["tenant_id"] + ":" +
|
|
166
|
+
workload_uuid = str(uuid.uuid5(uuid.NAMESPACE_URL, session["tenant_id"] + ":" + local_workload_id))
|
|
167
|
+
previous_session_path = root / ".agenticdome" / "connect-session.json"
|
|
168
|
+
if previous_session_path.is_file():
|
|
169
|
+
try:
|
|
170
|
+
previous_session = json.loads(previous_session_path.read_text(encoding="utf-8"))
|
|
171
|
+
if previous_session.get("tenant_id") == session["tenant_id"]:
|
|
172
|
+
workload_uuid = str(uuid.UUID(str(previous_session["workload_uuid"])))
|
|
173
|
+
except (OSError, ValueError, KeyError, TypeError, AttributeError, json.JSONDecodeError):
|
|
174
|
+
raise SystemExit("The previous Connect workload identity is unreadable. Restore its connect-session.json before retrying to avoid creating a duplicate workload.")
|
|
164
175
|
frameworks = [item["key"] for item in report.get("frameworks", [])]
|
|
165
176
|
_request(portal + "/api/agentguard/connect/workloads", {
|
|
166
177
|
"session_uuid": session["session_uuid"], "workload_uuid": workload_uuid,
|
|
@@ -171,38 +182,51 @@ def run_connect(root: Path, args: Any) -> Dict[str, Any]:
|
|
|
171
182
|
|
|
172
183
|
runtime_base = str(session["runtime"]["api_base"]).rstrip("/")
|
|
173
184
|
copilot_key = str(session["copilot"]["api_key"])
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
"schema": "agenticdome.copilot-request.v1", "ir": ir,
|
|
177
|
-
"catalog_binding": {"schema": CATALOG_SCHEMA, "catalog_digest": catalog_digest(), "verified_at": CATALOG_VERIFIED_AT},
|
|
178
|
-
}, headers={"X-API-Key": copilot_key, "X-Tenant-Id": session["tenant_id"], "Idempotency-Key": hashlib.sha256((session["tenant_id"] + ir_digest + catalog_digest()).encode()).hexdigest()})
|
|
179
|
-
if plan.get("schema") != "agenticdome.copilot-plan.v1" or plan.get("tenant_id") != session["tenant_id"]:
|
|
180
|
-
raise SystemExit("The assigned private Copilot returned an invalid tenant-bound plan.")
|
|
181
|
-
semantic = plan.get("semantic_analysis") or {}
|
|
182
|
-
if semantic.get("ir_sha256") != ir_digest:
|
|
183
|
-
raise SystemExit("The private Copilot plan was not bound to the submitted metadata.")
|
|
184
|
-
_request(portal + "/api/agentguard/connect/sessions/" + session["session_uuid"] + "/metadata", {"ir": ir, "signed_plan": plan}, session["access_token"])
|
|
185
|
-
|
|
185
|
+
from .onboarding_cli import _ir_sha256
|
|
186
|
+
ir_digest = _ir_sha256(ir)
|
|
186
187
|
state_dir = root / ".agenticdome"
|
|
187
188
|
state_dir.mkdir(parents=True, exist_ok=True)
|
|
188
|
-
cache = {"schema": "agenticdome.copilot-cache.v1", "source_upload": False, "tenant_id": session["tenant_id"], "api_base": runtime_base, "semantic_analysis": semantic, "catalog_binding": plan.get("catalog_binding")}
|
|
189
|
-
(state_dir / "copilot-analysis.json").write_text(json.dumps(cache, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
190
189
|
config_path = state_dir / "config.json"
|
|
191
190
|
if not config_path.exists():
|
|
192
191
|
config_path.write_text(json.dumps({
|
|
193
|
-
"schema": "agenticdome.project-config.v1", "
|
|
192
|
+
"schema": "agenticdome.project-config.v1", "workload_id": local_workload_id,
|
|
193
|
+
"frameworks": frameworks or ["custom-python"],
|
|
194
194
|
"execution_broker_mode": "policy",
|
|
195
195
|
"business_purpose": "REVIEW_REQUIRED_NOT_INVENTED", "sensitive_tools": [],
|
|
196
196
|
"deployment": {"preference": "managed", "region": session["runtime"].get("region") or "auto"},
|
|
197
197
|
"source_upload": False,
|
|
198
198
|
}, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
199
|
-
resumable = {"schema": "agenticdome.connect-state.v1", "portal": portal, "tenant_id": session["tenant_id"], "session_uuid": session["session_uuid"], "workload_uuid": workload_uuid, "token_expires_at": session["expires_at"], "access_token": session["access_token"]}
|
|
200
|
-
state_path = state_dir / "connect-session.json"
|
|
201
|
-
state_path.write_text(json.dumps(resumable, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
202
|
-
os.chmod(state_path, 0o600)
|
|
203
199
|
previous = {name: os.environ.get(name) for name in ("AGENTICDOME_API_BASE", "AGENTICDOME_COPILOT_API_KEY", "AGENTICDOME_TENANT_ID")}
|
|
204
200
|
os.environ.update({"AGENTICDOME_API_BASE": runtime_base, "AGENTICDOME_COPILOT_API_KEY": copilot_key, "AGENTICDOME_TENANT_ID": session["tenant_id"]})
|
|
205
201
|
try:
|
|
202
|
+
semantic = _copilot_semantic_analysis(root, ir, required=True)
|
|
203
|
+
binding = _active_copilot_catalog_binding(root)
|
|
204
|
+
if semantic.get("ir_sha256") != ir_digest or not binding:
|
|
205
|
+
raise SystemExit("The private Copilot result was not bound to this workload and the installed SDK catalog.")
|
|
206
|
+
# The portal stores a compact inventory, never the potentially large
|
|
207
|
+
# per-function graph. Every selected part was analyzed at the sidecar.
|
|
208
|
+
portal_ir = {
|
|
209
|
+
"schema": "agenticdome.copilot-ir.v1", "source_upload": False,
|
|
210
|
+
"metadata_kind": "bounded_workload_summary", "selected_ir_sha256": ir_digest,
|
|
211
|
+
"files": [], "functions": [],
|
|
212
|
+
"features": {"functions": len(ir.get("functions", []))},
|
|
213
|
+
"scope": {
|
|
214
|
+
"complete": True, "fingerprint": ir.get("scope", {}).get("fingerprint"),
|
|
215
|
+
"eligible_files_scanned": ir.get("scope", {}).get("eligible_files_scanned", 0),
|
|
216
|
+
},
|
|
217
|
+
"workload_coverage": {
|
|
218
|
+
"selected_parts": semantic.get("workload_coverage", {}).get("selected_parts", 1),
|
|
219
|
+
"analyzed_parts": semantic.get("workload_coverage", {}).get("analyzed_parts", 1),
|
|
220
|
+
"cross_part_flow_proven": semantic.get("workload_coverage", {}).get("cross_part_flow_proven", True),
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
plan = {
|
|
224
|
+
"schema": "agenticdome.copilot-plan.v1", "tenant_id": session["tenant_id"],
|
|
225
|
+
"semantic_analysis": semantic, "catalog_binding": binding,
|
|
226
|
+
"aggregation": {"kind": "sdk_local_bounded_parts", "signature_scope": "individual_sidecar_parts_only"},
|
|
227
|
+
}
|
|
228
|
+
_request(portal + "/api/agentguard/connect/sessions/" + session["session_uuid"] + "/metadata", {"ir": portal_ir, "signed_plan": plan}, session["access_token"])
|
|
229
|
+
|
|
206
230
|
from .onboarding_cli import create_scaffold
|
|
207
231
|
patch_path = create_scaffold(root)
|
|
208
232
|
finally:
|
|
@@ -211,6 +235,11 @@ def run_connect(root: Path, args: Any) -> Dict[str, Any]:
|
|
|
211
235
|
os.environ.pop(name, None)
|
|
212
236
|
else:
|
|
213
237
|
os.environ[name] = value
|
|
238
|
+
|
|
239
|
+
resumable = {"schema": "agenticdome.connect-state.v1", "portal": portal, "tenant_id": session["tenant_id"], "session_uuid": session["session_uuid"], "workload_uuid": workload_uuid, "token_expires_at": session["expires_at"], "access_token": session["access_token"]}
|
|
240
|
+
state_path = state_dir / "connect-session.json"
|
|
241
|
+
state_path.write_text(json.dumps(resumable, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
242
|
+
os.chmod(state_path, 0o600)
|
|
214
243
|
pull_request = None
|
|
215
244
|
if args.open_pr:
|
|
216
245
|
if not args.repository_connection:
|
|
@@ -222,6 +251,15 @@ def run_connect(root: Path, args: Any) -> Dict[str, Any]:
|
|
|
222
251
|
"next_action": "Ask AgenticDome to confirm policy-aware sidecar readiness before live verification.",
|
|
223
252
|
})
|
|
224
253
|
broker_setup["sdk_mode"] = effective_config.get("execution_broker_mode", "existing_sdk_or_environment_default")
|
|
254
|
+
cross_part_review_required = semantic.get("workload_coverage", {}).get("cross_part_flow_proven") is False
|
|
255
|
+
next_action = (
|
|
256
|
+
"Review the generated pull request and run agenticdome verify. Then review cross-part call paths and workload-specific tests in the portal before activation."
|
|
257
|
+
if args.open_pr else
|
|
258
|
+
"Review the generated patch, apply it on a branch, and run agenticdome verify. Then review cross-part call paths and workload-specific tests in the portal before activation."
|
|
259
|
+
) if cross_part_review_required else (
|
|
260
|
+
"Review the generated pull request and run agenticdome verify."
|
|
261
|
+
if args.open_pr else "Review the generated patch, apply it on a branch, then run agenticdome verify."
|
|
262
|
+
)
|
|
225
263
|
return {
|
|
226
264
|
"status": "changeset_ready", "source_upload": False, "tenant_id": session["tenant_id"],
|
|
227
265
|
"session_uuid": session["session_uuid"], "workload_uuid": workload_uuid,
|
|
@@ -229,9 +267,10 @@ def run_connect(root: Path, args: Any) -> Dict[str, Any]:
|
|
|
229
267
|
"execution_broker": broker_setup,
|
|
230
268
|
"attachment_points": len(semantic.get("attachment_points") or []),
|
|
231
269
|
"bypass_risks": len(semantic.get("bypass_risks") or []),
|
|
270
|
+
"cross_part_review_required": cross_part_review_required,
|
|
232
271
|
"patch": str(patch_path.relative_to(root)),
|
|
233
272
|
"pull_request": pull_request,
|
|
234
|
-
"next_action":
|
|
273
|
+
"next_action": next_action,
|
|
235
274
|
"sdk_version": _installed_sdk_version(),
|
|
236
275
|
}
|
|
237
276
|
|
agenticdome_sdk/copilot_ir.py
CHANGED
|
@@ -350,11 +350,13 @@ def _collect_typescript(root: Path, paths: Sequence[Path]) -> Tuple[List[Dict[st
|
|
|
350
350
|
def _typescript_fallback(root: Path, paths: Sequence[Path], reason: str) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
|
|
351
351
|
functions: List[Dict[str, Any]] = []
|
|
352
352
|
parsed = 0
|
|
353
|
+
read_errors = 0
|
|
353
354
|
call_pattern = re.compile(r"\b([A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*)\s*\(")
|
|
354
355
|
for path in paths:
|
|
355
356
|
try:
|
|
356
357
|
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
357
358
|
except OSError:
|
|
359
|
+
read_errors += 1
|
|
358
360
|
continue
|
|
359
361
|
events = []
|
|
360
362
|
for line_number, line in enumerate(lines, start=1):
|
|
@@ -368,7 +370,7 @@ def _typescript_fallback(root: Path, paths: Sequence[Path], reason: str) -> Tupl
|
|
|
368
370
|
"engine": "typescript-structural-fallback",
|
|
369
371
|
"available": False,
|
|
370
372
|
"files_parsed": parsed,
|
|
371
|
-
"parse_errors":
|
|
373
|
+
"parse_errors": read_errors,
|
|
372
374
|
"reason": reason,
|
|
373
375
|
"claim": "Generic structural collection only; install TypeScript for compiler AST evidence",
|
|
374
376
|
}
|
|
@@ -382,7 +384,10 @@ def collect_repository_ir(root: Path, paths: Sequence[Path]) -> Dict[str, Any]:
|
|
|
382
384
|
python_functions, python_engine = _collect_python(root, python_paths)
|
|
383
385
|
typescript_functions, typescript_engine = _collect_typescript(root, typescript_paths)
|
|
384
386
|
functions = python_functions + typescript_functions
|
|
385
|
-
|
|
387
|
+
collection_errors = int(python_engine.get("parse_errors", 0)) + int(typescript_engine.get("parse_errors", 0))
|
|
388
|
+
files_parsed = int(python_engine.get("files_parsed", 0)) + int(typescript_engine.get("files_parsed", 0))
|
|
389
|
+
complete = (len(paths) <= MAX_IR_FILES and len(functions) <= MAX_IR_FUNCTIONS
|
|
390
|
+
and collection_errors == 0 and files_parsed == len(paths))
|
|
386
391
|
return {
|
|
387
392
|
"schema": IR_SCHEMA,
|
|
388
393
|
"source_upload": False,
|
|
@@ -394,7 +399,8 @@ def collect_repository_ir(root: Path, paths: Sequence[Path]) -> Dict[str, Any]:
|
|
|
394
399
|
"files_selected": len(selected),
|
|
395
400
|
"symbols_found": len(functions),
|
|
396
401
|
"complete": complete,
|
|
397
|
-
"limit_reason": "file_limit" if len(paths) > MAX_IR_FILES else "symbol_limit" if len(functions) > MAX_IR_FUNCTIONS
|
|
402
|
+
"limit_reason": "file_limit" if len(paths) > MAX_IR_FILES else "symbol_limit" if len(functions) > MAX_IR_FUNCTIONS
|
|
403
|
+
else "parse_or_read_error" if collection_errors or files_parsed != len(paths) else None,
|
|
398
404
|
},
|
|
399
405
|
"privacy": {
|
|
400
406
|
"source_text": False,
|
|
@@ -16,9 +16,11 @@ import re
|
|
|
16
16
|
import shutil
|
|
17
17
|
import subprocess
|
|
18
18
|
import sys
|
|
19
|
+
import tempfile
|
|
19
20
|
import time
|
|
20
21
|
import urllib.error
|
|
21
22
|
import urllib.request
|
|
23
|
+
import uuid
|
|
22
24
|
from pathlib import Path
|
|
23
25
|
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
|
|
24
26
|
|
|
@@ -38,14 +40,21 @@ from .copilot_ir import collect_repository_ir
|
|
|
38
40
|
SCHEMA = "agenticdome.onboarding-report.v1"
|
|
39
41
|
CONFIG_SCHEMA = "agenticdome.project-config.v1"
|
|
40
42
|
COPILOT_ANALYSIS_REVISION = 4
|
|
43
|
+
COPILOT_MAX_WORKLOAD_BYTES = 4_000_000
|
|
44
|
+
COPILOT_MAX_WORKLOAD_PARTS = 24
|
|
45
|
+
COPILOT_MAX_EVENTS_PER_FUNCTION = 2_048
|
|
46
|
+
COPILOT_MAX_EVENTS_PER_PART = 100_000
|
|
41
47
|
MAX_FILES = 5_000
|
|
42
|
-
|
|
48
|
+
# Parse one source file at a time. Large generated files still fail with an
|
|
49
|
+
# explicit scope gap rather than being silently counted as protected.
|
|
50
|
+
MAX_TEXT_BYTES = 2_000_000
|
|
43
51
|
IGNORED_DIRECTORIES = {
|
|
44
52
|
".git", ".hg", ".svn", ".idea", ".vscode", ".tox", ".venv", "venv",
|
|
45
53
|
"node_modules", "dist", "build", "coverage", "__pycache__", ".mypy_cache",
|
|
46
54
|
".pytest_cache", ".ruff_cache", ".next", ".agenticdome", ".harness_runtime",
|
|
47
55
|
".harness_runtime_ts", ".nuxt", ".turbo", ".cache", ".yarn",
|
|
48
|
-
".pnpm-store", "
|
|
56
|
+
".pnpm-store", ".nvm", ".npm", ".cargo", ".rustup",
|
|
57
|
+
"target", "tests", "test", "__tests__", "spec",
|
|
49
58
|
}
|
|
50
59
|
TEXT_SUFFIXES = {".py", ".pyi", ".js", ".jsx", ".ts", ".tsx", ".json", ".toml", ".txt", ".yaml", ".yml"}
|
|
51
60
|
SENSITIVE_FILE_PATTERN = re.compile(
|
|
@@ -229,19 +238,28 @@ def _post_copilot(api_base: str, api_key: str, tenant_id: str, path: str, body:
|
|
|
229
238
|
if idempotency_key:
|
|
230
239
|
headers["Idempotency-Key"] = idempotency_key
|
|
231
240
|
request = urllib.request.Request(api_base + path, data=body, method=method, headers=headers)
|
|
232
|
-
|
|
233
|
-
with urllib.request.urlopen(request, timeout=60) as response: # noqa: S310 - tenant sidecar URL is explicit configuration
|
|
234
|
-
result = json.loads(response.read().decode("utf-8"))
|
|
235
|
-
except urllib.error.HTTPError as exc:
|
|
236
|
-
detail = ""
|
|
241
|
+
for attempt in range(3):
|
|
237
242
|
try:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
with urllib.request.urlopen(request, timeout=60) as response: # noqa: S310 - tenant sidecar URL is explicit configuration
|
|
244
|
+
result = json.loads(response.read().decode("utf-8"))
|
|
245
|
+
break
|
|
246
|
+
except urllib.error.HTTPError as exc:
|
|
247
|
+
if exc.code == 429 and attempt < 2:
|
|
248
|
+
try:
|
|
249
|
+
retry_after = max(1, min(int(exc.headers.get("Retry-After", "2")), 65))
|
|
250
|
+
except (ValueError, TypeError):
|
|
251
|
+
retry_after = 2
|
|
252
|
+
time.sleep(retry_after)
|
|
253
|
+
continue
|
|
254
|
+
detail = ""
|
|
255
|
+
try:
|
|
256
|
+
payload = json.loads(exc.read().decode("utf-8"))
|
|
257
|
+
detail = str(payload.get("detail") or "")[:240] if isinstance(payload, dict) else ""
|
|
258
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
|
|
259
|
+
pass
|
|
260
|
+
raise SystemExit(f"Integration Copilot sidecar request failed ({exc.code}): {detail or exc.reason}") from exc
|
|
261
|
+
except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
|
|
262
|
+
raise SystemExit(f"Integration Copilot sidecar request failed safely: {exc}") from exc
|
|
245
263
|
if not isinstance(result, dict):
|
|
246
264
|
raise SystemExit("Integration Copilot returned an invalid JSON response.")
|
|
247
265
|
return result
|
|
@@ -332,6 +350,200 @@ def _copilot_catalog_binding_matches_sdk(binding: Any) -> bool:
|
|
|
332
350
|
)
|
|
333
351
|
|
|
334
352
|
|
|
353
|
+
def _copilot_workload_parts(root: Path, ir: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]:
|
|
354
|
+
"""Partition structural metadata by the nearest deployable package.
|
|
355
|
+
|
|
356
|
+
This is a transport/analysis boundary, not a claim that calls between
|
|
357
|
+
packages have been proven safe. Every collected function is assigned once.
|
|
358
|
+
No source contents are sent or used to decide the grouping.
|
|
359
|
+
"""
|
|
360
|
+
markers = ("pyproject.toml", "package.json", "requirements.txt", "setup.py", "setup.cfg", "Dockerfile")
|
|
361
|
+
grouped: Dict[str, Dict[str, List[Dict[str, Any]]]] = {}
|
|
362
|
+
package_cache: Dict[str, str] = {}
|
|
363
|
+
functions = ir.get("functions", [])
|
|
364
|
+
if not isinstance(functions, list):
|
|
365
|
+
raise SystemExit("The local Copilot collector did not return a function inventory.")
|
|
366
|
+
for function in functions:
|
|
367
|
+
if not isinstance(function, dict):
|
|
368
|
+
raise SystemExit("The local Copilot collector returned an invalid function entry.")
|
|
369
|
+
relative = Path(str(function.get("path") or ""))
|
|
370
|
+
if not relative.parts or relative.is_absolute() or ".." in relative.parts:
|
|
371
|
+
raise SystemExit("The local Copilot collector returned a non-relative source path.")
|
|
372
|
+
events = function.get("events", [])
|
|
373
|
+
if not isinstance(events, list) or len(events) > COPILOT_MAX_EVENTS_PER_FUNCTION:
|
|
374
|
+
raise SystemExit(
|
|
375
|
+
f"{relative} contains a function above the {COPILOT_MAX_EVENTS_PER_FUNCTION}-event "
|
|
376
|
+
"analysis boundary. Split that function or select a narrower deployable workload."
|
|
377
|
+
)
|
|
378
|
+
directory = relative.parent.as_posix()
|
|
379
|
+
key = package_cache.get(directory)
|
|
380
|
+
if key is None:
|
|
381
|
+
package = Path(".")
|
|
382
|
+
for parent in relative.parents:
|
|
383
|
+
if parent == Path("."):
|
|
384
|
+
break
|
|
385
|
+
if any((root / parent / marker).is_file() for marker in markers):
|
|
386
|
+
package = parent
|
|
387
|
+
break
|
|
388
|
+
key = package.as_posix()
|
|
389
|
+
package_cache[directory] = key
|
|
390
|
+
grouped.setdefault(key, {}).setdefault(relative.as_posix(), []).append(function)
|
|
391
|
+
|
|
392
|
+
parts: List[Tuple[str, Dict[str, Any]]] = []
|
|
393
|
+
for package, files in sorted(grouped.items()):
|
|
394
|
+
batch: List[Dict[str, Any]] = []
|
|
395
|
+
byte_count = 0
|
|
396
|
+
event_count = 0
|
|
397
|
+
ordinal = 0
|
|
398
|
+
for filename, file_functions in sorted(files.items()):
|
|
399
|
+
file_bytes = sum(len(json.dumps(item, sort_keys=True, separators=(",", ":")).encode("utf-8")) + 1 for item in file_functions)
|
|
400
|
+
file_events = sum(len(item.get("events", [])) for item in file_functions)
|
|
401
|
+
if file_bytes > COPILOT_MAX_WORKLOAD_BYTES or file_events > COPILOT_MAX_EVENTS_PER_PART:
|
|
402
|
+
raise SystemExit(
|
|
403
|
+
f"{filename} alone exceeds the bounded Copilot analysis unit. "
|
|
404
|
+
"Select a smaller package or split this source file; no coverage has been claimed."
|
|
405
|
+
)
|
|
406
|
+
if batch and (byte_count + file_bytes > COPILOT_MAX_WORKLOAD_BYTES or event_count + file_events > COPILOT_MAX_EVENTS_PER_PART):
|
|
407
|
+
parts.append((f"{package}#{ordinal}", _copilot_part_ir(ir, batch)))
|
|
408
|
+
ordinal += 1
|
|
409
|
+
batch, byte_count, event_count = [], 0, 0
|
|
410
|
+
batch.extend(file_functions)
|
|
411
|
+
byte_count += file_bytes
|
|
412
|
+
event_count += file_events
|
|
413
|
+
if batch:
|
|
414
|
+
parts.append((f"{package}#{ordinal}", _copilot_part_ir(ir, batch)))
|
|
415
|
+
if not parts:
|
|
416
|
+
return [(".#0", ir)]
|
|
417
|
+
if len(parts) > COPILOT_MAX_WORKLOAD_PARTS:
|
|
418
|
+
raise SystemExit(
|
|
419
|
+
f"The selection contains {len(parts)} bounded Copilot analysis units "
|
|
420
|
+
f"(limit {COPILOT_MAX_WORKLOAD_PARTS}). Select a deployable application or package; "
|
|
421
|
+
"the tool will not silently omit the remaining units."
|
|
422
|
+
)
|
|
423
|
+
if sum(len(part["functions"]) for _, part in parts) != len(functions):
|
|
424
|
+
raise SystemExit("Copilot workload coverage did not account for every collected function.")
|
|
425
|
+
return parts
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _copilot_part_ir(ir: Dict[str, Any], functions: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
429
|
+
part = {key: value for key, value in ir.items() if key not in {"functions", "coverage"}}
|
|
430
|
+
part["functions"] = functions
|
|
431
|
+
paths = {str(function.get("path") or "") for function in functions}
|
|
432
|
+
part["coverage"] = {
|
|
433
|
+
"candidate_source_files": len(paths), "files_selected": len(paths),
|
|
434
|
+
"symbols_found": len(functions), "complete": True, "limit_reason": None,
|
|
435
|
+
}
|
|
436
|
+
return part
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _merge_copilot_parts(
|
|
440
|
+
ir: Dict[str, Any], results: List[Tuple[str, Dict[str, Any]]],
|
|
441
|
+
parts: Optional[List[Tuple[str, Dict[str, Any]]]] = None,
|
|
442
|
+
) -> Dict[str, Any]:
|
|
443
|
+
result_caps = {"attachment_points": 100, "bypass_risks": 60, "review_findings": 100, "execution_paths": 40}
|
|
444
|
+
at_cap = sorted({field for _, result in results for field, cap in result_caps.items() if len(result.get(field, [])) >= cap})
|
|
445
|
+
if len(results) == 1:
|
|
446
|
+
semantic = dict(results[0][1])
|
|
447
|
+
semantic["ir_sha256"] = _ir_sha256(ir)
|
|
448
|
+
if at_cap:
|
|
449
|
+
semantic["limitations"] = list(semantic.get("limitations", [])) + [
|
|
450
|
+
"One or more result lists reached the per-workload display cap; additional findings may exist."
|
|
451
|
+
]
|
|
452
|
+
semantic["workload_coverage"] = {
|
|
453
|
+
"complete": True, "selected_parts": 1, "analyzed_parts": 1,
|
|
454
|
+
"cross_part_flow_proven": True,
|
|
455
|
+
"result_lists_at_cap": at_cap,
|
|
456
|
+
"parts": [{"name": results[0][0], "symbols": semantic.get("symbols_indexed", 0)}],
|
|
457
|
+
}
|
|
458
|
+
return semantic
|
|
459
|
+
first = results[0][1]
|
|
460
|
+
merged = dict(first)
|
|
461
|
+
for field in ("attachment_points", "bypass_risks", "review_findings", "execution_paths"):
|
|
462
|
+
merged[field] = [item for _, result in results for item in result.get(field, [])]
|
|
463
|
+
for field in ("symbols_indexed", "call_edges", "protected_sinks", "events_analyzed"):
|
|
464
|
+
merged[field] = sum(int(result.get(field, 0) or 0) for _, result in results)
|
|
465
|
+
coverage: Dict[str, Dict[str, Any]] = {}
|
|
466
|
+
for _, result in results:
|
|
467
|
+
for boundary, values in result.get("coverage", {}).items():
|
|
468
|
+
if not isinstance(values, dict):
|
|
469
|
+
continue
|
|
470
|
+
totals = coverage.setdefault(boundary, {})
|
|
471
|
+
for key, value in values.items():
|
|
472
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
473
|
+
totals[key] = totals.get(key, 0) + value
|
|
474
|
+
merged["coverage"] = coverage
|
|
475
|
+
merged["scope"] = ir.get("scope", {})
|
|
476
|
+
merged["ir_sha256"] = _ir_sha256(ir)
|
|
477
|
+
merged["analysis_mode"] = "private_bounded_workload_flow"
|
|
478
|
+
merged["confidence"] = "partial"
|
|
479
|
+
merged["workload_coverage"] = {
|
|
480
|
+
"complete": True, "selected_parts": len(results), "analyzed_parts": len(results),
|
|
481
|
+
"cross_part_flow_proven": False,
|
|
482
|
+
"cross_part_review_required": True,
|
|
483
|
+
"cross_part_edges": _cross_part_edges(ir, parts or []),
|
|
484
|
+
"result_lists_at_cap": at_cap,
|
|
485
|
+
"parts": [{"name": name, "symbols": result.get("symbols_indexed", 0), "ir_sha256": result.get("ir_sha256")} for name, result in results],
|
|
486
|
+
}
|
|
487
|
+
merged["limitations"] = list(dict.fromkeys(
|
|
488
|
+
[item for _, result in results for item in result.get("limitations", [])]
|
|
489
|
+
+ ["All selected parts were analyzed separately. Cross-part call flow and guard dominance are not proven; review integration boundaries before claiming production readiness."]
|
|
490
|
+
+ (["One or more result lists reached a per-workload display cap; additional findings may exist."] if at_cap else [])
|
|
491
|
+
))
|
|
492
|
+
return merged
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _cross_part_edges(ir: Dict[str, Any], parts: List[Tuple[str, Dict[str, Any]]]) -> Dict[str, Any]:
|
|
496
|
+
"""Inventory resolvable inter-part calls for human review, not proof of safety.
|
|
497
|
+
|
|
498
|
+
Qualified or unambiguous local names are linked using the same conservative
|
|
499
|
+
shape as Core. Dynamic calls and external dependencies are not certified.
|
|
500
|
+
"""
|
|
501
|
+
functions = ir.get("functions", [])
|
|
502
|
+
if not isinstance(functions, list):
|
|
503
|
+
return {"observed_count": 0, "display_capped": False, "examples": []}
|
|
504
|
+
owners: Dict[str, str] = {}
|
|
505
|
+
for name, part in parts:
|
|
506
|
+
for function in part.get("functions", []):
|
|
507
|
+
owners[str(function.get("path") or "") + "\0" + str(function.get("symbol") or "")] = name
|
|
508
|
+
by_symbol: Dict[str, List[Dict[str, Any]]] = {}
|
|
509
|
+
by_tail: Dict[str, List[Dict[str, Any]]] = {}
|
|
510
|
+
for function in functions:
|
|
511
|
+
symbol = str(function.get("symbol") or "").lower()
|
|
512
|
+
by_symbol.setdefault(symbol, []).append(function)
|
|
513
|
+
by_tail.setdefault(symbol.rsplit(".", 1)[-1], []).append(function)
|
|
514
|
+
examples: List[Dict[str, Any]] = []
|
|
515
|
+
observed = 0
|
|
516
|
+
for caller in functions:
|
|
517
|
+
caller_symbol = str(caller.get("symbol") or "")
|
|
518
|
+
caller_part = owners.get(str(caller.get("path") or "") + "\0" + caller_symbol)
|
|
519
|
+
for event in caller.get("events", []):
|
|
520
|
+
if not isinstance(event, dict) or event.get("event") != "call":
|
|
521
|
+
continue
|
|
522
|
+
callee = str(event.get("callee") or "").lower()
|
|
523
|
+
targets = by_symbol.get(callee, [])
|
|
524
|
+
if not targets and (callee.startswith("self.") or callee.startswith("cls.")):
|
|
525
|
+
resolved = caller_symbol.lower().rsplit(".", 1)[0] + "." + callee.rsplit(".", 1)[-1]
|
|
526
|
+
targets = by_symbol.get(resolved, [])
|
|
527
|
+
if not targets and "." not in callee:
|
|
528
|
+
tail = by_tail.get(callee, [])
|
|
529
|
+
targets = tail if len(tail) == 1 else []
|
|
530
|
+
if len(targets) != 1:
|
|
531
|
+
continue
|
|
532
|
+
target = targets[0]
|
|
533
|
+
target_part = owners.get(str(target.get("path") or "") + "\0" + str(target.get("symbol") or ""))
|
|
534
|
+
if not caller_part or not target_part or caller_part == target_part:
|
|
535
|
+
continue
|
|
536
|
+
observed += 1
|
|
537
|
+
if len(examples) < 40:
|
|
538
|
+
examples.append({
|
|
539
|
+
"from_part": caller_part, "from_path": str(caller.get("path") or ""),
|
|
540
|
+
"from_symbol": caller_symbol[:240], "line": int(event.get("line") or caller.get("line") or 1),
|
|
541
|
+
"to_part": target_part, "to_path": str(target.get("path") or ""),
|
|
542
|
+
"to_symbol": str(target.get("symbol") or "")[:240],
|
|
543
|
+
})
|
|
544
|
+
return {"observed_count": observed, "display_capped": observed > len(examples), "examples": examples}
|
|
545
|
+
|
|
546
|
+
|
|
335
547
|
def _copilot_semantic_analysis(root: Path, ir: Dict[str, Any], *, required: bool) -> Dict[str, Any]:
|
|
336
548
|
ir_digest = _ir_sha256(ir)
|
|
337
549
|
api_base = os.getenv("AGENTICDOME_API_BASE", "").strip().rstrip("/")
|
|
@@ -363,45 +575,87 @@ def _copilot_semantic_analysis(root: Path, ir: Dict[str, Any], *, required: bool
|
|
|
363
575
|
):
|
|
364
576
|
return semantic
|
|
365
577
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}, separators=(",", ":")).encode("utf-8")
|
|
375
|
-
idempotency_key = hashlib.sha256(
|
|
376
|
-
f"{tenant_id}\n{api_base}\n{ir_digest}\n{expected_catalog_digest}".encode("utf-8")
|
|
377
|
-
).hexdigest()
|
|
378
|
-
result = (
|
|
379
|
-
_chunked_copilot_request(api_base, api_key, tenant_id, ir, ir_digest, idempotency_key)
|
|
380
|
-
if len(body) > 8_000_000 else
|
|
381
|
-
_post_copilot(api_base, api_key, tenant_id, "/integration-copilot/v1/analyze", body, idempotency_key=idempotency_key)
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
if not isinstance(result, dict) or result.get("schema") != "agenticdome.copilot-plan.v1":
|
|
385
|
-
raise SystemExit("Integration Copilot returned an unsupported response contract.")
|
|
386
|
-
if str(result.get("tenant_id") or "") != tenant_id:
|
|
387
|
-
raise SystemExit("Integration Copilot tenant binding did not match the requested tenant.")
|
|
388
|
-
semantic = result.get("semantic_analysis")
|
|
389
|
-
if not isinstance(semantic, dict) or semantic.get("ir_sha256") != ir_digest:
|
|
390
|
-
raise SystemExit("Integration Copilot response is not bound to the submitted structural IR.")
|
|
391
|
-
if semantic.get("analysis_revision") != COPILOT_ANALYSIS_REVISION:
|
|
392
|
-
raise SystemExit(
|
|
393
|
-
"Integration Copilot Core is older than this SDK's required analysis revision. "
|
|
394
|
-
"Update the assigned control plane and sidecar, then rerun the Copilot."
|
|
395
|
-
)
|
|
396
|
-
response_binding = result.get("catalog_binding")
|
|
578
|
+
parts = _copilot_workload_parts(root, ir)
|
|
579
|
+
part_cache_path = _agenticdome_dir(root) / "copilot-parts.json"
|
|
580
|
+
part_cache: Dict[str, Any] = {}
|
|
581
|
+
if part_cache_path.exists():
|
|
582
|
+
try:
|
|
583
|
+
part_cache = _load_json(part_cache_path)
|
|
584
|
+
except SystemExit:
|
|
585
|
+
part_cache = {}
|
|
397
586
|
if (
|
|
398
|
-
|
|
587
|
+
part_cache.get("schema") != "agenticdome.copilot-part-cache.v1"
|
|
588
|
+
or part_cache.get("tenant_id") != tenant_id
|
|
589
|
+
or part_cache.get("api_base") != api_base
|
|
590
|
+
or part_cache.get("catalog_digest") != expected_catalog_digest
|
|
591
|
+
or part_cache.get("selected_ir_sha256") != ir_digest
|
|
592
|
+
or not isinstance(part_cache.get("parts"), dict)
|
|
399
593
|
):
|
|
400
|
-
|
|
401
|
-
"
|
|
402
|
-
"
|
|
403
|
-
"
|
|
404
|
-
|
|
594
|
+
part_cache = {
|
|
595
|
+
"schema": "agenticdome.copilot-part-cache.v1", "tenant_id": tenant_id,
|
|
596
|
+
"api_base": api_base, "catalog_digest": expected_catalog_digest,
|
|
597
|
+
"selected_ir_sha256": ir_digest, "parts": {},
|
|
598
|
+
}
|
|
599
|
+
analyzed: List[Tuple[str, Dict[str, Any]]] = []
|
|
600
|
+
response_binding: Dict[str, Any] = {}
|
|
601
|
+
for ordinal, (name, part) in enumerate(parts, start=1):
|
|
602
|
+
part_digest = _ir_sha256(part)
|
|
603
|
+
cached_part = part_cache.get("parts", {}).get(part_digest)
|
|
604
|
+
if isinstance(cached_part, dict):
|
|
605
|
+
cached_semantic = cached_part.get("semantic_analysis")
|
|
606
|
+
cached_binding = cached_part.get("catalog_binding")
|
|
607
|
+
if (
|
|
608
|
+
isinstance(cached_semantic, dict)
|
|
609
|
+
and cached_semantic.get("ir_sha256") == part_digest
|
|
610
|
+
and cached_semantic.get("analysis_revision") == COPILOT_ANALYSIS_REVISION
|
|
611
|
+
and _copilot_catalog_binding_matches_sdk(cached_binding)
|
|
612
|
+
):
|
|
613
|
+
print(f"Integration Copilot: part {ordinal}/{len(parts)} already analyzed; resuming.", file=sys.stderr)
|
|
614
|
+
analyzed.append((name, cached_semantic))
|
|
615
|
+
response_binding = cached_binding
|
|
616
|
+
continue
|
|
617
|
+
print(f"Integration Copilot: analyzing part {ordinal}/{len(parts)}; completed parts are saved locally.", file=sys.stderr)
|
|
618
|
+
body = json.dumps({
|
|
619
|
+
"schema": "agenticdome.copilot-request.v1", "ir": part,
|
|
620
|
+
"catalog_binding": {
|
|
621
|
+
"schema": CATALOG_SCHEMA, "catalog_digest": expected_catalog_digest,
|
|
622
|
+
"verified_at": CATALOG_VERIFIED_AT,
|
|
623
|
+
},
|
|
624
|
+
}, separators=(",", ":")).encode("utf-8")
|
|
625
|
+
if len(body) > 4_500_000:
|
|
626
|
+
raise SystemExit(f"Bounded Copilot unit {name} exceeds its transport budget; no partial plan was saved.")
|
|
627
|
+
idempotency_key = hashlib.sha256(
|
|
628
|
+
f"{tenant_id}\n{api_base}\n{part_digest}\n{expected_catalog_digest}".encode("utf-8")
|
|
629
|
+
).hexdigest()
|
|
630
|
+
try:
|
|
631
|
+
result = _post_copilot(api_base, api_key, tenant_id, "/integration-copilot/v1/analyze", body, idempotency_key=idempotency_key)
|
|
632
|
+
except SystemExit as exc:
|
|
633
|
+
raise SystemExit(
|
|
634
|
+
f"Integration Copilot stopped at part {ordinal}/{len(parts)}. "
|
|
635
|
+
f"The first {len(analyzed)} completed part(s) are saved for retry. {exc}"
|
|
636
|
+
) from exc
|
|
637
|
+
if not isinstance(result, dict) or result.get("schema") != "agenticdome.copilot-plan.v1":
|
|
638
|
+
raise SystemExit("Integration Copilot returned an unsupported response contract.")
|
|
639
|
+
if str(result.get("tenant_id") or "") != tenant_id:
|
|
640
|
+
raise SystemExit("Integration Copilot tenant binding did not match the requested tenant.")
|
|
641
|
+
part_semantic = result.get("semantic_analysis")
|
|
642
|
+
if not isinstance(part_semantic, dict) or part_semantic.get("ir_sha256") != part_digest:
|
|
643
|
+
raise SystemExit("Integration Copilot response is not bound to the submitted workload metadata.")
|
|
644
|
+
if part_semantic.get("analysis_revision") != COPILOT_ANALYSIS_REVISION:
|
|
645
|
+
raise SystemExit("Integration Copilot Core is older than this SDK. Update the assigned control plane and sidecar.")
|
|
646
|
+
response_binding = result.get("catalog_binding")
|
|
647
|
+
if not _copilot_catalog_binding_matches_sdk(response_binding):
|
|
648
|
+
raise SystemExit("Integration Copilot's signed hook catalog does not match this installed SDK.")
|
|
649
|
+
analyzed.append((name, part_semantic))
|
|
650
|
+
part_cache["parts"][part_digest] = {
|
|
651
|
+
"semantic_analysis": part_semantic, "catalog_binding": response_binding,
|
|
652
|
+
}
|
|
653
|
+
_write_json(part_cache_path, part_cache)
|
|
654
|
+
# The sidecar allows ten expensive analyses per tenant-minute. Pace
|
|
655
|
+
# unusually broad selections rather than overwhelming its Core queue.
|
|
656
|
+
if len(parts) > 10 and len(analyzed) < len(parts):
|
|
657
|
+
time.sleep(6.1)
|
|
658
|
+
semantic = _merge_copilot_parts(ir, analyzed, parts)
|
|
405
659
|
_write_json(cached_path, {
|
|
406
660
|
"schema": "agenticdome.copilot-cache.v1",
|
|
407
661
|
"source_upload": False,
|
|
@@ -614,7 +868,7 @@ def _detect_mcp_protection(root: Path, paths: Sequence[Path], languages: Sequenc
|
|
|
614
868
|
}
|
|
615
869
|
|
|
616
870
|
|
|
617
|
-
def inspect_repository(root: Path) -> Dict[str, Any]:
|
|
871
|
+
def inspect_repository(root: Path, *, remote_analysis: bool = True) -> Dict[str, Any]:
|
|
618
872
|
root = root.resolve()
|
|
619
873
|
languages = set()
|
|
620
874
|
framework_hits: Dict[str, set[str]] = {key: set() for key in FRAMEWORK_MARKERS}
|
|
@@ -699,7 +953,7 @@ def inspect_repository(root: Path) -> Dict[str, Any]:
|
|
|
699
953
|
copilot_ir["scope"] = scope
|
|
700
954
|
semantic = (
|
|
701
955
|
_copilot_semantic_analysis(root, copilot_ir, required=False)
|
|
702
|
-
if scope["complete"] else _pending_semantic_analysis(copilot_ir)
|
|
956
|
+
if scope["complete"] and remote_analysis else _pending_semantic_analysis(copilot_ir)
|
|
703
957
|
)
|
|
704
958
|
if not scope["complete"]:
|
|
705
959
|
semantic["limitations"].append("The selected workload was not completely collected; narrow the scope before requesting a placement plan.")
|
|
@@ -714,7 +968,7 @@ def inspect_repository(root: Path) -> Dict[str, Any]:
|
|
|
714
968
|
"schema": SCHEMA,
|
|
715
969
|
"generated_by": "agenticdome local CLI",
|
|
716
970
|
"source_upload": False,
|
|
717
|
-
"project": {"name": root.name, "root_disclosed": False},
|
|
971
|
+
"project": {"name": root.name, "workload_id": _read_workload_id(root), "root_disclosed": False},
|
|
718
972
|
"languages": sorted(languages),
|
|
719
973
|
"frameworks": detected,
|
|
720
974
|
"scanned_files": scanned_files,
|
|
@@ -751,13 +1005,46 @@ def _exportable_inspection(report: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
751
1005
|
return exported
|
|
752
1006
|
|
|
753
1007
|
|
|
1008
|
+
def _scope_gap_message(report: Dict[str, Any]) -> str:
|
|
1009
|
+
gaps = report.get("scope", {}).get("unexamined_source_counts", {})
|
|
1010
|
+
if int(gaps.get("oversized_source_files", 0)):
|
|
1011
|
+
return (
|
|
1012
|
+
f"{gaps['oversized_source_files']} source file(s) exceed the {MAX_TEXT_BYTES // 1_000_000} MB per-file analysis limit. "
|
|
1013
|
+
"Split or isolate the required agent code, then rerun. Do not omit an execution path just to pass onboarding."
|
|
1014
|
+
)
|
|
1015
|
+
if int(gaps.get("unreadable_directories", 0)) or int(gaps.get("unreadable_files", 0)):
|
|
1016
|
+
return "Some source paths were unreadable. Fix local permissions and rerun; no source was sent."
|
|
1017
|
+
if report.get("copilot_ir", {}).get("coverage", {}).get("limit_reason") == "parse_or_read_error":
|
|
1018
|
+
return (
|
|
1019
|
+
"One or more source files could not be parsed for structural analysis. "
|
|
1020
|
+
"Fix the local syntax or language tooling and rerun; the uncovered paths are not certified."
|
|
1021
|
+
)
|
|
1022
|
+
if report.get("scan_limit_reached") or report.get("copilot_ir", {}).get("coverage", {}).get("limit_reason"):
|
|
1023
|
+
return (
|
|
1024
|
+
"The selected root exceeded a file or symbol limit. Choose the independently deployable application or package "
|
|
1025
|
+
"that owns the agent path, then rerun. If that complete workload still exceeds the limit, contact support; "
|
|
1026
|
+
"unexamined code must not be reported as protected."
|
|
1027
|
+
)
|
|
1028
|
+
return "The selected workload was not completely inspected. Resolve the local coverage gap before planning or importing."
|
|
1029
|
+
|
|
1030
|
+
|
|
754
1031
|
def _agenticdome_dir(root: Path) -> Path:
|
|
755
1032
|
return root / ".agenticdome"
|
|
756
1033
|
|
|
757
1034
|
|
|
758
1035
|
def _write_json(path: Path, value: Dict[str, Any]) -> None:
|
|
759
1036
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
760
|
-
|
|
1037
|
+
descriptor, staged = tempfile.mkstemp(prefix=".agenticdome-json-", dir=path.parent)
|
|
1038
|
+
try:
|
|
1039
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
1040
|
+
json.dump(value, handle, indent=2, sort_keys=True)
|
|
1041
|
+
handle.write("\n")
|
|
1042
|
+
handle.flush()
|
|
1043
|
+
os.fsync(handle.fileno())
|
|
1044
|
+
os.replace(staged, path)
|
|
1045
|
+
finally:
|
|
1046
|
+
if os.path.exists(staged):
|
|
1047
|
+
os.unlink(staged)
|
|
761
1048
|
|
|
762
1049
|
|
|
763
1050
|
def _load_json(path: Path) -> Dict[str, Any]:
|
|
@@ -770,12 +1057,43 @@ def _load_json(path: Path) -> Dict[str, Any]:
|
|
|
770
1057
|
return value
|
|
771
1058
|
|
|
772
1059
|
|
|
1060
|
+
def _read_workload_id(root: Path) -> Optional[str]:
|
|
1061
|
+
config_path = _agenticdome_dir(root) / "config.json"
|
|
1062
|
+
identity_path = _agenticdome_dir(root) / "workload.json"
|
|
1063
|
+
for path in (config_path, identity_path):
|
|
1064
|
+
if not path.exists():
|
|
1065
|
+
continue
|
|
1066
|
+
value = _load_json(path).get("workload_id")
|
|
1067
|
+
if value is None:
|
|
1068
|
+
continue
|
|
1069
|
+
try:
|
|
1070
|
+
canonical = str(uuid.UUID(str(value)))
|
|
1071
|
+
except (ValueError, AttributeError) as exc:
|
|
1072
|
+
raise SystemExit(f"{path} contains an invalid workload ID; restore its original value before inspecting.") from exc
|
|
1073
|
+
if str(value) != canonical:
|
|
1074
|
+
raise SystemExit(f"{path} contains a non-canonical workload ID; use the UUID shown when the workload was initialized.")
|
|
1075
|
+
return canonical
|
|
1076
|
+
return None
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
def _ensure_workload_id(root: Path) -> str:
|
|
1080
|
+
existing = _read_workload_id(root)
|
|
1081
|
+
if existing:
|
|
1082
|
+
return existing
|
|
1083
|
+
value = str(uuid.uuid4())
|
|
1084
|
+
_write_json(_agenticdome_dir(root) / "workload.json", {"workload_id": value, "source_upload": False})
|
|
1085
|
+
return value
|
|
1086
|
+
|
|
1087
|
+
|
|
773
1088
|
def init_project(root: Path, args: argparse.Namespace) -> Dict[str, Any]:
|
|
774
1089
|
target = _agenticdome_dir(root) / "config.json"
|
|
775
1090
|
# Reassessment must not silently migrate an established integration.
|
|
776
1091
|
if target.exists():
|
|
1092
|
+
_ensure_workload_id(root)
|
|
777
1093
|
return _load_json(target)
|
|
778
|
-
|
|
1094
|
+
# Creating local configuration must not depend on sidecar availability.
|
|
1095
|
+
# The explicit inspect/plan steps perform authenticated remote analysis.
|
|
1096
|
+
report = inspect_repository(root, remote_analysis=False)
|
|
779
1097
|
detected = [item["key"] for item in report["frameworks"]]
|
|
780
1098
|
frameworks = list(dict.fromkeys(args.framework or detected or ["custom-python"]))
|
|
781
1099
|
unknown = sorted(set(frameworks) - set(FRAMEWORK_MARKERS))
|
|
@@ -783,6 +1101,7 @@ def init_project(root: Path, args: argparse.Namespace) -> Dict[str, Any]:
|
|
|
783
1101
|
raise SystemExit("Unsupported framework key(s): " + ", ".join(unknown))
|
|
784
1102
|
config = {
|
|
785
1103
|
"schema": CONFIG_SCHEMA,
|
|
1104
|
+
"workload_id": str(uuid.uuid4()),
|
|
786
1105
|
"frameworks": frameworks,
|
|
787
1106
|
"business_purpose": args.business_purpose or "Protect agent prompts, tools, delegation and output",
|
|
788
1107
|
"sensitive_tools": list(dict.fromkeys(args.sensitive_tool or [])),
|
|
@@ -797,6 +1116,7 @@ def init_project(root: Path, args: argparse.Namespace) -> Dict[str, Any]:
|
|
|
797
1116
|
"execution_broker_mode": "policy",
|
|
798
1117
|
}
|
|
799
1118
|
_write_json(target, config)
|
|
1119
|
+
report["project"]["workload_id"] = config["workload_id"]
|
|
800
1120
|
_write_json(_agenticdome_dir(root) / "inspection.json", _exportable_inspection(report))
|
|
801
1121
|
return config
|
|
802
1122
|
|
|
@@ -991,10 +1311,7 @@ def _hook_plans(
|
|
|
991
1311
|
def integration_plan(root: Path) -> Dict[str, Any]:
|
|
992
1312
|
report = inspect_repository(root)
|
|
993
1313
|
if report.get("scope", {}).get("complete") is not True:
|
|
994
|
-
raise SystemExit(
|
|
995
|
-
"The selected workload exceeds the complete local analysis boundary. "
|
|
996
|
-
"Choose a narrower application or package root; unexamined code must not be reported as protected."
|
|
997
|
-
)
|
|
1314
|
+
raise SystemExit(_scope_gap_message(report))
|
|
998
1315
|
config_path = _agenticdome_dir(root) / "config.json"
|
|
999
1316
|
config = _load_json(config_path) if config_path.exists() else {
|
|
1000
1317
|
"schema": CONFIG_SCHEMA,
|
|
@@ -1070,7 +1387,12 @@ def integration_plan(root: Path) -> Dict[str, Any]:
|
|
|
1070
1387
|
"unresolved_bypasses": len(semantic_bypasses),
|
|
1071
1388
|
"high_severity_bypasses": sum(1 for item in semantic_bypasses if item.get("severity") == "high"),
|
|
1072
1389
|
"review_required": len(semantic_reviews),
|
|
1073
|
-
"production_ready": not semantic_bypasses and not semantic_reviews and semantic.get("confidence") in {"high", "partial"}
|
|
1390
|
+
"production_ready": not semantic_bypasses and not semantic_reviews and semantic.get("confidence") in {"high", "partial"}
|
|
1391
|
+
and semantic.get("workload_coverage", {}).get("cross_part_flow_proven", True)
|
|
1392
|
+
and not any(item in {"attachment_points", "bypass_risks", "review_findings"}
|
|
1393
|
+
for item in semantic.get("workload_coverage", {}).get("result_lists_at_cap", [])),
|
|
1394
|
+
"cross_workload_review_required": semantic.get("workload_coverage", {}).get("cross_part_flow_proven") is False,
|
|
1395
|
+
"cross_part_edges_observed": int(semantic.get("workload_coverage", {}).get("cross_part_edges", {}).get("observed_count", 0)),
|
|
1074
1396
|
},
|
|
1075
1397
|
"coverage": {"counts": counts, "required": required, "gaps": gaps},
|
|
1076
1398
|
"recommended_order": [
|
|
@@ -1594,13 +1916,14 @@ def protect_mcp(root: Path) -> Dict[str, Any]:
|
|
|
1594
1916
|
}
|
|
1595
1917
|
|
|
1596
1918
|
|
|
1597
|
-
def verify_mcp_project(root: Path, *, live: bool = True, run_tests: bool = True
|
|
1919
|
+
def verify_mcp_project(root: Path, *, live: bool = True, run_tests: bool = True,
|
|
1920
|
+
plan: Optional[Dict[str, Any]] = None) -> Tuple[int, Dict[str, Any]]:
|
|
1598
1921
|
from .mcp_verification import run_mcp_transport_verification
|
|
1599
1922
|
|
|
1923
|
+
plan = plan or integration_plan(root)
|
|
1600
1924
|
transport = run_mcp_transport_verification()
|
|
1601
|
-
exit_code, result = verify_project(root, live=live, run_tests=run_tests)
|
|
1602
|
-
|
|
1603
|
-
topology = report.get("mcp_protection") if isinstance(report.get("mcp_protection"), dict) else {}
|
|
1925
|
+
exit_code, result = verify_project(root, live=live, run_tests=run_tests, plan=plan)
|
|
1926
|
+
topology = plan.get("mcp_protection") if isinstance(plan.get("mcp_protection"), dict) else {}
|
|
1604
1927
|
known_transport = topology.get("detected") is True and "unknown_dynamic" not in topology.get("transports", [])
|
|
1605
1928
|
# Raw forwarding locations are candidates until the signed semantic plan
|
|
1606
1929
|
# proves whether the protected wrapper dominates them. The production gate
|
|
@@ -1772,9 +2095,15 @@ def protect_openclaw(root: Path) -> Dict[str, Any]:
|
|
|
1772
2095
|
|
|
1773
2096
|
|
|
1774
2097
|
def verify_openclaw_project(root: Path, *, live: bool = True, run_tests: bool = True) -> Tuple[int, Dict[str, Any]]:
|
|
1775
|
-
"""Verify
|
|
2098
|
+
"""Verify OpenClaw hooks and, when present, MCP transport in one report."""
|
|
1776
2099
|
protection = _openclaw_runtime_inspection(root)
|
|
1777
|
-
|
|
2100
|
+
plan = integration_plan(root)
|
|
2101
|
+
mcp_topology = plan.get("mcp_protection")
|
|
2102
|
+
mcp_detected = isinstance(mcp_topology, dict) and mcp_topology.get("detected") is True
|
|
2103
|
+
if mcp_detected:
|
|
2104
|
+
exit_code, result = verify_mcp_project(root, live=live, run_tests=run_tests, plan=plan)
|
|
2105
|
+
else:
|
|
2106
|
+
exit_code, result = verify_project(root, live=live, run_tests=run_tests, plan=plan)
|
|
1778
2107
|
decisions_ready = live and all(item.get("passed") for item in result.get("decision_cases", []))
|
|
1779
2108
|
openclaw_ready = bool(protection.get("ready")) and decisions_ready
|
|
1780
2109
|
result["openclaw_verification"] = {
|
|
@@ -1850,10 +2179,11 @@ def _run_existing_tests(root: Path) -> Dict[str, Any]:
|
|
|
1850
2179
|
}
|
|
1851
2180
|
|
|
1852
2181
|
|
|
1853
|
-
def verify_project(root: Path, live: bool = False, run_tests: bool = False
|
|
2182
|
+
def verify_project(root: Path, live: bool = False, run_tests: bool = False,
|
|
2183
|
+
*, plan: Optional[Dict[str, Any]] = None) -> Tuple[int, Dict[str, Any]]:
|
|
1854
2184
|
from .client import AgenticDomeClient
|
|
1855
2185
|
|
|
1856
|
-
plan = integration_plan(root)
|
|
2186
|
+
plan = plan or integration_plan(root)
|
|
1857
2187
|
config_path = _agenticdome_dir(root) / "config.json"
|
|
1858
2188
|
broker_options = _onboarding_broker_options(_load_json(config_path)) if config_path.exists() else {}
|
|
1859
2189
|
required_env = ["AGENTICDOME_API_BASE", "AGENTICDOME_API_KEY", "AGENTICDOME_TENANT_ID"]
|
|
@@ -1901,6 +2231,7 @@ def verify_project(root: Path, live: bool = False, run_tests: bool = False) -> T
|
|
|
1901
2231
|
"clarification": "Use --run-tests for the production onboarding gate.",
|
|
1902
2232
|
}
|
|
1903
2233
|
semantic_gate = plan.get("semantic_gate", {})
|
|
2234
|
+
cross_part_review_required = bool(semantic_gate.get("cross_workload_review_required"))
|
|
1904
2235
|
semantic_ready = (
|
|
1905
2236
|
semantic_gate.get("confidence") in {"high", "partial"}
|
|
1906
2237
|
and int(semantic_gate.get("unresolved_bypasses", 0)) == 0
|
|
@@ -1923,13 +2254,16 @@ def verify_project(root: Path, live: bool = False, run_tests: bool = False) -> T
|
|
|
1923
2254
|
"high_severity_bypasses": int(semantic_gate.get("high_severity_bypasses", 0)),
|
|
1924
2255
|
"review_required": int(semantic_gate.get("review_required", 0)),
|
|
1925
2256
|
"passed": semantic_ready,
|
|
2257
|
+
"cross_part_review_required": cross_part_review_required,
|
|
2258
|
+
"cross_part_edges_observed": int(semantic_gate.get("cross_part_edges_observed", 0)),
|
|
2259
|
+
"production_ready": semantic_ready and not cross_part_review_required,
|
|
1926
2260
|
},
|
|
1927
2261
|
"application_tests": application_tests,
|
|
1928
2262
|
"ready": all(item["passed"] for item in outcomes)
|
|
1929
2263
|
and not plan["coverage"]["gaps"]
|
|
1930
2264
|
and (not run_tests or semantic_ready)
|
|
1931
2265
|
and (not run_tests or application_tests["passed"] is True),
|
|
1932
|
-
"clarification": "Decision cases use fixed payloads.
|
|
2266
|
+
"clarification": "Decision cases use fixed payloads. ready=true means local checks passed, not production approval. Split analyses also require a tenant-admin review of cross-part paths, workload-specific integration tests and live runtime evidence; dynamic paths remain outside static proof.",
|
|
1933
2267
|
}
|
|
1934
2268
|
canonical = json.dumps(result, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
1935
2269
|
result["report_sha256"] = hashlib.sha256(canonical).hexdigest()
|
|
File without changes
|
{agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/licenses/NOTICE
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.33.dist-info → agenticdome_python_sdk-1.2.34.dist-info}/top_level.txt
RENAMED
|
File without changes
|