@datasynx/agentic-ai-cartography 2.5.0 → 2.7.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.
package/dist/api-bin.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  parseApiArgs,
4
4
  startApi
5
- } from "./chunk-NQXZUWOI.js";
6
- import "./chunk-GA4427LB.js";
5
+ } from "./chunk-TBPGFEMQ.js";
6
+ import "./chunk-YVV6NIT2.js";
7
7
  import "./chunk-QQOQBE2A.js";
8
8
  import "./chunk-2SZ5QHGH.js";
9
9
 
@@ -3,6 +3,7 @@ import {
3
3
  AuthorizationError,
4
4
  CartographyDB,
5
5
  RulesetSchema,
6
+ SCHEMA_VERSION,
6
7
  SqliteCredentialStore,
7
8
  assertSafeBind,
8
9
  authorize,
@@ -24,7 +25,7 @@ import {
24
25
  sanitizeUntrusted,
25
26
  stableStringify,
26
27
  stripSensitive
27
- } from "./chunk-GA4427LB.js";
28
+ } from "./chunk-YVV6NIT2.js";
28
29
  import {
29
30
  EdgeSchema,
30
31
  NODE_TYPES,
@@ -1538,7 +1539,7 @@ async function executeNlQuery(db, sessionId, search, intent, opts = {}) {
1538
1539
 
1539
1540
  // src/mcp/server.ts
1540
1541
  var SERVER_NAME = "cartography";
1541
- var SERVER_VERSION = "2.5.0";
1542
+ var SERVER_VERSION = "2.7.0";
1542
1543
  var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
1543
1544
  var DATA_TYPES = NODE_TYPE_GROUPS.data;
1544
1545
  var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
@@ -2106,6 +2107,16 @@ async function runHttp(factory, opts = {}) {
2106
2107
  const httpServer = http.createServer(async (req, res) => {
2107
2108
  try {
2108
2109
  const url = req.url ?? "";
2110
+ const probePath = new URL(url || "/", "http://probe").pathname;
2111
+ if (probePath === "/healthz") {
2112
+ res.writeHead(200, { "content-type": "application/json" }).end('{"status":"ok"}');
2113
+ return;
2114
+ }
2115
+ if (probePath === "/readyz") {
2116
+ const r = opts.readiness ? opts.readiness() : { ready: true };
2117
+ res.writeHead(r.ready ? 200 : 503, { "content-type": "application/json" }).end(JSON.stringify({ status: r.ready ? "ready" : "unready" }));
2118
+ return;
2119
+ }
2109
2120
  const isIngest = url.startsWith("/ingest") && opts.onIngest !== void 0;
2110
2121
  if (!url.startsWith("/mcp") && !isIngest) {
2111
2122
  res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
@@ -2133,7 +2144,7 @@ async function runHttp(factory, opts = {}) {
2133
2144
  return;
2134
2145
  }
2135
2146
  const out = onIngest(value);
2136
- res.writeHead(out.status, { "content-type": "application/json" }).end(JSON.stringify(out.body));
2147
+ res.writeHead(out.status, { "content-type": "application/json", ...out.headers ?? {} }).end(JSON.stringify(out.body));
2137
2148
  return;
2138
2149
  }
2139
2150
  const sessionId = req.headers["mcp-session-id"];
@@ -2461,7 +2472,7 @@ var ContributorSchema = z3.object({
2461
2472
  });
2462
2473
  var IngestEnvelopeSchema = z3.object({
2463
2474
  schemaVersion: z3.literal(INGEST_SCHEMA_VERSION),
2464
- org: z3.string().min(1).optional(),
2475
+ org: z3.string().min(1).max(128).optional(),
2465
2476
  items: z3.array(z3.object({
2466
2477
  contentHash: z3.string(),
2467
2478
  kind: z3.enum(["node", "edge"]),
@@ -2549,6 +2560,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
2549
2560
 
2550
2561
  // src/central/server.ts
2551
2562
  function createIngestHandler(store, opts = {}) {
2563
+ const quota = opts.quota;
2552
2564
  return (body) => {
2553
2565
  const parsed = IngestEnvelopeSchema.safeParse(body);
2554
2566
  if (!parsed.success) {
@@ -2556,6 +2568,14 @@ function createIngestHandler(store, opts = {}) {
2556
2568
  logWarn("ingest: rejected invalid envelope", { issues });
2557
2569
  return { status: 400, body: { error: "invalid envelope", issues } };
2558
2570
  }
2571
+ if (quota) {
2572
+ const org = normalizeTenant(parsed.data.org ?? opts.defaultOrg);
2573
+ const decision = quota.take(org);
2574
+ if (!decision.allowed) {
2575
+ logWarn("ingest: rate limited", { org, retryAfterSec: decision.retryAfterSec });
2576
+ return { status: 429, body: { error: "too many requests" }, headers: { "retry-after": String(decision.retryAfterSec) } };
2577
+ }
2578
+ }
2559
2579
  try {
2560
2580
  const result = ingestEnvelope(store, parsed.data, opts);
2561
2581
  return { status: 200, body: result };
@@ -2566,7 +2586,41 @@ function createIngestHandler(store, opts = {}) {
2566
2586
  };
2567
2587
  }
2568
2588
 
2589
+ // src/central/quota.ts
2590
+ var DEFAULT_INGEST_QUOTA = { capacity: 120, refillMs: 6e4 };
2591
+ var MAX_KEYS = 1e4;
2592
+ var RateLimiter = class {
2593
+ constructor(cfg = DEFAULT_INGEST_QUOTA, now = () => Date.now()) {
2594
+ this.cfg = cfg;
2595
+ this.now = now;
2596
+ }
2597
+ buckets = /* @__PURE__ */ new Map();
2598
+ /** Consume one token for `key`. Returns whether the request is allowed (+ Retry-After when not). */
2599
+ take(key) {
2600
+ const t = this.now();
2601
+ const ratePerMs = this.cfg.capacity / this.cfg.refillMs;
2602
+ let b = this.buckets.get(key);
2603
+ if (!b) {
2604
+ if (this.buckets.size >= MAX_KEYS) {
2605
+ const oldest = this.buckets.keys().next().value;
2606
+ if (oldest !== void 0) this.buckets.delete(oldest);
2607
+ }
2608
+ b = { tokens: this.cfg.capacity, last: t };
2609
+ this.buckets.set(key, b);
2610
+ }
2611
+ b.tokens = Math.min(this.cfg.capacity, b.tokens + Math.max(0, t - b.last) * ratePerMs);
2612
+ b.last = t;
2613
+ if (b.tokens >= 1) {
2614
+ b.tokens -= 1;
2615
+ return { allowed: true, retryAfterSec: 0 };
2616
+ }
2617
+ const waitMs = (1 - b.tokens) / ratePerMs;
2618
+ return { allowed: false, retryAfterSec: Math.max(1, Math.ceil(waitMs / 1e3)) };
2619
+ }
2620
+ };
2621
+
2569
2622
  // src/mcp/start.ts
2623
+ var EXPECTED_USER_VERSION = SCHEMA_VERSION;
2570
2624
  function parseMcpArgs(argv) {
2571
2625
  const opts = {};
2572
2626
  for (let i = 0; i < argv.length; i++) {
@@ -2626,11 +2680,21 @@ async function startMcp(opts = {}) {
2626
2680
  if (serverMode) {
2627
2681
  const store = new SqliteStoreBackend(db);
2628
2682
  const anonMode = opts.anonMode ?? "reject";
2629
- onIngest = createIngestHandler(store, { anonMode, defaultOrg: tenant });
2683
+ const quota = new RateLimiter(opts.ingestQuota ?? DEFAULT_INGEST_QUOTA);
2684
+ onIngest = createIngestHandler(store, { anonMode, defaultOrg: tenant, quota });
2630
2685
  }
2686
+ const readiness = () => {
2687
+ try {
2688
+ const v = db.rawConnection().pragma("user_version", { simple: true });
2689
+ return { ready: v === EXPECTED_USER_VERSION, detail: { schema: v } };
2690
+ } catch (err) {
2691
+ return { ready: false, detail: { error: err instanceof Error ? err.message : String(err) } };
2692
+ }
2693
+ };
2631
2694
  await runHttp(factory, {
2632
2695
  port,
2633
2696
  host,
2697
+ readiness,
2634
2698
  auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
2635
2699
  defaultTenant: tenant,
2636
2700
  ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
@@ -2659,4 +2723,4 @@ export {
2659
2723
  parseMcpArgs,
2660
2724
  startMcp
2661
2725
  };
2662
- //# sourceMappingURL=chunk-RYQ4KQCK.js.map
2726
+ //# sourceMappingURL=chunk-HLWNO3RF.js.map