neotoma 0.19.0 → 0.20.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 (68) hide show
  1. package/dist/actions.d.ts +58 -0
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +138 -23
  4. package/dist/actions.js.map +1 -1
  5. package/dist/cli/index.d.ts +14 -0
  6. package/dist/cli/index.d.ts.map +1 -1
  7. package/dist/cli/index.js +87 -4
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/docs/developer/cli_agent_instructions.md +37 -0
  10. package/dist/docs/developer/mcp/instructions.md +3 -2
  11. package/dist/docs/subsystems/errors.md +3 -0
  12. package/dist/docs/testing/automated_test_catalog.md +20 -12
  13. package/dist/repositories/sqlite/sqlite_client.d.ts.map +1 -1
  14. package/dist/repositories/sqlite/sqlite_client.js +7 -0
  15. package/dist/repositories/sqlite/sqlite_client.js.map +1 -1
  16. package/dist/server.d.ts.map +1 -1
  17. package/dist/server.js +59 -1
  18. package/dist/server.js.map +1 -1
  19. package/dist/services/entity_cursor.d.ts +57 -0
  20. package/dist/services/entity_cursor.d.ts.map +1 -0
  21. package/dist/services/entity_cursor.js +104 -0
  22. package/dist/services/entity_cursor.js.map +1 -0
  23. package/dist/services/entity_queries.d.ts +32 -0
  24. package/dist/services/entity_queries.d.ts.map +1 -1
  25. package/dist/services/entity_queries.js +64 -2
  26. package/dist/services/entity_queries.js.map +1 -1
  27. package/dist/services/entity_query_limits.d.ts +20 -0
  28. package/dist/services/entity_query_limits.d.ts.map +1 -0
  29. package/dist/services/entity_query_limits.js +25 -0
  30. package/dist/services/entity_query_limits.js.map +1 -0
  31. package/dist/services/interpretation.d.ts.map +1 -1
  32. package/dist/services/interpretation.js +17 -0
  33. package/dist/services/interpretation.js.map +1 -1
  34. package/dist/services/issues/issue_operations.d.ts +4 -0
  35. package/dist/services/issues/issue_operations.d.ts.map +1 -1
  36. package/dist/services/issues/issue_operations.js +18 -0
  37. package/dist/services/issues/issue_operations.js.map +1 -1
  38. package/dist/services/issues/neotoma_client.d.ts.map +1 -1
  39. package/dist/services/issues/neotoma_client.js +10 -5
  40. package/dist/services/issues/neotoma_client.js.map +1 -1
  41. package/dist/services/oauth_key_gate.d.ts +15 -1
  42. package/dist/services/oauth_key_gate.d.ts.map +1 -1
  43. package/dist/services/oauth_key_gate.js +42 -3
  44. package/dist/services/oauth_key_gate.js.map +1 -1
  45. package/dist/services/schema_registry.d.ts +12 -0
  46. package/dist/services/schema_registry.d.ts.map +1 -1
  47. package/dist/services/schema_registry.js +19 -0
  48. package/dist/services/schema_registry.js.map +1 -1
  49. package/dist/services/snapshot_computation.d.ts +18 -0
  50. package/dist/services/snapshot_computation.d.ts.map +1 -1
  51. package/dist/services/snapshot_computation.js +27 -3
  52. package/dist/services/snapshot_computation.js.map +1 -1
  53. package/dist/shared/action_handlers/entity_handlers.d.ts +9 -0
  54. package/dist/shared/action_handlers/entity_handlers.d.ts.map +1 -1
  55. package/dist/shared/action_handlers/entity_handlers.js +9 -2
  56. package/dist/shared/action_handlers/entity_handlers.js.map +1 -1
  57. package/dist/shared/action_schemas.d.ts +89 -20
  58. package/dist/shared/action_schemas.d.ts.map +1 -1
  59. package/dist/shared/action_schemas.js +154 -1
  60. package/dist/shared/action_schemas.js.map +1 -1
  61. package/dist/shared/openapi_types.d.ts +119 -5
  62. package/dist/shared/openapi_types.d.ts.map +1 -1
  63. package/dist/tool_definitions.d.ts.map +1 -1
  64. package/dist/tool_definitions.js +24 -4
  65. package/dist/tool_definitions.js.map +1 -1
  66. package/docs/developer/mcp/instructions.md +3 -2
  67. package/openapi.yaml +166 -5
  68. package/package.json +2 -1
