neotoma 0.18.3 → 0.18.5

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 (35) hide show
  1. package/dist/actions.d.ts +10 -0
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +152 -0
  4. package/dist/actions.js.map +1 -1
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -1
  7. package/dist/cli/index.js +22 -8
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/docs/developer/cli_reference.md +2 -1
  10. package/dist/docs/developer/fleet_onboarding.md +23 -0
  11. package/dist/docs/subsystems/conflict_resolution.md +72 -30
  12. package/dist/docs/subsystems/inspector_embed_cross_origin.md +163 -0
  13. package/dist/docs/testing/automated_test_catalog.md +16 -8
  14. package/dist/server.d.ts.map +1 -1
  15. package/dist/server.js +80 -0
  16. package/dist/server.js.map +1 -1
  17. package/dist/services/embed_cross_origin.d.ts +95 -0
  18. package/dist/services/embed_cross_origin.d.ts.map +1 -0
  19. package/dist/services/embed_cross_origin.js +174 -0
  20. package/dist/services/embed_cross_origin.js.map +1 -0
  21. package/dist/services/null_cleared_field_warning.d.ts +71 -0
  22. package/dist/services/null_cleared_field_warning.d.ts.map +1 -0
  23. package/dist/services/null_cleared_field_warning.js +77 -0
  24. package/dist/services/null_cleared_field_warning.js.map +1 -0
  25. package/dist/services/source_priority_warning.d.ts +57 -0
  26. package/dist/services/source_priority_warning.d.ts.map +1 -1
  27. package/dist/services/source_priority_warning.js +84 -0
  28. package/dist/services/source_priority_warning.js.map +1 -1
  29. package/dist/shared/action_schemas.d.ts +6 -0
  30. package/dist/shared/action_schemas.d.ts.map +1 -1
  31. package/dist/shared/action_schemas.js +6 -0
  32. package/dist/shared/action_schemas.js.map +1 -1
  33. package/package.json +2 -1
  34. package/skills/end/SKILL.md +28 -2
  35. package/skills/status/SKILL.md +27 -1
package/dist/actions.d.ts CHANGED
@@ -97,6 +97,16 @@ type GuestPrincipal = {
97
97
  type RoutePrincipal = UserPrincipal | GuestPrincipal;
98
98
  /** Exported for unit tests: routes where guest (AAuth / guest token) may be stamped before handlers run. */
99
99
  export declare function routeAcceptsGuestPrincipal(req: Pick<express.Request, "method" | "path">): boolean;
100
+ /**
101
+ * Exported for unit tests: the two issue-write routes that extend local-loopback
102
+ * trust to unauthenticated requests. A local request to `/issues/submit` or
103
+ * `/issues/add_message` with no `Authorization: Bearer` header falls through to
104
+ * the local dev user — the same offline-developer trust the entity-read routes
105
+ * already grant (see `resolveGuestUserId`). This is the ONLY place that trust is
106
+ * widened; every other route still requires a Bearer token, and remote requests
107
+ * (non-loopback / untrusted XFF) get AUTH_REQUIRED on these routes too.
108
+ */
109
+ export declare function routeAllowsLocalIssueWriteFallback(req: Pick<express.Request, "method" | "path">): boolean;
100
110
  /** Exported for unit tests: guest-capable routes that mutate state and should consume a guest write-rate budget. */
101
111
  export declare function routeConsumesGuestWriteBudget(req: Pick<express.Request, "method" | "path">): boolean;
102
112
  /** Exported for unit tests: guest-rate-limit key precedence is thumbprint -> guest token -> IP. */
@@ -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
@@ -47,6 +47,7 @@ import { getSandboxPack } from "./services/sandbox/pack_registry.js";
47
47
  import { buildEndpointsMap, buildLandingContext, buildRootLandingJson, buildRootLandingMarkdown, buildRobotsTxt, readGitSha, readNeotomaConfigEnvironment, readPackageVersion, resolveLandingMode, wantsHtml as acceptWantsHtml, wantsMarkdown as acceptWantsMarkdown, } from "./services/root_landing/index.js";
48
48
  import { mountDocsRoutes } from "./services/docs/index.js";
49
49
  import { installInspectorLegacyRedirect, installInspectorRootStaticAssets, installInspectorSpaFallback, installInspectorSpaShellEarly, resolveBundledInspectorDir, } from "./services/inspector_mount.js";
50
+ import { EMBED_ALLOWED_ORIGINS_ENV, applyFrameAncestorsToCsp, buildFrameAncestorsDirective, embedCorsHeaders, isEmbedReadEndpoint, isEmbedShellPath, isOriginAllowed, parseAllowedEmbedOrigins, } from "./services/embed_cross_origin.js";
50
51
  import { getSandboxTermsResponse } from "./services/sandbox/terms.js";
51
52
  import { resolveSandboxReportTransport } from "./services/sandbox/transport.js";
52
53
  import { getSqliteDb } from "./repositories/sqlite/sqlite_client.js";
@@ -113,6 +114,71 @@ app.use(helmet({
113
114
  },
114
115
  },
115
116
  }));
