feat: add generated scene skill platform hardening
This commit is contained in:
@@ -489,6 +489,9 @@ function collectDeterministicSignals(files, indexHtml) {
|
||||
const secondaryRequestMethods = new Set();
|
||||
const pageTitleKeywords = new Set();
|
||||
const staticParams = {};
|
||||
const g1eJoinKeys = new Set();
|
||||
const g1eAggregateRules = new Set();
|
||||
const g1eOutputColumns = [];
|
||||
|
||||
for (const file of files) {
|
||||
const content = file.content;
|
||||
@@ -500,6 +503,25 @@ function collectDeterministicSignals(files, indexHtml) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const match of content.matchAll(/\b(?:data|item|row)\.(wkOrderNo|countyCodeName|orgNo|orgCode)\b/g)) {
|
||||
g1eJoinKeys.add(match[1]);
|
||||
}
|
||||
|
||||
for (const match of content.matchAll(/\b(com|batchCom)\s*\([^)]*?["']([A-Za-z_][A-Za-z0-9_]*)["']\s*,\s*["']([A-Za-z_][A-Za-z0-9_]*)["']/g)) {
|
||||
g1eAggregateRules.add(`${match[1]}:${match[2]},${match[3]}`);
|
||||
}
|
||||
|
||||
const titleListBlock = content.match(/const\s+titleList\s*=\s*\[([\s\S]*?)\]\s*[;\n]/i);
|
||||
if (titleListBlock) {
|
||||
for (const triple of titleListBlock[1].matchAll(/\[\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*,\s*["']([^"'`]+)["']\s*,\s*["']([^"'`]*)["']\s*\]/g)) {
|
||||
const field = triple[1];
|
||||
const top = String(triple[2] || "").trim();
|
||||
const leaf = String(triple[3] || "").trim();
|
||||
if (!field || g1eOutputColumns.some((item) => item[0] === field)) continue;
|
||||
g1eOutputColumns.push([field, leaf ? `${top}-${leaf}` : top]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const match of content.matchAll(/\b(type|method)\s*:\s*['"`](GET|POST|PUT|DELETE|PATCH)['"`]/gi)) {
|
||||
methods.set(match[2].toUpperCase(), true);
|
||||
}
|
||||
@@ -557,8 +579,10 @@ function collectDeterministicSignals(files, indexHtml) {
|
||||
}
|
||||
|
||||
const bootstrapCandidates = collectBootstrapCandidates(files, indexHtml, Array.from(urls.values()));
|
||||
const allEndpoints = Array.from(urls.values());
|
||||
const g1eRequestRoles = deriveG1eRequestRoles(allEndpoints);
|
||||
return {
|
||||
endpoints: Array.from(urls.values()),
|
||||
endpoints: allEndpoints,
|
||||
methods: Array.from(methods.keys()),
|
||||
responsePaths: Array.from(responsePaths),
|
||||
branchFields: Array.from(branchFields),
|
||||
@@ -571,9 +595,39 @@ function collectDeterministicSignals(files, indexHtml) {
|
||||
pageTitleKeywords: Array.from(pageTitleKeywords).slice(0, 10),
|
||||
staticParams,
|
||||
bootstrapCandidates,
|
||||
g1eMainRequest: g1eRequestRoles.mainRequest,
|
||||
g1eEnrichmentRequests: g1eRequestRoles.enrichmentRequests,
|
||||
g1eJoinKeys: Array.from(g1eJoinKeys),
|
||||
g1eOutputColumns: g1eOutputColumns.slice(0, 24),
|
||||
g1eAggregateRules: Array.from(g1eAggregateRules).slice(0, 12),
|
||||
};
|
||||
}
|
||||
|
||||
function deriveG1eRequestRoles(endpoints) {
|
||||
const mainRequest = endpoints.find((endpoint) =>
|
||||
/getwkorderall|all|list/i.test(endpoint?.name || "") && !containsRowBinding(endpoint?.requestTemplate)
|
||||
) || null;
|
||||
const enrichmentRequests = (endpoints || [])
|
||||
.filter((endpoint) => {
|
||||
if (mainRequest && endpoint.url === mainRequest.url) return false;
|
||||
return (
|
||||
containsRowBinding(endpoint?.requestTemplate) ||
|
||||
/query|info|acpt/i.test(endpoint?.name || "") ||
|
||||
/query|info|acpt/i.test(endpoint?.url || "")
|
||||
);
|
||||
})
|
||||
.slice(0, 6);
|
||||
return { mainRequest, enrichmentRequests };
|
||||
}
|
||||
|
||||
function containsRowBinding(value) {
|
||||
if (!value) return false;
|
||||
if (typeof value === "string") return value.includes("${row.");
|
||||
if (Array.isArray(value)) return value.some(containsRowBinding);
|
||||
if (typeof value === "object") return Object.values(value).some(containsRowBinding);
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractEndpoints(content) {
|
||||
const endpoints = [];
|
||||
const seen = new Set();
|
||||
@@ -753,6 +807,9 @@ function buildDeterministicSceneIr(context, sourceDir) {
|
||||
const normalizeRules = buildNormalizeRules(signals);
|
||||
const params = buildParams(signals, workflowArchetype);
|
||||
const confidence = scoreConfidence(signals, workflowArchetype);
|
||||
const mainRequest = buildG1eMainRequest(signals);
|
||||
const enrichmentRequests = buildG1eEnrichmentRequests(signals);
|
||||
const mergePlan = buildG1eMergePlan(signals);
|
||||
const readiness = buildReadiness({
|
||||
sceneIdDiagnostics,
|
||||
workflowArchetype,
|
||||
@@ -760,6 +817,9 @@ function buildDeterministicSceneIr(context, sourceDir) {
|
||||
apiEndpoints: signals.endpoints || [],
|
||||
params,
|
||||
workflowSteps,
|
||||
mainRequest,
|
||||
enrichmentRequests,
|
||||
mergePlan,
|
||||
confidence,
|
||||
});
|
||||
|
||||
@@ -776,8 +836,11 @@ function buildDeterministicSceneIr(context, sourceDir) {
|
||||
modeSwitchField: signals.branchFields?.find((field) => /mode|period/i.test(field)) || null,
|
||||
workflowSteps,
|
||||
workflowEvidence,
|
||||
requestTemplate: {},
|
||||
responsePath,
|
||||
mainRequest,
|
||||
enrichmentRequests,
|
||||
mergePlan,
|
||||
requestTemplate: mainRequest?.requestTemplate || {},
|
||||
responsePath: mainRequest?.responsePath || responsePath,
|
||||
normalizeRules,
|
||||
artifactContract: {
|
||||
type: "report-artifact",
|
||||
@@ -796,13 +859,54 @@ function buildDeterministicSceneIr(context, sourceDir) {
|
||||
readiness,
|
||||
apiEndpoints: signals.endpoints || [],
|
||||
staticParams: signals.staticParams || {},
|
||||
columnDefs: [],
|
||||
columnDefs: mergePlan?.outputColumns || [],
|
||||
confidence,
|
||||
uncertainties: buildUncertainties(signals, workflowArchetype),
|
||||
deterministicSignals: signals,
|
||||
};
|
||||
}
|
||||
|
||||
function buildG1eMainRequest(signals) {
|
||||
const endpoint = signals.g1eMainRequest || null;
|
||||
if (!endpoint) return null;
|
||||
return {
|
||||
apiEndpoint: endpoint,
|
||||
requestTemplate: endpoint.requestTemplate || {},
|
||||
responsePath: signals.responsePaths?.[0] || "",
|
||||
columnDefs: signals.g1eOutputColumns || [],
|
||||
};
|
||||
}
|
||||
|
||||
function buildG1eEnrichmentRequests(signals) {
|
||||
return (signals.g1eEnrichmentRequests || []).map((endpoint) => ({
|
||||
name: endpoint.name,
|
||||
apiEndpoint: endpoint,
|
||||
paramBindings: endpoint.requestTemplate || {},
|
||||
responsePath: signals.responsePaths?.[0] || "",
|
||||
consumedFields: (signals.g1eAggregateRules || [])
|
||||
.flatMap((rule) => String(rule).split(":")[1]?.split(",") || [])
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
}));
|
||||
}
|
||||
|
||||
function buildG1eMergePlan(signals) {
|
||||
if (!(signals.g1eJoinKeys || []).length && !(signals.g1eOutputColumns || []).length && !(signals.g1eAggregateRules || []).length) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
joinKeys: signals.g1eJoinKeys || [],
|
||||
fieldMappings: (signals.g1eOutputColumns || []).map(([field]) => ({
|
||||
outputField: field,
|
||||
sourceType: (signals.g1eJoinKeys || []).includes(field) ? "main" : "aggregate",
|
||||
sourceField: field,
|
||||
requestName: null,
|
||||
})),
|
||||
aggregateRules: signals.g1eAggregateRules || [],
|
||||
outputColumns: signals.g1eOutputColumns || [],
|
||||
};
|
||||
}
|
||||
|
||||
function deriveSceneIdDiagnostics({ sourceDir, sceneName, signals }) {
|
||||
const baseName = path.basename(sourceDir || "");
|
||||
const candidates = [];
|
||||
@@ -951,6 +1055,12 @@ function classifyWorkflowArchetype(signals) {
|
||||
(signals.secondaryRequestMethods || []).length > 0 || businessEndpoints.length >= 2;
|
||||
const hasPostProcess =
|
||||
(signals.filterExpressions || []).length > 0 || (signals.exportMethods || []).length > 0;
|
||||
if (signals.g1eMainRequest && (signals.g1eEnrichmentRequests || []).length > 0) {
|
||||
const hasG1eMergeSignal = (signals.g1eJoinKeys || []).length > 0 || (signals.g1eOutputColumns || []).length > 0;
|
||||
if (hasG1eMergeSignal) {
|
||||
return "single_request_enrichment";
|
||||
}
|
||||
}
|
||||
if (hasPagination && hasSecondaryRequest && hasPostProcess) {
|
||||
return "paginated_enrichment";
|
||||
}
|
||||
@@ -970,61 +1080,226 @@ function classifyWorkflowArchetype(signals) {
|
||||
return "single_request_table";
|
||||
}
|
||||
|
||||
function createEvidenceItem({
|
||||
kind = "deterministic",
|
||||
evidenceType = "signal",
|
||||
layer = "business",
|
||||
subject = "",
|
||||
summary = "",
|
||||
source = "runner",
|
||||
confidence = 0.7,
|
||||
payload = null,
|
||||
}) {
|
||||
return {
|
||||
kind,
|
||||
evidenceType,
|
||||
layer,
|
||||
subject: subject || evidenceType,
|
||||
summary,
|
||||
source,
|
||||
confidence,
|
||||
payload: payload && typeof payload === "object" ? payload : null,
|
||||
};
|
||||
}
|
||||
|
||||
function buildEvidence(signals, workflowArchetype) {
|
||||
const evidence = [];
|
||||
const businessEndpoints = getBusinessEndpoints(signals);
|
||||
const localDependencies = (signals.endpoints || []).filter((endpoint) => endpoint.role === "local_helper");
|
||||
const exportServices = (signals.endpoints || []).filter((endpoint) => endpoint.role === "export_service");
|
||||
const bootstrapCandidate = (signals.bootstrapCandidates || []).find((candidate) => candidate.validForBootstrap);
|
||||
|
||||
if (businessEndpoints.length > 0) {
|
||||
evidence.push({
|
||||
kind: "deterministic",
|
||||
summary: `Detected ${businessEndpoints.length} business API endpoint(s).`,
|
||||
source: "runner",
|
||||
confidence: 0.9,
|
||||
});
|
||||
if (bootstrapCandidate) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "bootstrap_candidate",
|
||||
layer: "business",
|
||||
subject: bootstrapCandidate.expectedDomain || bootstrapCandidate.targetUrl || "bootstrap",
|
||||
summary: `Bootstrap candidate resolved to ${bootstrapCandidate.expectedDomain || bootstrapCandidate.targetUrl}.`,
|
||||
confidence: 0.92,
|
||||
payload: bootstrapCandidate,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.branchFields || []).length > 0) {
|
||||
evidence.push({
|
||||
kind: "deterministic",
|
||||
summary: `Branch fields: ${signals.branchFields.join(", ")}`,
|
||||
source: "runner",
|
||||
confidence: 0.86,
|
||||
});
|
||||
for (const endpoint of businessEndpoints) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "endpoint_candidate",
|
||||
layer: "business",
|
||||
subject: endpoint.name || endpoint.url,
|
||||
summary: `Business endpoint ${endpoint.name || endpoint.url} detected.`,
|
||||
confidence: 0.9,
|
||||
payload: endpoint,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.paginationVars || []).length > 0) {
|
||||
evidence.push({
|
||||
kind: "deterministic",
|
||||
summary: `Pagination vars: ${signals.paginationVars.join(", ")}`,
|
||||
source: "runner",
|
||||
confidence: 0.84,
|
||||
});
|
||||
if ((signals.branchFields || []).length > 0 || (signals.modeValues || []).length > 0) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "mode_candidate",
|
||||
layer: "business",
|
||||
subject: workflowArchetype,
|
||||
summary: `Mode signals: fields=${(signals.branchFields || []).join(", ") || "none"} values=${(signals.modeValues || []).join(", ") || "none"}`,
|
||||
confidence: 0.86,
|
||||
payload: {
|
||||
branchFields: signals.branchFields || [],
|
||||
modeValues: signals.modeValues || [],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.secondaryRequestMethods || []).length > 0) {
|
||||
evidence.push({
|
||||
kind: "deterministic",
|
||||
summary: `Secondary request methods: ${signals.secondaryRequestMethods.join(", ")}`,
|
||||
source: "runner",
|
||||
confidence: 0.82,
|
||||
});
|
||||
const requestSignals = uniqueStringValues([
|
||||
...(signals.entryMethods || []),
|
||||
...Object.keys(signals.staticParams || {}),
|
||||
]);
|
||||
if (requestSignals.length > 0) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "request_template_candidate",
|
||||
layer: "business",
|
||||
subject: requestSignals[0],
|
||||
summary: `Request-side signals detected: ${requestSignals.join(", ")}`,
|
||||
confidence: 0.8,
|
||||
payload: {
|
||||
entryMethods: signals.entryMethods || [],
|
||||
staticParams: signals.staticParams || {},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.exportMethods || []).length > 0) {
|
||||
evidence.push({
|
||||
kind: "deterministic",
|
||||
summary: `Export methods: ${signals.exportMethods.join(", ")}`,
|
||||
source: "runner",
|
||||
confidence: 0.78,
|
||||
});
|
||||
if (signals.g1eMainRequest) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "main_request_candidate",
|
||||
layer: "business",
|
||||
subject: signals.g1eMainRequest.name,
|
||||
summary: `G1-E main request candidate resolved to ${signals.g1eMainRequest.name}.`,
|
||||
confidence: 0.86,
|
||||
payload: signals.g1eMainRequest,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
evidence.push({
|
||||
kind: "classification",
|
||||
summary: `Workflow archetype classified as ${workflowArchetype}.`,
|
||||
source: "runner",
|
||||
confidence: 0.72,
|
||||
});
|
||||
for (const endpoint of signals.g1eEnrichmentRequests || []) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "enrichment_request_candidate",
|
||||
layer: "business",
|
||||
subject: endpoint.name,
|
||||
summary: `G1-E enrichment request candidate resolved to ${endpoint.name}.`,
|
||||
confidence: 0.84,
|
||||
payload: endpoint,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.g1eJoinKeys || []).length || (signals.g1eOutputColumns || []).length || (signals.g1eAggregateRules || []).length) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "merge_plan_candidate",
|
||||
layer: "workflow",
|
||||
subject: "g1e_merge_plan",
|
||||
summary: "G1-E merge plan candidate detected.",
|
||||
confidence: 0.83,
|
||||
payload: {
|
||||
joinKeys: signals.g1eJoinKeys || [],
|
||||
outputColumns: signals.g1eOutputColumns || [],
|
||||
aggregateRules: signals.g1eAggregateRules || [],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.responsePaths || []).length > 0) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "response_path_candidate",
|
||||
layer: "business",
|
||||
subject: signals.responsePaths[0],
|
||||
summary: `Response paths detected: ${signals.responsePaths.join(", ")}`,
|
||||
confidence: 0.84,
|
||||
payload: { responsePaths: signals.responsePaths || [] },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.filterExpressions || []).length > 0 || (signals.exportMethods || []).length > 0) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "normalize_rules_candidate",
|
||||
layer: "business",
|
||||
subject: "normalize_rules",
|
||||
summary: `Post-process signals detected: filters=${(signals.filterExpressions || []).length}, exports=${(signals.exportMethods || []).length}`,
|
||||
confidence: 0.76,
|
||||
payload: {
|
||||
filterExpressions: signals.filterExpressions || [],
|
||||
exportMethods: signals.exportMethods || [],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.paginationVars || []).length > 0 || (signals.secondaryRequestMethods || []).length > 0) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "workflow_candidate",
|
||||
layer: "workflow",
|
||||
subject: workflowArchetype,
|
||||
summary: `Workflow signals: pagination=${(signals.paginationVars || []).join(", ") || "none"} secondary=${(signals.secondaryRequestMethods || []).join(", ") || "none"}`,
|
||||
confidence: 0.82,
|
||||
payload: {
|
||||
paginationVars: signals.paginationVars || [],
|
||||
secondaryRequestMethods: signals.secondaryRequestMethods || [],
|
||||
exportMethods: signals.exportMethods || [],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const endpoint of [...localDependencies, ...exportServices]) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "localhost_dependency_candidate",
|
||||
layer: "host_runtime",
|
||||
subject: endpoint.name || endpoint.url,
|
||||
summary: `Host runtime dependency detected: ${endpoint.url}`,
|
||||
confidence: 0.88,
|
||||
payload: endpoint,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((signals.exportMethods || []).length > 0 || exportServices.length > 0) {
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
evidenceType: "export_candidate",
|
||||
layer: "output",
|
||||
subject: signals.exportMethods?.[0] || exportServices[0]?.name || "export",
|
||||
summary: `Export signals detected: ${(signals.exportMethods || []).join(", ") || exportServices.map((item) => item.url).join(", ")}`,
|
||||
confidence: 0.78,
|
||||
payload: {
|
||||
exportMethods: signals.exportMethods || [],
|
||||
exportEndpoints: exportServices,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
evidence.push(
|
||||
createEvidenceItem({
|
||||
kind: "classification",
|
||||
evidenceType: "workflow_candidate",
|
||||
layer: "classification",
|
||||
subject: workflowArchetype,
|
||||
summary: `Workflow archetype classified as ${workflowArchetype}.`,
|
||||
confidence: 0.72,
|
||||
payload: { workflowArchetype },
|
||||
})
|
||||
);
|
||||
|
||||
return evidence;
|
||||
}
|
||||
@@ -1086,6 +1361,27 @@ function buildWorkflowSteps(signals, workflowArchetype) {
|
||||
const primaryEndpoint = businessEndpoints[0]?.name || null;
|
||||
const secondaryEndpoint = businessEndpoints[1]?.name || null;
|
||||
|
||||
if (workflowArchetype === "single_request_enrichment") {
|
||||
steps.push({
|
||||
type: "request",
|
||||
entry: signals.entryMethods?.[0] || null,
|
||||
endpoint: signals.g1eMainRequest?.name || primaryEndpoint,
|
||||
description: "Query the main list for G1-E workflow.",
|
||||
});
|
||||
for (const endpoint of signals.g1eEnrichmentRequests || []) {
|
||||
steps.push({
|
||||
type: "enrichment_request",
|
||||
endpoint: endpoint.name,
|
||||
description: "Fetch lightweight enrichment payload.",
|
||||
});
|
||||
}
|
||||
steps.push({
|
||||
type: "transform",
|
||||
description: "Merge enrichment payloads into aggregate output.",
|
||||
});
|
||||
return steps;
|
||||
}
|
||||
|
||||
if (workflowArchetype === "multi_mode_request") {
|
||||
steps.push({
|
||||
type: "request",
|
||||
@@ -1238,6 +1534,9 @@ function buildUncertainties(signals, workflowArchetype) {
|
||||
if (workflowArchetype === "paginated_enrichment" && !(signals.filterExpressions || []).length && !(signals.exportMethods || []).length) {
|
||||
issues.push("Paginated enrichment is missing post-process evidence.");
|
||||
}
|
||||
if (workflowArchetype === "single_request_enrichment" && !(signals.g1eOutputColumns || []).length) {
|
||||
issues.push("G1-E output columns are still weakly inferred.");
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
@@ -1255,10 +1554,17 @@ function scoreConfidence(signals, workflowArchetype) {
|
||||
) {
|
||||
score += 0.14;
|
||||
}
|
||||
if (
|
||||
workflowArchetype === "single_request_enrichment" &&
|
||||
signals.g1eMainRequest &&
|
||||
(signals.g1eEnrichmentRequests || []).length > 0
|
||||
) {
|
||||
score += 0.14;
|
||||
}
|
||||
return Math.min(0.95, Number(score.toFixed(2)));
|
||||
}
|
||||
|
||||
function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiEndpoints, params, workflowSteps, confidence }) {
|
||||
function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiEndpoints, params, workflowSteps, mainRequest, enrichmentRequests, mergePlan, confidence }) {
|
||||
const risks = [];
|
||||
const missingPieces = [];
|
||||
const notes = [];
|
||||
@@ -1288,6 +1594,37 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
|
||||
risks.push("Workflow steps are incomplete.");
|
||||
}
|
||||
|
||||
const requestContract = evaluateRequestContract({
|
||||
workflowArchetype,
|
||||
apiEndpoints,
|
||||
params,
|
||||
workflowSteps,
|
||||
});
|
||||
if (!requestContract.passed) {
|
||||
missingPieces.push(requestContract.reason || "request_contract");
|
||||
risks.push(requestContract.message);
|
||||
}
|
||||
|
||||
const responseContract = evaluateResponseContract({
|
||||
workflowArchetype,
|
||||
workflowSteps,
|
||||
apiEndpoints,
|
||||
});
|
||||
if (!responseContract.passed) {
|
||||
missingPieces.push(responseContract.reason || "response_contract");
|
||||
risks.push(responseContract.message);
|
||||
}
|
||||
|
||||
const workflowContract = evaluateWorkflowContract({
|
||||
workflowArchetype,
|
||||
workflowSteps,
|
||||
businessApiEndpoints,
|
||||
});
|
||||
if (!workflowContract.passed) {
|
||||
missingPieces.push(workflowContract.reason || "workflow_contract");
|
||||
risks.push(workflowContract.message);
|
||||
}
|
||||
|
||||
if (workflowArchetype === "paginated_enrichment") {
|
||||
const hasPaginate = workflowSteps.some((step) => step.type === "paginate");
|
||||
const hasSecondary = workflowSteps.some((step) => step.type === "secondary_request");
|
||||
@@ -1306,6 +1643,21 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
|
||||
}
|
||||
}
|
||||
|
||||
if (workflowArchetype === "single_request_enrichment") {
|
||||
if (!mainRequest) {
|
||||
missingPieces.push("main_request");
|
||||
risks.push("G1-E workflow is missing a resolved main request.");
|
||||
}
|
||||
if (!(enrichmentRequests || []).length) {
|
||||
missingPieces.push("enrichment_requests");
|
||||
risks.push("G1-E workflow is missing enrichment request contracts.");
|
||||
}
|
||||
if (!mergePlan) {
|
||||
missingPieces.push("merge_plan");
|
||||
risks.push("G1-E workflow is missing merge plan evidence.");
|
||||
}
|
||||
}
|
||||
|
||||
if (workflowArchetype === "multi_mode_request" && !params.some((param) => param.name === "period")) {
|
||||
risks.push("Mode-aware workflow is missing a resolved period parameter.");
|
||||
}
|
||||
@@ -1340,15 +1692,25 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
|
||||
passed: Boolean(bootstrap.targetUrl || bootstrap.expectedDomain),
|
||||
reason: bootstrap.targetUrl || bootstrap.expectedDomain ? null : "bootstrap_target",
|
||||
},
|
||||
{
|
||||
name: "request_contract_complete",
|
||||
passed: requestContract.passed,
|
||||
reason: requestContract.passed ? null : requestContract.reason,
|
||||
},
|
||||
{
|
||||
name: "response_contract_complete",
|
||||
passed: responseContract.passed,
|
||||
reason: responseContract.passed ? null : responseContract.reason,
|
||||
},
|
||||
{
|
||||
name: "workflow_contract_complete",
|
||||
passed: workflowContract.passed,
|
||||
reason: workflowContract.passed ? null : workflowContract.reason,
|
||||
},
|
||||
{
|
||||
name: "workflow_complete_for_archetype",
|
||||
passed: !missingPieces.some((item) =>
|
||||
["workflow_steps", "paginate_step", "secondary_request", "post_process"].includes(item)
|
||||
),
|
||||
reason:
|
||||
missingPieces.find((item) =>
|
||||
["workflow_steps", "paginate_step", "secondary_request", "post_process"].includes(item)
|
||||
) || null,
|
||||
passed: workflowContract.passed,
|
||||
reason: workflowContract.passed ? null : workflowContract.reason,
|
||||
},
|
||||
{
|
||||
name: "runtime_contract_compatible",
|
||||
@@ -1361,6 +1723,26 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
|
||||
? null
|
||||
: "runtime_contract_incompatible",
|
||||
},
|
||||
{
|
||||
name: "main_request_resolved",
|
||||
passed: workflowArchetype !== "single_request_enrichment" || Boolean(mainRequest),
|
||||
reason: workflowArchetype !== "single_request_enrichment" || mainRequest ? null : "main_request",
|
||||
},
|
||||
{
|
||||
name: "enrichment_requests_resolved",
|
||||
passed: workflowArchetype !== "single_request_enrichment" || Boolean((enrichmentRequests || []).length),
|
||||
reason: workflowArchetype !== "single_request_enrichment" || (enrichmentRequests || []).length ? null : "enrichment_requests",
|
||||
},
|
||||
{
|
||||
name: "merge_plan_resolved",
|
||||
passed: workflowArchetype !== "single_request_enrichment" || Boolean(mergePlan),
|
||||
reason: workflowArchetype !== "single_request_enrichment" || mergePlan ? null : "merge_plan",
|
||||
},
|
||||
{
|
||||
name: "g1e_scope_compatible",
|
||||
passed: workflowArchetype !== "single_request_enrichment" || Boolean(mainRequest && (enrichmentRequests || []).length && mergePlan),
|
||||
reason: workflowArchetype !== "single_request_enrichment" || (mainRequest && (enrichmentRequests || []).length && mergePlan) ? null : "g1e_scope",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
@@ -1373,6 +1755,175 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
|
||||
};
|
||||
}
|
||||
|
||||
function evaluateRequestContract({ workflowArchetype, apiEndpoints, params, workflowSteps }) {
|
||||
const endpointCount = (apiEndpoints || []).filter((endpoint) =>
|
||||
["business_api", "gateway_api", "business_entry"].includes(endpoint.role)
|
||||
).length;
|
||||
const hasRequestStep = (workflowSteps || []).some((step) => ["request", "paginate", "secondary_request"].includes(step.type));
|
||||
const hasRuntimeInputs = (params || []).length > 0;
|
||||
|
||||
if (workflowArchetype === "multi_mode_request") {
|
||||
const periodParamReady = (params || []).some((param) => param.name === "period");
|
||||
return periodParamReady && endpointCount > 0
|
||||
? { passed: true }
|
||||
: {
|
||||
passed: false,
|
||||
reason: endpointCount > 0 ? "request_mode_param" : "request_endpoint",
|
||||
message: endpointCount > 0
|
||||
? "Multi-mode request is missing a resolved mode/period contract."
|
||||
: "Request contract is missing a business endpoint.",
|
||||
};
|
||||
}
|
||||
|
||||
if (workflowArchetype === "single_request_enrichment") {
|
||||
return endpointCount >= 2 && hasRequestStep
|
||||
? { passed: true }
|
||||
: {
|
||||
passed: false,
|
||||
reason: endpointCount >= 2 ? "main_request" : "request_endpoint",
|
||||
message: endpointCount >= 2
|
||||
? "G1-E workflow is missing a resolved main request."
|
||||
: "G1-E workflow requires both main and enrichment business endpoints.",
|
||||
};
|
||||
}
|
||||
|
||||
if (workflowArchetype === "paginated_enrichment") {
|
||||
return endpointCount >= 2 && hasRequestStep
|
||||
? { passed: true }
|
||||
: {
|
||||
passed: false,
|
||||
reason: endpointCount >= 2 ? "request_workflow" : "request_endpoint",
|
||||
message: endpointCount >= 2
|
||||
? "Paginated enrichment is missing request-side workflow signals."
|
||||
: "Paginated enrichment requires both primary and secondary request endpoints.",
|
||||
};
|
||||
}
|
||||
|
||||
if (workflowArchetype === "page_state_eval") {
|
||||
return hasRequestStep || endpointCount > 0
|
||||
? { passed: true }
|
||||
: {
|
||||
passed: false,
|
||||
reason: "request_workflow",
|
||||
message: "Page-state workflow is missing request or state-evaluation entry signals.",
|
||||
};
|
||||
}
|
||||
|
||||
return endpointCount > 0 || hasRuntimeInputs
|
||||
? { passed: true }
|
||||
: {
|
||||
passed: false,
|
||||
reason: "request_endpoint",
|
||||
message: "Request contract is missing a business endpoint or runtime input.",
|
||||
};
|
||||
}
|
||||
|
||||
function evaluateResponseContract({ workflowArchetype, workflowSteps, apiEndpoints }) {
|
||||
const endpointCount = (apiEndpoints || []).filter((endpoint) =>
|
||||
["business_api", "gateway_api", "business_entry"].includes(endpoint.role)
|
||||
).length;
|
||||
const hasTransform = (workflowSteps || []).some((step) => ["transform", "filter", "export"].includes(step.type));
|
||||
|
||||
if (workflowArchetype === "single_request_enrichment") {
|
||||
return endpointCount >= 2 && hasTransform
|
||||
? { passed: true }
|
||||
: {
|
||||
passed: false,
|
||||
reason: endpointCount >= 2 ? "merge_plan" : "response_path",
|
||||
message: endpointCount >= 2
|
||||
? "G1-E workflow is missing merge/transform evidence."
|
||||
: "G1-E workflow lacks enough response-side endpoint evidence.",
|
||||
};
|
||||
}
|
||||
|
||||
if (workflowArchetype === "paginated_enrichment") {
|
||||
return endpointCount >= 2
|
||||
? { passed: true }
|
||||
: {
|
||||
passed: false,
|
||||
reason: "response_path",
|
||||
message: "Paginated enrichment lacks enough response-side endpoints to confirm extraction.",
|
||||
};
|
||||
}
|
||||
|
||||
if (workflowArchetype === "page_state_eval") {
|
||||
return { passed: true };
|
||||
}
|
||||
|
||||
return endpointCount > 0 || hasTransform
|
||||
? { passed: true }
|
||||
: {
|
||||
passed: false,
|
||||
reason: "response_path",
|
||||
message: "Response contract is missing extraction or transform evidence.",
|
||||
};
|
||||
}
|
||||
|
||||
function evaluateWorkflowContract({ workflowArchetype, workflowSteps, businessApiEndpoints }) {
|
||||
const hasAnyWorkflow = (workflowSteps || []).length > 0;
|
||||
if (!hasAnyWorkflow) {
|
||||
return {
|
||||
passed: false,
|
||||
reason: "workflow_steps",
|
||||
message: "Workflow contract is missing executable steps.",
|
||||
};
|
||||
}
|
||||
|
||||
if (workflowArchetype === "paginated_enrichment") {
|
||||
const hasPaginate = workflowSteps.some((step) => step.type === "paginate");
|
||||
const hasSecondary = workflowSteps.some((step) => step.type === "secondary_request");
|
||||
const hasPostProcess = workflowSteps.some((step) => ["filter", "transform", "export"].includes(step.type));
|
||||
if (!hasPaginate) {
|
||||
return {
|
||||
passed: false,
|
||||
reason: "paginate_step",
|
||||
message: "Paginated enrichment lacks pagination evidence.",
|
||||
};
|
||||
}
|
||||
if (!hasSecondary || (businessApiEndpoints || []).length < 2) {
|
||||
return {
|
||||
passed: false,
|
||||
reason: "secondary_request",
|
||||
message: "Paginated enrichment lacks a strong secondary request signal.",
|
||||
};
|
||||
}
|
||||
if (!hasPostProcess) {
|
||||
return {
|
||||
passed: false,
|
||||
reason: "post_process",
|
||||
message: "Paginated enrichment lacks filter/transform/export evidence.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (workflowArchetype === "single_request_enrichment") {
|
||||
const hasRequest = workflowSteps.some((step) => step.type === "request");
|
||||
const hasEnrichment = workflowSteps.some((step) => step.type === "enrichment_request");
|
||||
const hasTransform = workflowSteps.some((step) => step.type === "transform");
|
||||
if (!hasRequest || !hasEnrichment || !hasTransform) {
|
||||
return {
|
||||
passed: false,
|
||||
reason: !hasRequest ? "workflow_request" : !hasEnrichment ? "enrichment_requests" : "workflow_transform",
|
||||
message: "G1-E workflow lacks a complete main/enrichment/transform chain.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (workflowArchetype === "multi_mode_request") {
|
||||
const hasRequest = workflowSteps.some((step) => step.type === "request");
|
||||
const hasTransform = workflowSteps.some((step) => step.type === "transform");
|
||||
if (!hasRequest || !hasTransform) {
|
||||
return {
|
||||
passed: false,
|
||||
reason: !hasRequest ? "workflow_request" : "workflow_transform",
|
||||
message: "Multi-mode request lacks a complete request/transform workflow.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { passed: true };
|
||||
}
|
||||
|
||||
function slugifyAscii(value) {
|
||||
return String(value || "")
|
||||
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
||||
|
||||
Reference in New Issue
Block a user