package/dist/actions.d.ts CHANGED
@@ -2,6 +2,7 @@ import express from "express";
2
2
  import { type GuestIdentity } from "./services/access_policy.js";
3
3
  import { type AgentIdentity } from "./crypto/agent_identity.js";
4
4
  import { type NeotomaSandboxModeName } from "./services/sandbox_mode.js";
5
+ import { OAuthKeySessionStore } from "./services/oauth_key_gate.js";
5
6
  import { type StoreInterpretationInput } from "./shared/action_schemas.js";
6
7
  /** Exposed for the /me route and any other surface that needs the boot-time mode verdict. */
7
8
  export declare function getResolvedServerMode(): NeotomaSandboxModeName | null;
@@ -76,6 +77,51 @@ export declare function isTrustedProxyIP(ip: string, env?: NodeJS.ProcessEnv): b
76
77
  * entirely and remains valid for single-host deployments without a tunnel.
77
78
  */
78
79
  export declare function isLocalRequest(req: express.Request): boolean;
80
+ /** Exported for tests asserting the cookie maxAge and the server-side session
81
+ * expiry agree — the invariant setOAuthKeySessionCookie's docstring names. */
82
+ export declare const oauthKeySessions: OAuthKeySessionStore;
83
+ /**
84
+ * Lifetime of a SIGN-IN session (Google). Distinct from the key-entry gate's
85
+ * short default: a user who signs in expects to stay signed in, whereas the
86
+ * key-entry gate deliberately closes fast. Reusing the 15-minute gate TTL for
87
+ * sign-in is what logged hosted-instance users out every quarter hour (#2005).
88
+ *
89
+ * Override with NEOTOMA_SIGN_IN_SESSION_TTL_MIN (minutes). Default 7 days.
90
+ */
91
+ export declare const DEFAULT_SIGN_IN_SESSION_TTL_MIN: number;
92
+ /** Upper bound for the sign-in TTL override: 365 days in minutes. A value past
93
+ * this is a typo (an extra zero), not an intent — without a ceiling,
94
+ * "9999999999999" yields a ~19-million-year session. Mirrors the low-end
95
+ * guard rather than guarding only one side (#2007 qa lens). */
96
+ export declare const MAX_SIGN_IN_SESSION_TTL_MIN: number;
97
+ /**
98
+ * Parse NEOTOMA_SIGN_IN_SESSION_TTL_MIN into milliseconds. Exported (and pure)
99
+ * so the override's precedence and malformed-input behavior are directly
100
+ * testable — a silently-wrong TTL here reintroduces exactly the desync class
101
+ * this change fixes.
102
+ *
103
+ * Precedence: an integer within (0, MAX_SIGN_IN_SESSION_TTL_MIN] wins.
104
+ * Everything else — unset, non-numeric, zero, negative, or above the ceiling —
105
+ * falls back to the 7-day default rather than producing a degenerate session.
106
+ *
107
+ * Note on parseInt semantics, deliberately kept: "7.5" truncates to 7 minutes
108
+ * and "1e10" parses as 1 minute (parseInt stops at the 'e'). Both are accepted
109
+ * as-is because they are in-range integers after parsing; the fallback exists
110
+ * for values that are absent, unparseable, or out of range, not to second-guess
111
+ * a caller who wrote a decimal. Asserted in tests so the behavior is documented
112
+ * rather than incidental.
113
+ */
114
+ export declare function resolveSignInSessionTtlMs(raw: string | undefined): number;
115
+ /**
116
+ * Issue an OAuth key-session cookie. `ttlMs` covers BOTH the server-side
117
+ * session and the cookie's own maxAge — they must agree, or the browser keeps
118
+ * presenting a cookie the server has already forgotten (or vice versa).
119
+ * Omit it for the short key-entry gate; pass SIGN_IN_SESSION_TTL_MS for
120
+ * sign-in (#2005).
121
+ */
122
+ export declare function setOAuthKeySessionCookie(req: express.Request, res: express.Response, ttlMs?: number): string;
123
+ /** Resolve the local-auth user_id a Google-verified session (if any) mapped to this request. */
124
+ export declare function getGoogleVerifiedUserId(req: express.Request): string | undefined;
79
125
  /**
80
126
  * Derive the canonical `@authority` the AAuth profile requires (AAuth §10.3.1).
81
127
  * We trust explicit configuration first, then fall back to the host the
@@ -111,6 +157,18 @@ export declare function routeAllowsLocalIssueWriteFallback(req: Pick<express.Req
111
157
  export declare function routeConsumesGuestWriteBudget(req: Pick<express.Request, "method" | "path">): boolean;
112
158
  /** Exported for unit tests: guest-rate-limit key precedence is thumbprint -> guest token -> IP. */
