@x12i/map-store-client 0.1.3 → 0.1.4

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 (62) hide show
  1. package/README.md +147 -1
  2. package/dist/estate/analytics.d.ts +41 -0
  3. package/dist/estate/analytics.d.ts.map +1 -0
  4. package/dist/estate/analytics.js +115 -0
  5. package/dist/estate/analytics.js.map +1 -0
  6. package/dist/estate/census.d.ts +115 -0
  7. package/dist/estate/census.d.ts.map +1 -0
  8. package/dist/estate/census.js +266 -0
  9. package/dist/estate/census.js.map +1 -0
  10. package/dist/estate/client.d.ts +66 -0
  11. package/dist/estate/client.d.ts.map +1 -0
  12. package/dist/estate/client.js +10 -0
  13. package/dist/estate/client.js.map +1 -0
  14. package/dist/estate/content.d.ts +77 -0
  15. package/dist/estate/content.d.ts.map +1 -0
  16. package/dist/estate/content.js +226 -0
  17. package/dist/estate/content.js.map +1 -0
  18. package/dist/estate/contract.d.ts +64 -0
  19. package/dist/estate/contract.d.ts.map +1 -0
  20. package/dist/estate/contract.js +258 -0
  21. package/dist/estate/contract.js.map +1 -0
  22. package/dist/estate/decisions.d.ts +32 -0
  23. package/dist/estate/decisions.d.ts.map +1 -0
  24. package/dist/estate/decisions.js +113 -0
  25. package/dist/estate/decisions.js.map +1 -0
  26. package/dist/estate/ids.d.ts +3 -0
  27. package/dist/estate/ids.d.ts.map +1 -0
  28. package/dist/estate/ids.js +6 -0
  29. package/dist/estate/ids.js.map +1 -0
  30. package/dist/estate/index.d.ts +26 -0
  31. package/dist/estate/index.d.ts.map +1 -0
  32. package/dist/estate/index.js +17 -0
  33. package/dist/estate/index.js.map +1 -0
  34. package/dist/estate/localStories.d.ts +28 -0
  35. package/dist/estate/localStories.d.ts.map +1 -0
  36. package/dist/estate/localStories.js +980 -0
  37. package/dist/estate/localStories.js.map +1 -0
  38. package/dist/estate/pack.d.ts +45 -0
  39. package/dist/estate/pack.d.ts.map +1 -0
  40. package/dist/estate/pack.js +83 -0
  41. package/dist/estate/pack.js.map +1 -0
  42. package/dist/estate/predictedAttack.d.ts +79 -0
  43. package/dist/estate/predictedAttack.d.ts.map +1 -0
  44. package/dist/estate/predictedAttack.js +321 -0
  45. package/dist/estate/predictedAttack.js.map +1 -0
  46. package/dist/estate/stories.d.ts +85 -0
  47. package/dist/estate/stories.d.ts.map +1 -0
  48. package/dist/estate/stories.js +215 -0
  49. package/dist/estate/stories.js.map +1 -0
  50. package/dist/fetch.d.ts +2 -0
  51. package/dist/fetch.d.ts.map +1 -1
  52. package/dist/fetch.js +13 -3
  53. package/dist/fetch.js.map +1 -1
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/queries.d.ts +98 -0
  59. package/dist/queries.d.ts.map +1 -0
  60. package/dist/queries.js +180 -0
  61. package/dist/queries.js.map +1 -0
  62. package/package.json +14 -1
package/README.md CHANGED
@@ -36,4 +36,150 @@ import { fetchBootstrap, walkWorkingPages, mapFetch } from '@x12i/map-store-clie
36
36
  import { asRecord } from '@x12i/map-store-client/util';
