@mailwoman/api 9.2.0 → 9.4.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.
@@ -9,15 +9,20 @@
9
9
  */
10
10
 
11
11
  import { OpenAPIHono } from "@hono/zod-openapi"
12
- import { errorResponse, attachOpenAPIDocs, type OpenAPIDocInfo } from "@mailwoman/api-kit"
12
+ import {
13
+ attachOpenAPIDocs,
14
+ engineHeaders,
15
+ errorResponse,
16
+ type OpenAPIDocInfo,
17
+ readServedDocumentInfo,
18
+ } from "@mailwoman/api-kit"
19
+ import type { EngineStamp } from "@mailwoman/core/license"
13
20
  import { bodyLimit } from "hono/body-limit"
14
21
  import { cors } from "hono/cors"
15
22
 
16
- import packageJson from "#package.json" with { type: "json" }
17
-
18
- import type { MailwomanAPIEngine } from "./engine.ts"
19
- import { DEFAULT_BATCH_MAX, registerMailwomanAPIRoutes } from "./routes.ts"
20
- import type { GeocodeOutcomeLike } from "./schema.ts"
23
+ import type { MailwomanAPIEngine } from "#engine"
24
+ import { DEFAULT_BATCH_MAX, registerMailwomanAPIRoutes } from "#routes"
25
+ import type { GeocodeOutcomeLike } from "#schema"
21
26
 
22
27
  /**
23
28
  * 2 MiB — carried from the express server's `express.json({ limit: "2mb" })` (`mailwoman/server/index.ts`).
@@ -45,6 +50,13 @@ export interface MailwomanAPIOptions {
45
50
  * Max `addresses` rows accepted by `POST /v1/batch`. Default 500 (see `routes.ts`'s `DEFAULT_BATCH_MAX`).
46
51
  */
47
52
  batchMax?: number
53
+
54
+ /**
55
+ * The engine stamp to carry on every response: `engine` in each `/v1` body and the `Server` + `Link: rel="license"`
56
+ * headers everywhere. Absent when an embedding application builds the app without the `mailwoman` package; the
57
+ * `mailwoman serve` command always passes one.
58
+ */
59
+ engine?: EngineStamp
48
60
  }
49
61
 
50
62
  /**
@@ -61,9 +73,7 @@ function summarizeValidationError(error: { issues: Array<{ path: PropertyKey[];
61
73
  * {@link attachOpenAPIDocs}) uses — one source of truth, no risk of the two drifting.
62
74
  */
63
75
  export const MAILWOMAN_API_DOC_INFO: OpenAPIDocInfo = {
64
- title: packageJson.name,
65
- version: packageJson.version,
66
- description: packageJson.description,
76
+ ...(await readServedDocumentInfo(import.meta.url, "@mailwoman/api")),
67
77
  license: { name: "AGPL-3.0-only OR LicenseRef-Commercial", identifier: "AGPL-3.0-only" },
68
78
  contact: { name: "Sister Software", url: "https://mailwoman.ai" },
69
79
  servers: [
@@ -110,6 +120,10 @@ export function createMailwomanAPI<T extends Partial<GeocodeOutcomeLike> = Geoco
110
120
  app.use(cors({ origin: "*", allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["*"], maxAge: 86_400 }))
111
121
  }
112
122
 
123
+ if (options.engine) {
124
+ app.use(engineHeaders(options.engine))
125
+ }
126
+
113
127
  // Safety net: an engine fault answers the native envelope, never a crash. `detail` carries the raw message —
114
128
  // this surface is ours to design, so (unlike the vendor-constrained drop-in envelopes) we can be helpful.
115
129
  app.onError((error, c) => {
@@ -133,7 +147,10 @@ export function createMailwomanAPI<T extends Partial<GeocodeOutcomeLike> = Geoco
133
147
  })
134
148
  )
135
149
 
136
- registerMailwomanAPIRoutes(app, engine, { batchMax: options.batchMax ?? DEFAULT_BATCH_MAX })
150
+ registerMailwomanAPIRoutes(app, engine, {
151
+ batchMax: options.batchMax ?? DEFAULT_BATCH_MAX,
152
+ engine: options.engine,
153
+ })
137
154
 
138
155
  attachOpenAPIDocs(app, MAILWOMAN_API_DOC_INFO)
139
156
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  import type { AddressTree } from "@mailwoman/core"
12
12
 
13
- import type { GeocodeOutcomeLike } from "./schema.ts"
13
+ import type { GeocodeOutcomeLike } from "#schema"
14
14
 
