devflow-engine 1.2.1__py3-none-any.whl → 1.2.3__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.
@@ -151,6 +151,34 @@ def _proof_candidate(verification: dict[str, Any] | None) -> dict[str, Any]:
151
151
  return {}
152
152
 
153
153
 
154
+ def _observation_fix_proof(context: dict[str, Any]) -> dict[str, Any]:
155
+ build_observability = context.get("BuildObservability")
156
+ if not isinstance(build_observability, dict):
157
+ return {}
158
+ output = build_observability.get("output")
159
+ if not isinstance(output, dict):
160
+ return {}
161
+ observation = output.get("observation")
162
+ if not isinstance(observation, dict):
163
+ return {}
164
+ proof = observation.get("fix_proof")
165
+ return proof if isinstance(proof, dict) else {}
166
+
167
+
168
+ def _verification_with_observed_fix_proof(
169
+ verification: dict[str, Any] | None,
170
+ context: dict[str, Any],
171
+ ) -> dict[str, Any] | None:
172
+ if _proof_candidate(verification):
173
+ return verification
174
+ observed_proof = _observation_fix_proof(context)
175
+ if not observed_proof:
176
+ return verification
177
+ merged = dict(verification or {})
178
+ merged["fix_proof"] = observed_proof
179
+ return merged
180
+
181
+
154
182
  def _resolve_artifact_path(repo_root: Path, artifact_path: Any) -> Path | None:
155
183
  raw = str(artifact_path or "").strip()
156
184
  if not raw:
@@ -216,7 +244,23 @@ def _validate_ui_fix_proof(
216
244
  return True, normalized, None
217
245
 
218
246
 
219
- def _validate_ui_observability_repro(observation: dict[str, Any]) -> tuple[bool, str | None]:
247
+ def _artifact_under_observability_sandbox(artifact: str, *, error_task_id: str) -> bool:
248
+ normalized = artifact.replace("\\", "/").strip()
249
+ allowed_prefixes = (
250
+ f".devflow/observability/{error_task_id}/",
251
+ ".devflow/observability/<id>/",
252
+ )
253
+ if any(normalized.startswith(prefix) for prefix in allowed_prefixes):
254
+ return True
255
+ marker = f"/.devflow/observability/{error_task_id}/"
256
+ return marker in normalized
257
+
258
+
259
+ def _validate_ui_observability_repro(
260
+ observation: dict[str, Any],
261
+ *,
262
+ error_task_id: str,
263
+ ) -> tuple[bool, str | None]:
220
264
  artifact = str(observation.get("repro_artifact_path") or "").strip()
221
265
  command = str(observation.get("repro_run_command") or "").strip()
222
266
  output = str(observation.get("repro_output") or "").strip()
@@ -228,6 +272,8 @@ def _validate_ui_observability_repro(observation: dict[str, Any]) -> tuple[bool,
228
272
  return False, "ui_observability_missing_playwright_repro_artifact"
229
273
  if "playwright" not in command.lower():
230
274
  return False, "ui_observability_requires_playwright_repro_command"
275
+ if not _artifact_under_observability_sandbox(artifact, error_task_id=error_task_id):
276
+ return False, "ui_observability_repro_artifact_outside_sandbox"
231
277
  if not output:
232
278
  return False, "ui_observability_missing_repro_output"
233
279
  if not isinstance(oracles, list) or not any(str(o or "").strip() for o in oracles):
@@ -848,7 +894,10 @@ class BuildObservabilityNode(Node):
848
894
  else {}
849
895
  )
850
896
  if observed_flag and _ui_proof_required({"runtimeContext": user_report_context_for_ui}):
851
- ui_repro_ok, ui_repro_failure_reason = _validate_ui_observability_repro(observation)
897
+ ui_repro_ok, ui_repro_failure_reason = _validate_ui_observability_repro(
898
+ observation,
899
+ error_task_id=error_task_id,
900
+ )
852
901
 
853
902
  # Split deferred questions via LLM classifier (v1.1.22).
854
903
  # An injectable hook is used when present so tests can
@@ -1138,6 +1187,10 @@ class ResolveNode(Node):
1138
1187
  error_task_id = task_context.event.error_task_id
1139
1188
 
1140
1189
  verification = task_context.metadata.get("verification")
1190
+ effective_verification = _verification_with_observed_fix_proof(
1191
+ verification if isinstance(verification, dict) else None,
1192
+ context,
1193
+ )
1141
1194
  # Red-before-fix invariant: ResolveNode refuses to mark resolved
1142
1195
  # unless BuildObservability persisted observable=True at intake.
1143
1196
  # Closes the "already-green-at-intake" leak — a green-gate True with
@@ -1147,14 +1200,14 @@ class ResolveNode(Node):
1147
1200
  if isinstance(bo, dict):
1148
1201
  bo_out = bo.get("output") if isinstance(bo.get("output"), dict) else {}
1149
1202
  observation_was_red = bool(bo_out.get("observable"))
1150
- green_gate = bool((verification or {}).get("green_gate"))
1203
+ green_gate = bool((effective_verification or {}).get("green_gate"))
1151
1204
  proof_verified = True
1152
1205
  fix_proof: dict[str, Any] | None = None
1153
1206
  proof_failure_reason: str | None = None
1154
1207
  if _ui_proof_required(context):
1155
1208
  proof_verified, fix_proof, proof_failure_reason = _validate_ui_fix_proof(
1156
1209
  repo_root=persistor.repo_root,
1157
- verification=verification if isinstance(verification, dict) else None,
1210
+ verification=effective_verification if isinstance(effective_verification, dict) else None,
1158
1211
  )
1159
1212
  verified = green_gate and observation_was_red and proof_verified
1160
1213
  runtime_context = context.get("runtimeContext") if isinstance(context.get("runtimeContext"), dict) else None
@@ -1165,13 +1218,13 @@ class ResolveNode(Node):
1165
1218
  fingerprint = runtime_context.get("fingerprint") or current_record.get("fingerprint")
1166
1219
  title = context.get("metadata", {}).get("title") if isinstance(context.get("metadata"), dict) else None
1167
1220
 
1168
- resolve_output: dict[str, Any] = {"verification": verification, "verified": verified}
1221
+ resolve_output: dict[str, Any] = {"verification": effective_verification, "verified": verified}
1169
1222
  if proof_failure_reason:
1170
1223
  resolve_output["proof_failure_reason"] = proof_failure_reason
1171
1224
  if fix_proof:
1172
1225
  resolve_output["fix_proof"] = fix_proof
1173
1226
  persistor.persist(context=context, node_name="Resolve", output=resolve_output)
1174
- self.save_output(ResolveNode.OutputType(verified=verified, verification=verification))
1227
+ self.save_output(ResolveNode.OutputType(verified=verified, verification=effective_verification))
1175
1228
 
1176
1229
  if verified:
1177
1230
  if fix_proof:
@@ -1185,7 +1238,7 @@ class ResolveNode(Node):
1185
1238
  persistor.repo_root,
1186
1239
  error_task_id=error_task_id,
1187
1240
  title=str(title) if title is not None else None,
1188
- verification=verification,
1241
+ verification=effective_verification if isinstance(effective_verification, dict) else None,
1189
1242
  )
