@netgreener/runtime 0.1.0

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.
Files changed (138) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +28 -0
  3. package/TUTORIAL.md +495 -0
  4. package/dist/aggregator.d.ts +43 -0
  5. package/dist/aggregator.js +270 -0
  6. package/dist/analyze/analyzeGate.d.ts +44 -0
  7. package/dist/analyze/analyzeGate.js +114 -0
  8. package/dist/analyze/apiOverconsumptionDetector.d.ts +13 -0
  9. package/dist/analyze/apiOverconsumptionDetector.js +75 -0
  10. package/dist/analyze/astDetectors.d.ts +38 -0
  11. package/dist/analyze/astDetectors.js +566 -0
  12. package/dist/analyze/buildResourceFindings.d.ts +34 -0
  13. package/dist/analyze/buildResourceFindings.js +86 -0
  14. package/dist/analyze/cli.d.ts +20 -0
  15. package/dist/analyze/cli.js +186 -0
  16. package/dist/analyze/cpuResourceDetectors.d.ts +35 -0
  17. package/dist/analyze/cpuResourceDetectors.js +163 -0
  18. package/dist/analyze/heuristicScan.d.ts +61 -0
  19. package/dist/analyze/heuristicScan.js +91 -0
  20. package/dist/analyze/index.d.ts +15 -0
  21. package/dist/analyze/index.js +15 -0
  22. package/dist/analyze/loadTypescript.d.ts +8 -0
  23. package/dist/analyze/loadTypescript.js +23 -0
  24. package/dist/analyze/memoryIoDetectors.d.ts +15 -0
  25. package/dist/analyze/memoryIoDetectors.js +68 -0
  26. package/dist/analyze/preferAstDetectors.d.ts +18 -0
  27. package/dist/analyze/preferAstDetectors.js +91 -0
  28. package/dist/analyze/projectScan.d.ts +19 -0
  29. package/dist/analyze/projectScan.js +115 -0
  30. package/dist/analyze/resourceAdmission.d.ts +33 -0
  31. package/dist/analyze/resourceAdmission.js +91 -0
  32. package/dist/analyze/resourceFindingCandidate.d.ts +61 -0
  33. package/dist/analyze/resourceFindingCandidate.js +75 -0
  34. package/dist/analyze/retryAmplificationDetector.d.ts +11 -0
  35. package/dist/analyze/retryAmplificationDetector.js +55 -0
  36. package/dist/analyze/schemas/resource-finding.schema.json +352 -0
  37. package/dist/analyze/serviceDiscovery.d.ts +66 -0
  38. package/dist/analyze/serviceDiscovery.js +210 -0
  39. package/dist/analyze/unboundedParallelismDetector.d.ts +11 -0
  40. package/dist/analyze/unboundedParallelismDetector.js +57 -0
  41. package/dist/analyze/validateResourceFinding.d.ts +19 -0
  42. package/dist/analyze/validateResourceFinding.js +46 -0
  43. package/dist/bullmq.d.ts +33 -0
  44. package/dist/bullmq.js +106 -0
  45. package/dist/cli/helpText.d.ts +8 -0
  46. package/dist/cli/helpText.js +99 -0
  47. package/dist/cli/netgreener.d.ts +12 -0
  48. package/dist/cli/netgreener.js +65 -0
  49. package/dist/collectorIpc.d.ts +28 -0
  50. package/dist/collectorIpc.js +189 -0
  51. package/dist/collectorMetadata.d.ts +15 -0
  52. package/dist/collectorMetadata.js +33 -0
  53. package/dist/config.d.ts +41 -0
  54. package/dist/config.js +97 -0
  55. package/dist/contract/index.d.ts +1 -0
  56. package/dist/contract/index.js +1 -0
  57. package/dist/contract/observation-envelope.schema.json +331 -0
  58. package/dist/contract/observation-protobuf-view.schema.json +773 -0
  59. package/dist/contract/observation-v1.d.mts +42 -0
  60. package/dist/contract/observation-v1.mjs +555 -0
  61. package/dist/contract/observationIdentity.d.ts +11 -0
  62. package/dist/contract/observationIdentity.js +48 -0
  63. package/dist/contract/observationProjection.d.ts +3 -0
  64. package/dist/contract/observationProjection.js +29 -0
  65. package/dist/contract/validate.d.ts +20 -0
  66. package/dist/contract/validate.js +227 -0
  67. package/dist/dogfood/mp3WorkerGates.d.ts +54 -0
  68. package/dist/dogfood/mp3WorkerGates.js +86 -0
  69. package/dist/dogfood/retainDryRunArtifact.d.ts +45 -0
  70. package/dist/dogfood/retainDryRunArtifact.js +103 -0
  71. package/dist/dogfood/tenantDogfoodGates.d.ts +76 -0
  72. package/dist/dogfood/tenantDogfoodGates.js +206 -0
  73. package/dist/exporter.d.ts +56 -0
  74. package/dist/exporter.js +304 -0
  75. package/dist/express.d.ts +44 -0
  76. package/dist/express.js +118 -0
  77. package/dist/externalApiMeter.d.ts +72 -0
  78. package/dist/externalApiMeter.js +1167 -0
  79. package/dist/fastify.d.ts +53 -0
  80. package/dist/fastify.js +148 -0
  81. package/dist/index.d.ts +32 -0
  82. package/dist/index.js +31 -0
  83. package/dist/n4/deploymentMatrix.d.ts +22 -0
  84. package/dist/n4/deploymentMatrix.js +57 -0
  85. package/dist/n4/missingnessInventory.d.ts +22 -0
  86. package/dist/n4/missingnessInventory.js +72 -0
  87. package/dist/nest.d.ts +42 -0
  88. package/dist/nest.js +87 -0
  89. package/dist/processResources.d.ts +28 -0
  90. package/dist/processResources.js +31 -0
  91. package/dist/processRuntime.d.ts +35 -0
  92. package/dist/processRuntime.js +96 -0
  93. package/dist/requestContext.d.ts +21 -0
  94. package/dist/requestContext.js +33 -0
  95. package/dist/runtime.d.ts +60 -0
  96. package/dist/runtime.js +289 -0
  97. package/dist/runtimeHealth.d.ts +35 -0
  98. package/dist/runtimeHealth.js +121 -0
  99. package/dist/serverlessHints.d.ts +26 -0
  100. package/dist/serverlessHints.js +52 -0
  101. package/dist/tenantContext.d.ts +78 -0
  102. package/dist/tenantContext.js +226 -0
  103. package/dist/tenantDefaults.d.ts +9 -0
  104. package/dist/tenantDefaults.js +9 -0
  105. package/dist/types.d.ts +131 -0
  106. package/dist/types.js +2 -0
  107. package/dist/uploader.d.ts +23 -0
  108. package/dist/uploader.js +52 -0
  109. package/dist/windowId.d.ts +2 -0
  110. package/dist/windowId.js +7 -0
  111. package/examples/analyze-manifest.mjs +30 -0
  112. package/examples/bullmq-live-smoke.mjs +178 -0
  113. package/examples/bullmq-mp3-worker-gate.mjs +237 -0
  114. package/examples/express-dry-run.mjs +72 -0
  115. package/examples/process-mp3-restart-gate.mjs +135 -0
  116. package/examples/retain-dry-run.mjs +110 -0
  117. package/examples/tenant-dogfood.mjs +269 -0
  118. package/fixtures/analyze-sample/architectureNoise.ts +13 -0
  119. package/fixtures/analyze-sample/cpuHotPaths.ts +27 -0
  120. package/fixtures/analyze-sample/fanOutClient.ts +15 -0
  121. package/fixtures/analyze-sample/inferenceHotPaths.ts +22 -0
  122. package/fixtures/analyze-sample/memoryIoHotPaths.ts +21 -0
  123. package/fixtures/analyze-sample/modelLoadHotPaths.ts +11 -0
  124. package/fixtures/analyze-sample/nestedLookup.ts +15 -0
  125. package/fixtures/analyze-sample/retryClient.ts +10 -0
  126. package/fixtures/analyze-sample/securitySmell.ts +8 -0
  127. package/fixtures/analyze-sample/server.ts +11 -0
  128. package/fixtures/analyze-sample/styleOnly.ts +7 -0
  129. package/fixtures/analyze-sample/worker.ts +4 -0
  130. package/fixtures/dual-view-projection.v1.json +53 -0
  131. package/fixtures/identity-preimages.v1.json +42 -0
  132. package/fixtures/mp2_shadow_digest_golden.v1.json +47 -0
  133. package/fixtures/projection-batch-document.v1.json +170 -0
  134. package/fixtures/projection-batch.v1.json +9 -0
  135. package/fixtures/python_reference_shapes.md +24 -0
  136. package/fixtures/session_metadata_runtime_v0.json +120 -0
  137. package/fixtures/session_metadata_with_tenant.json +85 -0
  138. package/package.json +137 -0