15
15
  /**
16
16
  * One parsed component in reading order (a `ComponentTag` + the covered text).
@@ -71,5 +71,5 @@ export interface MailwomanAPIEngine<T extends Partial<GeocodeOutcomeLike> = Geoc
71
71
  batch?(addresses: string[], opts?: ParseInit): Promise<{ results: BatchResultEntry<T>[] }>
72
72
  resolveTree?(tree: AddressTree, opts: Record<string, unknown>): Promise<ResolveTreeOutcome>
73
73
  reload?(): Promise<{ reloaded: boolean; versions: unknown }>
74
- health?(): HealthData
74
+ health?(): Promise<HealthData>
75
75
  }
@@ -16,7 +16,7 @@
16
16
  * engine contract in `engine.ts`; the zod wire schemas in `schema.ts`.
17
17
  */
18
18
 
19
- export * from "./app.ts"
20
- export * from "./engine.ts"
21
- export * from "./routes.ts"
22
- export * from "./schema.ts"
19
+ export * from "#app"
20
+ export * from "#engine"
21
+ export * from "#routes"
22
+ export * from "#schema"
@@ -20,14 +20,21 @@
20
20
  */
21
21
 
22
22
  import { createRoute, type OpenAPIHono, z } from "@hono/zod-openapi"
23
- import { geocoderUnavailableError, metricsSnapshot, recordTimed } from "@mailwoman/api-kit"
23
+ import {
24
+ geocoderUnavailableError,
25
+ metricsSnapshot,
26
+ recordTimed,
27
+ stampedResponseSchema,
28
+ withEngineStamp,
29
+ APIErrorSchema,
30
+ } from "@mailwoman/api-kit"
24
31
  import type { AddressTree } from "@mailwoman/core/decoder"
32
+ import type { EngineStamp } from "@mailwoman/core/license"
25
33
  import type { ComponentTag } from "@mailwoman/core/types"
26
34
  import { canonicalKey, type ComponentDict, formatAddress, type FormatAddressOptions } from "@mailwoman/formatter"
27
35
 
28
- import type { MailwomanAPIEngine } from "./engine.ts"
36
+ import type { MailwomanAPIEngine } from "#engine"
29
37
  import {
30
- APIErrorSchema,
31
38
  BatchRequestSchema,
32
39
  BatchResponseSchema,
33
40
  FormatRequestSchema,
@@ -40,12 +47,12 @@ import {
40
47
  ResolveRequestSchema,
41
48
  ResolveResponseSchema,
42
49
  type GeocodeOutcome,
43
- } from "./schema.ts"
50
+ } from "#schema"
44
51
 
45
52
  /**
46
53
  * Default `POST /v1/batch` row cap when {@link RegisterMailwomanAPIRoutesOptions.batchMax} is omitted. This is the
47
54
  * standalone-engine default, not derived from env — `mailwoman serve` always passes the env-derived value explicitly
48
- * (`$public.MAILWOMAN_BATCH_MAX`, default 1000; see `core/env/schema.ts`).
55
+ * (`$public.MAILWOMAN_BATCH_MAX`, default 1000; see `mailwoman/lib/env/schema.ts`).
49
56
  */
50
57
  export const DEFAULT_BATCH_MAX = 500
51
58
 
@@ -59,6 +66,11 @@ export interface RegisterMailwomanAPIRoutesOptions {
59
66
  * Max `addresses` rows accepted by `POST /v1/batch`. Default {@link DEFAULT_BATCH_MAX}.
60
67
  */
61
68
  batchMax?: number
69
+
70
+ /**
71
+ * The engine stamp attached as `engine` to every `/v1` success body. Absent: no field is added.
72
+ */
73
+ engine?: EngineStamp
62
74
  }
63
75
 
