@mailwoman/mcp 8.6.0 → 9.0.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/README.md +15 -7
- package/cli.ts +65 -13
- package/layer-guards.ts +7 -7
- package/out/cli.d.ts +12 -6
- package/out/cli.d.ts.map +1 -1
- package/out/cli.js +54 -14
- package/out/cli.js.map +1 -1
- package/out/layer-guards.d.ts +7 -7
- package/out/layer-guards.js +7 -7
- package/out/tools.d.ts +3 -3
- package/out/tools.js +3 -3
- package/package.json +12 -11
- package/tools.ts +3 -3
package/README.md
CHANGED
|
@@ -4,13 +4,21 @@ An **MCP server** exposing [Mailwoman](https://mailwoman.sister.software)'s pars
|
|
|
4
4
|
|
|
5
5
|
## Tools
|
|
6
6
|
|
|
7
|
-
| Tool
|
|
8
|
-
|
|
|
9
|
-
| `mailwoman_parse`
|
|
10
|
-
| `mailwoman_geocode`
|
|
11
|
-
| `mailwoman_poi_search`
|
|
12
|
-
| `mailwoman_overpass_export`
|
|
13
|
-
| `mailwoman_layer_manifest`
|
|
7
|
+
| Tool | What it does | Needs the model + a gazetteer |
|
|
8
|
+
| -------------------------------- | ---------------------------------------------------------------------------- | ----------------------------- |
|
|
9
|
+
| `mailwoman_parse` | Runtime-pipeline parse (optionally POI-aware) | Yes |
|
|
10
|
+
| `mailwoman_geocode` | Street-level geocode cascade | Yes |
|
|
11
|
+
| `mailwoman_poi_search` | POI-intent extraction, executed against a wired `poi.db` | Yes |
|
|
12
|
+
| `mailwoman_overpass_export` | Renders a POI query as OverpassQL (prints the query, never runs it) | Yes |
|
|
13
|
+
| `mailwoman_layer_manifest` | Reads a spatial-layer database's provenance manifest + coverage summary | No |
|
|
14
|
+
| `mailwoman_bdc_filing_landscape` | Reads a `bdc.db` layer's filing census over census blocks or H3 cells | No |
|
|
15
|
+
| `mailwoman_plausibility_check` | Scores one claimed broadband-service assertion against filing + POI evidence | Only when it geocodes |
|
|
16
|
+
| `mailwoman_filer_lookup` | Reads the FCC filer identity crosswalk from a `filer.db` layer | No |
|
|
17
|
+
| `mailwoman_filer_family` | Reads a corporate family's membership from a `filer.db` layer | No |
|
|
18
|
+
|
|
19
|
+
The four model-backed tools load the `en-US` weights and open a resolver on the first call that needs them, not at
|
|
20
|
+
startup. With neither `$MAILWOMAN_CANDIDATE_DB` nor a WOF distribution on the data root, that first call answers with
|
|
21
|
+
the `mailwoman data pull candidate` fix instead of an internal resolver error.
|
|
14
22
|
|
|
15
23
|
## Config
|
|
16
24
|
|
package/cli.ts
CHANGED
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* manifest tool, and `@mailwoman/bdc`'s `filingLandscape`/`plausibilityCheck` for the two BDC tools.
|
|
11
11
|
*
|
|
12
12
|
* Deps are LAZY: nothing here loads the neural weights or opens a gazetteer db at startup — an MCP client
|
|
13
|
-
* connects, lists tools, and may never call one (or may call
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* connects, lists tools, and may never call one (or may call only the layer-database tools, none of which touch the
|
|
14
|
+
* classifier). The shared classifier+resolver are built once, on the FIRST call to any tool that needs them, and
|
|
15
|
+
* cached for the process lifetime. `mailwoman_overpass_export` DOES need them despite never executing a query — it
|
|
16
|
+
* parses the input to find the subject and anchor before it can emit OverpassQL.
|
|
16
17
|
*
|
|
17
|
-
* **
|
|
18
|
+
* **Laziness moves the first failure into a tool call, so `loadCore` owns the friendly-failure messages** that
|
|
19
|
+
* `photon`/`nominatim`/`mailwoman serve` print at boot. Both are thrown, not printed: `server.ts` catches a handler
|
|
20
|
+
* throw and returns it as an `isError` tool result, so a thrown message reaches the agent where a `console.error` +
|
|
21
|
+
* `process.exit(1)` would just kill the transport mid-conversation. See `loadCore` for the two.
|
|
22
|
+
*
|
|
23
|
+
* **Graceful layer-absent guards (decision 6).** Both BDC-backed tools treat a missing/unreadable
|
|
18
24
|
* database file as absence, never a raw `node:sqlite` throw ("unable to open database file"): `bdcFilingLandscape`
|
|
19
25
|
* requires bdc.db unconditionally, so a missing file becomes one friendly thrown `Error` naming the layer;
|
|
20
26
|
* `plausibilityCheck`'s `bdcDB`/`poi` deps are each OPTIONAL, so a missing/absent `bdc_database_path`/
|
|
@@ -25,12 +31,12 @@
|
|
|
25
31
|
* this file opens at import time (which is exactly why THIS file can't be unit-tested directly; see
|
|
26
32
|
* `layer-guards.test.ts` for their branch coverage).
|
|
27
33
|
*
|
|
28
|
-
* `mailwoman_filer_lookup`
|
|
34
|
+
* `mailwoman_filer_lookup` follows the SAME "requires the layer unconditionally" discipline as
|
|
29
35
|
* `mailwoman_bdc_filing_landscape` (`assertFilerDatabaseExists` + `openFilerDatabaseIfPresent`, mirroring
|
|
30
36
|
* `assertBDCDatabaseExists` + the BDC open) — `filerLookup` itself has no optional-dep abstain shape (gate 4 makes
|
|
31
37
|
* it throw rather than answer unstamped), so a missing filer.db becomes one friendly thrown Error naming the layer.
|
|
32
38
|
*
|
|
33
|
-
* `mailwoman_filer_family`
|
|
39
|
+
* `mailwoman_filer_family` follows the IDENTICAL discipline, reusing the same two guards — `familyRollup`
|
|
34
40
|
* has no optional-dep abstain shape either (it throws on a bad `familyID`/`nodeID` XOR or a pre-`filer_family`
|
|
35
41
|
* schema), so filer.db is required unconditionally here too. Its result — always `FamilyRollup[]`, never `null` or
|
|
36
42
|
* a bare object — is passed through untouched: this data is who-owns-whom, and a silent reshape here would be a
|
|
@@ -57,6 +63,7 @@ import { createRuntimePipeline, type PipelineResult } from "mailwoman"
|
|
|
57
63
|
import { geocodeAddress, ShardProvider } from "mailwoman/geocode-core"
|
|
58
64
|
import { emitOverpassQL } from "mailwoman/poi-overpass"
|
|
59
65
|
import {
|
|
66
|
+
buildNoGazetteerMessage,
|
|
60
67
|
createResolverBackend,
|
|
61
68
|
mailwomanDataRoot,
|
|
62
69
|
resolveCandidateDBPath,
|
|
@@ -94,14 +101,59 @@ const poiDatabasePath = values["poi-db"]
|
|
|
94
101
|
*/
|
|
95
102
|
let corePromise: Promise<{ classifier: NeuralAddressClassifier; resolver: Resolver; shards: ShardProvider }> | undefined
|
|
96
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The four tools that need {@link loadCore} — every path through `getPlainPipeline`/`getPoiPipeline`/`resolveGeocode`.
|
|
106
|
+
* `mailwoman_layer_manifest`, `mailwoman_bdc_filing_landscape`, `mailwoman_filer_lookup` and `mailwoman_filer_family`
|
|
107
|
+
* never call it, so they keep working when this fails; `mailwoman_plausibility_check` only reaches it when it geocodes.
|
|
108
|
+
* Named in both guard messages below because an agent that just got one needs to know what it can still do.
|
|
109
|
+
*/
|
|
110
|
+
const CORE_BACKED_TOOLS = "mailwoman_parse, mailwoman_geocode, mailwoman_poi_search, mailwoman_overpass_export"
|
|
111
|
+
|
|
112
|
+
const CORE_FREE_TOOLS =
|
|
113
|
+
"mailwoman_layer_manifest, mailwoman_bdc_filing_landscape, mailwoman_filer_lookup, mailwoman_filer_family"
|
|
114
|
+
|
|
97
115
|
function loadCore(): Promise<{ classifier: NeuralAddressClassifier; resolver: Resolver; shards: ShardProvider }> {
|
|
98
116
|
corePromise ??= (async () => {
|
|
99
117
|
const resolverMod = await import("@mailwoman/resolver-wof-sqlite")
|
|
100
118
|
const wofPaths = wofShardPaths().filter(existsSync)
|
|
101
119
|
const candidateDb = resolveCandidateDBPath()
|
|
120
|
+
|
|
121
|
+
// #1009 friendly-failure discipline, the MCP shape of it. `server.ts` turns a thrown Error into an
|
|
122
|
+
// `isError` tool result carrying `error.message`, so the message an agent reads IS whatever is thrown
|
|
123
|
+
// here — which made the raw internal `resolveShards: at least one shard is required` the first thing a
|
|
124
|
+
// stranger saw from `mailwoman_parse` on a fresh install (measured 2026-08-03 against a standalone
|
|
125
|
+
// `npm install @mailwoman/mcp`). Same preflight as `photon`/`nominatim`/`mailwoman serve`, and the same
|
|
126
|
+
// discovery: #1444 moved the `<data-root>/wof/candidate.db` convention fallback INTO
|
|
127
|
+
// `resolveCandidateDBPath`, so this bare call picks a pulled gazetteer up with nothing exported. The
|
|
128
|
+
// `MAILWOMAN_DATA_ROOT` in the client's `env` block is enough on its own.
|
|
129
|
+
if (!candidateDb && !wofPaths.length) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`${buildNoGazetteerMessage({
|
|
132
|
+
dataRoot: mailwomanDataRoot(),
|
|
133
|
+
docsPath: "/docs/developers/how-to/use-the-mcp-server",
|
|
134
|
+
})}\n\n Needs it: ${CORE_BACKED_TOOLS}\n Works without it: ${CORE_FREE_TOOLS}`
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
102
138
|
const backend = createResolverBackend(resolverMod, { wofPaths, candidateDb })
|
|
103
139
|
const resolver = createWOFResolver(backend)
|
|
104
|
-
|
|
140
|
+
|
|
141
|
+
// Same discipline for the model weights. `@mailwoman/neural-weights-en-us` is a declared dependency of
|
|
142
|
+
// this package as of 2026-08-03 — before that a standalone `npm install @mailwoman/mcp` resolved
|
|
143
|
+
// nothing and every core-backed tool answered with `resolveWeights`' raw not-found text. The guard keeps
|
|
144
|
+
// that text (it already names the exact fix command) and adds what an agent mid-conversation needs next:
|
|
145
|
+
// which tools are down and which are not.
|
|
146
|
+
let classifier: NeuralAddressClassifier
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
classifier = await NeuralAddressClassifier.loadFromWeights({ locale: "en-US" })
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`✗ ${error instanceof Error ? error.message : String(error)}\n\n` +
|
|
153
|
+
` Needs it: ${CORE_BACKED_TOOLS}\n Works without it: ${CORE_FREE_TOOLS}`
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
105
157
|
const shards = new ShardProvider(resolverMod, mailwomanDataRoot())
|
|
106
158
|
|
|
107
159
|
return { classifier, resolver, shards }
|
|
@@ -150,9 +202,9 @@ async function getPoiPipeline(dbPath: string | undefined): Promise<Pipeline> {
|
|
|
150
202
|
|
|
151
203
|
/**
|
|
152
204
|
* `plausibilityCheck`'s geocode dep — reuses the SAME shared classifier+resolver `deps.geocode` builds from (see the
|
|
153
|
-
* module header's laziness note)
|
|
154
|
-
*
|
|
155
|
-
*
|
|
205
|
+
* module header's laziness note). `deriveGeocodeRegister`/the formatted register is the geocode dep's concern, so it is
|
|
206
|
+
* wired at this CLI/MCP layer rather than inside `plausibility.ts`. The real return type (`GeocodeResult`) is
|
|
207
|
+
* structurally assignable to `plausibility.ts`'s minimal `GeocodeLike` — no adapter needed.
|
|
156
208
|
*/
|
|
157
209
|
async function resolveGeocode(address: string) {
|
|
158
210
|
const { classifier, resolver, shards } = await loadCore()
|
|
@@ -220,7 +272,7 @@ const deps: MCPToolDeps = {
|
|
|
220
272
|
},
|
|
221
273
|
|
|
222
274
|
async bdcFilingLandscape(q) {
|
|
223
|
-
// Decision 6
|
|
275
|
+
// Decision 6: `mailwoman_bdc_filing_landscape` requires bdc.db unconditionally (no optional-dep
|
|
224
276
|
// abstain shape exists for this tool), so a missing file becomes a friendly thrown Error naming the layer —
|
|
225
277
|
// never the raw `node:sqlite` "unable to open database file" message.
|
|
226
278
|
assertBDCDatabaseExists("mailwoman_bdc_filing_landscape", q.databasePath)
|
|
@@ -252,7 +304,7 @@ const deps: MCPToolDeps = {
|
|
|
252
304
|
},
|
|
253
305
|
|
|
254
306
|
async filerLookup(q) {
|
|
255
|
-
// Decision 6/gate 4
|
|
307
|
+
// Decision 6/gate 4: filerLookup has no optional-dep abstain shape — it throws rather than
|
|
256
308
|
// answer unstamped — so filer.db is required unconditionally, same discipline as bdc.db is for
|
|
257
309
|
// mailwoman_bdc_filing_landscape.
|
|
258
310
|
assertFilerDatabaseExists("mailwoman_filer_lookup", q.databasePath)
|
|
@@ -280,7 +332,7 @@ const deps: MCPToolDeps = {
|
|
|
280
332
|
},
|
|
281
333
|
|
|
282
334
|
async filerFamily(q) {
|
|
283
|
-
// Same discipline as mailwoman_filer_lookup
|
|
335
|
+
// Same discipline as mailwoman_filer_lookup — familyRollup has no optional-dep abstain shape
|
|
284
336
|
// either, so filer.db is required unconditionally.
|
|
285
337
|
assertFilerDatabaseExists("mailwoman_filer_family", q.databasePath)
|
|
286
338
|
|
package/layer-guards.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Decision 6
|
|
7
|
-
*
|
|
6
|
+
* Decision 6 layer-absent guards — kept out of `cli.ts` in their own importable module so the
|
|
7
|
+
* branching itself has direct unit coverage (`layer-guards.test.ts`), not just
|
|
8
8
|
* "the tool handler passes an abstain through" (`tools.test.ts`'s stub-level dispatch tests). `cli.ts`
|
|
9
9
|
* top-level-`await`s a real stdio transport connection at import time, so IT can't be imported by vitest — these
|
|
10
10
|
* three functions have no such dependency (pure existence-check + open, or a thrown Error), so they live here and
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - `assertBDCDatabaseExists` — `mailwoman_bdc_filing_landscape` requires bdc.db unconditionally (no optional-dep
|
|
18
18
|
* abstain shape exists for that tool), so a missing file becomes one friendly thrown `Error` naming the layer
|
|
19
19
|
* instead of the raw `node:sqlite` "unable to open database file" message.
|
|
20
|
-
* - `openFilerDatabaseIfPresent` / `assertFilerDatabaseExists`
|
|
20
|
+
* - `openFilerDatabaseIfPresent` / `assertFilerDatabaseExists` — the SAME pairing, for filer.db.
|
|
21
21
|
* `mailwoman_filer_lookup` requires filer.db unconditionally (mirrors `mailwoman_bdc_filing_landscape`'s own
|
|
22
22
|
* "requires the layer" discipline — `filerLookup` itself has no optional-dep abstain shape either, since gate
|
|
23
23
|
* 4 makes it throw rather than answer unstamped), so `cli.ts` pairs `assertFilerDatabaseExists` (the friendly
|
|
@@ -77,7 +77,7 @@ export function assertBDCDatabaseExists(toolName: string, databasePath: string):
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Open a filer.db, or return `undefined` when `databasePath` is unset or the file is missing — NEVER a raw sqlite throw
|
|
80
|
-
* (
|
|
80
|
+
* (mirroring {@link openBDCDatabaseIfPresent}). Used by `cli.ts`'s `mailwoman_filer_lookup` handler after
|
|
81
81
|
* {@link assertFilerDatabaseExists} has already confirmed the file is present.
|
|
82
82
|
*/
|
|
83
83
|
export function openFilerDatabaseIfPresent(
|
|
@@ -89,9 +89,9 @@ export function openFilerDatabaseIfPresent(
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* Throws a friendly Error naming the layer when `databasePath` doesn't exist — `mailwoman_filer_lookup`'s guard
|
|
93
|
-
*
|
|
94
|
-
*
|
|
92
|
+
* Throws a friendly Error naming the layer when `databasePath` doesn't exist — `mailwoman_filer_lookup`'s guard
|
|
93
|
+
* (mirroring {@link assertBDCDatabaseExists}). `filerLookup` itself has no optional-dep abstain shape (gate 4 makes it
|
|
94
|
+
* throw rather than answer unstamped), so filer.db is required unconditionally, same as bdc.db is for
|
|
95
95
|
* `mailwoman_bdc_filing_landscape`.
|
|
96
96
|
*/
|
|
97
97
|
export function assertFilerDatabaseExists(toolName: string, databasePath: string): void {
|
package/out/cli.d.ts
CHANGED
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* manifest tool, and `@mailwoman/bdc`'s `filingLandscape`/`plausibilityCheck` for the two BDC tools.
|
|
11
11
|
*
|
|
12
12
|
* Deps are LAZY: nothing here loads the neural weights or opens a gazetteer db at startup — an MCP client
|
|
13
|
-
* connects, lists tools, and may never call one (or may call
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* connects, lists tools, and may never call one (or may call only the layer-database tools, none of which touch the
|
|
14
|
+
* classifier). The shared classifier+resolver are built once, on the FIRST call to any tool that needs them, and
|
|
15
|
+
* cached for the process lifetime. `mailwoman_overpass_export` DOES need them despite never executing a query — it
|
|
16
|
+
* parses the input to find the subject and anchor before it can emit OverpassQL.
|
|
16
17
|
*
|
|
17
|
-
* **
|
|
18
|
+
* **Laziness moves the first failure into a tool call, so `loadCore` owns the friendly-failure messages** that
|
|
19
|
+
* `photon`/`nominatim`/`mailwoman serve` print at boot. Both are thrown, not printed: `server.ts` catches a handler
|
|
20
|
+
* throw and returns it as an `isError` tool result, so a thrown message reaches the agent where a `console.error` +
|
|
21
|
+
* `process.exit(1)` would just kill the transport mid-conversation. See `loadCore` for the two.
|
|
22
|
+
*
|
|
23
|
+
* **Graceful layer-absent guards (decision 6).** Both BDC-backed tools treat a missing/unreadable
|
|
18
24
|
* database file as absence, never a raw `node:sqlite` throw ("unable to open database file"): `bdcFilingLandscape`
|
|
19
25
|
* requires bdc.db unconditionally, so a missing file becomes one friendly thrown `Error` naming the layer;
|
|
20
26
|
* `plausibilityCheck`'s `bdcDB`/`poi` deps are each OPTIONAL, so a missing/absent `bdc_database_path`/
|
|
@@ -25,12 +31,12 @@
|
|
|
25
31
|
* this file opens at import time (which is exactly why THIS file can't be unit-tested directly; see
|
|
26
32
|
* `layer-guards.test.ts` for their branch coverage).
|
|
27
33
|
*
|
|
28
|
-
* `mailwoman_filer_lookup`
|
|
34
|
+
* `mailwoman_filer_lookup` follows the SAME "requires the layer unconditionally" discipline as
|
|
29
35
|
* `mailwoman_bdc_filing_landscape` (`assertFilerDatabaseExists` + `openFilerDatabaseIfPresent`, mirroring
|
|
30
36
|
* `assertBDCDatabaseExists` + the BDC open) — `filerLookup` itself has no optional-dep abstain shape (gate 4 makes
|
|
31
37
|
* it throw rather than answer unstamped), so a missing filer.db becomes one friendly thrown Error naming the layer.
|
|
32
38
|
*
|
|
33
|
-
* `mailwoman_filer_family`
|
|
39
|
+
* `mailwoman_filer_family` follows the IDENTICAL discipline, reusing the same two guards — `familyRollup`
|
|
34
40
|
* has no optional-dep abstain shape either (it throws on a bad `familyID`/`nodeID` XOR or a pre-`filer_family`
|
|
35
41
|
* schema), so filer.db is required unconditionally here too. Its result — always `FamilyRollup[]`, never `null` or
|
|
36
42
|
* a bare object — is passed through untouched: this data is who-owns-whom, and a silent reshape here would be a
|
package/out/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG"}
|
package/out/cli.js
CHANGED
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* manifest tool, and `@mailwoman/bdc`'s `filingLandscape`/`plausibilityCheck` for the two BDC tools.
|
|
11
11
|
*
|
|
12
12
|
* Deps are LAZY: nothing here loads the neural weights or opens a gazetteer db at startup — an MCP client
|
|
13
|
-
* connects, lists tools, and may never call one (or may call
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* connects, lists tools, and may never call one (or may call only the layer-database tools, none of which touch the
|
|
14
|
+
* classifier). The shared classifier+resolver are built once, on the FIRST call to any tool that needs them, and
|
|
15
|
+
* cached for the process lifetime. `mailwoman_overpass_export` DOES need them despite never executing a query — it
|
|
16
|
+
* parses the input to find the subject and anchor before it can emit OverpassQL.
|
|
16
17
|
*
|
|
17
|
-
* **
|
|
18
|
+
* **Laziness moves the first failure into a tool call, so `loadCore` owns the friendly-failure messages** that
|
|
19
|
+
* `photon`/`nominatim`/`mailwoman serve` print at boot. Both are thrown, not printed: `server.ts` catches a handler
|
|
20
|
+
* throw and returns it as an `isError` tool result, so a thrown message reaches the agent where a `console.error` +
|
|
21
|
+
* `process.exit(1)` would just kill the transport mid-conversation. See `loadCore` for the two.
|
|
22
|
+
*
|
|
23
|
+
* **Graceful layer-absent guards (decision 6).** Both BDC-backed tools treat a missing/unreadable
|
|
18
24
|
* database file as absence, never a raw `node:sqlite` throw ("unable to open database file"): `bdcFilingLandscape`
|
|
19
25
|
* requires bdc.db unconditionally, so a missing file becomes one friendly thrown `Error` naming the layer;
|
|
20
26
|
* `plausibilityCheck`'s `bdcDB`/`poi` deps are each OPTIONAL, so a missing/absent `bdc_database_path`/
|
|
@@ -25,12 +31,12 @@
|
|
|
25
31
|
* this file opens at import time (which is exactly why THIS file can't be unit-tested directly; see
|
|
26
32
|
* `layer-guards.test.ts` for their branch coverage).
|
|
27
33
|
*
|
|
28
|
-
* `mailwoman_filer_lookup`
|
|
34
|
+
* `mailwoman_filer_lookup` follows the SAME "requires the layer unconditionally" discipline as
|
|
29
35
|
* `mailwoman_bdc_filing_landscape` (`assertFilerDatabaseExists` + `openFilerDatabaseIfPresent`, mirroring
|
|
30
36
|
* `assertBDCDatabaseExists` + the BDC open) — `filerLookup` itself has no optional-dep abstain shape (gate 4 makes
|
|
31
37
|
* it throw rather than answer unstamped), so a missing filer.db becomes one friendly thrown Error naming the layer.
|
|
32
38
|
*
|
|
33
|
-
* `mailwoman_filer_family`
|
|
39
|
+
* `mailwoman_filer_family` follows the IDENTICAL discipline, reusing the same two guards — `familyRollup`
|
|
34
40
|
* has no optional-dep abstain shape either (it throws on a bad `familyID`/`nodeID` XOR or a pre-`filer_family`
|
|
35
41
|
* schema), so filer.db is required unconditionally here too. Its result — always `FamilyRollup[]`, never `null` or
|
|
36
42
|
* a bare object — is passed through untouched: this data is who-owns-whom, and a silent reshape here would be a
|
|
@@ -54,7 +60,7 @@ import { createWOFResolver } from "@mailwoman/resolver";
|
|
|
54
60
|
import { createRuntimePipeline } from "mailwoman";
|
|
55
61
|
import { geocodeAddress, ShardProvider } from "mailwoman/geocode-core";
|
|
56
62
|
import { emitOverpassQL } from "mailwoman/poi-overpass";
|
|
57
|
-
import { createResolverBackend, mailwomanDataRoot, resolveCandidateDBPath, wofShardPaths, } from "mailwoman/resolver-backend";
|
|
63
|
+
import { buildNoGazetteerMessage, createResolverBackend, mailwomanDataRoot, resolveCandidateDBPath, wofShardPaths, } from "mailwoman/resolver-backend";
|
|
58
64
|
import { assertBDCDatabaseExists, assertFilerDatabaseExists, openBDCDatabaseIfPresent, openFilerDatabaseIfPresent, openPlausibilityPOIDeps, } from "./layer-guards.js";
|
|
59
65
|
import { createMCPServer } from "./server.js";
|
|
60
66
|
const { values } = parseArgs({
|
|
@@ -75,14 +81,48 @@ const poiDatabasePath = values["poi-db"];
|
|
|
75
81
|
* already on the data root — same selection `nominatim`/`photon`'s CLIs make.
|
|
76
82
|
*/
|
|
77
83
|
let corePromise;
|
|
84
|
+
/**
|
|
85
|
+
* The four tools that need {@link loadCore} — every path through `getPlainPipeline`/`getPoiPipeline`/`resolveGeocode`.
|
|
86
|
+
* `mailwoman_layer_manifest`, `mailwoman_bdc_filing_landscape`, `mailwoman_filer_lookup` and `mailwoman_filer_family`
|
|
87
|
+
* never call it, so they keep working when this fails; `mailwoman_plausibility_check` only reaches it when it geocodes.
|
|
88
|
+
* Named in both guard messages below because an agent that just got one needs to know what it can still do.
|
|
89
|
+
*/
|
|
90
|
+
const CORE_BACKED_TOOLS = "mailwoman_parse, mailwoman_geocode, mailwoman_poi_search, mailwoman_overpass_export";
|
|
91
|
+
const CORE_FREE_TOOLS = "mailwoman_layer_manifest, mailwoman_bdc_filing_landscape, mailwoman_filer_lookup, mailwoman_filer_family";
|
|
78
92
|
function loadCore() {
|
|
79
93
|
corePromise ??= (async () => {
|
|
80
94
|
const resolverMod = await import("@mailwoman/resolver-wof-sqlite");
|
|
81
95
|
const wofPaths = wofShardPaths().filter(existsSync);
|
|
82
96
|
const candidateDb = resolveCandidateDBPath();
|
|
97
|
+
// #1009 friendly-failure discipline, the MCP shape of it. `server.ts` turns a thrown Error into an
|
|
98
|
+
// `isError` tool result carrying `error.message`, so the message an agent reads IS whatever is thrown
|
|
99
|
+
// here — which made the raw internal `resolveShards: at least one shard is required` the first thing a
|
|
100
|
+
// stranger saw from `mailwoman_parse` on a fresh install (measured 2026-08-03 against a standalone
|
|
101
|
+
// `npm install @mailwoman/mcp`). Same preflight as `photon`/`nominatim`/`mailwoman serve`, and the same
|
|
102
|
+
// discovery: #1444 moved the `<data-root>/wof/candidate.db` convention fallback INTO
|
|
103
|
+
// `resolveCandidateDBPath`, so this bare call picks a pulled gazetteer up with nothing exported. The
|
|
104
|
+
// `MAILWOMAN_DATA_ROOT` in the client's `env` block is enough on its own.
|
|
105
|
+
if (!candidateDb && !wofPaths.length) {
|
|
106
|
+
throw new Error(`${buildNoGazetteerMessage({
|
|
107
|
+
dataRoot: mailwomanDataRoot(),
|
|
108
|
+
docsPath: "/docs/developers/how-to/use-the-mcp-server",
|
|
109
|
+
})}\n\n Needs it: ${CORE_BACKED_TOOLS}\n Works without it: ${CORE_FREE_TOOLS}`);
|
|
110
|
+
}
|
|
83
111
|
const backend = createResolverBackend(resolverMod, { wofPaths, candidateDb });
|
|
84
112
|
const resolver = createWOFResolver(backend);
|
|
85
|
-
|
|
113
|
+
// Same discipline for the model weights. `@mailwoman/neural-weights-en-us` is a declared dependency of
|
|
114
|
+
// this package as of 2026-08-03 — before that a standalone `npm install @mailwoman/mcp` resolved
|
|
115
|
+
// nothing and every core-backed tool answered with `resolveWeights`' raw not-found text. The guard keeps
|
|
116
|
+
// that text (it already names the exact fix command) and adds what an agent mid-conversation needs next:
|
|
117
|
+
// which tools are down and which are not.
|
|
118
|
+
let classifier;
|
|
119
|
+
try {
|
|
120
|
+
classifier = await NeuralAddressClassifier.loadFromWeights({ locale: "en-US" });
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
throw new Error(`✗ ${error instanceof Error ? error.message : String(error)}\n\n` +
|
|
124
|
+
` Needs it: ${CORE_BACKED_TOOLS}\n Works without it: ${CORE_FREE_TOOLS}`);
|
|
125
|
+
}
|
|
86
126
|
const shards = new ShardProvider(resolverMod, mailwomanDataRoot());
|
|
87
127
|
return { classifier, resolver, shards };
|
|
88
128
|
})();
|
|
@@ -118,9 +158,9 @@ async function getPoiPipeline(dbPath) {
|
|
|
118
158
|
}
|
|
119
159
|
/**
|
|
120
160
|
* `plausibilityCheck`'s geocode dep — reuses the SAME shared classifier+resolver `deps.geocode` builds from (see the
|
|
121
|
-
* module header's laziness note)
|
|
122
|
-
*
|
|
123
|
-
*
|
|
161
|
+
* module header's laziness note). `deriveGeocodeRegister`/the formatted register is the geocode dep's concern, so it is
|
|
162
|
+
* wired at this CLI/MCP layer rather than inside `plausibility.ts`. The real return type (`GeocodeResult`) is
|
|
163
|
+
* structurally assignable to `plausibility.ts`'s minimal `GeocodeLike` — no adapter needed.
|
|
124
164
|
*/
|
|
125
165
|
async function resolveGeocode(address) {
|
|
126
166
|
const { classifier, resolver, shards } = await loadCore();
|
|
@@ -170,7 +210,7 @@ const deps = {
|
|
|
170
210
|
return { manifest, coverage };
|
|
171
211
|
},
|
|
172
212
|
async bdcFilingLandscape(q) {
|
|
173
|
-
// Decision 6
|
|
213
|
+
// Decision 6: `mailwoman_bdc_filing_landscape` requires bdc.db unconditionally (no optional-dep
|
|
174
214
|
// abstain shape exists for this tool), so a missing file becomes a friendly thrown Error naming the layer —
|
|
175
215
|
// never the raw `node:sqlite` "unable to open database file" message.
|
|
176
216
|
assertBDCDatabaseExists("mailwoman_bdc_filing_landscape", q.databasePath);
|
|
@@ -195,7 +235,7 @@ const deps = {
|
|
|
195
235
|
}
|
|
196
236
|
},
|
|
197
237
|
async filerLookup(q) {
|
|
198
|
-
// Decision 6/gate 4
|
|
238
|
+
// Decision 6/gate 4: filerLookup has no optional-dep abstain shape — it throws rather than
|
|
199
239
|
// answer unstamped — so filer.db is required unconditionally, same discipline as bdc.db is for
|
|
200
240
|
// mailwoman_bdc_filing_landscape.
|
|
201
241
|
assertFilerDatabaseExists("mailwoman_filer_lookup", q.databasePath);
|
|
@@ -216,7 +256,7 @@ const deps = {
|
|
|
216
256
|
});
|
|
217
257
|
},
|
|
218
258
|
async filerFamily(q) {
|
|
219
|
-
// Same discipline as mailwoman_filer_lookup
|
|
259
|
+
// Same discipline as mailwoman_filer_lookup — familyRollup has no optional-dep abstain shape
|
|
220
260
|
// either, so filer.db is required unconditionally.
|
|
221
261
|
assertFilerDatabaseExists("mailwoman_filer_family", q.databasePath);
|
|
222
262
|
using db = openFilerDatabaseIfPresent(q.databasePath);
|
package/out/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"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"}
|
package/out/layer-guards.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Decision 6
|
|
7
|
-
*
|
|
6
|
+
* Decision 6 layer-absent guards — kept out of `cli.ts` in their own importable module so the
|
|
7
|
+
* branching itself has direct unit coverage (`layer-guards.test.ts`), not just
|
|
8
8
|
* "the tool handler passes an abstain through" (`tools.test.ts`'s stub-level dispatch tests). `cli.ts`
|
|
9
9
|
* top-level-`await`s a real stdio transport connection at import time, so IT can't be imported by vitest — these
|
|
10
10
|
* three functions have no such dependency (pure existence-check + open, or a thrown Error), so they live here and
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - `assertBDCDatabaseExists` — `mailwoman_bdc_filing_landscape` requires bdc.db unconditionally (no optional-dep
|
|
18
18
|
* abstain shape exists for that tool), so a missing file becomes one friendly thrown `Error` naming the layer
|
|
19
19
|
* instead of the raw `node:sqlite` "unable to open database file" message.
|
|
20
|
-
* - `openFilerDatabaseIfPresent` / `assertFilerDatabaseExists`
|
|
20
|
+
* - `openFilerDatabaseIfPresent` / `assertFilerDatabaseExists` — the SAME pairing, for filer.db.
|
|
21
21
|
* `mailwoman_filer_lookup` requires filer.db unconditionally (mirrors `mailwoman_bdc_filing_landscape`'s own
|
|
22
22
|
* "requires the layer" discipline — `filerLookup` itself has no optional-dep abstain shape either, since gate
|
|
23
23
|
* 4 makes it throw rather than answer unstamped), so `cli.ts` pairs `assertFilerDatabaseExists` (the friendly
|
|
@@ -50,14 +50,14 @@ export declare function openPlausibilityPOIDeps(databasePath: string | undefined
|
|
|
50
50
|
export declare function assertBDCDatabaseExists(toolName: string, databasePath: string): void;
|
|
51
51
|
/**
|
|
52
52
|
* Open a filer.db, or return `undefined` when `databasePath` is unset or the file is missing — NEVER a raw sqlite throw
|
|
53
|
-
* (
|
|
53
|
+
* (mirroring {@link openBDCDatabaseIfPresent}). Used by `cli.ts`'s `mailwoman_filer_lookup` handler after
|
|
54
54
|
* {@link assertFilerDatabaseExists} has already confirmed the file is present.
|
|
55
55
|
*/
|
|
56
56
|
export declare function openFilerDatabaseIfPresent(databasePath: string | undefined): DatabaseClient<FilerDatabase> | undefined;
|
|
57
57
|
/**
|
|
58
|
-
* Throws a friendly Error naming the layer when `databasePath` doesn't exist — `mailwoman_filer_lookup`'s guard
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* Throws a friendly Error naming the layer when `databasePath` doesn't exist — `mailwoman_filer_lookup`'s guard
|
|
59
|
+
* (mirroring {@link assertBDCDatabaseExists}). `filerLookup` itself has no optional-dep abstain shape (gate 4 makes it
|
|
60
|
+
* throw rather than answer unstamped), so filer.db is required unconditionally, same as bdc.db is for
|
|
61
61
|
* `mailwoman_bdc_filing_landscape`.
|
|
62
62
|
*/
|
|
63
63
|
export declare function assertFilerDatabaseExists(toolName: string, databasePath: string): void;
|
package/out/layer-guards.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Decision 6
|
|
7
|
-
*
|
|
6
|
+
* Decision 6 layer-absent guards — kept out of `cli.ts` in their own importable module so the
|
|
7
|
+
* branching itself has direct unit coverage (`layer-guards.test.ts`), not just
|
|
8
8
|
* "the tool handler passes an abstain through" (`tools.test.ts`'s stub-level dispatch tests). `cli.ts`
|
|
9
9
|
* top-level-`await`s a real stdio transport connection at import time, so IT can't be imported by vitest — these
|
|
10
10
|
* three functions have no such dependency (pure existence-check + open, or a thrown Error), so they live here and
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - `assertBDCDatabaseExists` — `mailwoman_bdc_filing_landscape` requires bdc.db unconditionally (no optional-dep
|
|
18
18
|
* abstain shape exists for that tool), so a missing file becomes one friendly thrown `Error` naming the layer
|
|
19
19
|
* instead of the raw `node:sqlite` "unable to open database file" message.
|
|
20
|
-
* - `openFilerDatabaseIfPresent` / `assertFilerDatabaseExists`
|
|
20
|
+
* - `openFilerDatabaseIfPresent` / `assertFilerDatabaseExists` — the SAME pairing, for filer.db.
|
|
21
21
|
* `mailwoman_filer_lookup` requires filer.db unconditionally (mirrors `mailwoman_bdc_filing_landscape`'s own
|
|
22
22
|
* "requires the layer" discipline — `filerLookup` itself has no optional-dep abstain shape either, since gate
|
|
23
23
|
* 4 makes it throw rather than answer unstamped), so `cli.ts` pairs `assertFilerDatabaseExists` (the friendly
|
|
@@ -67,7 +67,7 @@ export function assertBDCDatabaseExists(toolName, databasePath) {
|
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Open a filer.db, or return `undefined` when `databasePath` is unset or the file is missing — NEVER a raw sqlite throw
|
|
70
|
-
* (
|
|
70
|
+
* (mirroring {@link openBDCDatabaseIfPresent}). Used by `cli.ts`'s `mailwoman_filer_lookup` handler after
|
|
71
71
|
* {@link assertFilerDatabaseExists} has already confirmed the file is present.
|
|
72
72
|
*/
|
|
73
73
|
export function openFilerDatabaseIfPresent(databasePath) {
|
|
@@ -76,9 +76,9 @@ export function openFilerDatabaseIfPresent(databasePath) {
|
|
|
76
76
|
return new DatabaseClient({ database: new DatabaseSync(databasePath, { readOnly: true }) });
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Throws a friendly Error naming the layer when `databasePath` doesn't exist — `mailwoman_filer_lookup`'s guard
|
|
80
|
-
*
|
|
81
|
-
*
|
|
79
|
+
* Throws a friendly Error naming the layer when `databasePath` doesn't exist — `mailwoman_filer_lookup`'s guard
|
|
80
|
+
* (mirroring {@link assertBDCDatabaseExists}). `filerLookup` itself has no optional-dep abstain shape (gate 4 makes it
|
|
81
|
+
* throw rather than answer unstamped), so filer.db is required unconditionally, same as bdc.db is for
|
|
82
82
|
* `mailwoman_bdc_filing_landscape`.
|
|
83
83
|
*/
|
|
84
84
|
export function assertFilerDatabaseExists(toolName, databasePath) {
|
package/out/tools.d.ts
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
* - `mailwoman_plausibility_check` — score one claimed broadband-service assertion against BDC filing evidence and
|
|
25
25
|
* nearby telecom infrastructure (`@mailwoman/bdc`'s `plausibilityCheck`), returning a positive-evidence-only bundle
|
|
26
26
|
* with an always-present `coverage_confidence`. A missing/absent `bdc_database_path`/`poi_database_path` degrades
|
|
27
|
-
* to a typed abstain entry in the bundle, never a throw (
|
|
28
|
-
* - `mailwoman_filer_lookup`
|
|
27
|
+
* to a typed abstain entry in the bundle, never a throw (decision 6).
|
|
28
|
+
* - `mailwoman_filer_lookup` — read the FCC filer identity crosswalk (`@mailwoman/filer`'s
|
|
29
29
|
* `filerLookup`) for one identifier (FRN, Form 499 ID, or BDC provider ID): every OTHER identifier it shares an
|
|
30
30
|
* authoritative edge with, its current attributes, its authoritative entity cluster, and any inferred links —
|
|
31
31
|
* reported separately, never merged into the cluster. `as_of` is always present (defaults to today).
|
|
32
|
-
* - `mailwoman_filer_family`
|
|
32
|
+
* - `mailwoman_filer_family` — read a corporate family's membership (`@mailwoman/filer/sdk`'s
|
|
33
33
|
* `familyRollup`) from a filer.db layer database, given a `family_id` or a `node_id`. Distinct from an entity
|
|
34
34
|
* cluster (same filer, different identifiers) — a corporate family spans several DIFFERENT filers under a
|
|
35
35
|
* holding/parent/subsidiary/management relationship. The handler passes `familyRollup`'s result through
|
package/out/tools.js
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
* - `mailwoman_plausibility_check` — score one claimed broadband-service assertion against BDC filing evidence and
|
|
25
25
|
* nearby telecom infrastructure (`@mailwoman/bdc`'s `plausibilityCheck`), returning a positive-evidence-only bundle
|
|
26
26
|
* with an always-present `coverage_confidence`. A missing/absent `bdc_database_path`/`poi_database_path` degrades
|
|
27
|
-
* to a typed abstain entry in the bundle, never a throw (
|
|
28
|
-
* - `mailwoman_filer_lookup`
|
|
27
|
+
* to a typed abstain entry in the bundle, never a throw (decision 6).
|
|
28
|
+
* - `mailwoman_filer_lookup` — read the FCC filer identity crosswalk (`@mailwoman/filer`'s
|
|
29
29
|
* `filerLookup`) for one identifier (FRN, Form 499 ID, or BDC provider ID): every OTHER identifier it shares an
|
|
30
30
|
* authoritative edge with, its current attributes, its authoritative entity cluster, and any inferred links —
|
|
31
31
|
* reported separately, never merged into the cluster. `as_of` is always present (defaults to today).
|
|
32
|
-
* - `mailwoman_filer_family`
|
|
32
|
+
* - `mailwoman_filer_family` — read a corporate family's membership (`@mailwoman/filer/sdk`'s
|
|
33
33
|
* `familyRollup`) from a filer.db layer database, given a `family_id` or a `node_id`. Distinct from an entity
|
|
34
34
|
* cluster (same filer, different identifiers) — a corporate family spans several DIFFERENT filers under a
|
|
35
35
|
* holding/parent/subsidiary/management relationship. The handler passes `familyRollup`'s result through
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailwoman/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "MCP server — mailwoman's spatial toolset for agents (parse, geocode, poi_search, overpass_export, layer_manifest).",
|
|
5
5
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
6
6
|
"repository": {
|
|
@@ -45,19 +45,20 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@mailwoman/bdc": "
|
|
49
|
-
"@mailwoman/core": "
|
|
50
|
-
"@mailwoman/filer": "
|
|
51
|
-
"@mailwoman/neural": "
|
|
52
|
-
"@mailwoman/
|
|
53
|
-
"@mailwoman/
|
|
54
|
-
"@mailwoman/resolver
|
|
55
|
-
"@
|
|
56
|
-
"
|
|
48
|
+
"@mailwoman/bdc": "9.0.0",
|
|
49
|
+
"@mailwoman/core": "9.0.0",
|
|
50
|
+
"@mailwoman/filer": "9.0.0",
|
|
51
|
+
"@mailwoman/neural": "9.0.0",
|
|
52
|
+
"@mailwoman/neural-weights-en-us": "9.0.0",
|
|
53
|
+
"@mailwoman/poi-taxonomy": "9.0.0",
|
|
54
|
+
"@mailwoman/resolver": "9.0.0",
|
|
55
|
+
"@mailwoman/resolver-wof-sqlite": "9.0.0",
|
|
56
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
57
|
+
"mailwoman": "9.0.0",
|
|
57
58
|
"zod": "^4.4.3"
|
|
58
59
|
},
|
|
59
60
|
"peerDependencies": {
|
|
60
|
-
"@mailwoman/resolver-wof-sqlite": "
|
|
61
|
+
"@mailwoman/resolver-wof-sqlite": "9.0.0"
|
|
61
62
|
},
|
|
62
63
|
"peerDependenciesMeta": {
|
|
63
64
|
"@mailwoman/resolver-wof-sqlite": {
|
package/tools.ts
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
* - `mailwoman_plausibility_check` — score one claimed broadband-service assertion against BDC filing evidence and
|
|
25
25
|
* nearby telecom infrastructure (`@mailwoman/bdc`'s `plausibilityCheck`), returning a positive-evidence-only bundle
|
|
26
26
|
* with an always-present `coverage_confidence`. A missing/absent `bdc_database_path`/`poi_database_path` degrades
|
|
27
|
-
* to a typed abstain entry in the bundle, never a throw (
|
|
28
|
-
* - `mailwoman_filer_lookup`
|
|
27
|
+
* to a typed abstain entry in the bundle, never a throw (decision 6).
|
|
28
|
+
* - `mailwoman_filer_lookup` — read the FCC filer identity crosswalk (`@mailwoman/filer`'s
|
|
29
29
|
* `filerLookup`) for one identifier (FRN, Form 499 ID, or BDC provider ID): every OTHER identifier it shares an
|
|
30
30
|
* authoritative edge with, its current attributes, its authoritative entity cluster, and any inferred links —
|
|
31
31
|
* reported separately, never merged into the cluster. `as_of` is always present (defaults to today).
|
|
32
|
-
* - `mailwoman_filer_family`
|
|
32
|
+
* - `mailwoman_filer_family` — read a corporate family's membership (`@mailwoman/filer/sdk`'s
|
|
33
33
|
* `familyRollup`) from a filer.db layer database, given a `family_id` or a `node_id`. Distinct from an entity
|
|
34
34
|
* cluster (same filer, different identifiers) — a corporate family spans several DIFFERENT filers under a
|
|
35
35
|
* holding/parent/subsidiary/management relationship. The handler passes `familyRollup`'s result through
|