@tea-agent/loop-agent 0.40.0-next.10 → 0.40.0-next.12
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.
- package/CHANGELOG.md +18 -0
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/commands/client-recovery.js +8 -36
- package/dist/executors/pi-sdk-executor.js +33 -5
- package/dist/executors/shell-executor.js +64 -17
- package/dist/governance/checks.js +1 -0
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/worker/console/chat/pi-runtime.js +146 -5
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/routes.js +3 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/operator-actions.js +4 -3
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-C_hkwt2J.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-s2Q6r96B.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-CtgsrwU1.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-DZ-A7EiJ.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-D2qv0Nyo.js} +1 -1
- package/dist/worker/console/static/assets/channel-BluaMT3-.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-D-UYpSaW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-DAS3jrrt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-CSbuRcGB.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-BDH1ZO40.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-Dc2cqQIb.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-CkZuemIW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-URwjQnte.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-04RHylJK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-CvQX56Hh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-dUF7qura.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-H-ZpSafr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-H-ZpSafr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-COwbfxJE.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CaBEJekQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-COWUBdet.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-BLxVqTn4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-WL0yKfEQ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-DaVkKCle.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-DdTgnRGf.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-PG2Axo6A.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-6vuQzlzI.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BpXeBz-g.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-PxtUnaa1.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-B--Qgvs1.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-BIfQCgtx.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-BRaX_E4j.js} +1 -1
- package/dist/worker/console/static/assets/index-Crrgl8Mx.css +1 -0
- package/dist/worker/console/static/assets/{index-DqJbO3-p.js → index-vPsp-idE.js} +102 -72
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-Cd93r4Ab.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C4c80iHt.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-Ch5ajf3s.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-DbpyW1Ut.js} +1 -1
- package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-BHVjBnTW.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-DO70kJ34.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-_P44Zpvs.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-Bgi9FmEd.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-C9InDfJn.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-DtKrUouv.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-zgAD_6Yo.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-CQNO6kj4.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-knKu111P.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-D6tshiQ6.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-BMIWJcir.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-BP9wRDqc.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-ClEhytQG.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-DEkM5Dhe.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPg5IMuu.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-ByBKsL35.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-waFbNCUy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-1ljiY0eZ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-B4ZLHr_L.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/observe/static/styles.css +9 -0
- package/dist/worker/observe/static/views/dag-inspector.js +40 -0
- package/dist/worker/observe/static/views/session-timeline.js +135 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +157 -7
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +4 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
- package/dist/workflows/dag/init-hybrid.js +2 -2
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/backend-test-dag.json +4 -3
- package/package.json +3 -2
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
|
@@ -8,6 +8,7 @@ import { resolveBackendTestLayout, } from "./backend-test-layout.js";
|
|
|
8
8
|
import { resolveBackendTestManifestModules, backendManifestStatusFinding } from "./backend-test-markdown-workflow.js";
|
|
9
9
|
import { applyOpenApiPartitionDomainPolicy, assessScenarioPartitionCoverage, parseScenarioPartitions, } from "./backend-test-scenario-partitions.js";
|
|
10
10
|
import { backendTestCaseManifestSchema, computeCaseManifestCoverageSummary, } from "./backend-test-case-manifest.js";
|
|
11
|
+
import { inferScenarioParamIntent } from "./backend-test-scenario-param.js";
|
|
11
12
|
const CASE_HEADING = /^##\s+(BE-[A-Z0-9_-]+-\d{3})\b.*$/gm;
|
|
12
13
|
const CASE_ID_IN_TEXT = /\bBE-[A-Z0-9_-]+-\d{3}\b/g;
|
|
13
14
|
const NON_CANONICAL_CASE_HEADING = /^##\s+(BE-[A-Z0-9_-]+-(?:\d{2}|\d{2,3}[A-Z]+))\b.*$/gm;
|
|
@@ -281,6 +282,80 @@ export const backendTestMarkdownPytestCorrespondenceFactsSchema = z.object({
|
|
|
281
282
|
entries: z.array(correspondenceEntrySchema),
|
|
282
283
|
findings: z.array(z.string()),
|
|
283
284
|
}).strict();
|
|
285
|
+
function bindingCounts(entry) {
|
|
286
|
+
const counts = new Map();
|
|
287
|
+
for (const point of [...entry.variantTestPoints, ...entry.assertionTestPoints, ...entry.crossCuttingTestPoints]) {
|
|
288
|
+
counts.set(point, (counts.get(point) ?? 0) + 1);
|
|
289
|
+
}
|
|
290
|
+
return counts;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Classify correspondence defects by the only writer that can safely repair
|
|
294
|
+
* them. Markdown-owned contract defects must be fixed before pytest generation;
|
|
295
|
+
* pytest-owned generated-asset defects may enter the single bounded N11 repair.
|
|
296
|
+
*/
|
|
297
|
+
export function classifyBackendTestCorrespondenceRepair(facts, layout) {
|
|
298
|
+
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
299
|
+
const scriptRoot = resolved.scriptDir.replace(/\/$/, "");
|
|
300
|
+
const safePytestPath = (candidate) => {
|
|
301
|
+
const normalized = candidate.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
302
|
+
return normalized.endsWith(".py") && (normalized === scriptRoot || normalized.startsWith(`${scriptRoot}/`));
|
|
303
|
+
};
|
|
304
|
+
const markdownBlockingEntries = [];
|
|
305
|
+
const pytestRepairEntries = [];
|
|
306
|
+
for (const entry of facts.entries) {
|
|
307
|
+
const markdownReasons = [];
|
|
308
|
+
const pytestReasons = [];
|
|
309
|
+
const counts = bindingCounts(entry);
|
|
310
|
+
if (entry.testPoints.some((point) => (counts.get(point) ?? 0) !== 1) ||
|
|
311
|
+
[...counts.keys()].some((point) => !entry.testPoints.includes(point))) {
|
|
312
|
+
markdownReasons.push("markdown-test-point-binding-invalid");
|
|
313
|
+
}
|
|
314
|
+
if (entry.declaredScript && entry.declaredScript !== entry.expectedScript) {
|
|
315
|
+
markdownReasons.push("markdown-script-declaration-invalid");
|
|
316
|
+
}
|
|
317
|
+
if (entry.cardinality === "1:0")
|
|
318
|
+
pytestReasons.push("missing-pytest-symbol");
|
|
319
|
+
if (entry.cardinality === "1:N")
|
|
320
|
+
pytestReasons.push("multiple-pytest-symbols");
|
|
321
|
+
if (entry.cardinality === "0:1")
|
|
322
|
+
pytestReasons.push("extra-pytest-symbol");
|
|
323
|
+
if (entry.actualScripts.some((script) => script !== entry.expectedScript))
|
|
324
|
+
pytestReasons.push("pytest-script-mismatch");
|
|
325
|
+
if (entry.declaredPrimarySymbol && entry.pytestSymbols.some((symbol) => symbol !== entry.declaredPrimarySymbol)) {
|
|
326
|
+
pytestReasons.push("pytest-primary-symbol-mismatch");
|
|
327
|
+
}
|
|
328
|
+
if (entry.variantTestPoints.some((point) => !entry.parameterIds.includes(point)))
|
|
329
|
+
pytestReasons.push("variant-parameter-missing");
|
|
330
|
+
if (entry.assertionTestPoints.some((point) => !entry.mappedTestPoints.includes(point)))
|
|
331
|
+
pytestReasons.push("assertion-binding-missing");
|
|
332
|
+
if (entry.crossCuttingTestPoints.some((point) => !entry.mappedTestPoints.includes(point)))
|
|
333
|
+
pytestReasons.push("cross-cutting-binding-missing");
|
|
334
|
+
if (entry.parameterIds.some((point) => !entry.testPoints.includes(point)))
|
|
335
|
+
pytestReasons.push("pytest-test-point-binding-extra");
|
|
336
|
+
if (entry.payloadAssessment.status === "UNSAFE")
|
|
337
|
+
pytestReasons.push("pytest-payload-shape-mismatch");
|
|
338
|
+
if (entry.status !== "EXACT_1_TO_1" &&
|
|
339
|
+
!["TEST_POINT_BINDING_DUPLICATE", "PAYLOAD_CONTRACT_UNAVAILABLE"].includes(entry.status)) {
|
|
340
|
+
pytestReasons.push(`correspondence-${entry.status.toLowerCase().replaceAll("_", "-")}`);
|
|
341
|
+
}
|
|
342
|
+
if (entry.status === "PAYLOAD_CONTRACT_UNAVAILABLE") {
|
|
343
|
+
markdownReasons.push("markdown-payload-contract-unavailable");
|
|
344
|
+
}
|
|
345
|
+
const paths = orderedUnique([entry.expectedScript, ...entry.actualScripts].filter(safePytestPath));
|
|
346
|
+
if (markdownReasons.length > 0) {
|
|
347
|
+
markdownBlockingEntries.push({ ...(entry.caseId ? { caseId: entry.caseId } : {}), reasonCodes: orderedUnique(markdownReasons), paths });
|
|
348
|
+
}
|
|
349
|
+
if (pytestReasons.length > 0 && paths.length > 0) {
|
|
350
|
+
pytestRepairEntries.push({ ...(entry.caseId ? { caseId: entry.caseId } : {}), reasonCodes: orderedUnique(pytestReasons), paths });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
markdownBlockingEntries,
|
|
355
|
+
pytestRepairEntries,
|
|
356
|
+
pytestRepairPaths: orderedUnique(pytestRepairEntries.flatMap((entry) => entry.paths)).sort(),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
284
359
|
function isRecord(value) {
|
|
285
360
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
286
361
|
}
|
|
@@ -593,6 +668,19 @@ function automationLabelBody(body, labels) {
|
|
|
593
668
|
const match = new RegExp(`^\\s*[-*+]\\s*(?:${escaped})\\s*[::]\\s*(.*)$`, "mi").exec(automation);
|
|
594
669
|
return match?.[1]?.replaceAll("`", "").trim() ?? "";
|
|
595
670
|
}
|
|
671
|
+
export function inspectBackendTestMarkdownTestPointBindings(markdown) {
|
|
672
|
+
const headings = [...markdown.matchAll(CASE_HEADING)];
|
|
673
|
+
return headings.map((heading, index) => {
|
|
674
|
+
const body = markdown.slice(heading.index, headings[index + 1]?.index ?? markdown.length);
|
|
675
|
+
const testPoints = listTokens(body, CASE_SECTION_ALIASES.testPoints, TEST_POINT);
|
|
676
|
+
const bindings = testPointBindingFacts(body, testPoints);
|
|
677
|
+
return {
|
|
678
|
+
caseId: canonicalCaseId(heading[1]),
|
|
679
|
+
unclassifiedTestPoints: bindings.unclassifiedTestPoints,
|
|
680
|
+
duplicateBindingTestPoints: bindings.duplicateBindingTestPoints,
|
|
681
|
+
};
|
|
682
|
+
});
|
|
683
|
+
}
|
|
596
684
|
function testPointBindingFacts(body, testPoints) {
|
|
597
685
|
const bindings = TEST_POINT_BINDING_MODES.flatMap((mode) => {
|
|
598
686
|
const value = automationLabelBody(body, AUTOMATION_BINDING_LABELS[mode]);
|
|
@@ -1404,6 +1492,11 @@ def payload_expr_nodes(expr, assigned, parameters, seen=None):
|
|
|
1404
1492
|
if isinstance(key,ast.Constant) and key.value == expr.slice.value:
|
|
1405
1493
|
result.extend(shape_only(item) for item in payload_expr_nodes(value,assigned,parameters,set(seen)))
|
|
1406
1494
|
return result
|
|
1495
|
+
if isinstance(expr,ast.IfExp):
|
|
1496
|
+
result=[]
|
|
1497
|
+
result.extend(payload_expr_nodes(expr.body,assigned,parameters,set(seen)))
|
|
1498
|
+
result.extend(payload_expr_nodes(expr.orelse,assigned,parameters,set(seen)))
|
|
1499
|
+
return result
|
|
1407
1500
|
if isinstance(expr,ast.Call):
|
|
1408
1501
|
result=[]
|
|
1409
1502
|
if isinstance(expr.func,ast.Name) and expr.func.id in FUNCTION_PAYLOADS:
|
|
@@ -1444,6 +1537,30 @@ def decorator_payload_nodes(parameters,assigned):
|
|
|
1444
1537
|
for value in values: result.extend(payload_expr_nodes(value,assigned,parameters))
|
|
1445
1538
|
return result
|
|
1446
1539
|
|
|
1540
|
+
def decorator_payload_nodes_by_tp(fn,assigned):
|
|
1541
|
+
result={}
|
|
1542
|
+
for dec in fn.decorator_list:
|
|
1543
|
+
if not isinstance(dec,ast.Call) or not isinstance(dec.func,ast.Attribute) or dec.func.attr != 'parametrize' or len(dec.args) < 2: continue
|
|
1544
|
+
if not isinstance(dec.args[0],ast.Constant) or not isinstance(dec.args[0].value,str): continue
|
|
1545
|
+
names=[item.strip() for item in dec.args[0].value.split(',')]
|
|
1546
|
+
rows=dec.args[1]
|
|
1547
|
+
if isinstance(rows,ast.Name) and rows.id in GLOBAL_VALUES: rows=GLOBAL_VALUES[rows.id]
|
|
1548
|
+
for node in ast.walk(rows):
|
|
1549
|
+
if not isinstance(node,ast.Call) or not isinstance(node.func,ast.Attribute) or node.func.attr != 'param': continue
|
|
1550
|
+
tp=None
|
|
1551
|
+
for keyword in node.keywords:
|
|
1552
|
+
if keyword.arg == 'id' and isinstance(keyword.value,ast.Constant) and isinstance(keyword.value.value,str) and keyword.value.value.startswith('TP-'):
|
|
1553
|
+
tp=keyword.value.value
|
|
1554
|
+
if not tp: continue
|
|
1555
|
+
parameters={}
|
|
1556
|
+
for index,name in enumerate(names):
|
|
1557
|
+
if index < len(node.args): parameters.setdefault(name,[]).append(node.args[index])
|
|
1558
|
+
payload_nodes=decorator_payload_nodes(parameters,assigned)
|
|
1559
|
+
paths=set(); values={}
|
|
1560
|
+
for payload_node in payload_nodes: collect_dict(payload_node,'',paths,values)
|
|
1561
|
+
result[tp]={key:sorted(items) for key,items in sorted(values.items())}
|
|
1562
|
+
return result
|
|
1563
|
+
|
|
1447
1564
|
def fallback_payload_nodes(fn):
|
|
1448
1565
|
result=[]
|
|
1449
1566
|
assigned={}
|
|
@@ -1473,7 +1590,7 @@ def fallback_payload_nodes(fn):
|
|
|
1473
1590
|
|
|
1474
1591
|
def is_direct_transport_call(node):
|
|
1475
1592
|
if not isinstance(node,ast.Call) or not isinstance(node.func,ast.Attribute): return False
|
|
1476
|
-
if node.func.attr.lower() not in {'get','post','put','patch','delete','request'}: return False
|
|
1593
|
+
if node.func.attr.lower() not in {'get','post','put','patch','delete','request','call'}: return False
|
|
1477
1594
|
receiver_tokens=[]
|
|
1478
1595
|
current=node.func.value
|
|
1479
1596
|
while isinstance(current,ast.Attribute):
|
|
@@ -1521,6 +1638,7 @@ for fn in ast.walk(tree):
|
|
|
1521
1638
|
'values':{key:sorted(items) for key,items in sorted(values.items())},
|
|
1522
1639
|
'fallbackPaths':sorted(fallback_paths),
|
|
1523
1640
|
'fallbackValues':{key:sorted(items) for key,items in sorted(fallback_values.items())},
|
|
1641
|
+
'valuesByTestPoint':decorator_payload_nodes_by_tp(fn,{}),
|
|
1524
1642
|
}
|
|
1525
1643
|
print(json.dumps(out,ensure_ascii=True))
|
|
1526
1644
|
`;
|
|
@@ -1651,6 +1769,7 @@ function pytestSymbols(script, source) {
|
|
|
1651
1769
|
payloadValues: payloadShape.values,
|
|
1652
1770
|
fallbackPayloadPaths: payloadShape.fallbackPaths ?? [],
|
|
1653
1771
|
fallbackPayloadValues: payloadShape.fallbackValues ?? {},
|
|
1772
|
+
payloadValuesByTestPoint: payloadShape.valuesByTestPoint ?? {},
|
|
1654
1773
|
};
|
|
1655
1774
|
});
|
|
1656
1775
|
}
|
|
@@ -1664,6 +1783,33 @@ function executionSignature(testCase) {
|
|
|
1664
1783
|
.join("\n");
|
|
1665
1784
|
return createHash("sha256").update(normalized).digest("hex");
|
|
1666
1785
|
}
|
|
1786
|
+
function normalizePythonLiteralValue(value) {
|
|
1787
|
+
const trimmed = value.trim();
|
|
1788
|
+
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
|
1789
|
+
return trimmed.slice(1, -1);
|
|
1790
|
+
}
|
|
1791
|
+
return trimmed;
|
|
1792
|
+
}
|
|
1793
|
+
function intentionalInvalidEnumVariant(input) {
|
|
1794
|
+
if (!input.testCase.testPointBindings.some((binding) => binding.mode === "variant" && binding.testPoint === input.testPoint))
|
|
1795
|
+
return false;
|
|
1796
|
+
const inferred = inferScenarioParamIntent({ tpId: input.testPoint, caseBody: input.testCase.body });
|
|
1797
|
+
if (inferred.intentSource !== "machine-line" || inferred.field?.toLowerCase() !== input.field.toLowerCase())
|
|
1798
|
+
return false;
|
|
1799
|
+
const observed = normalizePythonLiteralValue(input.observedValue);
|
|
1800
|
+
if (input.allowed.includes(observed))
|
|
1801
|
+
return false;
|
|
1802
|
+
if (inferred.intent === "enum-invalid" || inferred.intent === "wrong-type")
|
|
1803
|
+
return true;
|
|
1804
|
+
if (inferred.intent === "null")
|
|
1805
|
+
return observed === "None" || observed === "null";
|
|
1806
|
+
if (inferred.intent === "empty")
|
|
1807
|
+
return observed === "";
|
|
1808
|
+
if (!inferred.intent.startsWith("custom-literal:"))
|
|
1809
|
+
return false;
|
|
1810
|
+
const expected = normalizePythonLiteralValue(inferred.example ?? inferred.intent.slice("custom-literal:".length));
|
|
1811
|
+
return observed === expected;
|
|
1812
|
+
}
|
|
1667
1813
|
function assessPayloadContract(testCase, refs) {
|
|
1668
1814
|
const requiredPaths = testCase.payloadContract.requiredPaths;
|
|
1669
1815
|
const directObservedPaths = orderedUnique(refs.flatMap((item) => item.payloadPaths));
|
|
@@ -1685,13 +1831,17 @@ function assessPayloadContract(testCase, refs) {
|
|
|
1685
1831
|
? observedPaths.filter((item) => !allowedPaths.includes(item) && !allowedPaths.some((allowed) => item.startsWith(`${allowed}.`)))
|
|
1686
1832
|
: [];
|
|
1687
1833
|
const enumMismatches = [];
|
|
1688
|
-
const delegatesInvalidEnumToScenarioGate = testCase.testPointBindings
|
|
1689
|
-
.filter((binding) => binding.mode === "variant")
|
|
1690
|
-
.some((binding) => /(?:ENUM|CATEGORY).*(?:INVALID|UNKNOWN|CASE|WHITESPACE|EMPTY|WRONG-TYPE)|(?:INVALID|UNKNOWN|CASE|WHITESPACE|EMPTY|WRONG-TYPE).*(?:ENUM|CATEGORY)/.test(binding.testPoint));
|
|
1691
1834
|
for (const [field, allowed] of Object.entries(testCase.payloadContract.enumValues)) {
|
|
1692
|
-
const observed = orderedUnique(refs.flatMap((item) => (useFallback ? item.fallbackPayloadValues[field] : item.payloadValues[field]) ?? [])
|
|
1693
|
-
for (const
|
|
1694
|
-
|
|
1835
|
+
const observed = orderedUnique(refs.flatMap((item) => (useFallback ? item.fallbackPayloadValues[field] : item.payloadValues[field]) ?? []));
|
|
1836
|
+
for (const rawValue of observed) {
|
|
1837
|
+
const value = normalizePythonLiteralValue(rawValue);
|
|
1838
|
+
if (allowed.includes(value))
|
|
1839
|
+
continue;
|
|
1840
|
+
const associatedTestPoints = orderedUnique(refs.flatMap((item) => Object.entries(item.payloadValuesByTestPoint)
|
|
1841
|
+
.filter(([, values]) => (values[field] ?? []).includes(rawValue))
|
|
1842
|
+
.map(([testPoint]) => testPoint)));
|
|
1843
|
+
const intentionallyInvalid = associatedTestPoints.length > 0 && associatedTestPoints.every((testPoint) => intentionalInvalidEnumVariant({ testCase, testPoint, field, observedValue: rawValue, allowed }));
|
|
1844
|
+
if (!intentionallyInvalid)
|
|
1695
1845
|
enumMismatches.push(`${field}=${value}; allowed=${allowed.join("|")}`);
|
|
1696
1846
|
}
|
|
1697
1847
|
}
|
|
@@ -20,6 +20,14 @@ export const backendPytestCollectionFindingSchema = z.object({
|
|
|
20
20
|
classification: z.literal("test-asset-defect"),
|
|
21
21
|
repairability: z.enum(["repairable", "blocked"]),
|
|
22
22
|
detail: z.string().min(1),
|
|
23
|
+
caseId: z.string().min(1).optional(),
|
|
24
|
+
reasonCodes: z.array(z.string().min(1)).optional(),
|
|
25
|
+
repairPaths: z.array(z.string().min(1)).optional(),
|
|
26
|
+
payloadExpectedPaths: z.array(z.string()).optional(),
|
|
27
|
+
payloadObservedPaths: z.array(z.string()).optional(),
|
|
28
|
+
payloadMissingPaths: z.array(z.string()).optional(),
|
|
29
|
+
payloadUnexpectedPaths: z.array(z.string()).optional(),
|
|
30
|
+
payloadEnumMismatches: z.array(z.string()).optional(),
|
|
23
31
|
}).strict();
|
|
24
32
|
export const backendPytestCollectionFactsSchema = z.object({
|
|
25
33
|
schemaId: z.literal("backend-test-pytest-collection-v3"),
|
|
@@ -61,11 +69,22 @@ export const backendPytestCollectionFactsSchema = z.object({
|
|
|
61
69
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "backend pytest fixture resolution without an attempt cannot have an exit code" });
|
|
62
70
|
}
|
|
63
71
|
});
|
|
72
|
+
const backendTestScenarioParamDiagnosticSchema = z.object({
|
|
73
|
+
reasonCode: z.enum(["SCENARIO_PARAM_MISMATCH", "SCENARIO_PARAM_UNDETERMINED"]),
|
|
74
|
+
tpId: z.string().min(1).max(256),
|
|
75
|
+
field: z.string().min(1).max(256).optional(),
|
|
76
|
+
expectedRaw: z.string().max(512).optional(),
|
|
77
|
+
observedRaw: z.string().max(512),
|
|
78
|
+
expectedNormalized: z.string().max(512).optional(),
|
|
79
|
+
observedNormalized: z.string().max(512).optional(),
|
|
80
|
+
sourceLocation: z.string().max(512).optional(),
|
|
81
|
+
}).strict();
|
|
64
82
|
const backendTestExcludedItemSchema = z.object({
|
|
65
83
|
itemId: z.string().min(1),
|
|
66
84
|
caseId: z.string().optional(),
|
|
67
85
|
symbol: z.string().optional(),
|
|
68
86
|
reasons: z.array(z.string().min(1)).min(1),
|
|
87
|
+
scenarioParamDiagnostics: z.array(backendTestScenarioParamDiagnosticSchema).max(16).optional(),
|
|
69
88
|
}).strict();
|
|
70
89
|
export const backendTestExecutionReadinessSchema = z.object({
|
|
71
90
|
schemaId: z.literal("backend-test-execution-readiness-v2"),
|
|
@@ -81,6 +100,14 @@ export const backendTestExecutionReadinessSchema = z.object({
|
|
|
81
100
|
collectedItemIds: z.array(z.string()),
|
|
82
101
|
eligibleItemIds: z.array(z.string()),
|
|
83
102
|
excludedItems: z.array(backendTestExcludedItemSchema),
|
|
103
|
+
exclusionSummary: z.object({
|
|
104
|
+
excludedItemCount: z.number().int().min(0),
|
|
105
|
+
excludedCaseCount: z.number().int().min(0),
|
|
106
|
+
excludedByReason: z.record(z.string(), z.number().int().min(1)),
|
|
107
|
+
excludedByCase: z.record(z.string(), z.number().int().min(1)),
|
|
108
|
+
}).strict(),
|
|
109
|
+
pytestSpawned: z.literal(false),
|
|
110
|
+
businessTestBodyExecutedCount: z.literal(0),
|
|
84
111
|
fixtureIssues: z.array(z.string()),
|
|
85
112
|
assetHashes: z.record(z.string(), z.string().regex(SHA256)),
|
|
86
113
|
eligibilityInputHashes: z.record(z.string(), z.string().regex(SHA256)),
|
|
@@ -702,6 +729,7 @@ export function buildBackendTestItemEligibility(collectedItemIds, input) {
|
|
|
702
729
|
const parameterTokens = orderedUnique((itemId.match(/TP-[A-Z0-9-]+/g) ?? []));
|
|
703
730
|
const mapping = input.correspondenceEntries.find((entry) => symbol && (entry.declaredPrimarySymbol === symbol || entry.pytestSymbols.includes(symbol)));
|
|
704
731
|
const reasons = [];
|
|
732
|
+
const scenarioParamDiagnostics = [];
|
|
705
733
|
if (!mapping)
|
|
706
734
|
reasons.push("no exact Markdown Case/primary-symbol correspondence");
|
|
707
735
|
if (mapping && mapping.status !== "EXACT_1_TO_1")
|
|
@@ -724,13 +752,30 @@ export function buildBackendTestItemEligibility(collectedItemIds, input) {
|
|
|
724
752
|
// or observed Python payload shape.
|
|
725
753
|
const normalizedField = entry.field?.toLowerCase();
|
|
726
754
|
const fieldIsPayloadBound = Boolean(normalizedField && payloadPaths.some((item) => item === normalizedField || item.endsWith(`.${normalizedField}`)));
|
|
727
|
-
if (fieldIsPayloadBound && entry.status !== "MATCH")
|
|
755
|
+
if (fieldIsPayloadBound && entry.status !== "MATCH") {
|
|
728
756
|
reasons.push(`scenario-param ${entry.tpId} field ${entry.field} is ${entry.status}`);
|
|
757
|
+
scenarioParamDiagnostics.push({
|
|
758
|
+
reasonCode: entry.status === "MISMATCH" ? "SCENARIO_PARAM_MISMATCH" : "SCENARIO_PARAM_UNDETERMINED",
|
|
759
|
+
tpId: entry.tpId,
|
|
760
|
+
...(entry.field ? { field: entry.field } : {}),
|
|
761
|
+
...(entry.expectedRaw !== undefined ? { expectedRaw: entry.expectedRaw.slice(0, 512) } : {}),
|
|
762
|
+
observedRaw: (entry.observedRaw ?? "unavailable").slice(0, 512),
|
|
763
|
+
...(entry.expectedNormalized !== undefined ? { expectedNormalized: entry.expectedNormalized.slice(0, 512) } : {}),
|
|
764
|
+
...(entry.observedNormalized !== undefined ? { observedNormalized: entry.observedNormalized.slice(0, 512) } : {}),
|
|
765
|
+
...(entry.sourceLocation ? { sourceLocation: entry.sourceLocation.slice(0, 512) } : {}),
|
|
766
|
+
});
|
|
767
|
+
}
|
|
729
768
|
}
|
|
730
769
|
if (reasons.length === 0)
|
|
731
770
|
eligibleItemIds.push(itemId);
|
|
732
771
|
else
|
|
733
|
-
excludedItems.push({
|
|
772
|
+
excludedItems.push({
|
|
773
|
+
itemId,
|
|
774
|
+
...(mapping?.caseId ? { caseId: mapping.caseId } : {}),
|
|
775
|
+
...(symbol ? { symbol } : {}),
|
|
776
|
+
reasons: orderedUnique(reasons),
|
|
777
|
+
...(scenarioParamDiagnostics.length > 0 ? { scenarioParamDiagnostics: scenarioParamDiagnostics.slice(0, 16) } : {}),
|
|
778
|
+
});
|
|
734
779
|
}
|
|
735
780
|
return { eligibleItemIds, excludedItems };
|
|
736
781
|
}
|
|
@@ -763,6 +808,9 @@ export async function materializeBackendTestExecutionReadiness(input) {
|
|
|
763
808
|
collectedItemIds: input.effective.collectedItemIds,
|
|
764
809
|
eligibleItemIds: eligibility.eligibleItemIds,
|
|
765
810
|
excludedItems: eligibility.excludedItems,
|
|
811
|
+
exclusionSummary: summarizeBackendTestExclusions(eligibility.excludedItems),
|
|
812
|
+
pytestSpawned: false,
|
|
813
|
+
businessTestBodyExecutedCount: 0,
|
|
766
814
|
fixtureIssues: input.effective.findings.filter((item) => /fixture/i.test(item.kind)).map((item) => item.detail),
|
|
767
815
|
assetHashes: input.effective.inputHashes,
|
|
768
816
|
eligibilityInputHashes: input.eligibilityInputHashes ?? {},
|
|
@@ -778,6 +826,26 @@ export async function readBackendTestExecutionReadiness(filePath) {
|
|
|
778
826
|
function formatReadinessCounts(readiness) {
|
|
779
827
|
return `mapped=${readiness.mappedScripts.length} collected=${readiness.collectedItemIds.length} eligible=${readiness.eligibleItemIds.length} excluded=${readiness.excludedItems.length}`;
|
|
780
828
|
}
|
|
829
|
+
export function summarizeBackendTestExclusions(excludedItems) {
|
|
830
|
+
const reasonCounts = new Map();
|
|
831
|
+
const caseCounts = new Map();
|
|
832
|
+
for (const item of excludedItems) {
|
|
833
|
+
if (item.caseId)
|
|
834
|
+
caseCounts.set(item.caseId, (caseCounts.get(item.caseId) ?? 0) + 1);
|
|
835
|
+
for (const reason of item.reasons) {
|
|
836
|
+
const category = classifyExclusionReason(reason);
|
|
837
|
+
reasonCounts.set(category, (reasonCounts.get(category) ?? 0) + 1);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
excludedItemCount: excludedItems.length,
|
|
842
|
+
excludedCaseCount: caseCounts.size,
|
|
843
|
+
excludedByReason: Object.fromEntries(READINESS_EXCLUSION_CATEGORIES
|
|
844
|
+
.filter((category) => (reasonCounts.get(category) ?? 0) > 0)
|
|
845
|
+
.map((category) => [category, reasonCounts.get(category)])),
|
|
846
|
+
excludedByCase: Object.fromEntries([...caseCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
|
|
847
|
+
};
|
|
848
|
+
}
|
|
781
849
|
export function classifyExclusionReason(reason) {
|
|
782
850
|
if (reason === "no exact Markdown Case/primary-symbol correspondence")
|
|
783
851
|
return "correspondence-missing";
|
|
@@ -70,6 +70,7 @@ export const backendTestResultContractSchema = z
|
|
|
70
70
|
schemaVersion: z.literal(1),
|
|
71
71
|
/** Process-level status of the pytest invocation. Task Pool: use with outcome. */
|
|
72
72
|
executionStatus: backendTestExecutionStatusSchema,
|
|
73
|
+
pytestSpawned: z.literal(true),
|
|
73
74
|
pytestExitCode: z.number().int().min(0).max(255),
|
|
74
75
|
collectionStatus: backendTestCollectionStatusSchema,
|
|
75
76
|
/** Total tests counted from JUnit (or 0 when report unavailable). */
|
|
@@ -659,6 +660,7 @@ export function deriveBackendTestResult(input) {
|
|
|
659
660
|
const result = {
|
|
660
661
|
schemaVersion: 1,
|
|
661
662
|
executionStatus,
|
|
663
|
+
pytestSpawned: true,
|
|
662
664
|
pytestExitCode: input.pytestExitCode,
|
|
663
665
|
collectionStatus: "unknown",
|
|
664
666
|
tests: 0,
|
|
@@ -733,6 +735,7 @@ export function deriveBackendTestResult(input) {
|
|
|
733
735
|
const result = {
|
|
734
736
|
schemaVersion: 1,
|
|
735
737
|
executionStatus,
|
|
738
|
+
pytestSpawned: true,
|
|
736
739
|
pytestExitCode: exit,
|
|
737
740
|
collectionStatus,
|
|
738
741
|
tests: parsed.tests,
|
|
@@ -936,6 +939,7 @@ export async function materializeBackendTestResultFromPytestHtml(input) {
|
|
|
936
939
|
const result = backendTestResultContractSchema.parse({
|
|
937
940
|
schemaVersion: 1,
|
|
938
941
|
executionStatus,
|
|
942
|
+
pytestSpawned: true,
|
|
939
943
|
pytestExitCode: exit,
|
|
940
944
|
collectionStatus,
|
|
941
945
|
tests: parsed.tests,
|