64
76
  const errorContent = (description: string) => ({
@@ -78,7 +90,7 @@ const parseQueryParams = z.object({
78
90
  const parseResponses = {
79
91
  200: {
80
92
  description: "The tokenized input span + ranked solutions.",
81
- content: { "application/json": { schema: ParseOutcomeSchema } },
93
+ content: { "application/json": { schema: stampedResponseSchema(ParseOutcomeSchema, "StampedParseOutcome") } },
82
94
  },
83
95
  400: errorContent("`address` is required."),
84
96
  501: errorContent("The backing engine method is not wired for this deployment."),
@@ -87,7 +99,7 @@ const parseResponses = {
87
99
  const geocodeResponses = {
88
100
  200: {
89
101
  description: "One geocode result (parse → resolve cascade), passed through from the engine verbatim.",
90
- content: { "application/json": { schema: GeocodeOutcomeSchema } },
102
+ content: { "application/json": { schema: stampedResponseSchema(GeocodeOutcomeSchema, "StampedGeocodeOutcome") } },
91
103
  },
92
104
  400: errorContent("`address` is required."),
93
105
  503: errorContent("The geocoding engine is not wired for this deployment (dependencies missing)."),
@@ -96,7 +108,7 @@ const geocodeResponses = {
96
108
  const batchResponses = {
97
109
  200: {
98
110
  description: "One result per input address, in input order (per-row error isolation).",
99
- content: { "application/json": { schema: BatchResponseSchema } },
111
+ content: { "application/json": { schema: stampedResponseSchema(BatchResponseSchema, "StampedBatchResponse") } },
100
112
  },
101
113
  400: errorContent("Body must be `{ addresses: string[] }`."),
102
114
  413: errorContent("`addresses.length` exceeds the configured batch cap."),
@@ -106,7 +118,7 @@ const batchResponses = {
106
118
  const resolveResponses = {
107
119
  200: {
108
120
  description: "The same tree, decorated in place with gazetteer coordinates + attribution.",
109
- content: { "application/json": { schema: ResolveResponseSchema } },
121
+ content: { "application/json": { schema: stampedResponseSchema(ResolveResponseSchema, "StampedResolveResponse") } },
110
122
  },
111
123
  400: errorContent("Body must be `{ tree: AddressTree, opts? }`."),
112
124
  503: errorContent("The resolver is not wired for this deployment (dependencies missing)."),
@@ -114,7 +126,7 @@ const resolveResponses = {
114
126
 
115
127
  const reloadResponses = {
116
128
  200: {
117
- description: "Versioned data switchover result — the new per-shard version map.",
129
+ description: "Versioned data switchover result — the new per-extract version map.",
118
130
  content: {
119
131
  "application/json": { schema: z.looseObject({ reloaded: z.boolean(), versions: z.unknown() }) },
120
132
  },
@@ -125,7 +137,7 @@ const reloadResponses = {
125
137
  const formatResponses = {
126
138
  200: {
127
139
  description: "The rendered address string + the deterministic canonical match key.",
128
- content: { "application/json": { schema: FormatResponseSchema } },
140
+ content: { "application/json": { schema: stampedResponseSchema(FormatResponseSchema, "StampedFormatResponse") } },
129
141
  },
130
142
  400: errorContent("Invalid request body."),
131
143
  }
@@ -198,7 +210,7 @@ const reloadRoute = createRoute({
198
210
  method: "post",
199
211
  path: "/v1/reload",
200
212
  operationId: "reload",
201
- summary: "Reload versioned data shards (deploy-only; gate at ingress)",
213
+ summary: "Reload versioned data extracts (deploy-only; check at ingress)",
202
214
  tags: ["meta"],
203
215
  responses: reloadResponses,
204
216
  })
@@ -259,6 +271,7 @@ export function registerMailwomanAPIRoutes<T extends Partial<GeocodeOutcome> = G
259
271
  options: RegisterMailwomanAPIRoutesOptions = {}
260
272
  ): void {
261
273
  const batchMax = options.batchMax ?? DEFAULT_BATCH_MAX
274
+ const stamp = options.engine
262
275
 
263
276
  app.openapi(parseGetRoute, async (c) => {
264
277
  if (!engine.parse) return c.json({ error: "parse not implemented" }, 501)
@@ -271,7 +284,7 @@ export function registerMailwomanAPIRoutes<T extends Partial<GeocodeOutcome> = G
271
284
  const inputMode = inputModeRaw === "fragmented" || inputModeRaw === "formatted" ? inputModeRaw : undefined
272
285
  const outcome = await engine.parse(address, { debug, inputMode })
273
286
 
274
- return c.json(outcome, 200)
287
+ return c.json(withEngineStamp(outcome, stamp), 200)
275
288
  })
276
289
 
277
290
  app.openapi(
@@ -290,7 +303,7 @@ export function registerMailwomanAPIRoutes<T extends Partial<GeocodeOutcome> = G
290
303
 
291
304
  const outcome = await engine.parse(trimmed, { debug: debug ?? false, inputMode: input_mode })
292
305
 
293
- return c.json(outcome, 200)
306
+ return c.json(withEngineStamp(outcome, stamp), 200)
294
307
  },
295
308
  (result, c) => {
296
309
  if (!result.success) return c.json({ error: "address is required" }, 400)
@@ -317,7 +330,7 @@ export function registerMailwomanAPIRoutes<T extends Partial<GeocodeOutcome> = G
317
330
  .then((outcome) => {
318
331
  recordTimed(performance.now() - t0, String(outcome.resolution_tier ?? "admin"))
319
332
 
320
- return c.json(outcome as unknown as GeocodeOutcome, 200)
333
+ return c.json(withEngineStamp(outcome as GeocodeOutcome, stamp), 200)
321
334
  })
322
335
  .catch((error) => {
323
336
  recordTimed(performance.now() - t0, "error")
@@ -339,7 +352,7 @@ export function registerMailwomanAPIRoutes<T extends Partial<GeocodeOutcome> = G
339
352
  async (c) => {
340
353
  const { addresses, input_mode } = c.req.valid("json")
341
354
 
342
- if (!addresses.length) return c.json({ results: [] }, 200)
355
+ if (!addresses.length) return c.json(withEngineStamp({ results: [] }, stamp), 200)
343
356
 
344
357
  if (addresses.length > batchMax) {
345
358
  return c.json({ error: `batch too large: ${addresses.length} > ${batchMax}` }, 413)
@@ -361,7 +374,7 @@ export function registerMailwomanAPIRoutes<T extends Partial<GeocodeOutcome> = G
361
374
  // Same wire-vs-domain cast as `/v1/geocode` above — `BatchRow`'s `GeocodeOutcome` half is a
362
375
  // `Record<string, unknown>` passthrough; `BatchResponseSchema` now types its `GeocodeOutcome` union
363
376
  // member as the real shape.
364
- return c.json(outcome as unknown as z.infer<typeof BatchResponseSchema>, 200)
377
+ return c.json(withEngineStamp(outcome as z.infer<typeof BatchResponseSchema>, stamp), 200)
365
378
  } catch (error) {
366
379
  recordTimed(performance.now() - t0, "error")
367
380
  throw error
@@ -388,9 +401,9 @@ export function registerMailwomanAPIRoutes<T extends Partial<GeocodeOutcome> = G
388
401
 
389
402
  const { tree, opts } = c.req.valid("json")
390
403
 
391
- const outcome = await engine.resolveTree(tree as unknown as AddressTree, opts ?? {})
404
+ const outcome = await engine.resolveTree(tree as AddressTree, opts ?? {})
392
405
 
393
- return c.json(outcome, 200)
406
+ return c.json(withEngineStamp(outcome, stamp), 200)
394
407
  },
395
408
  (result, c) => {
396
409
  if (!result.success) return c.json({ error: "body must be { tree: AddressTree, opts? }" }, 400)
@@ -414,13 +427,13 @@ export function registerMailwomanAPIRoutes<T extends Partial<GeocodeOutcome> = G
414
427
  const dict = toComponentDict(components)
415
428
  const formatted = formatAddress(dict, country, formatOptions as FormatAddressOptions | undefined)
416
429
 
417
- return c.json({ formatted, canonicalKey: canonicalKey(dict) }, 200)
430
+ return c.json(withEngineStamp({ formatted, canonicalKey: canonicalKey(dict) }, stamp), 200)
418
431
  })
419
432
 
420
- app.openapi(healthRoute, (c) => {
433
+ app.openapi(healthRoute, async (c) => {
421
434
  const uptimeSeconds = Math.round((Date.now() - startedAt) / 1000)
422
435
 
423
- return c.json({ status: "ok", uptime_s: uptimeSeconds, ...engine.health?.() }, 200)
436
+ return c.json({ status: "ok", uptime_s: uptimeSeconds, ...(await engine.health?.()) }, 200)
424
437
  })
425
438
 
426
439
  app.openapi(metricsRoute, (c) => c.json(metricsSnapshot(), 200))
@@ -17,12 +17,23 @@
17
17
  */
18
18
 
19
19
  import { z } from "@hono/zod-openapi"
20
-
21
- export { APIErrorSchema } from "@mailwoman/api-kit"
20
+ import type { AddressNode } from "@mailwoman/core/decoder"
21
+ import type { DerivationProjection, Evidence } from "@mailwoman/evidence"
22
22
 
23
23
  /**
24
24
  * `POST /v1/parse` request body.
25
25
  */
26
+ /**
27
+ * One node of the decoded address tree. The decoder's `AddressNode` is a recursive union the OpenAPI generator cannot
28
+ * derive a schema for on its own, so it is registered as an open object; the shape is documented by the type.
29
+ */
30
+ export const AddressNodeSchema = z.custom<AddressNode>().openapi("AddressNode", {
31
+ type: "object",
32
+ additionalProperties: true,
33
+ description:
34
+ "A decoded address-tree node: a tag, its span, and its children. See `AddressNode` in `@mailwoman/core/decoder`.",
35
+ })
36
+
26
37
  /**
27
38
  * The input register (Decision A / GTM B10): `fragmented` = the map-search register (evidence-bundle channels feed);
28
39
  * `formatted` = the validation/record register (channels off). Unset → the engine derives it from the input's shape.
@@ -70,7 +81,7 @@ export const ParseOutcomeSchema = z
70
81
  .object({
71
82
  input: z.string(),
72
83
  components: z.array(ParseComponentSchema),
73
- tree: z.looseObject({ roots: z.array(z.unknown()) }),
84
+ tree: z.looseObject({ raw: z.string(), roots: z.array(AddressNodeSchema) }),
74
85
  debug: z.string().optional(),
75
86
  })
76
87
  .openapi("ParseOutcome")
@@ -150,6 +161,7 @@ const ComponentTagSchema = z.enum([
150
161
  "sub_block",
151
162
  "building_number",
152
163
  "building_name",
164
+ "locality_unit",
153
165
  ])
154
166
 
155
167
  /**
@@ -186,13 +198,80 @@ const QueryIntentMarkerSchema = z
186
198
  "near_me",
187
199
  "poi_category",
188
200
  ]),
189
- code: z.enum(["declared_ambiguity", "declared_fork", "focus_point_required", "poi_category"]),
201
+ code: z.enum([
202
+ "declared_ambiguity",
203
+ "declared_fork",
204
+ "focus_point_required",
205
+ "poi_category",
206
+ "coverage_qualified_absence",
207
+ "authority_designation",
208
+ ]),
190
209
  mechanism: z.string(),
191
210
  message: z.string(),
192
211
  evidence: z.record(z.string(), z.unknown()).optional(),
193
212
  })
194
213
  .openapi("QueryIntentMarker")
195
214
 
215
+ /**
216
+ * One authoritative-provider match on the wire (#1901) — hoisted so the outcome schema below stays inside the
217
+ * call-nesting bound. Field-for-field mirror of `mailwoman/authoritative.ts`'s `AuthoritativeAssertionMatch`.
218
+ */
219
+ const AuthoritativeMatchSchema = z.object({
220
+ provider_place_id: z.string(),
221
+ object_ids: z.record(z.string(), z.string()).optional(),
222
+ canonical_fields: z.record(z.string(), z.string()).optional(),
223
+ lat: z.number().optional(),
224
+ lon: z.number().optional(),
225
+ precision: z.string().optional(),
226
+ match_status: z.enum(["exact", "approximate"]),
227
+ provider_score: z.number().optional(),
228
+ })
229
+
230
+ const EpistemicStatusSchema = z.enum(["designated", "observed", "derived", "inferred", "unresolved"])
231
+
232
+ const CoverageBasisSchema = z.enum(["designated", "surveyed", "source_present"])
233
+
234
+ /**
235
+ * `@mailwoman/evidence`'s `Evidence` union, spelled for the wire. The `EvidencePin` below fails to compile the moment
236
+ * either side gains, loses or retypes a field.
237
+ */
238
+ const EvidenceSchema = z.discriminatedUnion("kind", [
239
+ z.object({ kind: z.literal("observation"), source: z.string(), vintage: z.string().nullable(), value: z.unknown() }),
240
+ z.object({
241
+ kind: z.literal("exclusion"),
242
+ source: z.string(),
243
+ vintage: z.string(),
244
+ scope: z.object({ layer: z.string(), h3Cell: z.number(), basis: CoverageBasisSchema, fold: z.string() }),
245
+ }),
246
+ z.object({
247
+ kind: z.literal("relation"),
248
+ source: z.string(),
249
+ vintage: z.string(),
250
+ relationship: z.string(),
251
+ assertion: z.enum(["authoritative", "inferred"]),
252
+ score: z.number().optional(),
253
+ }),
254
+ z.object({ kind: z.literal("prior"), source: z.string(), label: z.string(), weight: z.number() }),
255
+ ])
256
+
257
+ /**
258
+ * The derivation behind a geocode answer, present only when the engine was asked to trace — `@mailwoman/evidence`'s
259
+ * `DerivationProjection` on the wire.
260
+ */
261
+ export const DerivationProjectionSchema = z.object({
262
+ status: EpistemicStatusSchema,
263
+ constraints: z.array(z.object({ label: z.string(), evidence: EvidenceSchema, contribution: z.string() })).readonly(),
264
+ uncertaintyM: z.number().nullable(),
265
+ })
266
+
267
+ // Both directions: the schema's inferred type is exactly the evidence package's, or this does not compile.
268
+ type Mutual<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false
269
+ const evidencePin: Mutual<z.infer<typeof EvidenceSchema>, Evidence> = true
270
+ const derivationPin: Mutual<z.infer<typeof DerivationProjectionSchema>, DerivationProjection> = true
271
+
272
+ void evidencePin
273
+ void derivationPin
274
+
196
275
  /**
197
276
  * `POST /v1/geocode` response — a hand-modeled mirror of `GeocodeResult`'s wire shape (`mailwoman/geocode-core.ts`),
198
277
  * `.loose()` so a field the engine adds that this schema doesn't yet know about still rides through undocumented rather
@@ -200,8 +279,8 @@ const QueryIntentMarkerSchema = z
200
279
  * (`GeocodeOutcome = Record<string, unknown>`, `api/engine.ts`) — nothing here validates a real response, so a
201
280
  * schema/engine mismatch can never reject or mutate a result at runtime. Deliberately carries NO import from
202
281
  * `mailwoman` (the engine-agnosticism boundary — `mailwoman` is the one workspace allowed to depend on
203
- * `@mailwoman/api`, never the reverse). `mailwoman/test/api-schema-drift.test.ts` is the compile-time tripwire that
204
- * catches this shape drifting from the real `GeocodeResult` interface.
282
+ * `@mailwoman/api`, never the reverse). `mailwoman/test/api-schema-drift.test.ts` is the compile-time regression check
283
+ * that catches this shape drifting from the real `GeocodeResult` interface.
205
284
  */
206
285
  export const GeocodeOutcomeLikeSchema = z.object({
207
286
  input: z.string(),
@@ -209,6 +288,12 @@ export const GeocodeOutcomeLikeSchema = z.object({
209
288
  lat: z.number().nullable(),
210
289
  lon: z.number().nullable(),
211
290
  resolution_tier: z.enum(["address_point", "interpolated", "street", "admin", "venue", "plus_code"]),
291
+ // What the evidence permits a consumer to claim about the coordinate, orthogonal to how it was produced; see
292
+ // `@mailwoman/evidence`'s `EpistemicStatus`.
293
+ epistemic_status: z.enum(["designated", "observed", "derived", "inferred", "unresolved"]),
294
+ // The derivation behind the answer, present only when the engine was asked to trace. `DerivationProjectionSchema` is
295
+ // pinned to `@mailwoman/evidence`'s types below, so the wire contract and the evidence union cannot drift apart.
296
+ derivation: DerivationProjectionSchema.optional(),
212
297
  // The fork→entity probe's answer (#1585) — present only on the `venue` tier; see geocode-core's
213
298
  // GeocodeResult.entity.
214
299
  entity: z
@@ -234,7 +319,7 @@ export const GeocodeOutcomeLikeSchema = z.object({
234
319
  countryCode: z.string().nullable(),
235
320
  hierarchy: z.array(GeocodeHierarchyEntrySchema),
236
321
  candidates: z.array(GeocodeCandidateSchema),
237
- // The register row's OWN scope tags when the address_point tier answered and its shard carries
322
+ // The register row's OWN scope tags when the address_point tier answered and its extract carries
238
323
  // them (normalized locality key + postcode of the ROOFTOP) — see geocode-core's GeocodeResult.rooftop.
239
324
  rooftop: z
240
325
  .object({
@@ -246,11 +331,17 @@ export const GeocodeOutcomeLikeSchema = z.object({
246
331
  // OVERRODE the request's country prior — so a caller who asked for US and got an FR answer can see which
247
332
  // evidence bought the change instead of reading it as a bug.
248
333
  postcode_country_scope: z.string().nullable(),
334
+ // #1880: the capital promotion's firing receipt — the promoted candidate's country, present only when the
335
+ // promotion changed some node's leading candidate. Advisory, same posture as postcode_country_scope.
336
+ capital_promotion: z.string().optional(),
337
+ // #1893: the variant-alias exemption's firing receipt — present (true) only when the winning candidate reached
338
+ // the top because the exemption spared it the cross-country alias penalty. Advisory, same posture again.
339
+ variant_alias_exemption: z.literal(true).optional(),
249
340
  // ROAD_TO_V9 §4: query-intent advisories. Always present; empty means the vocabulary looked and had nothing to
250
341
  // say. Advisory ONLY — no marker changed which answer won, and a client is free to ignore the array entirely.
251
342
  intent_markers: z.array(QueryIntentMarkerSchema),
252
343
  // #1717 stage 1: flag-only admin-coherence verdicts — did the winning candidate's resolved ancestry confirm,
253
- // contradict, or fail to speak to the PARSED region/country qualifiers? Nothing ranks or gates on these; present
344
+ // contradict, or fail to speak to the PARSED region/country qualifiers? Nothing ranks or filters on these; present
254
345
  // whenever a winner resolved (both members always populated — `unstated` is the explicit no-qualifier claim),
255
346
  // absent when nothing resolved to check against. See mailwoman's `admin-coherence.ts` for the verdict contract.
256
347
  admin_coherence: z
@@ -259,6 +350,23 @@ export const GeocodeOutcomeLikeSchema = z.object({
259
350
  country: z.enum(["confirmed", "contradicted", "unstated", "unverifiable"]),
260
351
  })
261
352
  .optional(),
353
+ // #1901: a configured authoritative provider's answer, carried BESIDE the open result — every value inside is
354
+ // the PROVIDER'S assertion, hand-modeled here to match `mailwoman/authoritative.ts`'s wire shape (the
355
+ // engine-agnosticism boundary forbids importing it). Absent when no provider is configured; `refused` is the
356
+ // provider declining (distinct from a parse failure or a gazetteer miss); `transport_error` is the provider
357
+ // being unreachable, reported rather than silently dropped. An `ambiguous` status carries EVERY candidate.
358
+ authoritative: z
359
+ .object({
360
+ provider: z.string(),
361
+ status: z.enum(["matched", "ambiguous", "refused", "transport_error"]),
362
+ matches: z.array(AuthoritativeMatchSchema).optional(),
363
+ attribution: z.string().optional(),
364
+ license: z.string().optional(),
365
+ retrieved_at: z.string().optional(),
366
+ dataset_version: z.string().optional(),
367
+ error: z.string().optional(),
368
+ })
369
+ .optional(),
262
370
  // #1755: spans the flat `components` map could not represent. `components` holds one value per tag, so a second
263
371
  // `locality` span ceases to exist there — and without this line `region: null` means both "the input named no
264
372
  // region" and "it named one and we deleted it". Absent when nothing was dropped; never an empty array on the wire,
@@ -284,8 +392,8 @@ export type GeocodeOutcomeLike = z.infer<typeof GeocodeOutcomeLikeSchema>
284
392
  * (`GeocodeOutcome = Record<string, unknown>`, `api/engine.ts`) — nothing here validates a real response, so a
285
393
  * schema/engine mismatch can never reject or mutate a result at runtime. Deliberately carries NO import from
286
394
  * `mailwoman` (the engine-agnosticism boundary — `mailwoman` is the one workspace allowed to depend on
287
- * `@mailwoman/api`, never the reverse). `mailwoman/test/api-schema-drift.test.ts` is the compile-time tripwire that
288
- * catches this shape drifting from the real `GeocodeResult` interface.
395
+ * `@mailwoman/api`, never the reverse). `mailwoman/test/api-schema-drift.test.ts` is the compile-time regression check
396
+ * that catches this shape drifting from the real `GeocodeResult` interface.
289
397
  */
290
398
  export const GeocodeOutcomeSchema = GeocodeOutcomeLikeSchema.loose().openapi("GeocodeOutcome")
291
399
 
@@ -331,7 +439,7 @@ export const BatchResponseSchema = z
331
439
  */
332
440
  export const ResolveRequestSchema = z
333
441
  .object({
334
- tree: z.looseObject({ roots: z.array(z.unknown()) }),
442
+ tree: z.looseObject({ raw: z.string(), roots: z.array(AddressNodeSchema) }),
335
443
  opts: z.looseObject({}).optional(),
336
444
  })
337
445
  .openapi("ResolveRequest")
@@ -341,7 +449,7 @@ export const ResolveRequestSchema = z
341
449
  */
342
450
  export const ResolveResponseSchema = z
343
451
  .object({
344
- tree: z.looseObject({ roots: z.array(z.unknown()) }),
452
+ tree: z.looseObject({ raw: z.string(), roots: z.array(AddressNodeSchema) }),
345
453
  })
346
454
  .openapi("ResolveResponse")
347
455
 
@@ -377,9 +485,11 @@ export const FormatResponseSchema = z
377
485
 
378
486
  /**
379
487
  * `GET /health` response — `status`/`uptime_s` are stamped by the ROUTE itself, unconditionally, regardless of engine
380
- * (`api/routes.ts`'s `healthRoute` handler: `{ status: "ok", uptime_s, ...engine.health?.() }`), so those two are cheap
381
- * + accurate to pin. Everything else is `HealthData` (`api/engine.ts`) — an engine-defined block (model card, data-root
382
- * inventory for `mailwoman serve`; something else entirely for another engine) — stays loose.
488
+ * (`api/routes.ts`'s `healthRoute` handler: `{ status: "ok", uptime_s, ...engine.health?.() }`), so those two are
489
+ * cheap
490
+ *
491
+ * - Accurate to pin. Everything else is `HealthData` (`api/engine.ts`) — an engine-defined block (model card, data-root
492
+ * inventory for `mailwoman serve`; something else entirely for another engine) — stays loose.
383
493
  */
384
494
  export const HealthResponseSchema = z
385
495
  .object({
package/out/app.d.ts CHANGED
@@ -9,8 +9,9 @@
9
9
  */
10
10
  import { OpenAPIHono } from "@hono/zod-openapi";
11
11
  import { type OpenAPIDocInfo } from "@mailwoman/api-kit";
12
- import type { MailwomanAPIEngine } from "./engine.ts";
13
- import type { GeocodeOutcomeLike } from "./schema.ts";
12
+ import type { EngineStamp } from "@mailwoman/core/license";
13
+ import type { MailwomanAPIEngine } from "#engine";
14
+ import type { GeocodeOutcomeLike } from "#schema";
14
15
  /**
15
16
  * Options for {@link createMailwomanAPI}.
16
17
  */
@@ -30,6 +31,12 @@ export interface MailwomanAPIOptions {
30
31
  * Max `addresses` rows accepted by `POST /v1/batch`. Default 500 (see `routes.ts`'s `DEFAULT_BATCH_MAX`).
31
32
  */
32
33
  batchMax?: number;
34
+ /**
35
+ * The engine stamp to carry on every response: `engine` in each `/v1` body and the `Server` + `Link: rel="license"`
36
+ * headers everywhere. Absent when an embedding application builds the app without the `mailwoman` package; the
37
+ * `mailwoman serve` command always passes one.
38
+ */
39
+ engine?: EngineStamp;
33
40
  }
34
41
  /**
35
42
  * The document info stamped into the emitted OpenAPI document. Exported (not inlined) so the `mailwoman openapi`
package/out/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../app.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAoC,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAM1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAOrD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IAEd;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAUD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAoBpC,CAAA;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,EAC5F,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC7B,OAAO,GAAE,mBAAwB,GAC/B,WAAW,CAkDb"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../lib/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAIN,KAAK,cAAc,EAEnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAI1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAEjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAOjD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IAEd;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;CACpB;AAUD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAkBpC,CAAA;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,EAC5F,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC7B,OAAO,GAAE,mBAAwB,GAC/B,WAAW,CAyDb"}
package/out/app.js CHANGED
@@ -8,11 +8,10 @@
8
8
  * CLI wires the real parse/geocode/resolve stack (phase 4b); tests inject fixtures.
9
9
  */
10
10
  import { OpenAPIHono } from "@hono/zod-openapi";
11
- import { errorResponse, attachOpenAPIDocs } from "@mailwoman/api-kit";
11
+ import { attachOpenAPIDocs, engineHeaders, errorResponse, readServedDocumentInfo, } from "@mailwoman/api-kit";
12
12
  import { bodyLimit } from "hono/body-limit";
13
13
  import { cors } from "hono/cors";
14
- import packageJson from "#package.json" with { type: "json" };
15
- import { DEFAULT_BATCH_MAX, registerMailwomanAPIRoutes } from "./routes.js";
14
+ import { DEFAULT_BATCH_MAX, registerMailwomanAPIRoutes } from "#routes";
16
15
  /**
17
16
  * 2 MiB — carried from the express server's `express.json({ limit: "2mb" })` (`mailwoman/server/index.ts`).
18
17
  */
@@ -30,9 +29,7 @@ function summarizeValidationError(error) {
30
29
  * {@link attachOpenAPIDocs}) uses — one source of truth, no risk of the two drifting.
31
30
  */
32
31
  export const MAILWOMAN_API_DOC_INFO = {
33
- title: packageJson.name,
34
- version: packageJson.version,
35
- description: packageJson.description,
32
+ ...(await readServedDocumentInfo(import.meta.url, "@mailwoman/api")),
36
33
  license: { name: "AGPL-3.0-only OR LicenseRef-Commercial", identifier: "AGPL-3.0-only" },
37
34
  contact: { name: "Sister Software", url: "https://mailwoman.ai" },
38
35
  servers: [
@@ -72,6 +69,9 @@ export function createMailwomanAPI(engine, options = {}) {
72
69
  if (options.cors !== false) {
73
70
  app.use(cors({ origin: "*", allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["*"], maxAge: 86_400 }));
74
71
  }
72
+ if (options.engine) {
73
+ app.use(engineHeaders(options.engine));
74
+ }
75
75
  // Safety net: an engine fault answers the native envelope, never a crash. `detail` carries the raw message —
76
76
  // this surface is ours to design, so (unlike the vendor-constrained drop-in envelopes) we can be helpful.
77
77
  app.onError((error, c) => {
@@ -89,7 +89,10 @@ export function createMailwomanAPI(engine, options = {}) {
89
89
  maxSize: options.bodyLimitBytes ?? DEFAULT_BODY_LIMIT_BYTES,
90
90
  onError: (c) => errorResponse(c, 413, "request body too large"),
91
91
  }));
92
- registerMailwomanAPIRoutes(app, engine, { batchMax: options.batchMax ?? DEFAULT_BATCH_MAX });
92
+ registerMailwomanAPIRoutes(app, engine, {
93
+ batchMax: options.batchMax ?? DEFAULT_BATCH_MAX,
94
+ engine: options.engine,
95
+ });
93
96
  attachOpenAPIDocs(app, MAILWOMAN_API_DOC_INFO);
94
97
  return app;
95
98
  }
package/out/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../lib/app.ts"],"names":[],"mappings":"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"}
package/out/engine.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * exception — it's wired in-package from `@mailwoman/formatter` (the surface exists to expose it).
9
9
  */
10
10
  import type { AddressTree } from "@mailwoman/core";
11
- import type { GeocodeOutcomeLike } from "./schema.ts";
11
+ import type { GeocodeOutcomeLike } from "#schema";
12
12
  /**
13
13
  * One parsed component in reading order (a `ComponentTag` + the covered text).
14
14
  */
@@ -61,6 +61,6 @@ export interface MailwomanAPIEngine<T extends Partial<GeocodeOutcomeLike> = Geoc
61
61
  reloaded: boolean;
62
62
  versions: unknown;
63
63
  }>;
64
- health?(): HealthData;
64
+ health?(): Promise<HealthData>;
65
65
  }
66
66
  //# sourceMappingURL=engine.d.ts.map