@neat.is/core 0.9.13 → 0.9.15-dev.20260902
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/dist/{chunk-BKSM2YLV.js → chunk-NV4WWJSU.js} +45 -5
- package/dist/chunk-NV4WWJSU.js.map +1 -0
- package/dist/{chunk-RL6H3VVD.js → chunk-SSLPBQWY.js} +107 -37
- package/dist/chunk-SSLPBQWY.js.map +1 -0
- package/dist/cli.cjs +156 -58
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +138 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +138 -41
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +73 -36
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-BKSM2YLV.js.map +0 -1
- package/dist/chunk-RL6H3VVD.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1284,13 +1284,19 @@ function resolveOwningService(graph, nodeId) {
|
|
|
1284
1284
|
}
|
|
1285
1285
|
return null;
|
|
1286
1286
|
}
|
|
1287
|
+
function isFrontierEdge(e) {
|
|
1288
|
+
return e.provenance === import_types.Provenance.FRONTIER;
|
|
1289
|
+
}
|
|
1290
|
+
function rankOf(p) {
|
|
1291
|
+
return p === import_types.Provenance.FRONTIER ? -1 : import_types.PROV_RANK[p];
|
|
1292
|
+
}
|
|
1287
1293
|
function bestEdgeBySource(graph, edgeIds) {
|
|
1288
1294
|
const best = /* @__PURE__ */ new Map();
|
|
1289
1295
|
for (const id of edgeIds) {
|
|
1290
1296
|
const e = graph.getEdgeAttributes(id);
|
|
1291
|
-
if (isFrontierNode(graph, e.source)) continue;
|
|
1297
|
+
if (isFrontierNode(graph, e.source) || isFrontierEdge(e)) continue;
|
|
1292
1298
|
const cur = best.get(e.source);
|
|
1293
|
-
if (!cur ||
|
|
1299
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
1294
1300
|
best.set(e.source, e);
|
|
1295
1301
|
}
|
|
1296
1302
|
}
|
|
@@ -1300,9 +1306,9 @@ function bestEdgeByTarget(graph, edgeIds) {
|
|
|
1300
1306
|
const best = /* @__PURE__ */ new Map();
|
|
1301
1307
|
for (const id of edgeIds) {
|
|
1302
1308
|
const e = graph.getEdgeAttributes(id);
|
|
1303
|
-
if (isFrontierNode(graph, e.target)) continue;
|
|
1309
|
+
if (isFrontierNode(graph, e.target) || isFrontierEdge(e)) continue;
|
|
1304
1310
|
const cur = best.get(e.target);
|
|
1305
|
-
if (!cur ||
|
|
1311
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
1306
1312
|
best.set(e.target, e);
|
|
1307
1313
|
}
|
|
1308
1314
|
}
|
|
@@ -1312,7 +1318,10 @@ var PROVENANCE_CEILING = {
|
|
|
1312
1318
|
OBSERVED: 1,
|
|
1313
1319
|
INFERRED: 0.7,
|
|
1314
1320
|
EXTRACTED: 0.5,
|
|
1315
|
-
STALE: 0.3
|
|
1321
|
+
STALE: 0.3,
|
|
1322
|
+
// A FRONTIER surface (ADR-226) is a proposal about a cause NEAT could not observe
|
|
1323
|
+
// — the least-trusted claim it makes, below STALE (which was at least once seen).
|
|
1324
|
+
FRONTIER: 0.2
|
|
1316
1325
|
};
|
|
1317
1326
|
function volumeWeight(spanCount) {
|
|
1318
1327
|
if (!spanCount || spanCount <= 0) return 0.5;
|
|
@@ -1547,7 +1556,7 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1547
1556
|
});
|
|
1548
1557
|
}
|
|
1549
1558
|
function isFailingCallEdge(e) {
|
|
1550
|
-
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1559
|
+
return e.type === import_types.EdgeType.CALLS && !isFrontierEdge(e) && (e.signal?.errorCount ?? 0) > 0;
|
|
1551
1560
|
}
|
|
1552
1561
|
function callSourcesForService(graph, serviceId17) {
|
|
1553
1562
|
const ids = [serviceId17];
|
|
@@ -1570,8 +1579,8 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1570
1579
|
const ec = e.signal?.errorCount ?? 0;
|
|
1571
1580
|
const cc = curEdge.signal?.errorCount ?? 0;
|
|
1572
1581
|
if (ec !== cc) return ec > cc;
|
|
1573
|
-
if (
|
|
1574
|
-
return
|
|
1582
|
+
if (rankOf(e.provenance) !== rankOf(curEdge.provenance)) {
|
|
1583
|
+
return rankOf(e.provenance) > rankOf(curEdge.provenance);
|
|
1575
1584
|
}
|
|
1576
1585
|
return id < curId;
|
|
1577
1586
|
}
|
|
@@ -1622,11 +1631,11 @@ function dominantStaleCall(graph, serviceId17, visited) {
|
|
|
1622
1631
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1623
1632
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1624
1633
|
if (e.type !== import_types.EdgeType.CALLS) continue;
|
|
1625
|
-
if (isFrontierNode(graph, e.target)) continue;
|
|
1634
|
+
if (isFrontierNode(graph, e.target) || isFrontierEdge(e)) continue;
|
|
1626
1635
|
const owner = resolveOwningService(graph, e.target);
|
|
1627
1636
|
if (!owner || visited.has(owner.id)) continue;
|
|
1628
1637
|
const cur = bestByCallee.get(owner.id);
|
|
1629
|
-
if (!cur ||
|
|
1638
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
1630
1639
|
bestByCallee.set(owner.id, e);
|
|
1631
1640
|
}
|
|
1632
1641
|
}
|
|
@@ -1899,6 +1908,7 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1899
1908
|
for (const edgeId of graph.inboundEdges(n)) {
|
|
1900
1909
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1901
1910
|
if (e.type === import_types.EdgeType.CONTAINS) continue;
|
|
1911
|
+
if (isFrontierEdge(e)) continue;
|
|
1902
1912
|
errorsFromCallers += e.signal?.errorCount ?? 0;
|
|
1903
1913
|
inboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
1904
1914
|
if (e.provenance === import_types.Provenance.STALE) stale = true;
|
|
@@ -1912,6 +1922,7 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1912
1922
|
for (const edgeId of graph.outboundEdges(n)) {
|
|
1913
1923
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1914
1924
|
if (e.type === import_types.EdgeType.CONTAINS) continue;
|
|
1925
|
+
if (isFrontierEdge(e)) continue;
|
|
1915
1926
|
outboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
1916
1927
|
if (e.type === import_types.EdgeType.CALLS) outboundErrors += e.signal?.errorCount ?? 0;
|
|
1917
1928
|
if (e.provenance === import_types.Provenance.STALE) stale = true;
|
|
@@ -2195,22 +2206,44 @@ function routeMatches(declared, incident) {
|
|
|
2195
2206
|
const b = normalizeRoute(incident);
|
|
2196
2207
|
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
2197
2208
|
}
|
|
2198
|
-
function
|
|
2199
|
-
const
|
|
2200
|
-
const callees = declaredOutboundCallees(graph, boundaryNode);
|
|
2209
|
+
function firstDeclaredCallee(graph, node, route) {
|
|
2210
|
+
const callees = declaredOutboundCallees(graph, node);
|
|
2201
2211
|
if (callees.length === 0) return null;
|
|
2202
2212
|
const narrowed = route !== void 0 ? callees.filter((c) => c.route !== void 0 && routeMatches(c.route, route)) : callees;
|
|
2203
2213
|
const chosen = narrowed.length === 1 ? narrowed[0] : callees.length === 1 ? callees[0] : null;
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
if (
|
|
2210
|
-
|
|
2211
|
-
|
|
2214
|
+
return chosen ? chosen.target : null;
|
|
2215
|
+
}
|
|
2216
|
+
function observedOutboundNextServices(graph, nodeId) {
|
|
2217
|
+
const targets = /* @__PURE__ */ new Set();
|
|
2218
|
+
for (const n of nodeScope(graph, nodeId)) {
|
|
2219
|
+
if (!graph.hasNode(n)) continue;
|
|
2220
|
+
for (const edgeId of graph.outboundEdges(n)) {
|
|
2221
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
2222
|
+
if (!DEP_EDGE_TYPES.has(e.type)) continue;
|
|
2223
|
+
if (e.provenance !== import_types.Provenance.OBSERVED) continue;
|
|
2224
|
+
if (e.target !== nodeId) targets.add(e.target);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
return [...targets];
|
|
2228
|
+
}
|
|
2229
|
+
function resolveHangHop(graph, boundaryNode, incidents) {
|
|
2230
|
+
const route = boundaryIncidentRoute(boundaryNode, incidents);
|
|
2231
|
+
const own = firstDeclaredCallee(graph, boundaryNode, route);
|
|
2232
|
+
if (own) {
|
|
2233
|
+
return route !== void 0 ? { source: boundaryNode, target: own, route } : { source: boundaryNode, target: own };
|
|
2234
|
+
}
|
|
2235
|
+
const resolved = observedOutboundNextServices(graph, boundaryNode).map((n) => ({ source: n, target: firstDeclaredCallee(graph, n, route) })).filter((r) => r.target !== null);
|
|
2236
|
+
if (resolved.length === 1) {
|
|
2237
|
+
const hop = resolved[0];
|
|
2238
|
+
return route !== void 0 ? { ...hop, route } : hop;
|
|
2212
2239
|
}
|
|
2213
|
-
return
|
|
2240
|
+
return null;
|
|
2241
|
+
}
|
|
2242
|
+
function stagedHangProposal(graph, boundaryNode, incidents) {
|
|
2243
|
+
const hop = resolveHangHop(graph, boundaryNode, incidents);
|
|
2244
|
+
if (!hop) return null;
|
|
2245
|
+
if (!graph.hasEdge((0, import_types.frontierEdgeId)(hop.source, hop.target, import_types.EdgeType.CALLS))) return null;
|
|
2246
|
+
return hop.route !== void 0 ? { node: hop.target, route: hop.route } : { node: hop.target };
|
|
2214
2247
|
}
|
|
2215
2248
|
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
2216
2249
|
const legacy = tagged.result;
|
|
@@ -2256,28 +2289,28 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
2256
2289
|
provenance: import_types.Provenance.OBSERVED
|
|
2257
2290
|
});
|
|
2258
2291
|
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
2259
|
-
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
2260
2292
|
const seedName = displayNameOf(seedNode);
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2263
|
-
const causeName = displayNameOf(pointer.node);
|
|
2264
|
-
candidates.push({
|
|
2265
|
-
node: pointer.node,
|
|
2266
|
-
classification: "primary-failure",
|
|
2267
|
-
reason: `${causeName} is the likely root cause (structural, INFERRED \u2014 not observed): the boundary ${seedName} only timed out waiting, and the actual culprit hung without exporting a span, so this is walked from the declared call graph${routeNote}, not from runtime signal. Restore instrumentation / inspect ${causeName} to confirm.`,
|
|
2268
|
-
context: nodeContext(graph, pointer.node, incidents, now),
|
|
2269
|
-
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.EXTRACTED),
|
|
2270
|
-
provenance: import_types.Provenance.INFERRED
|
|
2271
|
-
});
|
|
2272
|
-
}
|
|
2293
|
+
const proposal = stagedHangProposal(graph, seedNode, incidents);
|
|
2294
|
+
const routeNote = proposal?.route ? ` serving ${proposal.route}` : "";
|
|
2273
2295
|
candidates.push({
|
|
2274
2296
|
node: seedNode,
|
|
2275
2297
|
classification: "symptom-only",
|
|
2276
|
-
reason:
|
|
2298
|
+
reason: proposal ? `${seedName} timed out waiting on a downstream that hung and exported no span \u2014 a boundary reporting an upstream fault, not the fault itself. The cause is unobservable; NEAT proposes the staged FRONTIER surface below, a hypothesis to confirm, not an observed cause.` : `${seedName} timed out but nothing it can see downstream is erroring \u2014 the cause is downstream and unobserved (the culprit hung and exported no span). No FRONTIER surface${routeNote} is staged, so no cause is proposed; inspect ${seedName}'s declared downstream and restore instrumentation.`,
|
|
2277
2299
|
context: seedCtx,
|
|
2278
2300
|
confidence: Math.min(legacy.confidence, 0.3),
|
|
2279
2301
|
...lastProv ? { provenance: lastProv } : {}
|
|
2280
2302
|
});
|
|
2303
|
+
if (proposal) {
|
|
2304
|
+
const causeName = displayNameOf(proposal.node);
|
|
2305
|
+
candidates.push({
|
|
2306
|
+
node: proposal.node,
|
|
2307
|
+
classification: "primary-failure",
|
|
2308
|
+
reason: `${causeName} is the proposed hang cause (FRONTIER \u2014 a hypothesis, not observed): the boundary ${seedName} only timed out waiting and the culprit hung without exporting a span, so NEAT staged a FRONTIER surface on the unobservable hop${routeNote}. It graduates to OBSERVED when ${causeName} comes back; until then, inspect it / restore instrumentation to confirm, or it is culled.`,
|
|
2309
|
+
context: nodeContext(graph, proposal.node, incidents, now),
|
|
2310
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.FRONTIER),
|
|
2311
|
+
provenance: import_types.Provenance.FRONTIER
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2281
2314
|
} else if (staleChain) {
|
|
2282
2315
|
const culprit = staleChain.culprit;
|
|
2283
2316
|
const culpritName = displayNameOf(culprit);
|
|
@@ -2341,6 +2374,9 @@ function fixRecommendationForTop(top, seedNode, legacy) {
|
|
|
2341
2374
|
return legacy.fixRecommendation;
|
|
2342
2375
|
}
|
|
2343
2376
|
const name = top.node.replace(/^service:/, "");
|
|
2377
|
+
if (top.classification === "symptom-only" && top.context.boundaryTimeout === true) {
|
|
2378
|
+
return `${name} timed out waiting on a downstream that hung and exported no span \u2014 the cause is unobservable from traces. Inspect the FRONTIER surface NEAT staged on the unobservable hop (see candidates); it graduates to OBSERVED when that service recovers. Restore instrumentation on that path to confirm, rather than treating ${name} as the fault.`;
|
|
2379
|
+
}
|
|
2344
2380
|
if (top.provenance === import_types.Provenance.STALE) {
|
|
2345
2381
|
return `Live telemetry for this path has gone quiet; the last-observed topology traces the failure downstream to ${name}. Restore instrumentation (or re-run with live traces) to confirm, then inspect ${name}.`;
|
|
2346
2382
|
}
|
|
@@ -7006,6 +7042,32 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
7006
7042
|
}
|
|
7007
7043
|
return promoted;
|
|
7008
7044
|
}
|
|
7045
|
+
function stageFrontierEdge(graph, source, target, type) {
|
|
7046
|
+
if (!graph.hasNode(source) || !graph.hasNode(target)) return false;
|
|
7047
|
+
if (graph.hasEdge((0, import_types8.observedEdgeId)(source, target, type))) return false;
|
|
7048
|
+
const key = (0, import_types8.frontierEdgeId)(source, target, type);
|
|
7049
|
+
if (graph.hasEdge(key)) return false;
|
|
7050
|
+
graph.addEdgeWithKey(key, source, target, {
|
|
7051
|
+
id: key,
|
|
7052
|
+
source,
|
|
7053
|
+
target,
|
|
7054
|
+
type,
|
|
7055
|
+
provenance: import_types8.Provenance.FRONTIER
|
|
7056
|
+
});
|
|
7057
|
+
return true;
|
|
7058
|
+
}
|
|
7059
|
+
function promoteFrontierEdges(graph) {
|
|
7060
|
+
const graduated = [];
|
|
7061
|
+
graph.forEachEdge((edgeId, attrs) => {
|
|
7062
|
+
const e = attrs;
|
|
7063
|
+
if (e.provenance !== import_types8.Provenance.FRONTIER) return;
|
|
7064
|
+
if (graph.hasEdge((0, import_types8.observedEdgeId)(e.source, e.target, e.type))) {
|
|
7065
|
+
graduated.push(edgeId);
|
|
7066
|
+
}
|
|
7067
|
+
});
|
|
7068
|
+
for (const edgeId of graduated) graph.dropEdge(edgeId);
|
|
7069
|
+
return graduated.length;
|
|
7070
|
+
}
|
|
7009
7071
|
function rewireFrontierEdges(graph, frontierId2, serviceId17) {
|
|
7010
7072
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
7011
7073
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
@@ -7114,6 +7176,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
7114
7176
|
project: options.project
|
|
7115
7177
|
});
|
|
7116
7178
|
if (options.onPolicyTrigger) await options.onPolicyTrigger(graph);
|
|
7179
|
+
if (options.onReconcile) await options.onReconcile(graph);
|
|
7117
7180
|
} catch (err) {
|
|
7118
7181
|
console.error("staleness tick failed", err);
|
|
7119
7182
|
}
|
|
@@ -23309,6 +23372,36 @@ var import_node_path77 = __toESM(require("path"), 1);
|
|
|
23309
23372
|
var import_node_module = require("module");
|
|
23310
23373
|
init_otel();
|
|
23311
23374
|
|
|
23375
|
+
// src/hang-sensor.ts
|
|
23376
|
+
init_cjs_shims();
|
|
23377
|
+
var import_types101 = require("@neat.is/types");
|
|
23378
|
+
var RECONCILE_INCIDENT_LIMIT = 500;
|
|
23379
|
+
function stageHangSurfaces(graph, incidents) {
|
|
23380
|
+
const boundaries = /* @__PURE__ */ new Set();
|
|
23381
|
+
for (const ev of incidents) {
|
|
23382
|
+
if (ev.affectedNode && graph.hasNode(ev.affectedNode)) boundaries.add(ev.affectedNode);
|
|
23383
|
+
}
|
|
23384
|
+
let staged = 0;
|
|
23385
|
+
for (const boundary of boundaries) {
|
|
23386
|
+
const ctx = nodeContext(graph, boundary, incidents);
|
|
23387
|
+
if (!isBoundaryTimeoutSymptom(ctx)) continue;
|
|
23388
|
+
const hop = resolveHangHop(graph, boundary, incidents);
|
|
23389
|
+
if (!hop) continue;
|
|
23390
|
+
if (stageFrontierEdge(graph, hop.source, hop.target, import_types101.EdgeType.CALLS)) staged++;
|
|
23391
|
+
}
|
|
23392
|
+
return staged;
|
|
23393
|
+
}
|
|
23394
|
+
async function reconcileFrontierSurfaces(graph, errorsPath) {
|
|
23395
|
+
promoteFrontierEdges(graph);
|
|
23396
|
+
let incidents = [];
|
|
23397
|
+
try {
|
|
23398
|
+
incidents = await readErrorEvents(errorsPath, { limit: RECONCILE_INCIDENT_LIMIT });
|
|
23399
|
+
} catch {
|
|
23400
|
+
incidents = [];
|
|
23401
|
+
}
|
|
23402
|
+
stageHangSurfaces(graph, incidents);
|
|
23403
|
+
}
|
|
23404
|
+
|
|
23312
23405
|
// src/connectors/kubernetes/index.ts
|
|
23313
23406
|
init_cjs_shims();
|
|
23314
23407
|
|
|
@@ -23658,7 +23751,7 @@ function mapWorkloadsToSignals(deployments, pods, config) {
|
|
|
23658
23751
|
|
|
23659
23752
|
// src/connectors/kubernetes/resolve.ts
|
|
23660
23753
|
init_cjs_shims();
|
|
23661
|
-
var
|
|
23754
|
+
var import_types103 = require("@neat.is/types");
|
|
23662
23755
|
var NO_ENV3 = "unknown";
|
|
23663
23756
|
function createK8sResolveTarget(graph) {
|
|
23664
23757
|
return (signal) => {
|
|
@@ -23669,7 +23762,7 @@ function createK8sResolveTarget(graph) {
|
|
|
23669
23762
|
return {
|
|
23670
23763
|
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV3),
|
|
23671
23764
|
serviceName,
|
|
23672
|
-
edgeType:
|
|
23765
|
+
edgeType: import_types103.EdgeType.CALLS
|
|
23673
23766
|
};
|
|
23674
23767
|
};
|
|
23675
23768
|
}
|
|
@@ -23804,7 +23897,7 @@ function unroutedErrorsPath(neatHome3) {
|
|
|
23804
23897
|
}
|
|
23805
23898
|
|
|
23806
23899
|
// src/daemon.ts
|
|
23807
|
-
var
|
|
23900
|
+
var import_types106 = require("@neat.is/types");
|
|
23808
23901
|
function daemonJsonPath(scanPath) {
|
|
23809
23902
|
return import_node_path77.default.join(scanPath, "neat-out", "daemon.json");
|
|
23810
23903
|
}
|
|
@@ -23933,7 +24026,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
23933
24026
|
if (!serviceName) return true;
|
|
23934
24027
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
23935
24028
|
return graph.someNode(
|
|
23936
|
-
(_id, attrs) => attrs.type ===
|
|
24029
|
+
(_id, attrs) => attrs.type === import_types106.NodeType.ServiceNode && attrs.name === serviceName
|
|
23937
24030
|
);
|
|
23938
24031
|
}
|
|
23939
24032
|
async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
@@ -23977,7 +24070,11 @@ async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
|
23977
24070
|
const stopPersist = startPersistLoop(graph, outPath, { exitOnSignal: false });
|
|
23978
24071
|
const stopStaleness = startStalenessLoop(graph, {
|
|
23979
24072
|
staleEventsPath: paths.staleEventsPath,
|
|
23980
|
-
project: entry.name
|
|
24073
|
+
project: entry.name,
|
|
24074
|
+
// Graduate FRONTIER surfaces whose OBSERVED twin has arrived + stage surfaces
|
|
24075
|
+
// for current hangs (ADR-226), each post-ingest tick — the periodic point
|
|
24076
|
+
// where OBSERVED state has settled and the errors ledger is current.
|
|
24077
|
+
onReconcile: (g) => reconcileFrontierSurfaces(g, paths.errorsPath)
|
|
23981
24078
|
});
|
|
23982
24079
|
const stopConnectors = await startConnectorPolling({
|
|
23983
24080
|
project: entry.name,
|