1190
1243
  task_context.metadata["commit_result"] = commit_result
1191
1244
  # v1.1.19: persist resolution_commit_sha + portal_token so the
@@ -1228,7 +1281,7 @@ class ResolveNode(Node):
1228
1281
  outcome=str(task_context.metadata.get("outcome") or "in_progress"),
1229
1282
  title=str(title) if title is not None else None,
1230
1283
  fingerprint=str(fingerprint) if fingerprint is not None else None,
1231
- verification=verification,
1284
+ verification=effective_verification if isinstance(effective_verification, dict) else None,
1232
1285
  reason=str(task_context.metadata.get("reason")) if task_context.metadata.get("reason") else None,
1233
1286
  context_path=persistor.context_path,
1234
1287
  runtime_context=runtime_context,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devflow-engine
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: DevFlow v2 CLI-first engine (projects, stories, execution store, review packets)
5
5
  Author: DevFlow
6
6
  License: Proprietary
@@ -32,7 +32,7 @@ devflow_engine/devin2/agent_definition.py,sha256=pCp915tojwPlepRpvjlU1DoPuT_anxR
32
32
  devflow_engine/devin2/pi_runner.py,sha256=xRl-qA3RgMFgiR5fnG6fTwwL95PMk6zVdRdFa-fXy2E,7466
33
33
  devflow_engine/error/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  devflow_engine/error/remediation.py,sha256=Z4LJQzZPzb7_6tea_XtasDvFYQiEdpkFlyxoCEO1I2k,548
35
- devflow_engine/errors/error_solver_dag.py,sha256=9SQT74F485gJvN0h0fXojd6kGU4YJxt2g4tsio9LKCQ,58180
35
+ devflow_engine/errors/error_solver_dag.py,sha256=GZXVqssgqUgoqRqA5r7_fnz7noZVKD-g3NS5r0ShBms,60195
36
36
  devflow_engine/errors/question_classifier.py,sha256=oBJ29hszG--TUna62BQHnNmi6hPwf-wEE8eGLmgS624,9729
37
37
  devflow_engine/errors/repo_quality_judge.py,sha256=LVBaXN6AsID-x8qb-IriQKYY1c9AMMRNo-tWMeiuMaA,9962
38
38
  devflow_engine/errors/runtime_observability.py,sha256=Do93Ah3vxLEocYtC0JpnRIRQN6uNAOkYjBz0pmOqg5g,29719
@@ -382,7 +382,7 @@ devflow_engine/prompts/source_doc_mutation/source_doc_enrichment_coherence/promp
382
382
  devflow_engine/prompts/source_doc_mutation/user_workflows/prompt.md,sha256=2Bv-EkZwzxtPSsDGdw9YuuuzT1c_2R9c6HPSMdP-MD0,406
383
383
  devflow_engine/prompts/source_scope/doctrine/prompt.md,sha256=AJk1gfePTdaBSw5DHvCr7Sigsk2orGaRVNZ3Dj3NdcQ,837
384
384
  devflow_engine/prompts/ui_grounding/doctrine/prompt.md,sha256=0L5y-QjjquPABqxAHZNPkcjyHMZQSDRVncNDQa2app0,409
385
- devflow_engine-1.2.1.dist-info/METADATA,sha256=y30DsM64igxG19kjixCV0h-RN0NbLptfTWThXz4AbXQ,7755
386
- devflow_engine-1.2.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
387
- devflow_engine-1.2.1.dist-info/entry_points.txt,sha256=U5Pw4y4v4u4xcRS5DbXfemBg0RKp74kMxcnEoL0MACY,99
388
- devflow_engine-1.2.1.dist-info/RECORD,,
385
+ devflow_engine-1.2.3.dist-info/METADATA,sha256=spmTNfOipkpqYWkJx_uOVJjLtfYG_ZZ9PBs6aOWSjDM,7755
386
+ devflow_engine-1.2.3.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
387
+ devflow_engine-1.2.3.dist-info/entry_points.txt,sha256=U5Pw4y4v4u4xcRS5DbXfemBg0RKp74kMxcnEoL0MACY,99
388
+ devflow_engine-1.2.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any