@@ -0,0 +1,352 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.netgreener.com/contracts/resource/v1/resource-finding.schema.json",
4
+ "title": "NetGreener resource_finding_v1 (RD1 draft)",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schema_version",
9
+ "finding_id",
10
+ "project_id",
11
+ "evidence_state",
12
+ "comparison_origin",
13
+ "lifecycle_status",
14
+ "verification_outcome",
15
+ "primary_domain",
16
+ "mechanism_id",
17
+ "title",
18
+ "scope",
19
+ "lineage"
20
+ ],
21
+ "properties": {
22
+ "$schema": { "type": "string" },
23
+ "schema_version": { "const": "resource_finding_v1" },
24
+ "finding_id": { "type": "string", "minLength": 8, "maxLength": 128 },
25
+ "project_id": { "type": "string", "minLength": 1, "maxLength": 64 },
26
+ "evidence_state": {
27
+ "enum": ["candidate", "observed", "regression_supported", "causal_supported"]
28
+ },
29
+ "comparison_origin": {
30
+ "enum": ["none", "ci", "production", "benchmark", "manual_experiment"]
31
+ },
32
+ "lifecycle_status": {
33
+ "enum": ["pending_admission", "open", "acknowledged", "resolved", "dismissed", "reopened"]
34
+ },
35
+ "verification_outcome": {
36
+ "enum": [
37
+ "not_run",
38
+ "verified_improvement",
39
+ "no_material_change",
40
+ "regressed",
41
+ "inconclusive",
42
+ "not_applicable",
43
+ "failed_guardrail"
44
+ ]
45
+ },
46
+ "primary_domain": {
47
+ "enum": [
48
+ "compute.cpu",
49
+ "compute.gpu",
50
+ "memory.host",
51
+ "memory.gpu",
52
+ "storage.io",
53
+ "network.io",
54
+ "external_api"
55
+ ]
56
+ },
57
+ "secondary_domains": {
58
+ "type": "array",
59
+ "uniqueItems": true,
60
+ "items": {
61
+ "enum": [
62
+ "compute.cpu",
63
+ "compute.gpu",
64
+ "memory.host",
65
+ "memory.gpu",
66
+ "storage.io",
67
+ "network.io",
68
+ "external_api"
69
+ ]
70
+ }
71
+ },
72
+ "mechanism_id": { "type": "string", "pattern": "^[a-z][a-z0-9_]{2,63}$" },
73
+ "title": { "type": "string", "minLength": 8, "maxLength": 200 },
74
+ "scope": { "$ref": "#/$defs/scope" },
75
+ "workload": { "$ref": "#/$defs/workload" },
76
+ "observed": { "$ref": "#/$defs/metricPoint" },
77
+ "baseline": { "$ref": "#/$defs/baseline" },
78
+ "impact": { "$ref": "#/$defs/impact" },
79
+ "materiality": { "$ref": "#/$defs/materiality" },
80
+ "evidence": {
81
+ "type": "array",
82
+ "items": { "$ref": "#/$defs/evidenceItem" }
83
+ },
84
+ "confidence": { "$ref": "#/$defs/confidence" },
85
+ "optimization": { "$ref": "#/$defs/optimization" },
86
+ "lineage": { "$ref": "#/$defs/lineage" },
87
+ "compatibility_fingerprint": { "$ref": "#/$defs/compatibilityFingerprint" }
88
+ },
89
+ "allOf": [
90
+ {
91
+ "if": { "properties": { "evidence_state": { "const": "candidate" } } },
92
+ "then": {
93
+ "properties": {
94
+ "comparison_origin": { "const": "none" },
95
+ "lifecycle_status": { "const": "pending_admission" },
96
+ "verification_outcome": { "const": "not_run" }
97
+ }
98
+ }
99
+ },
100
+ {
101
+ "if": {
102
+ "properties": {
103
+ "evidence_state": { "enum": ["regression_supported", "causal_supported"] }
104
+ }
105
+ },
106
+ "then": {
107
+ "required": ["workload", "observed", "baseline", "impact", "materiality", "evidence", "confidence", "optimization"]
108
+ }
109
+ }
110
+ ],
111
+ "$defs": {
112
+ "scope": {
113
+ "type": "object",
114
+ "additionalProperties": false,
115
+ "required": ["kind", "id", "language"],
116
+ "properties": {
117
+ "kind": { "enum": ["service_unit", "task", "process", "ci_step", "script", "function"] },
118
+ "id": { "type": "string", "minLength": 1, "maxLength": 200 },
119
+ "language": { "type": "string", "pattern": "^[a-z][a-z0-9+-]{0,31}$" },
120
+ "runtime": { "type": "string", "maxLength": 80 },
121
+ "release_id": { "type": "string", "maxLength": 120 },
122
+ "git_sha": { "type": "string", "maxLength": 64 },
123
+ "source_locations": {
124
+ "type": "array",
125
+ "items": {
126
+ "type": "object",
127
+ "additionalProperties": false,
128
+ "required": ["path"],
129
+ "properties": {
130
+ "path": { "type": "string", "maxLength": 512 },
131
+ "start_line": { "type": "integer", "minimum": 1 },
132
+ "end_line": { "type": "integer", "minimum": 1 },
133
+ "symbol": { "type": "string", "maxLength": 120 }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ },
139
+ "workload": {
140
+ "type": "object",
141
+ "additionalProperties": false,
142
+ "required": ["functional_unit"],
143
+ "properties": {
144
+ "functional_unit": { "type": "string", "minLength": 1, "maxLength": 80 },
145
+ "units": { "type": "number", "minimum": 0 },
146
+ "input_fingerprint": { "type": "string", "maxLength": 128 },
147
+ "quality_guards": {
148
+ "type": "array",
149
+ "items": {
150
+ "type": "object",
151
+ "additionalProperties": false,
152
+ "required": ["metric", "operator", "threshold", "passed"],
153
+ "properties": {
154
+ "metric": { "type": "string" },
155
+ "operator": { "enum": [">=", "<=", "=="] },
156
+ "threshold": { "type": "number" },
157
+ "observed": { "type": ["number", "null"] },
158
+ "passed": { "type": "boolean" }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ },
164
+ "metricPoint": {
165
+ "type": "object",
166
+ "additionalProperties": false,
167
+ "required": ["metric", "value", "unit"],
168
+ "properties": {
169
+ "metric": { "type": "string", "minLength": 1 },
170
+ "value": { "type": "number" },
171
+ "unit": { "type": "string", "minLength": 1 },
172
+ "window_start": { "type": "string", "format": "date-time" },
173
+ "window_end": { "type": "string", "format": "date-time" }
174
+ }
175
+ },
176
+ "baseline": {
177
+ "type": "object",
178
+ "additionalProperties": false,
179
+ "required": ["value", "unit", "sample_count", "cohort_key", "statistic"],
180
+ "properties": {
181
+ "value": { "type": "number" },
182
+ "unit": { "type": "string" },
183
+ "sample_count": { "type": "integer", "minimum": 1 },
184
+ "cohort_key": { "type": "string", "minLength": 8 },
185
+ "statistic": { "enum": ["median", "mean", "p50", "robust_mean"] },
186
+ "uncertainty": { "$ref": "#/$defs/uncertainty" }
187
+ }
188
+ },
189
+ "uncertainty": {
190
+ "type": "object",
191
+ "additionalProperties": false,
192
+ "required": ["method", "low", "high", "unit"],
193
+ "properties": {
194
+ "method": { "type": "string" },
195
+ "low": { "type": "number" },
196
+ "high": { "type": "number" },
197
+ "unit": { "type": "string" }
198
+ }
199
+ },
200
+ "impact": {
201
+ "type": "object",
202
+ "additionalProperties": false,
203
+ "required": ["impact_state"],
204
+ "properties": {
205
+ "normalized_delta": { "$ref": "#/$defs/metricPoint" },
206
+ "projected_excess": { "$ref": "#/$defs/metricPoint" },
207
+ "relative_delta_fraction": { "type": ["number", "null"] },
208
+ "recoverable_low": { "type": ["number", "null"] },
209
+ "recoverable_likely": { "type": ["number", "null"] },
210
+ "recoverable_high": { "type": ["number", "null"] },
211
+ "cost_usd": { "type": ["number", "null"] },
212
+ "energy_kwh": { "type": ["number", "null"] },
213
+ "impact_state": { "enum": ["unknown", "modeled", "measured"] }
214
+ }
215
+ },
216
+ "materiality": {
217
+ "type": "object",
218
+ "additionalProperties": false,
219
+ "required": ["policy_id", "decision"],
220
+ "properties": {
221
+ "policy_id": { "type": "string" },
222
+ "absolute_floor_passed": { "type": "boolean" },
223
+ "relative_floor_passed": { "type": "boolean" },
224
+ "sample_floor_passed": { "type": "boolean" },
225
+ "decision": { "enum": ["pending", "material", "not_material", "abstain"] }
226
+ }
227
+ },
228
+ "evidenceItem": {
229
+ "type": "object",
230
+ "additionalProperties": false,
231
+ "required": ["evidence_id", "kind", "captured_at", "value_state"],
232
+ "properties": {
233
+ "evidence_id": { "type": "string", "minLength": 8 },
234
+ "kind": {
235
+ "enum": [
236
+ "source",
237
+ "observation",
238
+ "profile",
239
+ "ci_comparison",
240
+ "runtime_baseline",
241
+ "provider_usage",
242
+ "billing",
243
+ "benchmark",
244
+ "user_disposition"
245
+ ]
246
+ },
247
+ "captured_at": { "type": "string", "format": "date-time" },
248
+ "scope": {
249
+ "type": "object",
250
+ "additionalProperties": false,
251
+ "required": ["kind", "id"],
252
+ "properties": {
253
+ "kind": { "type": "string" },
254
+ "id": { "type": "string" }
255
+ }
256
+ },
257
+ "temporality": { "enum": ["delta", "cumulative", "instant"] },
258
+ "interval": {
259
+ "type": "object",
260
+ "additionalProperties": false,
261
+ "properties": {
262
+ "start": { "type": "string", "format": "date-time" },
263
+ "end": { "type": "string", "format": "date-time" }
264
+ }
265
+ },
266
+ "observation_ids": { "type": "array", "items": { "type": "string" } },
267
+ "metric": { "type": "string" },
268
+ "value": { "type": "number" },
269
+ "unit": { "type": "string" },
270
+ "aggregation": { "type": "string" },
271
+ "functional_unit": { "type": "string" },
272
+ "method": { "type": "string" },
273
+ "method_version": { "type": "string" },
274
+ "evidence_grade": { "type": "string" },
275
+ "coverage_fraction": { "type": "number", "minimum": 0, "maximum": 1 },
276
+ "dropped_event_state": {
277
+ "type": "object",
278
+ "additionalProperties": false,
279
+ "required": ["dropped", "count"],
280
+ "properties": {
281
+ "dropped": { "type": "boolean" },
282
+ "count": { "type": "integer", "minimum": 0 }
283
+ }
284
+ },
285
+ "uncertainty": { "$ref": "#/$defs/uncertainty" },
286
+ "value_state": { "enum": ["observed", "estimated", "derived", "missing"] }
287
+ }
288
+ },
289
+ "confidence": {
290
+ "type": "object",
291
+ "additionalProperties": false,
292
+ "required": [
293
+ "detection_confidence",
294
+ "attribution_confidence",
295
+ "measurement_confidence",
296
+ "comparison_confidence",
297
+ "optimization_confidence",
298
+ "causal_confidence"
299
+ ],
300
+ "properties": {
301
+ "detection_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
302
+ "attribution_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
303
+ "measurement_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
304
+ "comparison_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
305
+ "optimization_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
306
+ "causal_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
307
+ "reason_codes": { "type": "array", "items": { "type": "string" } }
308
+ }
309
+ },
310
+ "optimization": {
311
+ "type": "object",
312
+ "additionalProperties": false,
313
+ "required": ["hypothesis_id", "summary", "risk"],
314
+ "properties": {
315
+ "hypothesis_id": { "type": "string", "minLength": 4 },
316
+ "summary": { "type": "string", "minLength": 8, "maxLength": 400 },
317
+ "verification_plan": { "type": "object" },
318
+ "risk": { "enum": ["low", "medium", "high"] }
319
+ }
320
+ },
321
+ "lineage": {
322
+ "type": "object",
323
+ "additionalProperties": false,
324
+ "required": ["detector_id", "detector_version", "policy_version"],
325
+ "properties": {
326
+ "detector_id": { "type": "string", "minLength": 1 },
327
+ "detector_version": { "type": "string", "minLength": 1 },
328
+ "policy_version": { "type": "string", "minLength": 1 },
329
+ "observation_ids": { "type": "array", "items": { "type": "string" } },
330
+ "benchmark_result_ids": { "type": "array", "items": { "type": "string" } }
331
+ }
332
+ },
333
+ "compatibilityFingerprint": {
334
+ "type": "object",
335
+ "additionalProperties": false,
336
+ "properties": {
337
+ "language": { "type": "string" },
338
+ "runtime": { "type": "string" },
339
+ "framework": { "type": "string" },
340
+ "dependency_lock_digest": { "type": "string" },
341
+ "hardware_model": { "type": "string" },
342
+ "cpu_quota": { "type": "string" },
343
+ "memory_limit": { "type": "string" },
344
+ "workload_id": { "type": "string" },
345
+ "functional_unit": { "type": "string" },
346
+ "environment": { "type": "string" },
347
+ "release_id": { "type": "string" },
348
+ "ci_workflow_sha": { "type": "string" }
349
+ }
350
+ }
351
+ }
352
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * MP4 Analyze scaffold — JS/TS service discovery (experimental).
3
+ *
4
+ * Twin of Python ``netgreener.service_discovery`` for Node ecosystems.
5
+ * Heuristic / regex extraction only (no TypeScript AST yet). Additive: does
6
+ * **not** wire into CLI upload, VSCE, or the RunSession pipeline.
7
+ *
8
+ * Canonical ``service_unit`` keys match runtime:
9
+ * - endpoints: ``"<METHOD> <path>"``
10
+ * - tasks: ``"task:<name>"``
11
+ */
12
+ export type ServiceEndpointUnit = {
13
+ service_unit: string;
14
+ unit_type: 'endpoint';
15
+ method: string;
16
+ path: string;
17
+ handler: string;
18
+ file: string;
19
+ line: number;
20
+ end_line: number;
21
+ is_async: boolean;
22
+ dynamic: boolean;
23
+ prefix_resolved: boolean;
24
+ /** Heuristic extractor confidence — not measured runtime evidence. */
25
+ evidence: 'heuristic_source';
26
+ };
27
+ export type ServiceTaskUnit = {
28
+ service_unit: string;
29
+ unit_type: 'task';
30
+ name: string;
31
+ handler: string;
32
+ file: string;
33
+ line: number;
34
+ end_line: number;
35
+ evidence: 'heuristic_source';
36
+ };
37
+ export type ServiceFileExtraction = {
38
+ frameworks: string[];
39
+ endpoints: ServiceEndpointUnit[];
40
+ tasks: ServiceTaskUnit[];
41
+ };
42
+ export type ServiceManifestV0 = {
43
+ schema: 'service_manifest_v0';
44
+ contract_status: 'mp4_scaffold';
45
+ framework: string | null;
46
+ frameworks_detected: string[];
47
+ entry_files: string[];
48
+ endpoints: ServiceEndpointUnit[];
49
+ tasks: ServiceTaskUnit[];
50
+ stats: {
51
+ endpoints: number;
52
+ tasks: number;
53
+ files_with_units: number;
54
+ };
55
+ notes: string[];
56
+ };
57
+ /** Frameworks whose imports / requires appear in ``source``. */
58
+ export declare function detectFrameworks(source: string): string[];
59
+ /**
60
+ * Extract service units from one JS/TS source string.
61
+ * Never throws on bad source — returns empty collections.
62
+ */
63
+ export declare function extractServiceUnits(sourceCode: string, relPath?: string): ServiceFileExtraction;
64
+ /** Project-level ``service_manifest_v0`` from ``{relPath: source}``. */
65
+ export declare function buildServiceManifest(fileSources: Record<string, string>): ServiceManifestV0;
66
+ export declare function isServiceProject(manifest: ServiceManifestV0): boolean;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * MP4 Analyze scaffold — JS/TS service discovery (experimental).
3
+ *
4
+ * Twin of Python ``netgreener.service_discovery`` for Node ecosystems.
5
+ * Heuristic / regex extraction only (no TypeScript AST yet). Additive: does
6
+ * **not** wire into CLI upload, VSCE, or the RunSession pipeline.
7
+ *
8
+ * Canonical ``service_unit`` keys match runtime:
9
+ * - endpoints: ``"<METHOD> <path>"``
10
+ * - tasks: ``"task:<name>"``
11
+ */
12
+ const FRAMEWORK_IMPORT_HINTS = [
13
+ { id: 'express', pattern: /\bfrom\s+['"]express['"]|\brequire\(\s*['"]express['"]\s*\)/ },
14
+ { id: 'fastify', pattern: /\bfrom\s+['"]fastify['"]|\brequire\(\s*['"]fastify['"]\s*\)/ },
15
+ {
16
+ id: 'nest',
17
+ pattern: /\bfrom\s+['"]@nestjs\/(common|core|platform-express|platform-fastify)['"]/,
18
+ },
19
+ { id: 'bullmq', pattern: /\bfrom\s+['"]bullmq['"]|\brequire\(\s*['"]bullmq['"]\s*\)/ },
20
+ { id: 'next', pattern: /\bfrom\s+['"]next(\/|['"])|\brequire\(\s*['"]next['"]\s*\)/ },
21
+ ];
22
+ const HTTP_ROUTE = /\b(?:app|router|server|fastify|instance)\.(get|post|put|delete|patch|head|options)\(\s*['"`]([^'"`]+)['"`]/gi;
23
+ const NEST_MAPPING = /@(Get|Post|Put|Delete|Patch|Head|Options)\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g;
24
+ const BULLMQ_QUEUE_ADD = /\.add\(\s*['"`]([A-Za-z0-9_.:-]+)['"`]/g;
25
+ function lineOf(source, index) {
26
+ if (index <= 0)
27
+ return 1;
28
+ let line = 1;
29
+ for (let i = 0; i < index && i < source.length; i += 1) {
30
+ if (source.charCodeAt(i) === 10)
31
+ line += 1;
32
+ }
33
+ return line;
34
+ }
35
+ function looksDynamic(path) {
36
+ return path.includes(':') || path.includes('*') || path.includes('{');
37
+ }
38
+ function normalizePath(path) {
39
+ if (!path)
40
+ return '/';
41
+ return path.startsWith('/') ? path : `/${path}`;
42
+ }
43
+ /** Frameworks whose imports / requires appear in ``source``. */
44
+ export function detectFrameworks(source) {
45
+ const found = [];
46
+ for (const hint of FRAMEWORK_IMPORT_HINTS) {
47
+ if (hint.pattern.test(source) && !found.includes(hint.id)) {
48
+ found.push(hint.id);
49
+ }
50
+ }
51
+ return found;
52
+ }
53
+ function collectHttpRoutes(source, relPath) {
54
+ const endpoints = [];
55
+ const re = new RegExp(HTTP_ROUTE.source, HTTP_ROUTE.flags);
56
+ let match;
57
+ while ((match = re.exec(source)) !== null) {
58
+ const method = (match[1] || 'get').toUpperCase();
59
+ const path = normalizePath(match[2] || '/');
60
+ const start = lineOf(source, match.index);
61
+ endpoints.push({
62
+ service_unit: `${method} ${path}`,
63
+ unit_type: 'endpoint',
64
+ method,
65
+ path,
66
+ handler: '<heuristic>',
67
+ file: relPath,
68
+ line: start,
69
+ end_line: start,
70
+ is_async: false,
71
+ dynamic: looksDynamic(path),
72
+ prefix_resolved: false,
73
+ evidence: 'heuristic_source',
74
+ });
75
+ }
76
+ return endpoints;
77
+ }
78
+ function collectNestRoutes(source, relPath) {
79
+ const endpoints = [];
80
+ const re = new RegExp(NEST_MAPPING.source, NEST_MAPPING.flags);
81
+ let match;
82
+ while ((match = re.exec(source)) !== null) {
83
+ const method = (match[1] || 'Get').toUpperCase();
84
+ const pathRaw = match[2];
85
+ const path = pathRaw && pathRaw.length ? normalizePath(pathRaw) : '/';
86
+ const start = lineOf(source, match.index);
87
+ endpoints.push({
88
+ service_unit: `${method} ${path}`,
89
+ unit_type: 'endpoint',
90
+ method,
91
+ path,
92
+ handler: '<nest-heuristic>',
93
+ file: relPath,
94
+ line: start,
95
+ end_line: start,
96
+ is_async: false,
97
+ dynamic: looksDynamic(path),
98
+ prefix_resolved: false,
99
+ evidence: 'heuristic_source',
100
+ });
101
+ }
102
+ return endpoints;
103
+ }
104
+ function collectBullMqTasks(source, relPath) {
105
+ const tasks = [];
106
+ const seen = new Set();
107
+ const re = new RegExp(BULLMQ_QUEUE_ADD.source, BULLMQ_QUEUE_ADD.flags);
108
+ let match;
109
+ while ((match = re.exec(source)) !== null) {
110
+ const name = String(match[1] || '').trim();
111
+ if (!name || seen.has(name))
112
+ continue;
113
+ seen.add(name);
114
+ const start = lineOf(source, match.index);
115
+ tasks.push({
116
+ service_unit: `task:${name}`,
117
+ unit_type: 'task',
118
+ name,
119
+ handler: '<bullmq-heuristic>',
120
+ file: relPath,
121
+ line: start,
122
+ end_line: start,
123
+ evidence: 'heuristic_source',
124
+ });
125
+ }
126
+ return tasks;
127
+ }
128
+ function dedupeUnits(items) {
129
+ const seen = new Set();
130
+ const out = [];
131
+ for (const item of items) {
132
+ const key = `${item.service_unit}|${item.file}|${item.line}`;
133
+ if (seen.has(key))
134
+ continue;
135
+ seen.add(key);
136
+ out.push(item);
137
+ }
138
+ return out;
139
+ }
140
+ /**
141
+ * Extract service units from one JS/TS source string.
142
+ * Never throws on bad source — returns empty collections.
143
+ */
144
+ export function extractServiceUnits(sourceCode, relPath = '<unknown>') {
145
+ const source = String(sourceCode || '');
146
+ const frameworks = detectFrameworks(source);
147
+ const endpoints = [
148
+ ...collectHttpRoutes(source, relPath),
149
+ ...(frameworks.includes('nest') ? collectNestRoutes(source, relPath) : []),
150
+ ];
151
+ const tasks = frameworks.includes('bullmq') || /\bbullmq\b/.test(source)
152
+ ? collectBullMqTasks(source, relPath)
153
+ : [];
154
+ return {
155
+ frameworks,
156
+ endpoints: dedupeUnits(endpoints),
157
+ tasks: dedupeUnits(tasks),
158
+ };
159
+ }
160
+ /** Project-level ``service_manifest_v0`` from ``{relPath: source}``. */
161
+ export function buildServiceManifest(fileSources) {
162
+ const frameworksDetected = [];
163
+ const endpoints = [];
164
+ const tasks = [];
165
+ const entryFiles = new Set();
166
+ for (const [relPath, source] of Object.entries(fileSources)) {
167
+ const extracted = extractServiceUnits(source, relPath);
168
+ for (const fw of extracted.frameworks) {
169
+ if (!frameworksDetected.includes(fw))
170
+ frameworksDetected.push(fw);
171
+ }
172
+ if (extracted.endpoints.length || extracted.tasks.length) {
173
+ entryFiles.add(relPath);
174
+ }
175
+ endpoints.push(...extracted.endpoints);
176
+ tasks.push(...extracted.tasks);
177
+ }
178
+ let framework = null;
179
+ if (endpoints.length && frameworksDetected.includes('express'))
180
+ framework = 'express';
181
+ else if (endpoints.length && frameworksDetected.includes('fastify'))
182
+ framework = 'fastify';
183
+ else if (endpoints.length && frameworksDetected.includes('nest'))
184
+ framework = 'nest';
185
+ else if (tasks.length && frameworksDetected.includes('bullmq'))
186
+ framework = 'bullmq';
187
+ else if (frameworksDetected.length)
188
+ framework = frameworksDetected[0];
189
+ return {
190
+ schema: 'service_manifest_v0',
191
+ contract_status: 'mp4_scaffold',
192
+ framework,
193
+ frameworks_detected: frameworksDetected,
194
+ entry_files: [...entryFiles].sort(),
195
+ endpoints,
196
+ tasks,
197
+ stats: {
198
+ endpoints: endpoints.length,
199
+ tasks: tasks.length,
200
+ files_with_units: entryFiles.size,
201
+ },
202
+ notes: [
203
+ 'MP4 scaffold: heuristic JS/TS discovery only — not TypeScript AST, not CLI/VSCE wired, not resource findings',
204
+ 'Does not modify RunSession upload or runtime metering pipelines',
205
+ ],
206
+ };
207
+ }
208
+ export function isServiceProject(manifest) {
209
+ return Boolean(manifest.stats.endpoints || manifest.stats.tasks);
210
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Heuristic JS/TS twin of Python ``unbounded_asyncio_gather_with_io``.
3
+ *
4
+ * RD0 disposition → mechanism ``unbounded_parallelism`` / ``network.io``.
5
+ * Flags ``Promise.all(…map…fetch)`` style fan-out without an obvious concurrency cap.
6
+ */
7
+ import { type ResourceCandidateHit } from './resourceFindingCandidate.js';
8
+ /**
9
+ * Detect unbounded Promise.all + API fan-out in one source file.
10
+ */
11
+ export declare function detectUnboundedParallelismHits(source: string, relPath: string): ResourceCandidateHit[];