113
159
  export declare function guestWriteRateLimitKey(req: express.Request): string;
160
+ /**
161
+ * Issue *submission* is the one route a third party may reach with no identity
162
+ * at all: the guest access token is minted BY the submit, so requiring one to
163
+ * perform it is a chicken-and-egg that closes the inbox to first contact.
164
+ *
165
+ * Deliberately submit-only. `add_message` stays authenticated: by the time a
166
+ * reporter has a thread to append to, they hold the token returned by their
167
+ * submit, so anonymity buys nothing there — and letting an unidentified caller
168
+ * write into an existing thread is a strictly wider blast radius than letting
169
+ * them open a new one. The token is what proves "I opened this thread".
170
+ */
171
+ export declare function routeAcceptsAnonymousIssueSubmit(req: Pick<express.Request, "method" | "path">): boolean;
114
172
  /**
115
173
  * Derive a userId for a guest principal from the validated token grant.
116
174
  *
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
package/dist/actions.js CHANGED
@@ -22,6 +22,7 @@ import { aauthAdmission, getAAuthAdmissionFromRequest } from "./middleware/aauth
22
22
  import { buildSessionInfo, normalizeSessionOrigin } from "./services/session_info.js";
23
23
  import { AttributionPolicyError, enforceAttributionPolicy } from "./services/attribution_policy.js";
24
24
  import { OverridePolicyViolationError } from "./services/override_validation.js";
25
+ import { CursorError } from "./services/entity_cursor.js";
25
26
  import { AgentCapabilityError, contextFromAgentIdentity, enforceAgentCapability, } from "./services/agent_capabilities.js";
26
27
  import { assertGuestWriteAllowed, AccessPolicyError, } from "./services/access_policy.js";
27
28
  import { hashGuestAccessToken } from "./services/guest_access_token.js";
@@ -1096,17 +1097,47 @@ export function isLocalRequest(req) {
1096
1097
  return !isProductionEnvironment();
1097
1098
  }
1098
1099
  const OAUTH_KEY_SESSION_COOKIE = "neotoma_oauth_key_session";
1099
- const oauthKeySessions = new OAuthKeySessionStore();
1100
+ /** Exported for tests asserting the cookie maxAge and the server-side session
1101
+ * expiry agree — the invariant setOAuthKeySessionCookie's docstring names. */
1102
+ export const oauthKeySessions = new OAuthKeySessionStore();
1100
1103
  /**
1101
- * Maps an OAuth key-session token to the user_id resolved via Google
1102
- * sign-in for that browser session. Populated only by the
1103
- * `/mcp/oauth/google/callback` route; every other credential path
1104
- * (private key / mnemonic / bearer) never writes here, so
1105
- * `/mcp/oauth/local-login` falls through to `ensureLocalDevUser()`
1106
- * exactly as before when this map has no entry — behavior for every
1107
- * existing deploy (Google flag off) is unchanged.
1104
+ * Lifetime of a SIGN-IN session (Google). Distinct from the key-entry gate's
1105
+ * short default: a user who signs in expects to stay signed in, whereas the
1106
+ * key-entry gate deliberately closes fast. Reusing the 15-minute gate TTL for
1107
+ * sign-in is what logged hosted-instance users out every quarter hour (#2005).
1108
+ *
1109
+ * Override with NEOTOMA_SIGN_IN_SESSION_TTL_MIN (minutes). Default 7 days.
1110
+ */
1111
+ export const DEFAULT_SIGN_IN_SESSION_TTL_MIN = 7 * 24 * 60;
1112
+ /** Upper bound for the sign-in TTL override: 365 days in minutes. A value past
1113
+ * this is a typo (an extra zero), not an intent — without a ceiling,
1114
+ * "9999999999999" yields a ~19-million-year session. Mirrors the low-end
1115
+ * guard rather than guarding only one side (#2007 qa lens). */
1116
+ export const MAX_SIGN_IN_SESSION_TTL_MIN = 365 * 24 * 60;
1117
+ /**
1118
+ * Parse NEOTOMA_SIGN_IN_SESSION_TTL_MIN into milliseconds. Exported (and pure)
1119
+ * so the override's precedence and malformed-input behavior are directly
1120
+ * testable — a silently-wrong TTL here reintroduces exactly the desync class
1121
+ * this change fixes.
1122
+ *
1123
+ * Precedence: an integer within (0, MAX_SIGN_IN_SESSION_TTL_MIN] wins.
1124
+ * Everything else — unset, non-numeric, zero, negative, or above the ceiling —
1125
+ * falls back to the 7-day default rather than producing a degenerate session.
1126
+ *
1127
+ * Note on parseInt semantics, deliberately kept: "7.5" truncates to 7 minutes
1128
+ * and "1e10" parses as 1 minute (parseInt stops at the 'e'). Both are accepted
1129
+ * as-is because they are in-range integers after parsing; the fallback exists
1130
+ * for values that are absent, unparseable, or out of range, not to second-guess
1131
+ * a caller who wrote a decimal. Asserted in tests so the behavior is documented
1132
+ * rather than incidental.
1108
1133
  */