37
37
  ```
38
38
 
39
- Subpath exports: `/util`, `/types`, `/errors`, `/products`, `/bagCatalog`, `/attackSurfaces`, `/packets`.
39
+ Subpath exports: `/util`, `/types`, `/errors`, `/products`, `/bagCatalog`, `/attackSurfaces`, `/packets`, `/estate`.
40
+
41
+ `queryFindings` and `fetchVulnerabilitiesBriefing` are exported from the package root. They are the two map-store routes that are POST queries rather than working products.
42
+
43
+ ## The `estate` entry
44
+
45
+ `@x12i/map-store-client/estate` holds the reads that do not go through map-store: the Memorix content lists, the census and analytics records, the triage decisions, and the story pipeline. It takes the Memorix read client you build yourself, so it adds no dependency on `@x12i/memorix-client-api-client`.
46
+
47
+ Drawing stories needs `@x12i/storyflowix` at version 1.2.0 or newer. It is an optional peer: install it when you import `estate`. The rest of the package does not use it.
48
+
49
+ ```ts
50
+ import { createMemorixReadClient } from '@x12i/memorix-client-api-client';
51
+ import { listContent, PREDICTED_ATTACK_LIST, isPredictedAttack } from '@x12i/map-store-client/estate';
52
+
53
+ const memorix = createMemorixReadClient({
54
+ baseUrl: 'http://memorix-client.invalid', // rewritten onto the /memorix-client proxy
55
+ orgId: 'tgi',
56
+ agentIds: ['network-synapix'],
57
+ caller: 'your-app',
58
+ });
59
+
60
+ // One list per object type the contract declares, markdown included, up to 20 pages of 200.
61
+ const attacks = (
62
+ await Promise.all(
63
+ PREDICTED_ATTACK_LIST.objectTypes.map((objectType) =>
64
+ listContent(memorix.data, { objectType, contentType: 'predicted-attack', includeMarkdown: true })
65
+ )
66
+ )
67
+ ).flat().filter((row) => isPredictedAttack(row.data));
68
+ ```
69
+
70
+ The browser reaches the Memorix client API through your proxy at `/memorix-client`, with `x-memorix-org-id`, `x-memorix-agent-ids`, and `x-memorix-caller`. Story functions run through your proxy at `/skill-api` (`runStoryFunction`, agent `network-synapix`). Neither call goes to Memorix directly.
71
+
72
+ ## What each page reads
73
+
74
+ Pins (`snapshotSetId`, `analysisSnapshotId`) come from `fetchBootstrap` and are passed as `expectedPair` on walks. They are never sent back as URL pins. A working product that pages returns `pageVariants`; `walkWorkingPages` fetches each of them.
75
+
76
+ ### Once per session, shared by every page
77
+
78
+ | Read | Call |
79
+ |---|---|
80
+ | `GET /map-store/v1/health` | `fetchHealth` |
81
+ | `GET /map-store/v1/bootstrap` | `fetchBootstrap` |
82
+ | `working/estate-object-inventory`, all pages | `walkWorkingPages('estate-object-inventory', parseInventoryObject)` |
83
+ | `working/object-pre-ai-context`, all pages | `walkWorkingPages('object-pre-ai-context', …)` |
84
+ | `working/findings-query`, entry variant `open-vuln` | `walkWorkingPages('findings-query', …)`. If no row carries a CVE, fall back to `investigation-assessment` |
85
+ | `working/cve-catalog`, all pages | `walkWorkingPages('cve-catalog', …)` |
86
+ | `working/source-capability` | `fetchWorkingProduct('source-capability')` |
87
+ | `working/object-classification-census` | `fetchWorkingProduct('object-classification-census')` |
88
+ | `working/attack-surfaces` and `working/vpn-sites` | `fetchAttackSurfacesIndex`, `fetchVpnSitesIndex` |
89
+ | `working/open-questions` | `fetchWorkingProduct('open-questions')` |
90
+ | `working/known-unknowns-summary`, falling back to `known-unknowns` | `fetchWorkingProduct` |
91
+ | `working/reachability-claims-summary` | `fetchWorkingProduct('reachability-claims-summary')` |
92
+ | `working/attack-path-assessments` | `fetchWorkingProduct('attack-path-assessments')` |
93
+ | `working/alerts-query?variant=all` and `?variant=relevant` | `fetchWorkingProduct('alerts-query', 'all' \| 'relevant')` |
94
+ | `working/alerts-briefing` | `fetchWorkingProduct('alerts-briefing')` |
95
+ | `working/live-bootstrap` | `fetchLiveBootstrap` |
96
+ | `working/prospective-capability` | `fetchWorkingProduct('prospective-capability')` |
97
+ | `security-map` / `estate-analytics` / `working/estate-analytics`, discriminator `publication` | `readEstateAnalytics` |
98
+ | Connector census | `readSourceCensus` |
99
+ | Ranked CVE hubs | `fetchVulnerabilitiesBriefing` |
100
+
101
+ When `estateAnalyticsSuppliesTallies` is false, the triage counts are not on that record. Count them from `TRIAGE_TALLY_LISTS` with `listContent`: `finding` / `triage` (up to 400 pages) and `endpoint-asset` / `triage` (up to 20).
102
+
103
+ ### Data sources
104
+
105
+ **Asset inventory** (`/assets`, plus `/assets/vulnerable`, `/endpoints`, `/identified`, `/unidentified`)
106
+
107
+ - The walked inventory, identities, and findings, and the CVE catalog for the severity dots.
108
+ - `loadTier0Census(data, 'asset')` — `security-map` / `tier0-analytics` / `working/asset-census`, discriminator `asset-census`.
109
+ - `listContent` of `endpoint-asset` / `triage`.
110
+ - A drill-down by decision uses `loadAssetDecisionIds` instead of the full list. It filters `data.decisionType=asset-triage` plus `data.urgency`, `data.decision=NOT_APPLICABLE`, or `data.assessmentState`. `loadAssetUrgencies` maps each asset to its urgency.
111
+ - `walkWorkingPages('attack-path-assessments', …)` for the zone reach tier. A row's `toZoneId` plus a `hypothesis` containing "deny" marks the zone cleared; any other hypothesis marks it possible.
112
+
113
+ **Networks** (`/networks`)
114
+
115
+ - Subnets and zones are derived from the walked inventory. No separate read.
116
+ - The attack-surfaces index, the VPN-sites index, and `reachability-claims-summary`.
117
+ - `readEstateAnalytics`.
118
+
119
+ **Vulnerability catalog** (`/vulnerabilities`, plus `/cves`, `/kev`, `/exploitable`, `/findings`)
120
+
121
+ - The walked findings joined to the inventory, the CVE catalog, and `fetchVulnerabilitiesBriefing`.
122
+ - `queryFindings` for scanner severity, CVSS, KEV, EPSS, and the vendor description. Join on finding id and on CVE id.
123
+ - `loadTier0Census(data, 'vulnerability')` — `working/vulnerability-census`.
124
+ - `listContent` of `finding` / `triage`, up to 120 pages.
125
+ - One finding's decision: `listRecordContent` of `finding` / `triage` with `discriminator: { field: 'decisionType', value: 'finding-triage' }`. `triageInstance(objectType)` names the instance.
126
+ - The CVE panel's analyses: `listContent` of `finding` / `analysis` filtered by `data.cveId`.
127
+ - A verdict line: `listRecordContent` of `finding` / `verdict-reason` for that record id, with markdown.
128
+
129
+ **Recent alerts** (`/alerts`)
130
+
131
+ - `source-capability`, `alerts-query` (`all` and `relevant`), and `alerts-briefing`.
132
+ - The walked inventory, for the asset names.
133
+
134
+ ### Analysis
135
+
136
+ Both pages list one content type on every object type that declares it (`PREDICTED_ATTACK_LIST`, `MITIGATION_LIST`), with markdown, up to 20 pages of 200.
137
+
138
+ **Predicted attacks** (`/predicted-attacks`)
139
+
140
+ - `listContent` of `predicted-attack` on `finding`, `endpoint-asset`, `alert`, `network-device`, and `network-zone`. Keep a row when `isPredictedAttack` is true (`data.predictionType` is `finding-attack`, `asset-attack`, `alert-attack`, `device-attack`, or `zone-attack`).
141
+ - Fields read: `summary`, `scenarioSupport`, `score`, `asOf`, and `actions[]` (`action`, `phase`, `technique`, `assets`, `controls`, `chance`). `steps[]` is the fallback when no action names a technique.
142
+
143
+ **Mitigations** (`/mitigations`)
144
+
145
+ - `listContent` of `mitigation` on `finding`, `endpoint-asset`, `alert`, and `network-zone`. Keep a row when `isMitigationPlan` is true (`data.mitigationType` is `response-plan` and `data.plan.schemaVersion` is `response-plan/v1`).
146
+ - Fields read: `planStatus`, `summary`, `plan.subject.displayName`, and `plan.recommendations[]` (`title`, `readiness`, `conditions[].statement`, `steps`, `proposedOwnerRole`, `why`, `targetEffect`).
147
+
148
+ **Subject names.** `inventoryDisplayName(data, objectType, recordId)` reads `data.displayName` from that subject's `inventory` row.
149
+
150
+ **The diagram.** `predictionStepsFrom(row)` reads the steps, and `predictionDiagram(source)` builds the Storyflowix input. Every step carrying a technique id uses the `attack-path` template. Otherwise it uses `predicted-attack`, which `predictedAttackReady()` checks for in the installed Storyflowix. No steps, or `scenarioSupport` of `INSUFFICIENT_EVIDENCE`, produces the withheld block. No further request is made.
151
+
152
+ ### Exposure
153
+
154
+ **Attack surfaces** (`/attack-surfaces/entries`, `/attack-surfaces/shared`)
155
+
156
+ - `fetchAttackSurfacesIndex`, then `fetchAttackSurfaceDossier(surfaceId)` per surface. Read `epistemicBuckets`, `destZones`, `instances`, and `honesty`.
157
+
158
+ **Exploitability assessments** (`/epa`)
159
+
160
+ - `walkWorkingPages('object-exploitability-assessments', …)`.
161
+ - `fetchBootstrap().assessmentCoverage` and `loadTier0Census(data, 'asset')`.
162
+
163
+ **VPN sites** (`/vpn-sites/sites`, `/vpn-sites/shared`)
164
+
165
+ - `fetchVpnSitesIndex`, then `fetchVpnSiteDossier(siteId)` per site. Read `peerPublicIp`, `remoteInnerOrigins`, `localInnerSelectors`, `nativeAcceptDestZones`, and `epistemicBuckets`.
166
+
167
+ **Reachability** (`/reachability`)
168
+
169
+ - `reachability-claims-summary`, the walked inventory, and `loadTier0Census(data, 'asset')`.
170
+ - `listContent` of `endpoint-asset` / `reachability-analysis`.
171
+
172
+ The estate census board, where a page shows one, is `loadTier0Census(data, 'estate')` — `working/estate-census`.
173
+
174
+ ### Opening one record
175
+
176
+ **Context diagram.** An asset reads `listRecordContent` of `triage` with `triageInstance(objectType)`, and of `analysis`, using the `asset-exposure` and `zone-candidate` rows. A finding reads `finding` / `triage` with `finding-triage`, and `finding` / `analysis`, using the `vulnerability` or `detection` row.
177
+
178
+ **Story.**
179
+
180
+ 1. `loadStoryTabs(catalog, objectType)` — `GET /api/v1/object-types/{type}` for `functions[]`, and `GET /api/v1/object-types/{type}/content-types/story` for the presentation labels.
181
+ 2. `readStoredStory(data, objectType, recordId, template)` — the stored `story` row. Render `input` with Storyflowix and show `text`, `facts`, and `gaps`.
182
+ 3. When nothing is stored, `runStoryFunction(functionId, objectType, recordId)` — `POST /skill-api/api/v1/agent-functions/agents/network-synapix/functions/{id}/run` with `{ objectType, recordId }`. Render `result.storyInput`.
183
+ 4. When the run stops, `loadRecordContents(catalog, objectType, recordId)` then `buildLocalStory(template, objectType, contents)`. One `listRecords` call with `representation: 'both'`.
184
+
185
+ `declaringObjectTypes(contentType)` and `skillsContentTypes(objectType)` are the contract behind the lists: which object types declare which content types, and which discriminator values a row may carry. `requiredDiscriminator` reads the installed pack and names the instance a single-record read must send, so the API does not reject it with `READ_DISCRIMINATOR_REQUIRED`.
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The materialized estate rollup, and the connector census.
3
+ * Both are single Memorix reads. Neither is a working product.
4
+ */
5
+ import type { EstateAnalyticsApi, EstateData } from './client';
6
+ export declare const ESTATE_ANALYTICS_TARGET: {
7
+ readonly objectType: "security-map";
8
+ readonly contentType: "estate-analytics";
9
+ readonly recordId: "working/estate-analytics";
10
+ readonly discriminatorValue: "publication";
11
+ };
12
+ /**
13
+ * `data` of `working/estate-analytics`.
14
+ * Null when the record is missing or the content type is not registered. Any other error is thrown.
15
+ */
16
+ export declare function readEstateAnalytics(client: EstateData, signal?: AbortSignal): Promise<Record<string, unknown> | null>;
17
+ /**
18
+ * True when the estate-analytics record already carries the triage counts:
19
+ * finding decisions, asset decisions, priority bands, findings triaged,
20
+ * and both exploitability measures.
21
+ * When this is false, count them from the triage lists instead
22
+ * (`finding` / `triage` and `endpoint-asset` / `triage`). Nothing else is walked.
23
+ */
24
+ export declare function estateAnalyticsSuppliesTallies(raw: unknown): boolean;
25
+ /** Lists to count when {@link estateAnalyticsSuppliesTallies} is false. */
26
+ export declare const TRIAGE_TALLY_LISTS: readonly [{
27
+ readonly objectType: "finding";
28
+ readonly contentType: "triage";
29
+ readonly maxPages: 400;
30
+ }, {
31
+ readonly objectType: "endpoint-asset";
32
+ readonly contentType: "triage";
33
+ readonly maxPages: 20;
34
+ }];
35
+ /**
36
+ * Connector census from `analytics.sourceCensus`.
37
+ * Null when the call fails or the body has no `status` and `subjects`.
38
+ * A `status` other than `live` is returned as-is and is not ready to render.
39
+ */
40
+ export declare function readSourceCensus(analytics: EstateAnalyticsApi, signal?: AbortSignal): Promise<Record<string, unknown> | null>;
41
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/estate/analytics.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG/D,eAAO,MAAM,uBAAuB;;;;;CAK1B,CAAC;AASX;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,UAAU,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAUzC;AAsBD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAsBpE;AAED,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB;;;;;;;;EAGrB,CAAC;AAEX;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,kBAAkB,EAC7B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CASzC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The materialized estate rollup, and the connector census.
3
+ * Both are single Memorix reads. Neither is a working product.
4
+ */
5
+ import { asRecord } from '../util';
6
+ import { contentData } from './content';
7
+ export const ESTATE_ANALYTICS_TARGET = {
8
+ objectType: 'security-map',
9
+ contentType: 'estate-analytics',
10
+ recordId: 'working/estate-analytics',
11
+ discriminatorValue: 'publication',
12
+ };
13
+ function absent(err) {
14
+ const status = err?.status;
15
+ if (status === 404)
16
+ return true;
17
+ const code = asRecord(asRecord(err.body)?.error)?.code;
18
+ return status === 400 && code === 'ROUTE_UNKNOWN_CONTENT_TYPE';
19
+ }
20
+ /**
21
+ * `data` of `working/estate-analytics`.
22
+ * Null when the record is missing or the content type is not registered. Any other error is thrown.
23
+ */
24
+ export async function readEstateAnalytics(client, signal) {
25
+ if (signal?.aborted)
26
+ return null;
27
+ try {
28
+ const page = await client.getContent({ ...ESTATE_ANALYTICS_TARGET });
29
+ if (signal?.aborted)
30
+ return null;
31
+ return contentData(page);
32
+ }
33
+ catch (err) {
34
+ if (absent(err))
35
+ return null;
36
+ throw err;
37
+ }
38
+ }
39
+ const TRIAGE_DECISIONS = ['FIX_NOW', 'PATCH_NOW', 'MITIGATE', 'ACCEPT_RISK'];
40
+ function readCount(leaf) {
41
+ if (typeof leaf === 'number' && Number.isFinite(leaf))
42
+ return leaf;
43
+ const rec = asRecord(leaf);
44
+ if (!rec || rec.produced === false)
45
+ return null;
46
+ const raw = rec.value ?? rec.count;
47
+ return typeof raw === 'number' && Number.isFinite(raw) ? raw : null;
48
+ }
49
+ function metricProduced(root, key) {
50
+ if (!root)
51
+ return false;
52
+ const direct = asRecord(root[key]);
53
+ if (direct?.produced === true && typeof direct.value === 'number')
54
+ return true;
55
+ const [section, name] = key.split('.');
56
+ const bag = asRecord(root[section ?? '']);
57
+ const nested = name ? asRecord(bag?.[name]) : null;
58
+ return nested?.produced === true && typeof nested.value === 'number';
59
+ }
60
+ /**
61
+ * True when the estate-analytics record already carries the triage counts:
62
+ * finding decisions, asset decisions, priority bands, findings triaged,
63
+ * and both exploitability measures.
64
+ * When this is false, count them from the triage lists instead
65
+ * (`finding` / `triage` and `endpoint-asset` / `triage`). Nothing else is walked.
66
+ */
67
+ export function estateAnalyticsSuppliesTallies(raw) {
68
+ const top = asRecord(raw);
69
+ const groups = asRecord(top?.groups);
70
+ if (!top || !groups)
71
+ return false;
72
+ const bagReady = (group) => {
73
+ if (!group || group.produced === false)
74
+ return false;
75
+ const bag = asRecord(group.buckets) ?? group;
76
+ return TRIAGE_DECISIONS.every((code) => readCount(bag[code] ?? bag[code.toLowerCase()]) != null);
77
+ };
78
+ if (!bagReady(asRecord(groups.findingDecisions)) || !bagReady(asRecord(groups.assetDecisions)))
79
+ return false;
80
+ const priority = asRecord(groups.priorityBands);
81
+ if (!priority || priority.produced === false)
82
+ return false;
83
+ if (readCount(priority.high) == null || readCount(priority.medium) == null || readCount(priority.low) == null) {
84
+ return false;
85
+ }
86
+ const measures = asRecord(top.measures);
87
+ return ((metricProduced(measures, 'exploitability.reviewed') || metricProduced(top, 'exploitability.reviewed')) &&
88
+ (metricProduced(measures, 'exploitability.highOrCritical') ||
89
+ metricProduced(top, 'exploitability.highOrCritical')) &&
90
+ (metricProduced(measures, 'planned.findingsTriaged') || metricProduced(top, 'planned.findingsTriaged')));
91
+ }
92
+ /** Lists to count when {@link estateAnalyticsSuppliesTallies} is false. */
93
+ export const TRIAGE_TALLY_LISTS = [
94
+ { objectType: 'finding', contentType: 'triage', maxPages: 400 },
95
+ { objectType: 'endpoint-asset', contentType: 'triage', maxPages: 20 },
96
+ ];
97
+ /**
98
+ * Connector census from `analytics.sourceCensus`.
99
+ * Null when the call fails or the body has no `status` and `subjects`.
100
+ * A `status` other than `live` is returned as-is and is not ready to render.
101
+ */
102
+ export async function readSourceCensus(analytics, signal) {
103
+ try {
104
+ const body = asRecord(await analytics.sourceCensus());
105
+ if (signal?.aborted)
106
+ return null;
107
+ if (!body || typeof body.status !== 'string' || !Array.isArray(body.subjects))
108
+ return null;
109
+ return body;
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
115
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/estate/analytics.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,UAAU,EAAE,cAAc;IAC1B,WAAW,EAAE,kBAAkB;IAC/B,QAAQ,EAAE,0BAA0B;IACpC,kBAAkB,EAAE,aAAa;CACzB,CAAC;AAEX,SAAS,MAAM,CAAC,GAAY;IAC1B,MAAM,MAAM,GAAI,GAAmC,EAAE,MAAM,CAAC;IAC5D,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAE,GAA0B,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC;IAC/E,OAAO,MAAM,KAAK,GAAG,IAAI,IAAI,KAAK,4BAA4B,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAkB,EAClB,MAAoB;IAEpB,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,EAAE,GAAG,uBAAuB,EAAE,CAAC,CAAC;QACrE,IAAI,MAAM,EAAE,OAAO;YAAE,OAAO,IAAI,CAAC;QACjC,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;AAE7E,SAAS,SAAS,CAAC,IAAa;IAC9B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnE,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAChD,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC;IACnC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED,SAAS,cAAc,CAAC,IAAoC,EAAE,GAAW;IACvE,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACnC,IAAI,MAAM,EAAE,QAAQ,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC/E,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,OAAO,MAAM,EAAE,QAAQ,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC;AACvE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAAC,GAAY;IACzD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAClC,MAAM,QAAQ,GAAG,CAAC,KAAqC,EAAW,EAAE;QAClE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QACrD,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;QAC7C,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;IACnG,CAAC,CAAC;IACF,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7G,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAChD,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAC3D,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QAC9G,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxC,OAAO,CACL,CAAC,cAAc,CAAC,QAAQ,EAAE,yBAAyB,CAAC,IAAI,cAAc,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC;QACvG,CAAC,cAAc,CAAC,QAAQ,EAAE,+BAA+B,CAAC;YACxD,cAAc,CAAC,GAAG,EAAE,+BAA+B,CAAC,CAAC;QACvD,CAAC,cAAc,CAAC,QAAQ,EAAE,yBAAyB,CAAC,IAAI,cAAc,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC,CACxG,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE;IAC/D,EAAE,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE;CAC7D,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,SAA6B,EAC7B,MAAoB;IAEpB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;QACtD,IAAI,MAAM,EAAE,OAAO;YAAE,OAAO,IAAI,CAAC;QACjC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3F,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Tier-0 census records. Three processes count published lander rows and each
3
+ * save one content record. They do not read AI products. They are not
4
+ * `estate-analytics` and they are not a bag total.
5
+ *
6
+ * One GET per board, after `working/bootstrap` has pinned the session:
7
+ * `security-map` / `tier0-analytics` / `working/{vulnerability,asset,estate}-census`.
8
+ * `data.kind` selects the record.
9
+ *
10
+ * `produced: true` and `value: 0` is a finished empty count.
11
+ * `produced: false` and `value: 0` means that source was not read.
12
+ * A missing field is the bucket `absent`. A missing KEV flag is `absent`, not `false`.
13
+ */
14
+ import type { EstateData } from './client';
15
+ export type Tier0Metric = {
16
+ value: number;
17
+ produced: boolean;
18
+ };
19
+ export type MetricMap = Record<string, Tier0Metric>;
20
+ export type VulnerabilityCensusRow = {
21
+ id: string;
22
+ cveId: string | null;
23
+ label: string | null;
24
+ affectedAssets: number | null;
25
+ findings: number | null;
26
+ };
27
+ export type AssetCensusRow = {
28
+ id: string;
29
+ label: string | null;
30
+ findings: number | null;
31
+ distinctCves: number | null;
32
+ };
33
+ export type VulnerabilityCensus = {
34
+ kind: 'vulnerability-census';
35
+ totals: {
36
+ findings: Tier0Metric;
37
+ vulnerabilities: Tier0Metric;
38
+ distinctCves: Tier0Metric;
39
+ };
40
+ rows: VulnerabilityCensusRow[];
41
+ groups: Record<string, MetricMap>;
42
+ };
43
+ export type AssetCensus = {
44
+ kind: 'asset-census';
45
+ totals: {
46
+ assets: Tier0Metric;
47
+ findings: Tier0Metric;
48
+ };
49
+ rows: AssetCensusRow[];
50
+ groups: Record<string, MetricMap>;
51
+ };
52
+ export type EstateCensus = {
53
+ kind: 'estate-census';
54
+ totals: {
55
+ subjects: {
56
+ endpointAssets: Tier0Metric;
57
+ findings: Tier0Metric;
58
+ alerts: Tier0Metric;
59
+ zones: Tier0Metric;
60
+ devices: Tier0Metric;
61
+ unresolvedSubjects: Tier0Metric;
62
+ };
63
+ bags: {
64
+ hypothesizedPaths: Tier0Metric;
65
+ reachabilityClaims: Tier0Metric;
66
+ };
67
+ };
68
+ rows: readonly [];
69
+ groups: Record<string, MetricMap>;
70
+ };
71
+ export type Tier0Census = VulnerabilityCensus | AssetCensus | EstateCensus;
72
+ export declare const EMPTY_VULNERABILITY_ROWS: VulnerabilityCensusRow[];
73
+ export declare const EMPTY_ASSET_ROWS: AssetCensusRow[];
74
+ export type Tier0BoardId = 'vulnerability' | 'asset' | 'estate';
75
+ export declare const TIER0_TARGETS: {
76
+ readonly vulnerability: {
77
+ readonly recordId: "working/vulnerability-census";
78
+ readonly kind: "vulnerability-census";
79
+ };
80
+ readonly asset: {
81
+ readonly recordId: "working/asset-census";
82
+ readonly kind: "asset-census";
83
+ };
84
+ readonly estate: {
85
+ readonly recordId: "working/estate-census";
86
+ readonly kind: "estate-census";
87
+ };
88
+ };
89
+ /** A figure the census has not produced is absent, not zero. */
90
+ export declare function tier0Value(metric: Tier0Metric | undefined): number | null;
91
+ export declare function publishedCount(value: number | null | undefined): string;
92
+ /**
93
+ * List figure. A loaded census uses its own number, including a finished `0`.
94
+ * A missing row on a loaded census is unavailable.
95
+ * Before that document exists, the lander figure keeps the column from going blank.
96
+ */
97
+ export declare function censusOrLander(censusReady: boolean, censusRow: object | null, censusValue: number | null | undefined, landerValue: number | null | undefined): number | null;
98
+ /** Parse one census body. A mismatched `data.kind` is not that board's record. */
99
+ export declare function parseTier0Census(page: unknown, kind: Tier0Census['kind']): Tier0Census | null;
100
+ /**
101
+ * One client-API read of `security-map` / `tier0-analytics`.
102
+ * A missing record or an unregistered content type yields null.
103
+ */
104
+ export declare function loadTier0Census(client: EstateData, board: Tier0BoardId, signal?: AbortSignal): Promise<Tier0Census | null>;
105
+ /** Dialog lookup. `rows[].id` is the lander record id. */
106
+ export declare function censusEntryById<T extends {
107
+ id: string;
108
+ }>(rows: readonly T[], id: string): T | null;
109
+ /**
110
+ * CVE dialog join. The panel's key is the CVE text. Use the lander id when that
111
+ * id is the CVE, otherwise the single census row that publishes this `cveId`.
112
+ * Several rows for one CVE stay unresolved here. Each dialog still matches by id.
113
+ */
114
+ export declare function vulnerabilityEntryForCve(rows: readonly VulnerabilityCensusRow[], cveId: string): VulnerabilityCensusRow | null;
115
+ //# sourceMappingURL=census.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"census.d.ts","sourceRoot":"","sources":["../../src/estate/census.ts"],"names":[],"mappings":"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"}