@yottagraph-app/aether-instructions 1.1.20 → 1.1.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottagraph-app/aether-instructions",
3
- "version": "1.1.20",
3
+ "version": "1.1.22",
4
4
  "description": "Cursor rules, commands, and skills for Aether development",
5
5
  "files": [
6
6
  "rules",
package/rules/api.mdc CHANGED
@@ -28,13 +28,32 @@ For Lovelace **entity types, properties, relationships, and per-source schemas**
28
28
 
29
29
  ## Test Before You Build
30
30
 
31
- **ALWAYS test API calls via curl before writing code that depends on them.**
32
- This is not optional — the Elemental API has response shapes that differ from
33
- what the TypeScript types suggest, and assumptions about nesting, property
34
- formats, and field names will be wrong without testing. This applies doubly
35
- to server-side code, where you can't inspect responses in the browser console.
31
+ **ALWAYS test data access before writing application code.** The Elemental
32
+ API has response shapes that differ from what the TypeScript types suggest,
33
+ and assumptions about nesting, property formats, and field names will be
34
+ wrong without testing.
36
35
 
37
- ### How to test
36
+ ### Step 1: MCP tools (interactive exploration)
37
+
38
+ **If MCP tools appear in your tool list, start here.** MCP handles entity
39
+ resolution, PID lookups, and NEID formatting automatically — use it to
40
+ verify what data exists and how it's structured.
41
+
42
+ ```
43
+ elemental_get_schema() → list all entity types
44
+ elemental_get_schema(flavor="article") → properties for a type
45
+ elemental_get_entity(entity="Apple") → resolve + fetch entity
46
+ elemental_get_related(entity="Apple",
47
+ related_flavor="person") → follow relationships
48
+ ```
49
+
50
+ MCP tells you the correct flavor IDs, property IDs, and data shapes. Use
51
+ these to inform your REST implementation.
52
+
53
+ ### Step 2: curl (verify exact request/response shapes)
54
+
55
+ MCP doesn't cover every REST endpoint (e.g. `/elemental/find` expressions).
56
+ Test those with curl before implementing them in code.
38
57
 
39
58
  The gateway proxy authenticates on your behalf — no Auth0 tokens needed.
40
59
  Read `broadchurch.yaml` for the three values you need:
@@ -48,8 +67,6 @@ Read `broadchurch.yaml` for the three values you need:
48
67
  Build the request URL as `{gateway.url}/api/qs/{tenant.org_id}/{endpoint}`
49
68
  and include the header `X-Api-Key: {gateway.qs_api_key}`.
50
69
 
51
- ### Example calls
52
-
53
70
  ```bash
54
71
  # Variables — read these from broadchurch.yaml
55
72
  GW="https://broadchurch-portal-194773164895.us-central1.run.app"
@@ -62,6 +79,13 @@ curl -s "$GW/api/qs/$ORG/entities/search" \
62
79
  -H "X-Api-Key: $KEY" \
63
80
  -d '{"queries":[{"queryId":1,"query":"Microsoft"}],"maxResults":3}'
64
81
 
82
+ # Test a find expression
83
+ curl -s -X POST "$GW/api/qs/$ORG/elemental/find" \
84
+ -H "X-Api-Key: $KEY" \
85
+ -H "Content-Type: application/x-www-form-urlencoded" \
86
+ --data-urlencode 'expression={"type":"is_type","is_type":{"fid":12}}' \
87
+ --data-urlencode 'limit=5'
88
+
65
89
  # Get entity properties (form-encoded)
66
90
  curl -s -X POST "$GW/api/qs/$ORG/elemental/entities/properties" \
67
91
  -H "X-Api-Key: $KEY" \
@@ -74,6 +98,14 @@ curl -s -X POST "$GW/api/qs/$ORG/elemental/entities/properties" \
74
98
  `application/x-www-form-urlencoded` with JSON-stringified parameter values.
75
99
  All other endpoints accept `application/json`.
76
100
 
101
+ **Interpreting errors:** 400 = expression syntax is wrong. 500 = expression
102
+ is valid but the query failed (wrong PID, unsupported operator for that
103
+ property type). 200 + empty `eids` = query worked but no results match.
104
+
105
+ ### Step 3: Implement with confidence
106
+
107
+ Now write your composable or server route, knowing the exact API shapes.
108
+
77
109
  ## Client Usage
78
110
 
79
111
  All API calls go through `useElementalClient()` from `@yottagraph-app/elemental-api/client`.
@@ -85,19 +117,12 @@ import { useElementalClient } from '@yottagraph-app/elemental-api/client';
85
117
  const client = useElementalClient();
86
118
 
87
119
  const schema = await client.getSchema();
88
- const report = await client.getNamedEntityReport('00508379502570440213');
89
120
  const entities = await client.findEntities({
90
121
  expression: JSON.stringify({ type: 'comparison', comparison: { operator: 'string_like', pid: 8, value: 'Apple' } }),
91
122
  limit: 5,
92
123
  });
93
124
  ```
94
125
 
95
- Types are also imported from the client:
96
-
97
- ```typescript
98
- import type { NamedEntityReport } from '@yottagraph-app/elemental-api/client';
99
- ```
100
-
101
126
  ### Client Method Quick Reference
102
127
 
103
128
  All methods return data directly and throw on non-2xx responses.
@@ -107,14 +132,17 @@ All methods return data directly and throw on non-2xx responses.
107
132
  | Method | Signature | Purpose |
108
133
  |---|---|---|
109
134
  | `findEntities` | `(body: FindEntitiesBody)` | Expression-based search (see `find.md`) |
110
- | `getNamedEntityReport` | `(neid: string)` | Entity details (name, aliases, type) |
111
- | `getEntityDetails` | `(neid: string)` | Alias for entity reports |
112
135
 
113
136
  > **Entity search**: Use `findEntities()` with `string_like` on the name PID
114
137
  > for name-based searches, or call `POST /entities/search` directly via
115
138
  > `$fetch` for batch name resolution with scored ranking (this endpoint is
116
139
  > not wrapped by the generated client).
117
140
 
141
+ > **Entity name lookup**: To get an entity's display name from its NEID,
142
+ > call `GET /entities/{neid}/name` directly via `$fetch` (not on the
143
+ > generated client). Returns `{"name": "..."}`. For all other entity
144
+ > data, use `getPropertyValues()`.
145
+
118
146
  **Properties and schema:**
119
147
 
120
148
  | Method | Signature | Purpose |
@@ -167,56 +195,54 @@ knowledge of what's in the graph.
167
195
 
168
196
  ## API Gotchas
169
197
 
170
- ### `getSchema()` response is nested WILL crash if you don't handle it
198
+ ### `getSchema()` response structure differs by endpoint
199
+
200
+ There are two schema endpoints with **different response shapes**:
201
+
202
+ | Endpoint | Flavors at | Flavor ID field | Detail level |
203
+ |----------|-----------|-----------------|--------------|
204
+ | `GET /schema` | top-level (`res.flavors`) | `findex` | Rich (display names, units, domains) |
205
+ | `GET /elemental/metadata/schema` | nested (`res.schema.flavors`) | `fid` | Basic (name + type only) |
171
206
 
172
- The generated TypeScript types suggest `response.properties` and
173
- `response.flavors` exist at the top level. **They don't.** The API nests
174
- them under `response.schema`. This mismatch between types and reality
175
- causes `Cannot read properties of undefined` every time.
207
+ The TypeScript client's `getSchema()` calls `/elemental/metadata/schema`,
208
+ so the response nests data under `.schema`. The generated types may suggest
209
+ top-level access, but it won't work at runtime.
176
210
 
177
211
  ```typescript
178
- // WRONG — will crash at runtime despite TypeScript compiling fine:
212
+ // WRONG — will crash (data is nested under .schema):
179
213
  const res = await client.getSchema();
180
214
  const props = res.properties; // undefined!
181
215
 
182
- // CORRECT — always access through .schema:
216
+ // CORRECT — always use fallback to handle both shapes:
183
217
  const res = await client.getSchema();
184
218
  const properties = res.schema?.properties ?? (res as any).properties ?? [];
185
219
  const flavors = res.schema?.flavors ?? (res as any).flavors ?? [];
186
220
  ```
187
221
 
188
- The `(res as any).properties` fallback is there in case the API is ever
189
- fixed to match the types. Use this pattern every time.
222
+ ### Flavor ID field: `fid` vs `findex`
190
223
 
191
- ### `getNamedEntityReport()` response is nested under `.report`
192
-
193
- Same problem as `getSchema()`. The TypeScript types suggest entity fields
194
- (`name`, `aliases`, `type`) exist at the top level. **They don't.** The
195
- API wraps them in a `.report` container. The generated client does NOT
196
- unwrap this automatically.
224
+ The flavor identifier has **different field names** depending on the endpoint:
225
+ `GET /schema` returns `findex`, `/elemental/metadata/schema` returns `fid`.
226
+ Same value, different key. Always use a fallback:
197
227
 
198
228
  ```typescript
199
- // WRONG name will be undefined:
200
- const res = await client.getNamedEntityReport(neid);
201
- const name = res.name; // undefined!
202
-
203
- // CORRECT always access through .report:
204
- const res = await client.getNamedEntityReport(neid);
205
- const name = res.report?.name ?? (res as any).name ?? neid;
206
- const aliases = res.report?.aliases ?? (res as any).aliases ?? [];
207
- const type = res.report?.type ?? (res as any).type;
229
+ const articleFlavor = flavors.find(f => f.name === 'article');
230
+ const articleFid = articleFlavor?.fid ?? articleFlavor?.findex ?? null;
231
+
232
+ // When building a FID lookup map:
233
+ const fidMap = new Map(flavors.map(f => [f.fid ?? f.findex, f.name]));
208
234
  ```
209
235
 
210
- The `(res as any).name` fallback handles the case where the client is
211
- eventually fixed to unwrap the response.
236
+ The `is_type` expression in `/elemental/find` always uses the `fid` key
237
+ regardless of which schema endpoint provided the value.
212
238
 
213
239
  ### Relationship property values need zero-padding to form valid NEIDs
214
240
 
215
241
  Relationship properties (`data_nindex`) return linked entity IDs as raw
216
242
  numbers (e.g. `4926132345040704022`). These must be **zero-padded to 20
217
243
  characters** to form valid NEIDs. This is easy to miss and causes silent
218
- failures — `getNamedEntityReport` returns a 404, `getPropertyValues`
219
- returns empty results.
244
+ failures — `getPropertyValues` returns empty results and
245
+ `/entities/{neid}/name` returns a 404.
220
246
 
221
247
  ```typescript
222
248
  // WRONG — raw value is NOT a valid NEID:
@@ -299,6 +325,23 @@ const docNeids = (res.values ?? []).map((v) => String(v.value).padStart(20, '0')
299
325
 
300
326
  See the **cookbook** rule for a full "Get filings for a company" recipe.
301
327
 
328
+ ### Expression language pitfalls
329
+
330
+ These mistakes come up repeatedly when building `/elemental/find` queries:
331
+
332
+ - **Entity type filtering**: Use `is_type` (not `comparison` with pid=0).
333
+ `comparison` requires `pid != 0`.
334
+ - **`string_like` is name-only**: Only works on the name property (PID 8).
335
+ Use `eq` for exact matches on other string properties.
336
+ - **Boolean combinators**: Use `{"type": "and", "and": [...]}` — not
337
+ `conjunction` or any other name.
338
+ - **`lt`/`gt` are numeric-only**: Only work on `data_int` and `data_float`
339
+ properties.
340
+ - **`regex` is not implemented**: Will return an error.
341
+
342
+ Read the "Common Mistakes" section in the **elemental-api skill** (`find.md`)
343
+ for examples of each.
344
+
302
345
  ### Entity Search
303
346
 
304
347
  Use `client.findEntities()` (`POST /elemental/find`) for entity search.
@@ -353,17 +396,13 @@ const response = await getArticle(artid);
353
396
  if (response.status === 404) { /* handle not found */ }
354
397
  ```
355
398
 
356
- ## Lovelace MCP Servers (Optional)
399
+ ## Lovelace MCP Servers
357
400
 
358
- Four MCP servers **may** be configured in `.cursor/mcp.json` for interactive
359
- data exploration. They are NOT requiredthe REST client
360
- (`useElementalClient()`) and skill docs cover the same data and are the
361
- primary path for building features.
362
-
363
- **Before referencing MCP tools, check your available tool list.** If tools
364
- like `elemental_get_schema` don't appear in your MCP server list, the
365
- servers aren't connected — just use the REST client and skill docs instead.
366
- Do not report this as a problem; it's a normal configuration state.
401
+ Four MCP servers may be configured in `.cursor/mcp.json` for interactive
402
+ data exploration. **Check your tool list**if tools like
403
+ `elemental_get_schema` appear, use them as your primary testing and
404
+ exploration interface before writing code. If they don't appear, the
405
+ servers aren't connected; use curl and the skill docs instead.
367
406
 
368
407
  | Server | What it provides |
369
408
  |---|---|
@@ -372,6 +411,23 @@ Do not report this as a problem; it's a normal configuration state.
372
411
  | `lovelace-wiki` | Wikipedia entity enrichment |
373
412
  | `lovelace-polymarket` | Prediction market data |
374
413
 
414
+ ### MCP Tool Quick Reference
415
+
416
+ | Tool | Purpose | Use to verify... |
417
+ |---|---|---|
418
+ | `elemental_get_schema` | Discover entity types (flavors), properties, and relationships | Flavor IDs, property IDs, data types |
419
+ | `elemental_get_entity` | Look up entity by name or NEID; returns properties | Entity resolution, property shapes |
420
+ | `elemental_get_related` | Related entities with type/relationship filters | Relationship types and traversal |
421
+ | `elemental_get_relationships` | Relationship types and counts between two entities | Edge types between specific entities |
422
+ | `elemental_graph_neighborhood` | Most influential neighbors of an entity | Graph connectivity |
423
+ | `elemental_graph_sentiment` | Sentiment analysis from news articles | Sentiment data availability |
424
+ | `elemental_get_events` | Events for an entity or by search query | Event categories and shapes |
425
+ | `elemental_health` | Health check | Server connectivity |
426
+
427
+ MCP tools handle entity resolution, PID lookups, and NEID formatting
428
+ automatically. Use them to discover IDs and verify data exists before
429
+ building REST-based features with `useElementalClient()`.
430
+
375
431
  ### Setup
376
432
 
377
433
  `.cursor/mcp.json` is auto-generated by `init-project.js`. If it's missing,
@@ -379,25 +435,3 @@ run `node init-project.js --local` to regenerate it. For provisioned projects,
379
435
  the servers route through the Portal Gateway proxy (no credentials needed).
380
436
  For local development without a gateway, the servers require an
381
437
  `AUTH0_M2M_DEV_TOKEN` environment variable.
382
-
383
- These servers are also accessible from the browser through the Portal
384
- Gateway, so you can build MCP tool exploration UIs if needed.
385
-
386
- ### When MCP Servers Are Available
387
-
388
- If the MCP tools appear in your tool list, you can use them for interactive
389
- exploration alongside the REST client:
390
-
391
- | Tool | Purpose |
392
- |---|---|
393
- | `elemental_get_schema` | Discover entity types (flavors), properties, and relationships |
394
- | `elemental_get_entity` | Look up entity by name or NEID; returns properties |
395
- | `elemental_get_related` | Related entities with type/relationship filters |
396
- | `elemental_get_relationships` | Relationship types and counts between two entities |
397
- | `elemental_graph_neighborhood` | Most influential neighbors of an entity |
398
- | `elemental_graph_sentiment` | Sentiment analysis from news articles |
399
- | `elemental_get_events` | Events for an entity or by search query |
400
- | `elemental_health` | Health check |
401
-
402
- MCP is convenient for schema discovery and entity resolution during planning.
403
- For building UI features, always use the REST client (`useElementalClient()`).
@@ -501,11 +501,21 @@ relationship PID. See the `api` rule for the two-layer architecture.
501
501
  String(v.value).padStart(20, '0'),
502
502
  );
503
503
 
504
+ function getEntityNameUrl(neid: string) {
505
+ const config = useRuntimeConfig();
506
+ const gw = (config.public as any).gatewayUrl as string;
507
+ const org = (config.public as any).tenantOrgId as string;
508
+ return `${gw}/api/qs/${org}/entities/${neid}/name`;
509
+ }
510
+
504
511
  const names = await Promise.all(
505
512
  docNeids.map(async (neid: string) => {
506
513
  try {
507
- const r = await client.getNamedEntityReport(neid);
508
- return r.report?.name ?? (r as any).name ?? neid;
514
+ const res = await $fetch<{ name: string }>(
515
+ getEntityNameUrl(neid),
516
+ { headers: { 'X-Api-Key': getApiKey() } },
517
+ );
518
+ return res.name || neid;
509
519
  } catch {
510
520
  return neid;
511
521
  }