@saptools/service-flow 0.1.31 → 0.1.32
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 +7 -0
- package/README.md +11 -0
- package/dist/{chunk-AM2JZFQ6.js → chunk-VT5FVMOA.js} +115 -98
- package/dist/chunk-VT5FVMOA.js.map +1 -0
- package/dist/cli.js +8 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-AM2JZFQ6.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.32
|
|
4
|
+
|
|
5
|
+
- Hardened operation-first trace starts to fail closed on ambiguous, rejected, or non-executable implementation evidence.
|
|
6
|
+
- Made decorator normalization explicit and conservative for unsupported expressions.
|
|
7
|
+
- Populated queryable external HTTP target metadata with sanitized labels and kept CAP candidates distinct from HTTP endpoints.
|
|
8
|
+
- Removed accidental fresh-schema external-target columns from symbols and documented migration/re-index expectations.
|
|
9
|
+
|
|
3
10
|
## 0.1.31
|
|
4
11
|
|
|
5
12
|
- Resolve operation and path trace selectors from indexed CDS operations and persisted implementation edges before conservative handler fallback, including generated `Action<Name>` and `Func<Name>` decorator constants whose method names differ from public operation names.
|
package/README.md
CHANGED
|
@@ -490,3 +490,14 @@ The `graph` command accepts repeatable `--var key=value` options, matching `trac
|
|
|
490
490
|
### 0.1.17 parser ownership policy
|
|
491
491
|
|
|
492
492
|
Outbound call extraction is AST-based and ignores comments, block comments, and string literals. CAP/service `.on(...)` registrations are indexed only when the receiver has CAP/service evidence, and top-level registrations receive `module:<relative-file>#event:<event-name>:<line>` synthetic owners. Generic event emitters such as desktop or window events are ignored by default rather than guessed as CAP async edges. Unsupported source shapes are surfaced through diagnostics and strict doctor ownerless categories instead of guessed graph edges.
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
## 0.1.32 hardening notes
|
|
496
|
+
|
|
497
|
+
Operation trace starts resolve indexed CDS operations before handler fallback. Ambiguous operation matches, ambiguous/rejected implementation edges, and resolved edges without an executable handler scope now fail closed with structured `resolutionStage` and `resolutionStatus` diagnostics and no speculative traversal. Handler fallback is only considered when no indexed CDS operation matches and the fallback is unique under the existing registration and ownership policy.
|
|
498
|
+
|
|
499
|
+
Decorator signals are interpreted as resolved, absent, unsupported, or malformed. Only resolved string/template values, generated `Action<Name>.name`/`Func<Name>.name` constants, qualified generated constants ending in `.name`, and narrow safe `String(identifier)` wrappers can contradict method-name fallback. Unsupported expressions are preserved as evidence but are not treated as proof that a handler targets another operation.
|
|
500
|
+
|
|
501
|
+
External HTTP facts persist queryable target kind, stable id, safe label, and dynamic flag on `outbound_calls`; graph evidence uses the same sanitized model. URL user info, fragments, query values, headers, cookies, credentials, tokens, API keys, and request/response bodies are not persisted. Legacy workspaces may relink from old evidence when possible, but full semantic enrichment requires re-index plus relink when old parser facts did not contain safe target metadata.
|
|
502
|
+
|
|
503
|
+
Graph taxonomy now keeps CAP operation candidates separate from external HTTP targets. Dynamic, ambiguous, and unresolved CAP calls use operation-candidate semantics, external destinations/endpoints are produced only by `external_http` calls, and terminal local transport-client methods use transport-method semantics rather than external HTTP edges. `doctor --strict` reports external target quality and taxonomy consistency.
|
|
@@ -720,6 +720,52 @@ function topLevelQueryIndex(text) {
|
|
|
720
720
|
import fs6 from "fs/promises";
|
|
721
721
|
import path7 from "path";
|
|
722
722
|
import ts4 from "typescript";
|
|
723
|
+
|
|
724
|
+
// src/linker/external-http-target.ts
|
|
725
|
+
import { createHash } from "crypto";
|
|
726
|
+
var sensitiveKeys = /* @__PURE__ */ new Set(["token", "access_token", "id_token", "api_key", "apikey", "key", "password", "passwd", "pwd", "secret", "client_secret", "authorization", "cookie", "signature"]);
|
|
727
|
+
function hash(value) {
|
|
728
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
729
|
+
}
|
|
730
|
+
function methodPrefix(method) {
|
|
731
|
+
return typeof method === "string" && method.length > 0 ? `${method.toUpperCase()} ` : "";
|
|
732
|
+
}
|
|
733
|
+
function redactUrl(value) {
|
|
734
|
+
try {
|
|
735
|
+
const url = new URL(value, value.startsWith("/") ? "https://relative.invalid" : void 0);
|
|
736
|
+
url.username = "";
|
|
737
|
+
url.password = "";
|
|
738
|
+
for (const key of [...url.searchParams.keys()]) url.searchParams.set(key, sensitiveKeys.has(key.toLowerCase()) ? "<redacted>" : "<redacted>");
|
|
739
|
+
const path9 = `${url.pathname}${url.search ? url.search : ""}`;
|
|
740
|
+
return value.startsWith("/") ? path9 : `${url.origin}${path9}`;
|
|
741
|
+
} catch {
|
|
742
|
+
return value.replace(/([?&][^=;&]*(?:token|key|password|secret|cookie|authorization)[^=;&]*=)[^&]*/gi, "$1<redacted>");
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function externalHttpTarget(call) {
|
|
746
|
+
const evidence = typeof call.evidence_json === "string" ? safeParse(call.evidence_json) : {};
|
|
747
|
+
const target = evidence.externalTarget && typeof evidence.externalTarget === "object" && !Array.isArray(evidence.externalTarget) ? evidence.externalTarget : {};
|
|
748
|
+
const method = typeof call.method === "string" ? call.method : typeof target.method === "string" ? target.method : void 0;
|
|
749
|
+
const kind = typeof target.kind === "string" ? target.kind : "unknown";
|
|
750
|
+
const expression = typeof target.expression === "string" ? target.expression : void 0;
|
|
751
|
+
if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
|
|
752
|
+
if (kind === "static_url" && expression) {
|
|
753
|
+
const redacted = redactUrl(expression);
|
|
754
|
+
return { kind, toKind: "external_endpoint", toId: `endpoint:${hash(`${method ?? ""}:${redacted}`)}`, label: `External endpoint: ${methodPrefix(method)}${redacted}`, method, dynamic: false, expression: redacted };
|
|
755
|
+
}
|
|
756
|
+
if (kind === "url_expression" && expression) return { kind, toKind: "external_endpoint", toId: `dynamic:${hash(expression)}`, label: `External endpoint: ${methodPrefix(method)}dynamic URL`, method, dynamic: true, expression: `expr:${hash(expression)}` };
|
|
757
|
+
return { kind: "unknown", toKind: "external_endpoint", toId: "unknown", label: "External endpoint: unknown", method, dynamic: false };
|
|
758
|
+
}
|
|
759
|
+
function safeParse(value) {
|
|
760
|
+
try {
|
|
761
|
+
const parsed = JSON.parse(value);
|
|
762
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
763
|
+
} catch {
|
|
764
|
+
return {};
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/parsers/outbound-call-parser.ts
|
|
723
769
|
function lineOf3(text, idx) {
|
|
724
770
|
return text.slice(0, idx).split("\n").length;
|
|
725
771
|
}
|
|
@@ -996,7 +1042,11 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
996
1042
|
}
|
|
997
1043
|
} else {
|
|
998
1044
|
const external = externalHttpEvidence(node, source, initializers);
|
|
999
|
-
if (external)
|
|
1045
|
+
if (external) {
|
|
1046
|
+
const evidenceTarget = { ...external.externalTarget, method: external.method, parserClassifier: external.classifier, sourceCallShape: external.sourceCallShape };
|
|
1047
|
+
const safeTarget = externalHttpTarget({ method: external.method, evidence_json: JSON.stringify({ externalTarget: evidenceTarget }) });
|
|
1048
|
+
add(node, { callType: "external_http", method: external.method, payloadSummary: void 0, confidence: 0.7, unresolvedReason: "External HTTP destination is outside indexed CAP services", externalTarget: { kind: safeTarget.kind, stableId: safeTarget.toId, label: safeTarget.label, dynamic: safeTarget.dynamic } }, { classifier: external.classifier, externalTarget: safeTarget, sourceCallShape: external.sourceCallShape });
|
|
1049
|
+
}
|
|
1000
1050
|
}
|
|
1001
1051
|
}
|
|
1002
1052
|
ts4.forEachChild(node, visit);
|
|
@@ -1450,16 +1500,16 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1450
1500
|
helperChain: aliasKind === "assignment" ? [{ callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind, scopeRule: "same-file-source-order" }] : void 0
|
|
1451
1501
|
});
|
|
1452
1502
|
else if (ts5.isIdentifier(call.expression)) {
|
|
1453
|
-
const
|
|
1454
|
-
if (
|
|
1503
|
+
const resolved2 = await importedHelper(call.expression.text);
|
|
1504
|
+
if (resolved2)
|
|
1455
1505
|
out.push({
|
|
1456
1506
|
variableName: targetName,
|
|
1457
|
-
alias:
|
|
1458
|
-
aliasExpr:
|
|
1459
|
-
destinationExpr:
|
|
1460
|
-
servicePathExpr:
|
|
1461
|
-
isDynamic:
|
|
1462
|
-
placeholders:
|
|
1507
|
+
alias: resolved2.helper.alias,
|
|
1508
|
+
aliasExpr: resolved2.helper.aliasExpr,
|
|
1509
|
+
destinationExpr: resolved2.helper.destinationExpr,
|
|
1510
|
+
servicePathExpr: resolved2.helper.servicePathExpr,
|
|
1511
|
+
isDynamic: resolved2.helper.isDynamic,
|
|
1512
|
+
placeholders: resolved2.helper.placeholders,
|
|
1463
1513
|
sourceFile: normalizePath(filePath),
|
|
1464
1514
|
sourceLine: lineOf4(sourceFileAst, node),
|
|
1465
1515
|
helperChain: [
|
|
@@ -1467,10 +1517,10 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1467
1517
|
callerVariable: targetName,
|
|
1468
1518
|
...aliasKind === "assignment" ? { assignedFrom: call.expression.text, aliasKind, scopeRule: "same-file-source-order" } : {},
|
|
1469
1519
|
importedHelper: call.expression.text,
|
|
1470
|
-
importSource:
|
|
1471
|
-
exportedSymbol:
|
|
1472
|
-
helperSourceFile:
|
|
1473
|
-
helperSourceLine:
|
|
1520
|
+
importSource: resolved2.imp.sourceFile,
|
|
1521
|
+
exportedSymbol: resolved2.imp.exportedName,
|
|
1522
|
+
helperSourceFile: resolved2.helper.sourceFile,
|
|
1523
|
+
helperSourceLine: resolved2.helper.sourceLine
|
|
1474
1524
|
}
|
|
1475
1525
|
]
|
|
1476
1526
|
});
|
|
@@ -1497,18 +1547,18 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1497
1547
|
const propertyName = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
1498
1548
|
const matches = helpers.filter((row) => row.helper.returnedProperty === propertyName);
|
|
1499
1549
|
if (matches.length !== 1) continue;
|
|
1500
|
-
const
|
|
1550
|
+
const resolved2 = matches[0];
|
|
1501
1551
|
out.push({
|
|
1502
1552
|
variableName: el.name.text,
|
|
1503
|
-
alias:
|
|
1504
|
-
aliasExpr:
|
|
1505
|
-
destinationExpr:
|
|
1506
|
-
servicePathExpr:
|
|
1507
|
-
isDynamic:
|
|
1508
|
-
placeholders:
|
|
1553
|
+
alias: resolved2.helper.alias,
|
|
1554
|
+
aliasExpr: resolved2.helper.aliasExpr,
|
|
1555
|
+
destinationExpr: resolved2.helper.destinationExpr,
|
|
1556
|
+
servicePathExpr: resolved2.helper.servicePathExpr,
|
|
1557
|
+
isDynamic: resolved2.helper.isDynamic,
|
|
1558
|
+
placeholders: resolved2.helper.placeholders,
|
|
1509
1559
|
sourceFile: normalizePath(filePath),
|
|
1510
1560
|
sourceLine: lineOf4(sourceFileAst, decl),
|
|
1511
|
-
helperChain: [{ callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName, importSource:
|
|
1561
|
+
helperChain: [{ callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
|
|
1512
1562
|
});
|
|
1513
1563
|
}
|
|
1514
1564
|
}
|
|
@@ -1530,18 +1580,18 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1530
1580
|
if (!propertyName || !targetName) continue;
|
|
1531
1581
|
const matches = helpers.filter((row) => row.helper.returnedProperty === propertyName);
|
|
1532
1582
|
if (matches.length !== 1) continue;
|
|
1533
|
-
const
|
|
1583
|
+
const resolved2 = matches[0];
|
|
1534
1584
|
out.push({
|
|
1535
1585
|
variableName: targetName,
|
|
1536
|
-
alias:
|
|
1537
|
-
aliasExpr:
|
|
1538
|
-
destinationExpr:
|
|
1539
|
-
servicePathExpr:
|
|
1540
|
-
isDynamic:
|
|
1541
|
-
placeholders:
|
|
1586
|
+
alias: resolved2.helper.alias,
|
|
1587
|
+
aliasExpr: resolved2.helper.aliasExpr,
|
|
1588
|
+
destinationExpr: resolved2.helper.destinationExpr,
|
|
1589
|
+
servicePathExpr: resolved2.helper.servicePathExpr,
|
|
1590
|
+
isDynamic: resolved2.helper.isDynamic,
|
|
1591
|
+
placeholders: resolved2.helper.placeholders,
|
|
1542
1592
|
sourceFile: normalizePath(filePath),
|
|
1543
1593
|
sourceLine: lineOf4(sourceFileAst, node),
|
|
1544
|
-
helperChain: [{ callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName, importSource:
|
|
1594
|
+
helperChain: [{ callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
|
|
1545
1595
|
});
|
|
1546
1596
|
}
|
|
1547
1597
|
}
|
|
@@ -1942,62 +1992,28 @@ function generatedFromConstantName(value) {
|
|
|
1942
1992
|
}
|
|
1943
1993
|
return void 0;
|
|
1944
1994
|
}
|
|
1945
|
-
function
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
if (
|
|
1995
|
+
function resolved(value, raw) {
|
|
1996
|
+
const literal = clean(value);
|
|
1997
|
+
const generated = generatedFromConstantName(literal);
|
|
1998
|
+
return { status: "resolved", operationName: generated ?? normalizedOperationName(literal), raw };
|
|
1999
|
+
}
|
|
2000
|
+
function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
|
|
2001
|
+
if (value) return resolved(value, raw);
|
|
2002
|
+
if (!raw || raw.trim().length === 0) return { status: "none", raw };
|
|
1952
2003
|
const expression = raw.trim();
|
|
1953
2004
|
const nameMatch = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(expression);
|
|
1954
|
-
if (nameMatch?.[1]) return
|
|
1955
|
-
const
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
return typeof method === "string" && method.length > 0 ? `${method.toUpperCase()} ` : "";
|
|
1967
|
-
}
|
|
1968
|
-
function redactUrl(value) {
|
|
1969
|
-
try {
|
|
1970
|
-
const url = new URL(value, value.startsWith("/") ? "https://relative.invalid" : void 0);
|
|
1971
|
-
url.username = "";
|
|
1972
|
-
url.password = "";
|
|
1973
|
-
for (const key of [...url.searchParams.keys()]) url.searchParams.set(key, sensitiveKeys.has(key.toLowerCase()) ? "<redacted>" : "<redacted>");
|
|
1974
|
-
const path9 = `${url.pathname}${url.search ? url.search : ""}`;
|
|
1975
|
-
return value.startsWith("/") ? path9 : `${url.origin}${path9}`;
|
|
1976
|
-
} catch {
|
|
1977
|
-
return value.replace(/([?&][^=;&]*(?:token|key|password|secret|cookie|authorization)[^=;&]*=)[^&]*/gi, "$1<redacted>");
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
function externalHttpTarget(call) {
|
|
1981
|
-
const evidence = typeof call.evidence_json === "string" ? safeParse(call.evidence_json) : {};
|
|
1982
|
-
const target = evidence.externalTarget && typeof evidence.externalTarget === "object" && !Array.isArray(evidence.externalTarget) ? evidence.externalTarget : {};
|
|
1983
|
-
const method = typeof call.method === "string" ? call.method : typeof target.method === "string" ? target.method : void 0;
|
|
1984
|
-
const kind = typeof target.kind === "string" ? target.kind : "unknown";
|
|
1985
|
-
const expression = typeof target.expression === "string" ? target.expression : void 0;
|
|
1986
|
-
if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
|
|
1987
|
-
if (kind === "static_url" && expression) {
|
|
1988
|
-
const redacted = redactUrl(expression);
|
|
1989
|
-
return { kind, toKind: "external_endpoint", toId: `endpoint:${hash(`${method ?? ""}:${redacted}`)}`, label: `External endpoint: ${methodPrefix(method)}${redacted}`, method, dynamic: false, expression: redacted };
|
|
1990
|
-
}
|
|
1991
|
-
if (kind === "url_expression" && expression) return { kind, toKind: "external_endpoint", toId: `dynamic:${hash(expression)}`, label: `External endpoint: ${methodPrefix(method)}dynamic URL`, method, dynamic: true, expression: `expr:${hash(expression)}` };
|
|
1992
|
-
return { kind: "unknown", toKind: "external_endpoint", toId: "unknown", label: "External endpoint: unknown", method, dynamic: false };
|
|
1993
|
-
}
|
|
1994
|
-
function safeParse(value) {
|
|
1995
|
-
try {
|
|
1996
|
-
const parsed = JSON.parse(value);
|
|
1997
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1998
|
-
} catch {
|
|
1999
|
-
return {};
|
|
2000
|
-
}
|
|
2005
|
+
if (nameMatch?.[1]) return resolved(nameMatch[1], expression);
|
|
2006
|
+
const stringMatch = /^String\(([A-Za-z_$][\w$]*)\)$/.exec(expression);
|
|
2007
|
+
if (stringMatch?.[1]) {
|
|
2008
|
+
const identifier = stringMatch[1];
|
|
2009
|
+
const generated = generatedFromConstantName(identifier);
|
|
2010
|
+
const normalizedCandidate = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
|
|
2011
|
+
if (generated) return { status: "resolved", operationName: generated, raw: expression };
|
|
2012
|
+
if (normalizedCandidate && identifier === normalizedCandidate) return { status: "resolved", operationName: identifier, raw: expression };
|
|
2013
|
+
return { status: "unsupported", raw: expression, reason: "string_wrapper_identifier_not_resolved" };
|
|
2014
|
+
}
|
|
2015
|
+
if (/^[`'"]/.test(expression) && !/[`'"]$/.test(expression)) return { status: "malformed", raw: expression, reason: "unterminated_literal" };
|
|
2016
|
+
return { status: "unsupported", raw: expression, reason: "unsupported_decorator_expression" };
|
|
2001
2017
|
}
|
|
2002
2018
|
|
|
2003
2019
|
// src/linker/cross-repo-linker.ts
|
|
@@ -2059,7 +2075,7 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
2059
2075
|
return { status: "terminal", callType };
|
|
2060
2076
|
}
|
|
2061
2077
|
if (callType === "local_service_call" && call.unresolved_reason === "transport_client_method" && !resolution.target && resolution.candidates.length === 0) {
|
|
2062
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "
|
|
2078
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "HANDLER_CALLS_TRANSPORT_METHOD", "terminal", "call", String(call.id), "transport_method", String(op || "transport_client_method"), Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, classification: "transport_client_method" }), 0, generation);
|
|
2063
2079
|
return { status: "terminal", callType };
|
|
2064
2080
|
}
|
|
2065
2081
|
if (resolution.target) {
|
|
@@ -2070,7 +2086,7 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
2070
2086
|
const status = edgeType === "DYNAMIC_EDGE_CANDIDATE" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : edgeType === "UNRESOLVED_EDGE" ? "unresolved" : "terminal";
|
|
2071
2087
|
const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? unresolvedOperationReason(resolution));
|
|
2072
2088
|
const externalTarget = callType === "external_http" ? externalHttpTarget(call) : void 0;
|
|
2073
|
-
const targetKind = callType === "local_db_query" ? "db_entity" : callType.startsWith("async_") ? "event" : externalTarget?.toKind ?? "external_endpoint";
|
|
2089
|
+
const targetKind = callType === "local_db_query" ? "db_entity" : callType.startsWith("async_") ? "event" : callType === "external_http" ? externalTarget?.toKind ?? "external_endpoint" : "operation_candidate";
|
|
2074
2090
|
const targetId = callType === "local_db_query" ? String(call.query_entity ?? "unknown") : callType === "remote_action" ? op ? `Remote action: ${op}` : call.unresolved_reason === "dynamic_operation_path_identifier" ? "Remote action: dynamic path" : "Remote action: unknown path" : callType === "external_http" ? String(externalTarget?.toId ?? "unknown") : String(call.event_name_expr ?? op ?? "unknown");
|
|
2075
2091
|
const graphLevelDynamic = edgeType === "DYNAMIC_EDGE_CANDIDATE" && resolution.status === "dynamic";
|
|
2076
2092
|
const finalEvidence = externalTarget ? { ...evidence, externalTarget } : evidence;
|
|
@@ -2329,9 +2345,9 @@ function candidateEvidence(candidate, rank) {
|
|
|
2329
2345
|
}
|
|
2330
2346
|
function implementationMethodSignal(row, operation) {
|
|
2331
2347
|
const op = normalizedOperationName(String(operation.operationPath ?? operation.operationName ?? ""));
|
|
2332
|
-
const decorator =
|
|
2333
|
-
if (decorator && decorator === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
2334
|
-
if (decorator && decorator !== op) return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["method_name_matches_but_decorator_targets_different_operation"] };
|
|
2348
|
+
const decorator = normalizeDecoratorOperationSignal(typeof row.decoratorValue === "string" ? row.decoratorValue : void 0, typeof row.decoratorRawExpression === "string" ? row.decoratorRawExpression : void 0, op);
|
|
2349
|
+
if (decorator.status === "resolved" && decorator.operationName === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
2350
|
+
if (decorator.status === "resolved" && decorator.operationName !== op) return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["method_name_matches_but_decorator_targets_different_operation"] };
|
|
2335
2351
|
if (String(row.methodName ?? "") === op) return { matches: true, contradicted: false, acceptedReasons: ["method name fallback matched operation"], rejectedReasons: [] };
|
|
2336
2352
|
return { matches: false, contradicted: false, acceptedReasons: [], rejectedReasons: ["method name does not match operation"] };
|
|
2337
2353
|
}
|
|
@@ -2363,17 +2379,17 @@ function operationStartScope(db, repoId, start) {
|
|
|
2363
2379
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
2364
2380
|
WHERE (? IS NULL OR r.id=?) AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
|
|
2365
2381
|
ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith("/") ? requested : `/${requested}`);
|
|
2366
|
-
if (rows2.length === 0) return
|
|
2382
|
+
if (rows2.length === 0) return void 0;
|
|
2367
2383
|
const repoCount = new Set(rows2.map((row) => String(row.repoName))).size;
|
|
2368
2384
|
const serviceCount = new Set(rows2.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
|
|
2369
|
-
if (!repoId && repoCount > 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple repositories; add --repo to disambiguate", normalizedSelectorValue: requested, candidates: rows2 }] };
|
|
2370
|
-
if (!start.servicePath && serviceCount > 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple services; add --service to disambiguate", normalizedSelectorValue: requested, candidates: rows2 }] };
|
|
2371
|
-
if (rows2.length !== 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple indexed operations", normalizedSelectorValue: requested, candidates: rows2 }] };
|
|
2385
|
+
if (!repoId && repoCount > 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple repositories; add --repo to disambiguate", normalizedSelectorValue: requested, resolutionStage: "operation", resolutionStatus: "ambiguous_operation", candidates: rows2 }] };
|
|
2386
|
+
if (!start.servicePath && serviceCount > 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple services; add --service to disambiguate", normalizedSelectorValue: requested, resolutionStage: "operation", resolutionStatus: "ambiguous_operation", candidates: rows2 }] };
|
|
2387
|
+
if (rows2.length !== 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple indexed operations", normalizedSelectorValue: requested, resolutionStage: "operation", resolutionStatus: "ambiguous_operation", candidates: rows2 }] };
|
|
2372
2388
|
const operationId = String(rows2[0]?.operationId);
|
|
2373
2389
|
const impl = implementationScope(db, operationId);
|
|
2374
2390
|
if (impl.edge?.status === "resolved" && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? /* @__PURE__ */ new Set([impl.symbolId]) : void 0, operationId, diagnostics: [] };
|
|
2375
|
-
if (impl.edge) return { operationId, diagnostics: [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "
|
|
2376
|
-
return { operationId, diagnostics: [{ severity: "warning", code: "
|
|
2391
|
+
if (impl.edge) return { operationId, diagnostics: [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved", message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? "unresolved")}`, resolutionStage: "implementation", resolutionStatus: impl.edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation", implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, candidates: parseEvidence(impl.edge.evidence_json).candidates }] };
|
|
2392
|
+
return { operationId, diagnostics: [{ severity: "warning", code: "trace_start_implementation_unresolved", message: "Indexed operation matched but no implementation candidate exists", resolutionStage: "implementation", resolutionStatus: "operation_without_implementation" }] };
|
|
2377
2393
|
}
|
|
2378
2394
|
function sourceFilesForStart(db, repoId, start) {
|
|
2379
2395
|
const handler = start.handler;
|
|
@@ -2421,7 +2437,8 @@ function startScope(db, start) {
|
|
|
2421
2437
|
).get(start.repo, start.repo) : void 0;
|
|
2422
2438
|
if (start.repo && !repo) return { repo, selectorMatched: false };
|
|
2423
2439
|
const operationScope = operationStartScope(db, repo?.id, start);
|
|
2424
|
-
const
|
|
2440
|
+
const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === "operation" || d.resolutionStage === "implementation");
|
|
2441
|
+
const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
|
|
2425
2442
|
const sourceFiles = sourceScope?.files;
|
|
2426
2443
|
const hasSelector = Boolean(
|
|
2427
2444
|
start.handler ?? start.operation ?? start.operationPath ?? start.servicePath
|
|
@@ -2432,7 +2449,7 @@ function startScope(db, start) {
|
|
|
2432
2449
|
repo,
|
|
2433
2450
|
sourceFiles,
|
|
2434
2451
|
symbolIds: sourceScope?.symbols,
|
|
2435
|
-
selectorMatched: !hasSelector || sourceFiles !== void 0,
|
|
2452
|
+
selectorMatched: !terminalOperationScope && (!hasSelector || sourceFiles !== void 0),
|
|
2436
2453
|
startOperationId: operationScope?.operationId,
|
|
2437
2454
|
startDiagnostics: operationScope?.diagnostics
|
|
2438
2455
|
};
|
|
@@ -2711,7 +2728,7 @@ function trace(db, start, options) {
|
|
|
2711
2728
|
const op = operationNode(db, scope.startOperationId);
|
|
2712
2729
|
const impl = implementationScope(db, scope.startOperationId);
|
|
2713
2730
|
if (op) nodes.set(String(op.id), op);
|
|
2714
|
-
if (impl.edge) {
|
|
2731
|
+
if (impl.edge && impl.edge.status === "resolved") {
|
|
2715
2732
|
const implEvidence = { ...parseEvidence(impl.edge.evidence_json), startResolution: { strategy: "indexed_operation_graph", matchedOperationId: scope.startOperationId, implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, selectedHandlerMethodId: impl.edge.status === "resolved" ? impl.edge.to_id : void 0 } };
|
|
2716
2733
|
const handlerNode = impl.edge.status === "resolved" ? handlerMethodNode(db, impl.edge.to_id) : void 0;
|
|
2717
2734
|
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
@@ -2867,4 +2884,4 @@ export {
|
|
|
2867
2884
|
linkWorkspace,
|
|
2868
2885
|
trace
|
|
2869
2886
|
};
|
|
2870
|
-
//# sourceMappingURL=chunk-
|
|
2887
|
+
//# sourceMappingURL=chunk-VT5FVMOA.js.map
|