1109
- const googleVerifiedUserBySessionToken = new Map();
1134
+ export function resolveSignInSessionTtlMs(raw) {
1135
+ const parsed = Number.parseInt(raw || "", 10);
1136
+ const inRange = Number.isFinite(parsed) && parsed > 0 && parsed <= MAX_SIGN_IN_SESSION_TTL_MIN;
1137
+ const minutes = inRange ? parsed : DEFAULT_SIGN_IN_SESSION_TTL_MIN;
1138
+ return minutes * 60 * 1000;
1139
+ }
1140
+ const SIGN_IN_SESSION_TTL_MS = resolveSignInSessionTtlMs(process.env.NEOTOMA_SIGN_IN_SESSION_TTL_MIN);
1110
1141
  function readCookie(req, name) {
1111
1142
  const header = (req.headers["cookie"] || req.headers["Cookie"] || "");
1112
1143
  if (!header)
@@ -1332,8 +1363,15 @@ function hasValidOAuthKeySession(req) {
1332
1363
  const token = readCookie(req, OAUTH_KEY_SESSION_COOKIE);
1333
1364
  return oauthKeySessions.isValid(token);
1334
1365
  }
1335
- function setOAuthKeySessionCookie(req, res) {
1336
- const token = oauthKeySessions.create();
1366
+ /**
1367
+ * Issue an OAuth key-session cookie. `ttlMs` covers BOTH the server-side
1368
+ * session and the cookie's own maxAge — they must agree, or the browser keeps
1369
+ * presenting a cookie the server has already forgotten (or vice versa).
1370
+ * Omit it for the short key-entry gate; pass SIGN_IN_SESSION_TTL_MS for
1371
+ * sign-in (#2005).
1372
+ */
1373
+ export function setOAuthKeySessionCookie(req, res, ttlMs) {
1374
+ const token = oauthKeySessions.create(Date.now(), ttlMs);
1337
1375
  const forwardedProto = (req.headers["x-forwarded-proto"] || req.headers["X-Forwarded-Proto"])
1338
1376
  ?.split(",")[0]
1339
1377
  ?.trim()
@@ -1344,16 +1382,16 @@ function setOAuthKeySessionCookie(req, res) {
1344
1382
  sameSite: "lax",
1345
1383
  secure,
1346
1384
  path: "/mcp/oauth",
1347
- maxAge: 15 * 60 * 1000,
1385
+ maxAge: ttlMs != null && ttlMs > 0 ? ttlMs : 15 * 60 * 1000,
1348
1386
  });
1349
1387
  return token;
1350
1388
  }
1351
1389
  /** Resolve the local-auth user_id a Google-verified session (if any) mapped to this request. */
1352
- function getGoogleVerifiedUserId(req) {
1390
+ export function getGoogleVerifiedUserId(req) {
1353
1391
  const token = readCookie(req, OAUTH_KEY_SESSION_COOKIE);
1354
- if (!token || !oauthKeySessions.isValid(token))
1355
- return undefined;
1356
- return googleVerifiedUserBySessionToken.get(token);
1392
+ // getBoundUser enforces session validity itself, so an expired session can
1393
+ // never resolve a user even if a binding is still in memory.
1394
+ return oauthKeySessions.getBoundUser(token);
1357
1395
  }
1358
1396
  /**
1359
1397
  * Derive the canonical `@authority` the AAuth profile requires (AAuth §10.3.1).
@@ -1905,9 +1943,32 @@ function sendStoreValidationError(res, issues) {
1905
1943
  }
1906
1944
  return sendValidationError(res, issues);
1907
1945
  }
1946
+ /**
1947
+ * Lift a structured `hint` off the first Zod issue that carries one.
1948
+ *
1949
+ * Zod nests custom issue metadata under `params`, so a `ctx.addIssue({ params:
1950
+ * { hint } })` lands at `details.issues[].params.hint`. Clients (and the
1951
+ * legacy-payload replay harness, per docs/subsystems/errors.md § Tightening-change
1952
+ * hint obligation) read the migration hint from the flat `details.hint` slot that
1953
+ * every other standard-envelope error uses. Without this lift, a tightening can
1954
+ * populate `hint` exactly as the guardrail requires and still be invisible to the
1955
+ * gate meant to catch it.
1956
+ */
1957
+ function firstIssueHint(issues) {
1958
+ if (!Array.isArray(issues))
1959
+ return undefined;
1960
+ for (const issue of issues) {
1961
+ const hint = issue?.params?.hint;
1962
+ if (typeof hint === "string" && hint.length > 0)
1963
+ return hint;
1964
+ }
1965
+ return undefined;
1966
+ }
1908
1967
  function sendValidationError(res, issues) {
1968
+ const hint = firstIssueHint(issues);
1909
1969
  return res.status(400).json(buildErrorEnvelope("VALIDATION_INVALID_FORMAT", "Invalid request payload.", {
1910
1970
  issues,
1971
+ ...(hint ? { hint } : {}),
1911
1972
  }));
1912
1973
  }
1913
1974
  // Public health endpoint (no auth)
@@ -2324,8 +2385,10 @@ app.get("/mcp/oauth/google/callback", async (req, res) => {
2324
2385
  // mnemonic/bearer path sets. `/mcp/oauth/authorize` -> `/mcp/oauth/
2325
2386
  // local-login` then runs completely unmodified, except local-login now
2326
2387
  // resolves the user via this session instead of always ensureLocalDevUser().
2327
- const sessionToken = setOAuthKeySessionCookie(req, res);
2328
- googleVerifiedUserBySessionToken.set(sessionToken, resolvedUserId);
2388
+ // Sign-in gets the long session TTL, not the key-entry gate's 15 minutes,
2389
+ // and the user binding lives in the same store so it expires with it (#2005).
2390
+ const sessionToken = setOAuthKeySessionCookie(req, res, SIGN_IN_SESSION_TTL_MS);
2391
+ oauthKeySessions.bindUser(sessionToken, resolvedUserId);
2329
2392
  return res.redirect(nextPath);
2330
2393
  }
2331
2394
  catch (error) {
@@ -2872,6 +2935,23 @@ function guestWriteRateLimit(req, res, next) {
2872
2935
  return next();
2873
2936
  void guestWriteRateLimitMiddleware(req, res, next);
2874
2937
  }
2938
+ /**
2939
+ * Issue *submission* is the one route a third party may reach with no identity
2940
+ * at all: the guest access token is minted BY the submit, so requiring one to
2941
+ * perform it is a chicken-and-egg that closes the inbox to first contact.
2942
+ *
2943
+ * Deliberately submit-only. `add_message` stays authenticated: by the time a
2944
+ * reporter has a thread to append to, they hold the token returned by their
2945
+ * submit, so anonymity buys nothing there — and letting an unidentified caller
2946
+ * write into an existing thread is a strictly wider blast radius than letting
2947
+ * them open a new one. The token is what proves "I opened this thread".
2948
+ */
2949
+ export function routeAcceptsAnonymousIssueSubmit(req) {
2950
+ return (req.method === "POST" && (req.path === "/issues/submit" || req.path === "/api/issues/submit"));
2951
+ }
2952
+ function buildAnonymousGuestPrincipal() {
2953
+ return { kind: "guest", guestId: {}, agentIdentity: null, accessToken: undefined };
2954
+ }
2875
2955
  function buildGuestPrincipalFromRequest(req) {
2876
2956
  const agentIdentity = getCurrentAgentIdentity();
2877
2957
  const accessToken = guestAccessTokenFromRequest(req);
@@ -2895,8 +2975,21 @@ async function maybeStampGuestPrincipal(req) {
2895
2975
  if (!routeAcceptsGuestPrincipal(req))
2896
2976
  return false;
2897
2977
  const guestPrincipal = buildGuestPrincipalFromRequest(req);
2898
- if (!guestPrincipal)
2899
- return false;
2978
+ if (!guestPrincipal) {
2979
+ // No AAuth signature and no guest token: for issue submission this is the
2980
+ // NORMAL first contact from a third party's agent, since the token is an
2981
+ // OUTPUT of the submit rather than a precondition for it (#1953). Remains
2982
+ // the operator's switch — NEOTOMA_ACCESS_POLICY_ISSUE=closed shuts it.
2983
+ if (!routeAcceptsAnonymousIssueSubmit(req))
2984
+ return false;
2985
+ const { resolveAccessPolicy } = await import("./services/access_policy.js");
2986
+ const mode = await resolveAccessPolicy("issue");
2987
+ if (mode !== "submit_only" && mode !== "submitter_scoped" && mode !== "open") {
2988
+ return false;
2989
+ }
2990
+ stampGuestPrincipal(req, buildAnonymousGuestPrincipal());
2991
+ return true;
2992
+ }
2900
2993
  const headerAuth = (req.headers.authorization || req.headers.Authorization || "");
2901
2994
  if (headerAuth.startsWith("Bearer ") && guestPrincipal.accessToken) {
2902
2995
  const { validateGuestAccessToken } = await import("./services/guest_access_token.js");
@@ -3459,6 +3552,12 @@ function handleApiError(req, res, error, defaultMessage, errorCode = "DB_QUERY_F
3459
3552
  .status(403)
3460
3553
  .json(buildErrorEnvelope(error.code, error.message, error.toErrorEnvelope()));
3461
3554
  }
3555
+ if (error instanceof CursorError) {
3556
+ logWarn(logContext || "CursorRejection", req, error.toErrorEnvelope());
3557
+ return res
3558
+ .status(error.statusCode)
3559
+ .json(buildErrorEnvelope(error.code, error.message, error.toErrorEnvelope()));
3560
+ }
3462
3561
  if (error instanceof IssueValidationError) {
3463
3562
  logWarn(logContext || "IssueValidationError", req, {
3464
3563
  code: error.code,
@@ -3509,8 +3608,8 @@ async function runEntitiesQuery(req, res, rawInput) {
3509
3608
  // scoping only via getAuthenticatedUserId; it is never read directly off
3510
3609
  // the request for access control.
3511
3610
  const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
3512
- const { entity_type, entity_types, search, limit, offset, sort_by, sort_order, published, published_after, published_before, include_snapshots, include_merged, updated_since, created_since, identity_basis, snapshot_filters, exclude_bookkeeping, } = parsed.data;
3513
- const { entities, total, applied_search_strategies, search_mode } = await queryEntitiesWithCount({
3611
+ const { entity_type, entity_types, search, limit, offset, cursor, sort_by, sort_order, published, published_after, published_before, include_snapshots, include_merged, updated_since, created_since, identity_basis, snapshot_filters, exclude_bookkeeping, } = parsed.data;
3612
+ const { entities, total, applied_search_strategies, search_mode, next_cursor } = await queryEntitiesWithCount({
3514
3613
  userId,
3515
3614
  entityType: entity_type,
3516
3615
  entityTypes: entity_types,
@@ -3524,6 +3623,7 @@ async function runEntitiesQuery(req, res, rawInput) {
3524
3623
  search,
3525
3624
  limit,
3526
3625
  offset,
3626
+ cursor,
3527
3627
  updatedSince: updated_since,
3528
3628
  createdSince: created_since,
3529
3629
  identityBasis: identity_basis,
@@ -3537,6 +3637,7 @@ async function runEntitiesQuery(req, res, rawInput) {
3537
3637
  offset,
3538
3638
  ...(applied_search_strategies ? { applied_search_strategies } : {}),
3539
3639
  search_mode,
3640
+ ...(next_cursor ? { next_cursor } : {}),
3540
3641
  });
3541
3642
  }
3542
3643
  catch (error) {
@@ -3577,6 +3678,7 @@ function coerceEntitiesQueryParams(query) {
3577
3678
  "updated_since",
3578
3679
  "created_since",
3579
3680
  "identity_basis",
3681
+ "cursor",
3580
3682
  ]) {
3581
3683
  const value = readString(key);
3582
3684
  if (value !== undefined)
@@ -8074,6 +8176,10 @@ const handleIssuesSubmitHttp = async (req, res) => {
8074
8176
  author: parsed.data.author,
8075
8177
  local_issue_id: parsed.data.local_issue_id,
8076
8178
  submission_timestamp: parsed.data.submission_timestamp,
8179
+ reporter_git_sha: parsed.data.reporter_git_sha,
8180
+ reporter_git_ref: parsed.data.reporter_git_ref,
8181
+ reporter_channel: parsed.data.reporter_channel,
8182
+ reporter_app_version: parsed.data.reporter_app_version,
8077
8183
  });
8078
8184
  return {
8079
8185
  issue_number: 0,
@@ -8456,7 +8562,7 @@ app.post("/update_schema_incremental", async (req, res) => {
8456
8562
  }
8457
8563
  try {
8458
8564
  const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
8459
- const { entity_type, fields_to_add, fields_to_remove, schema_version, user_specific = false, activate = true, migrate_existing = false, force = false, } = parsed.data;
8565
+ const { entity_type, fields_to_add, fields_to_remove, canonical_name_fields, schema_version, user_specific = false, activate = true, migrate_existing = false, force = false, } = parsed.data;
8460
8566
  const { schemaRegistry } = await import("./services/schema_registry.js");
8461
8567
  let newSchema;
8462
8568
  try {
@@ -8464,6 +8570,11 @@ app.post("/update_schema_incremental", async (req, res) => {
8464
8570
  entity_type,
8465
8571
  fields_to_add: (fields_to_add || []),
8466
8572
  fields_to_remove: fields_to_remove || [],
8573
+ // #2018: thread the identity-rule change through the HTTP/offline path.
8574
+ // Omitted here originally, so CLI/HTTP callers silently no-op'd the
8575
+ // re-key (the MCP server handler had it, but this Express route — which
8576
+ // the CLI offline transport uses — did not).
8577
+ canonical_name_fields,
8467
8578
  schema_version,
8468
8579
  user_specific,
8469
8580
  user_id: userId,
@@ -8490,6 +8601,10 @@ app.post("/update_schema_incremental", async (req, res) => {
8490
8601
  schema: newSchema,
8491
8602
  schema_version: newSchema.schema_version,
8492
8603
  fields_removed: fields_to_remove || [],
8604
+ // Echo the resolved identity rule so CLI/HTTP callers can confirm the
8605
+ // re-key without a second describe_entity_type round trip (#2020 ux).
8606
+ canonical_name_fields: newSchema.schema_definition
8607
+ .canonical_name_fields ?? null,
8493
8608
  });
8494
8609
  }
8495
8610
  catch (error) {