117
+ // ── Official cross-origin Inspector-embed support (opt-in, secure by default) ──
118
+ //
119
+ // When NEOTOMA_EMBED_ALLOWED_ORIGINS lists one or more host origins, an
120
+ // allowlisted host may iframe `/embed/graph` directly and its browser may
121
+ // `fetch()` the two graph read endpoints cross-origin — WITHOUT running a
122
+ // same-origin reverse proxy. Two response behaviors change, ONLY for
123
+ // allowlisted traffic:
124
+ //
125
+ // 1. Embed shell paths (`/embed/*`) get `Content-Security-Policy:
126
+ // frame-ancestors 'self' <origins>` and drop `X-Frame-Options`, so the
127
+ // allowlisted host can frame them. Non-embed paths are untouched.
128
+ // 2. The two blessed read endpoints (`/entities/query`,
129
+ // `/retrieve_graph_neighborhood`) get scoped CORS (exact-origin echo,
130
+ // never `*`, no credentials) + preflight handling — and ONLY those two.
131
+ // Write endpoints are never CORS-enabled here, so a browser on an
132
+ // allowlisted origin can never reach `/store`,`/correct`,… cross-origin.
133
+ //
134
+ // SECURE BY DEFAULT: with no allowlist configured, this middleware is inert and
135
+ // every response is byte-identical to the locked-down `'self'`/`SAMEORIGIN`
136
+ // posture. Registered AFTER Helmet (so it can override the `X-Frame-Options`
137
+ // Helmet set and rewrite Helmet's CSP), BEFORE the global `cors()` (so it owns
138
+ // the embed preflight and its response is not pre-empted), and before the auth
139
+ // stack (so a CORS preflight OPTIONS is answered without a bearer, as browsers
140
+ // require).
141
+ const embedAllowedOrigins = parseAllowedEmbedOrigins(process.env[EMBED_ALLOWED_ORIGINS_ENV]);
142
+ if (embedAllowedOrigins.length > 0) {
143
+ logger.info(`[EmbedCORS] Cross-origin Inspector embed enabled for: ${embedAllowedOrigins.join(", ")}`);
144
+ }
145
+ app.use((req, res, next) => {
146
+ if (embedAllowedOrigins.length === 0)
147
+ return next();
148
+ const requestOrigin = req.headers.origin;
149
+ const reqPath = req.path;
150
+ // (1) Relax framing on the embed shell so an allowlisted host can iframe it.
151
+ // Applied on embed paths (framing is enforced by the browser against the CSP
152
+ // `frame-ancestors` list, which is itself restricted to the configured
153
+ // origins — so emitting it grants nothing beyond the allowlist). Rewrite the
154
+ // existing Helmet CSP if present, else set a standalone `frame-ancestors`
155
+ // policy, and remove `X-Frame-Options` (which has no allowlist form and would
156
+ // otherwise still block the frame).
157
+ if (isEmbedShellPath(reqPath)) {
158
+ const existingCsp = res.getHeader("Content-Security-Policy");
159
+ if (typeof existingCsp === "string") {
160
+ res.setHeader("Content-Security-Policy", applyFrameAncestorsToCsp(existingCsp, embedAllowedOrigins));
161
+ }
162
+ else {
163
+ const directive = buildFrameAncestorsDirective(embedAllowedOrigins);
164
+ if (directive)
165
+ res.setHeader("Content-Security-Policy", directive);
166
+ }
167
+ res.removeHeader("X-Frame-Options");
168
+ }
169
+ // (2) Scoped CORS on the two blessed read endpoints, only for an allowlisted
170
+ // Origin. Preflight (OPTIONS) is answered here directly with 204.
171
+ if (isEmbedReadEndpoint(reqPath) && isOriginAllowed(requestOrigin, embedAllowedOrigins)) {
172
+ const canonical = new URL(requestOrigin).origin;
173
+ for (const [name, value] of Object.entries(embedCorsHeaders(canonical))) {
174
+ res.setHeader(name, value);
175
+ }
176
+ if (req.method === "OPTIONS") {
177
+ return res.status(204).end();
178
+ }
179
+ }
180
+ return next();
181
+ });
116
182
  // Configure CORS to allow frontend origin
