@mailwoman/geographic-model 10.0.0 → 10.2.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 +32 -32
- package/data/geographic-model.json +5 -5
- package/data/model/concepts.json +5 -5
- package/lib/artifact.ts +37 -65
- package/lib/compile.ts +55 -70
- package/lib/index.ts +22 -35
- package/lib/load.ts +60 -72
- package/lib/lookup.ts +12 -28
- package/lib/packaged.ts +50 -0
- package/lib/schema.ts +104 -97
- package/lib/validate.ts +32 -56
- package/lib/validation/issues.ts +32 -36
- package/lib/validation/references.ts +24 -27
- package/out/artifact.d.ts +28 -45
- package/out/artifact.d.ts.map +1 -1
- package/out/artifact.js +25 -52
- package/out/artifact.js.map +1 -1
- package/out/compile.d.ts +27 -41
- package/out/compile.d.ts.map +1 -1
- package/out/compile.js +42 -64
- package/out/compile.js.map +1 -1
- package/out/index.d.ts +22 -35
- package/out/index.d.ts.map +1 -1
- package/out/index.js +22 -35
- package/out/index.js.map +1 -1
- package/out/load.d.ts +30 -39
- package/out/load.d.ts.map +1 -1
- package/out/load.js +42 -56
- package/out/load.js.map +1 -1
- package/out/lookup.d.ts +12 -28
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +4 -16
- package/out/lookup.js.map +1 -1
- package/out/packaged.d.ts +24 -0
- package/out/packaged.d.ts.map +1 -0
- package/out/packaged.js +42 -0
- package/out/packaged.js.map +1 -0
- package/out/schema.d.ts +104 -97
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +37 -52
- package/out/schema.js.map +1 -1
- package/out/tools/build-artifact.d.ts +27 -0
- package/out/tools/build-artifact.d.ts.map +1 -0
- package/out/tools/build-artifact.js +44 -0
- package/out/tools/build-artifact.js.map +1 -0
- package/out/validate.d.ts +29 -50
- package/out/validate.d.ts.map +1 -1
- package/out/validate.js +12 -35
- package/out/validate.js.map +1 -1
- package/out/validation/issues.d.ts +17 -20
- package/out/validation/issues.d.ts.map +1 -1
- package/out/validation/issues.js +25 -29
- package/out/validation/issues.js.map +1 -1
- package/out/validation/references.d.ts.map +1 -1
- package/out/validation/references.js +20 -23
- package/out/validation/references.js.map +1 -1
- package/package.json +35 -76
- package/tools/build-artifact.ts +56 -0
- package/out/scripts/build-artifact.d.ts +0 -51
- package/out/scripts/build-artifact.d.ts.map +0 -1
- package/out/scripts/build-artifact.js +0 -85
- package/out/scripts/build-artifact.js.map +0 -1
package/lib/compile.ts
CHANGED
|
@@ -3,32 +3,17 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Compiles a validated {@link GeographicModelDocument} into the runtime artifact deterministically.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* is computed. There is no second validator here, and no partial artifact on failure — a compile
|
|
11
|
-
* either produces the whole artifact or produces nothing.
|
|
8
|
+
* `parseGeographicModelDocument` performs all document validation and throws before compilation
|
|
9
|
+
* starts. A compile either returns the whole artifact or throws.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Only `isA` defines inheritance. The compiler materializes the transitive `isA` closure for each
|
|
12
|
+
* concept. It copies each ancestor's assertions onto descendants as {@link DerivedFactRecord}
|
|
13
|
+
* entries. The compiler does not close over relations marked `transitive` or `inverse`.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 2. Every assertion an ancestor carries is materialized onto its descendants as a
|
|
19
|
-
* {@link DerivedFactRecord}, naming {@link DERIVATION_ISA_INHERITANCE} and every record the
|
|
20
|
-
* derivation read. Without this a consumer would still be traversing — the closure alone tells it
|
|
21
|
-
* which concepts to go and read, which is the traversal it was supposed to be spared.
|
|
22
|
-
*
|
|
23
|
-
* A relation declaring `transitive` or `inverse` is NOT closed over. Those fields are vocabulary
|
|
24
|
-
* describing what the relation means; materializing them is a reasoning step no executable need has
|
|
25
|
-
* asked for, and the boundary record excludes general reasoning from this package. The day one is
|
|
26
|
-
* needed it arrives as its own named derivation beside this one.
|
|
27
|
-
*
|
|
28
|
-
* Cycles never reach the derivations: `parseGeographicModelDocument` refuses a direct or indirect
|
|
29
|
-
* `isA` cycle and names the trail it followed, so a cyclic document fails as a validation error
|
|
30
|
-
* rather than as a hang. The walk below is breadth-first over a visited set regardless, which makes
|
|
31
|
-
* it total for any graph rather than for the graphs the validator happens to admit.
|
|
15
|
+
* The validator rejects `isA` cycles, but the ancestor walk also tracks visited concepts, so it
|
|
16
|
+
* terminates on any graph.
|
|
32
17
|
*/
|
|
33
18
|
|
|
34
19
|
import { compareByCodePoint as compareIdentifiers } from "@mailwoman/core/strings/compare"
|
|
@@ -48,34 +33,38 @@ import {
|
|
|
48
33
|
} from "#schema"
|
|
49
34
|
import { parseGeographicModelDocument } from "#validate"
|
|
50
35
|
/**
|
|
51
|
-
* The
|
|
52
|
-
* than on where the record sits.
|
|
36
|
+
* The `derivation` value of a fact derived by `isA` inheritance.
|
|
53
37
|
*/
|
|
54
38
|
export const DERIVATION_ISA_INHERITANCE = "isa-assertion-inheritance"
|
|
55
39
|
|
|
56
40
|
/**
|
|
57
|
-
*
|
|
41
|
+
* Reasons that compilation rejects a document that passed validation.
|
|
58
42
|
*
|
|
59
|
-
* Both
|
|
60
|
-
*
|
|
43
|
+
* Both problems concern the derived records.
|
|
44
|
+
* The validator never sees those records.
|
|
61
45
|
*/
|
|
62
46
|
export const CompileIssueCode = {
|
|
63
47
|
/**
|
|
64
|
-
* An inherited assertion would
|
|
65
|
-
* Emitting it would put a record in the artifact that the document validator would reject if it were authored.
|
|
48
|
+
* An inherited assertion would apply to a concept whose kind the relation's `domainKinds` excludes.
|
|
66
49
|
*/
|
|
67
50
|
InheritedDomainKindMismatch: "inherited_domain_kind_mismatch",
|
|
68
51
|
/**
|
|
69
|
-
* Two derived facts
|
|
70
|
-
*
|
|
52
|
+
* Two derived facts share one identifier.
|
|
53
|
+
*
|
|
54
|
+
* This happens when an authored derived fact uses an identifier that a derivation also
|
|
55
|
+
* produces, or when authored identifiers contain the separators of the derived form.
|
|
71
56
|
*/
|
|
72
57
|
DuplicateDerivedFactID: "duplicate_derived_fact_id",
|
|
73
58
|
} as const
|
|
74
59
|
|
|
60
|
+
/**
|
|
61
|
+
* A {@link CompileIssueCode} value.
|
|
62
|
+
*/
|
|
63
|
+
|
|
75
64
|
export type CompileIssueCode = (typeof CompileIssueCode)[keyof typeof CompileIssueCode]
|
|
76
65
|
|
|
77
66
|
/**
|
|
78
|
-
* One reason a document
|
|
67
|
+
* One reason that a valid document does not compile.
|
|
79
68
|
*/
|
|
80
69
|
export interface CompileIssue {
|
|
81
70
|
code: CompileIssueCode
|
|
@@ -83,8 +72,8 @@ export interface CompileIssue {
|
|
|
83
72
|
}
|
|
84
73
|
|
|
85
74
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
75
|
+
* The error that {@link compileGeographicModel} throws.
|
|
76
|
+
* Its message lists every issue.
|
|
88
77
|
*/
|
|
89
78
|
export class GeographicModelCompileError extends Error {
|
|
90
79
|
readonly issues: readonly CompileIssue[]
|
|
@@ -100,8 +89,8 @@ export class GeographicModelCompileError extends Error {
|
|
|
100
89
|
}
|
|
101
90
|
|
|
102
91
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
92
|
+
* Sort order of derivation inputs by kind.
|
|
93
|
+
* Identifiers break ties within a kind.
|
|
105
94
|
*/
|
|
106
95
|
const DERIVATION_INPUT_ORDER: readonly DerivationInputKind[] = [
|
|
107
96
|
DerivationInputKind.Concept,
|
|
@@ -113,14 +102,14 @@ const DERIVATION_INPUT_ORDER: readonly DerivationInputKind[] = [
|
|
|
113
102
|
]
|
|
114
103
|
|
|
115
104
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
105
|
+
* Separator for internal grouping keys.
|
|
106
|
+
*
|
|
107
|
+
* Identifiers never contain `U+0000`, so distinct key tuples cannot produce the same string.
|
|
119
108
|
*/
|
|
120
109
|
const KEY_SEPARATOR = "\u0000"
|
|
121
110
|
|
|
122
111
|
/**
|
|
123
|
-
*
|
|
112
|
+
* Separator between country codes in a derived identifier.
|
|
124
113
|
*/
|
|
125
114
|
const COUNTRY_SEPARATOR = "+"
|
|
126
115
|
|
|
@@ -129,11 +118,8 @@ function compareByID(left: { id: string }, right: { id: string }): number {
|
|
|
129
118
|
}
|
|
130
119
|
|
|
131
120
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* Breadth-first over a visited set: each concept is expanded once, so the walk terminates on any graph and the answer
|
|
135
|
-
* does not depend on how the parents were authored. The concept itself is never in its own list — it could only get
|
|
136
|
-
* there around a cycle, and the validator refuses those before the compiler runs.
|
|
121
|
+
* Returns every concept reachable from `conceptID` through `isA`, sorted by code point.
|
|
122
|
+
* The result excludes the concept itself.
|
|
137
123
|
*/
|
|
138
124
|
function ancestorsOfConcept(
|
|
139
125
|
conceptID: ConceptID,
|
|
@@ -142,8 +128,8 @@ function ancestorsOfConcept(
|
|
|
142
128
|
const visited = new Set<string>()
|
|
143
129
|
const frontier: ConceptID[] = [...(parents.get(conceptID) ?? [])]
|
|
144
130
|
|
|
145
|
-
//
|
|
146
|
-
//
|
|
131
|
+
// The array iterator also visits entries pushed during the loop.
|
|
132
|
+
// This makes the walk breadth-first.
|
|
147
133
|
for (const next of frontier) {
|
|
148
134
|
if (next === conceptID || visited.has(next)) continue
|
|
149
135
|
|
|
@@ -157,14 +143,17 @@ function ancestorsOfConcept(
|
|
|
157
143
|
}
|
|
158
144
|
|
|
159
145
|
/**
|
|
160
|
-
* One derived fact under construction.
|
|
161
|
-
*
|
|
162
|
-
*
|
|
146
|
+
* One derived fact under construction.
|
|
147
|
+
*
|
|
148
|
+
* Drafts are keyed by subject, relation, target, modality and countries.
|
|
149
|
+
* Two ancestors with the same assertion produce one fact that lists both as inputs.
|
|
150
|
+
*
|
|
151
|
+
* Assertions that differ only in modality produce separate facts, so a contradiction stays visible.
|
|
163
152
|
*/
|
|
164
153
|
interface DerivedDraft {
|
|
165
154
|
subject: ConceptID
|
|
166
155
|
assertion: RelationAssertion
|
|
167
|
-
countries
|
|
156
|
+
countries: readonly string[] | null
|
|
168
157
|
inputs: Map<string, DerivationInput>
|
|
169
158
|
}
|
|
170
159
|
|
|
@@ -183,8 +172,9 @@ function draftInputs(draft: DerivedDraft): DerivationInput[] {
|
|
|
183
172
|
}
|
|
184
173
|
|
|
185
174
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
175
|
+
* Builds a readable derived-fact identifier from the fact's proposition.
|
|
176
|
+
*
|
|
177
|
+
* The identifier stays stable when unrelated parts of the document change.
|
|
188
178
|
*/
|
|
189
179
|
function derivedFactID(draft: DerivedDraft): string {
|
|
190
180
|
const scope = draft.countries?.length ? `:${draft.countries.join(COUNTRY_SEPARATOR)}` : ""
|
|
@@ -194,24 +184,22 @@ function derivedFactID(draft: DerivedDraft): string {
|
|
|
194
184
|
}
|
|
195
185
|
|
|
196
186
|
/**
|
|
197
|
-
*
|
|
187
|
+
* Returns a key for an assertion's relation and target.
|
|
198
188
|
*/
|
|
199
189
|
function edgeKey(assertion: RelationAssertion): string {
|
|
200
190
|
return `${assertion.relation}${KEY_SEPARATOR}${assertion.target}`
|
|
201
191
|
}
|
|
202
192
|
|
|
203
|
-
function draftKey(subject: ConceptID, assertion: RelationAssertion, countries: readonly string[] |
|
|
193
|
+
function draftKey(subject: ConceptID, assertion: RelationAssertion, countries: readonly string[] | null): string {
|
|
204
194
|
const scope = countries?.join(COUNTRY_SEPARATOR) ?? ""
|
|
205
195
|
|
|
206
196
|
return `${subject}${KEY_SEPARATOR}${edgeKey(assertion)}${KEY_SEPARATOR}${assertion.modality}${KEY_SEPARATOR}${scope}`
|
|
207
197
|
}
|
|
208
198
|
|
|
209
199
|
/**
|
|
210
|
-
*
|
|
200
|
+
* Copies each ancestor's assertions onto its descendants as derived facts.
|
|
211
201
|
*
|
|
212
|
-
* A concept
|
|
213
|
-
* authored record is the more specific one, which is what `isA` means, and re-stating the pair would put two modalities
|
|
214
|
-
* for one proposition into the artifact with no rule saying which of them holds.
|
|
202
|
+
* A concept's own assertion for a relation and target overrides every inherited assertion for that pair.
|
|
215
203
|
*/
|
|
216
204
|
function deriveInheritedFacts(
|
|
217
205
|
concepts: readonly ConceptRecord[],
|
|
@@ -233,9 +221,8 @@ function deriveInheritedFacts(
|
|
|
233
221
|
for (const ancestorID of entry.ancestors) {
|
|
234
222
|
const ancestor = conceptByID.get(String(ancestorID))
|
|
235
223
|
|
|
236
|
-
// Validation
|
|
237
|
-
//
|
|
238
|
-
// describe a state the artifact can hold.
|
|
224
|
+
// Validation guarantees that every referenced concept and relation exists.
|
|
225
|
+
// These guards only satisfy the type checker.
|
|
239
226
|
if (!ancestor) continue
|
|
240
227
|
|
|
241
228
|
for (const assertion of ancestor.assertions) {
|
|
@@ -256,7 +243,7 @@ function deriveInheritedFacts(
|
|
|
256
243
|
continue
|
|
257
244
|
}
|
|
258
245
|
|
|
259
|
-
const countries = assertion.countries?.length ? assertion.countries.toSorted(compareIdentifiers) :
|
|
246
|
+
const countries = assertion.countries?.length ? assertion.countries.toSorted(compareIdentifiers) : null
|
|
260
247
|
const key = draftKey(concept.id, assertion, countries)
|
|
261
248
|
const existing = drafts.get(key)
|
|
262
249
|
const draft: DerivedDraft = existing ?? { subject: concept.id, assertion, countries, inputs: new Map() }
|
|
@@ -305,14 +292,12 @@ function checkDerivedIdentifiers(facts: readonly DerivedFactRecord[], issues: Co
|
|
|
305
292
|
}
|
|
306
293
|
|
|
307
294
|
/**
|
|
308
|
-
*
|
|
295
|
+
* Compiles an authored geographic-model document into its runtime artifact.
|
|
309
296
|
*
|
|
310
|
-
*
|
|
311
|
-
* {@link GeographicModelCompileError} with every reason if it validates but its derivations cannot be written. Nothing
|
|
312
|
-
* partial is ever returned.
|
|
297
|
+
* The artifact's tables are new sorted arrays that hold the authored records unchanged.
|
|
313
298
|
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
299
|
+
* @throws {GeographicModelValidationError} When the input is not a valid document.
|
|
300
|
+
* @throws {GeographicModelCompileError} When the document is valid but its derived facts are invalid.
|
|
316
301
|
*/
|
|
317
302
|
export function compileGeographicModel(input: unknown): CompiledGeographicModel {
|
|
318
303
|
const document: GeographicModelDocument = parseGeographicModelDocument(input)
|
package/lib/index.ts
CHANGED
|
@@ -3,45 +3,31 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* and mappings from external vocabularies into world concepts.
|
|
6
|
+
* `@mailwoman/geographic-model` — the world-semantic layer: stable concepts beyond the POI
|
|
7
|
+
* vocabulary, relation definitions, activities, affordances, rule modality, source observations,
|
|
8
|
+
* derived facts, derivation provenance, deterministic compilation, plus validation.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* runtime consumer reads it through (`./lookup.ts`). All five are re-exported here and all five are
|
|
15
|
-
* reachable as curated subpaths.
|
|
10
|
+
* The authoring loader (`./load.ts`, the `./load` subpath) is deliberately not re-exported here: it is
|
|
11
|
+
* the only module in the package that touches a filesystem and belongs to the build step rather than
|
|
12
|
+
* to the consumers that read an artifact.
|
|
16
13
|
*
|
|
17
|
-
*
|
|
18
|
-
* is the only module in the package that touches a filesystem, and it belongs to the build step that
|
|
19
|
-
* produces an artifact rather than to the consumers that read one. The first authored document
|
|
20
|
-
* arrives with #1927; this entry point carries no data.
|
|
14
|
+
* Four things this package must never hold, each owned elsewhere:
|
|
21
15
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* 1. The resolver owns ranking policy. It defines weights, boosts, penalties and candidate order.
|
|
17
|
+
* The resolver package also owns rank order. The neural package and `@mailwoman/core/decoder` own
|
|
18
|
+
* the decode objective. This package creates observations and leaves candidate order to the resolver.
|
|
19
|
+
* 2. A second POI vocabulary — external and curated POI categories, their containment hierarchy, the
|
|
20
|
+
* Overture-leaf translation, the query-phrase lexicon and the brand table belong to
|
|
21
|
+
* `@mailwoman/poi-taxonomy`; this package maps into those identifiers.
|
|
22
|
+
* 3. A second coverage register — dataset identity and coverage epistemics belong to
|
|
23
|
+
* `@mailwoman/core/layers`, and an expected-but-absent observation becomes negative evidence only
|
|
24
|
+
* where `supportsExclusion` permits it there.
|
|
25
|
+
* 4. Empirical affordance statistics — this package owns the stable activity and affordance identifiers.
|
|
26
|
+
* It does not own numeric values fitted against those identifiers.
|
|
24
27
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* observations; it never overrides learned interpretation.
|
|
29
|
-
* 2. **A second POI vocabulary.** External and curated POI categories, their containment
|
|
30
|
-
* hierarchy, the Overture-leaf translation, the query-phrase lexicon and the brand table all
|
|
31
|
-
* belong to `@mailwoman/poi-taxonomy`. This package maps INTO those identifiers.
|
|
32
|
-
* 3. **A second coverage register.** Dataset identity and coverage epistemics belong to
|
|
33
|
-
* `@mailwoman/core/layers`. An expected-but-absent observation becomes negative evidence only
|
|
34
|
-
* where `supportsExclusion` permits it there.
|
|
35
|
-
* 4. **Empirical affordance statistics.** #1683 fits those. This package owns the stable
|
|
36
|
-
* activity and affordance identifiers they are fitted against, and nothing numeric about them.
|
|
37
|
-
*
|
|
38
|
-
* `@mailwoman/core` must not depend on this package. Core ships the pipeline contract and roughly
|
|
39
|
-
* 9 MB of reference data to every consumer, so a world-semantics dependency there is one every
|
|
40
|
-
* drop-in API inherits without asking for it. Reversing that direction is an explicit amendment to
|
|
41
|
-
* the boundary record, not a convenience during implementation.
|
|
42
|
-
*
|
|
43
|
-
* Boundary record: `docs/superpowers/specs/2026-08-26-geographic-model-boundaries.md` (#1917).
|
|
44
|
-
* Program parent: #1916.
|
|
28
|
+
* `@mailwoman/core` must not depend on this package: core ships the pipeline interface and roughly
|
|
29
|
+
* 9 MB of reference data to every consumer, so a world-semantics dependency there is one every
|
|
30
|
+
* drop-in API inherits without asking.
|
|
45
31
|
*/
|
|
46
32
|
|
|
47
33
|
export * from "#artifact"
|
|
@@ -49,3 +35,4 @@ export * from "#compile"
|
|
|
49
35
|
export * from "#lookup"
|
|
50
36
|
export * from "#schema"
|
|
51
37
|
export * from "#validate"
|
|
38
|
+
export { type ValidationIssue, ValidationIssueCode } from "#validation/issues"
|