117
183
  const corsOptions = {
118
184
  origin: process.env.NEOTOMA_FRONTEND_URL || process.env.FRONTEND_URL || "http://localhost:5195",
@@ -2611,6 +2677,23 @@ export function routeAcceptsGuestPrincipal(req) {
2611
2677
  }
2612
2678
  return false;
2613
2679
  }
2680
+ /**
2681
+ * Exported for unit tests: the two issue-write routes that extend local-loopback
2682
+ * trust to unauthenticated requests. A local request to `/issues/submit` or
2683
+ * `/issues/add_message` with no `Authorization: Bearer` header falls through to
2684
+ * the local dev user — the same offline-developer trust the entity-read routes
2685
+ * already grant (see `resolveGuestUserId`). This is the ONLY place that trust is
2686
+ * widened; every other route still requires a Bearer token, and remote requests
2687
+ * (non-loopback / untrusted XFF) get AUTH_REQUIRED on these routes too.
2688
+ */
2689
+ export function routeAllowsLocalIssueWriteFallback(req) {
2690
+ const path = req.path;
2691
+ return (req.method === "POST" &&
2692
+ (path === "/issues/submit" ||
2693
+ path === "/api/issues/submit" ||
2694
+ path === "/issues/add_message" ||
2695
+ path === "/api/issues/add_message"));
2696
+ }
2614
2697
  /** Exported for unit tests: guest-capable routes that mutate state and should consume a guest write-rate budget. */
2615
2698
  export function routeConsumesGuestWriteBudget(req) {
2616
2699
  const path = req.path;
@@ -2704,6 +2787,24 @@ async function resolveRoutePrincipal(req, accept) {
2704
2787
  }
2705
2788
  }
2706
2789
  if (accept.includes("user")) {
2790
+ // Local-loopback trust for the two issue-write routes: a local request with
2791
+ // no Bearer token resolves to the local dev user instead of failing
2792
+ // AUTH_REQUIRED. This mirrors the existing local fallback in
2793
+ // `resolveGuestUserId` and keeps `neotoma issues create` working offline
2794
+ // (gh-CLI auth alone does not configure a Neotoma Bearer token). Scoped as
2795
+ // narrowly as possible: ONLY these routes, ONLY local requests, ONLY when
2796
+ // no Bearer header is present — remote/Bearer requests fall through to the
2797
+ // normal `getAuthenticatedUserId` gate below.
2798
+ const headerAuth = (req.headers.authorization || req.headers.Authorization || "");
2799
+ if (routeAllowsLocalIssueWriteFallback(req) &&
2800
+ isLocalRequest(req) &&
2801
+ !headerAuth.startsWith("Bearer ")) {
2802
+ const userId = _localSandboxActive ? ensureLocalSandboxUser().id : ensureLocalDevUser().id;
2803
+ // Stamp the principal so a later `getAuthenticatedUserId` in the handler
2804
+ // resolves the same local user without re-tripping the missing-Bearer gate.
2805
+ stampUserPrincipal(req, userId);
2806
+ return { kind: "user", userId };
2807
+ }
2707
2808
  const userId = await getAuthenticatedUserId(req, (req.body?.user_id ??
2708
2809
  req.query.user_id));
2709
2810
  return { kind: "user", userId };
@@ -5704,6 +5805,12 @@ export async function storeStructuredForApi(params) {
5704
5805
  }
5705
5806
  }
5706
5807
  const createdEntities = [];
5808
+ // Issue #1839: capture each entity's prior and post-reduction snapshot so the
5809
+ // store_warnings loop below can detect a NULL_CLEARED_FIELD event (an incoming
5810
+ // null clearing a prior non-null value under highest_priority). Keyed by
5811
+ // entity_id.
5812
+ const priorSnapshotByEntityId = new Map();
5813
+ const newSnapshotByEntityId = new Map();
5707
5814
  for (const r of resolved) {
5708
5815
  let observation_id = null;
5709
5816
  let snapshotAfter = null;
@@ -5715,6 +5822,7 @@ export async function storeStructuredForApi(params) {
5715
5822
  .eq("user_id", userId)
5716
5823
  .maybeSingle();
5717
5824
  const priorSnapshot = priorSnapRow?.snapshot ?? {};
5825
+ priorSnapshotByEntityId.set(r.entity_id, priorSnapshot);
5718
5826
  const obsData = await createObservation({
5719
5827
  entity_id: r.entity_id,
5720
5828
  entity_type: r.entity_type,
@@ -5737,6 +5845,8 @@ export async function storeStructuredForApi(params) {
5737
5845
  const snap = await recomputeSnapshot(r.entity_id, userId);
5738
5846
  snapshotAfter =
5739
5847
  snap?.snapshot ?? null;
5848
+ if (snapshotAfter)
5849
+ newSnapshotByEntityId.set(r.entity_id, snapshotAfter);
5740
5850
  }
5741
5851
  catch (snapshotErr) {
5742
5852
  logger.warn(`Snapshot recompute failed for ${r.entity_id}: ${snapshotErr}`);
@@ -6076,6 +6186,48 @@ export async function storeStructuredForApi(params) {
6076
6186
  });
6077
6187
  if (spWarn)
6078
6188
  schemaStoreWarnings.push(spWarn);
6189
+ // Issue #1838: SOURCE_PRIORITY_ESCALATION — mirror-image advisory. A
6190
+ // write at the DEFAULT source_priority (100) into a `highest_priority`
6191
+ // field silently OUTRANKS any prior observation written with an
6192
+ // explicit lower priority. Non-blocking; reuses the same import.
6193
+ const { buildSourcePriorityEscalationWarning } = await import("./services/source_priority_warning.js");
6194
+ const spEsc = buildSourcePriorityEscalationWarning({
6195
+ sourcePriority,
6196
+ writtenFields: r.fields,
6197
+ mergePolicies: schemaEntry?.reducer_config?.merge_policies,
6198
+ observationIndex: r.observation_index,
6199
+ entityType: r.entity_type,
6200
+ entityId: r.entity_id,
6201
+ });
6202
+ if (spEsc)
6203
+ schemaStoreWarnings.push(spEsc);
6204
+ }
6205
+ // Issue #1839: NULL_CLEARED_FIELD — non-blocking advisory warning when an
6206
+ // incoming null clears a prior non-null value under a `highest_priority`
6207
+ // field. The clear is by design (null is an explicit tombstone), but it
6208
+ // is silent today; this surfaces the data-loss vector. Warn-only: no
6209
+ // change to snapshot computation or clearing semantics.
6210
+ {
6211
+ const mergePolicies = schemaEntry?.reducer_config?.merge_policies;
6212
+ if (mergePolicies) {
6213
+ const priorSnapshot = priorSnapshotByEntityId.get(r.entity_id) ?? {};
6214
+ const newSnapshot = newSnapshotByEntityId.get(r.entity_id) ?? {};
6215
+ const { buildNullClearedFieldWarning } = await import("./services/null_cleared_field_warning.js");
6216
+ for (const field of Object.keys(r.fields)) {
6217
+ const nullWarn = buildNullClearedFieldWarning({
6218
+ field,
6219
+ strategy: mergePolicies[field]?.strategy,
6220
+ prior_value: priorSnapshot[field],
6221
+ incoming_value: r.fields[field],
6222
+ new_value: newSnapshot[field],
6223
+ observation_index: r.observation_index,
6224
+ entity_type: r.entity_type,
6225
+ entity_id: r.entity_id,
6226
+ });
6227
+ if (nullWarn)
6228
+ schemaStoreWarnings.push(nullWarn);
6229
+ }
6230
+ }
6079
6231
  }
6080
6232
  }
6081
6233
  }