@heroiclands/package-build 23.1.0 → 23.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.
Files changed (248) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/bin/content-commands.mjs +68 -149
  3. package/bin/package-build.mjs +56 -10
  4. package/ci/check-banners.mjs +42 -6
  5. package/ci/confirm-npm-version.mjs +135 -0
  6. package/ci/content-format-reference.mjs +14 -5
  7. package/config.mjs +1 -0
  8. package/container.mjs +185 -4
  9. package/content-config.mjs +333 -210
  10. package/deploy.mjs +116 -10
  11. package/docs/api.md +238 -246
  12. package/docs/authoring/assets.md +11 -5
  13. package/docs/authoring/frontmatter.md +1 -1
  14. package/docs/authoring/links-and-markup.md +87 -0
  15. package/docs/commands.md +138 -99
  16. package/docs/configuration.md +277 -125
  17. package/docs/content-format.md +2 -0
  18. package/docs/diagnostics.md +48 -13
  19. package/docs/guides/book.md +341 -21
  20. package/docs/guides/e2e.md +13 -2
  21. package/docs/guides/icons.md +14 -4
  22. package/docs/guides/module-project.md +18 -8
  23. package/docs/guides/release-and-deploy.md +54 -6
  24. package/docs/guides/site.md +5 -2
  25. package/docs/project-setup.md +30 -10
  26. package/docs/reference/format-details.md +373 -170
  27. package/docs/reference/note-types.md +128 -109
  28. package/docs/reference/theme-pages.md +31 -6
  29. package/e2e.mjs +140 -28
  30. package/engine/actor-compiler.mjs +22 -31
  31. package/engine/address-charset.mjs +4 -17
  32. package/engine/address-fields.mjs +1 -1
  33. package/engine/address-render.mjs +12 -1
  34. package/engine/address-values.mjs +6 -0
  35. package/engine/address.mjs +46 -33
  36. package/engine/art-fields.mjs +30 -5
  37. package/engine/art-slots.mjs +10 -1
  38. package/engine/asset-bindings.mjs +12 -1
  39. package/engine/base-compiler.mjs +34 -65
  40. package/engine/being-age.mjs +2 -7
  41. package/engine/being-measurements.mjs +12 -1
  42. package/engine/calendar-artifacts.mjs +6 -0
  43. package/engine/calendar-choice.mjs +12 -1
  44. package/engine/calendar-format.mjs +12 -1
  45. package/engine/calendar-human.mjs +12 -1
  46. package/engine/compile-corpus.mjs +3 -1
  47. package/engine/content-address.mjs +9 -12
  48. package/engine/content-alerts.mjs +13 -1
  49. package/engine/content-blocks.mjs +14 -3
  50. package/engine/content-embeds.mjs +11 -3
  51. package/engine/content-figures.mjs +9 -0
  52. package/engine/content-footnotes.mjs +12 -1
  53. package/engine/content-format-check.mjs +11 -262
  54. package/engine/content-format.yaml +5 -2
  55. package/engine/content-images.mjs +3 -0
  56. package/engine/content-index.mjs +443 -20
  57. package/engine/content-links.mjs +29 -35
  58. package/engine/content-lint.mjs +2 -3
  59. package/engine/content-poetry.mjs +12 -1
  60. package/engine/content-slug.mjs +2 -14
  61. package/engine/content-spans.mjs +13 -1
  62. package/engine/content-tables.mjs +12 -1
  63. package/engine/culture-choice.mjs +210 -13
  64. package/engine/data-keys.mjs +19 -16
  65. package/engine/date-conversion.mjs +12 -1
  66. package/engine/declaration-check.mjs +9 -0
  67. package/engine/derived-fields.mjs +6 -7
  68. package/engine/diagnostics.mjs +34 -4
  69. package/engine/docs-checks.mjs +9 -0
  70. package/engine/document-subtypes.mjs +47 -34
  71. package/engine/event-views.mjs +139 -14
  72. package/engine/exported-scene.mjs +92 -28
  73. package/engine/extension-attributes.mjs +12 -1
  74. package/engine/field-spec.mjs +11 -102
  75. package/engine/folder-notes.mjs +0 -64
  76. package/engine/foreign-catalog.mjs +2 -1
  77. package/engine/format-generated.mjs +9 -0
  78. package/engine/foundry-entries.mjs +10 -28
  79. package/engine/frontmatter-lint.mjs +83 -392
  80. package/engine/frontmatter.mjs +20 -70
  81. package/engine/generate.mjs +10 -15
  82. package/engine/heading-attributes.mjs +12 -1
  83. package/engine/helpers.mjs +41 -102
  84. package/engine/holdings.mjs +1 -1
  85. package/engine/homepage.mjs +3 -10
  86. package/engine/ids.mjs +1 -167
  87. package/engine/index-records.mjs +6 -4
  88. package/engine/index.mjs +0 -3
  89. package/engine/infobox-render.mjs +15 -3
  90. package/engine/infobox.mjs +13 -14
  91. package/engine/item-compiler.mjs +4 -10
  92. package/engine/item-docs.mjs +3 -5
  93. package/engine/item-registry.mjs +2 -10
  94. package/engine/journals.mjs +1 -5
  95. package/engine/literature-notes.mjs +4 -12
  96. package/engine/macros.mjs +1 -2
  97. package/engine/map-graphviz.mjs +12 -1
  98. package/engine/markdown-expressions.mjs +12 -1
  99. package/engine/markdownlint-run.mjs +567 -0
  100. package/engine/metadata-index.mjs +42 -12
  101. package/engine/note-addresses.mjs +143 -35
  102. package/engine/note-claims.mjs +14 -28
  103. package/engine/note-dates.mjs +3 -3
  104. package/engine/note-frontmatter.mjs +6 -0
  105. package/engine/note-ids.mjs +8 -14
  106. package/engine/note-package.mjs +8 -25
  107. package/engine/note-state.mjs +1 -3
  108. package/engine/note-vocabulary.mjs +148 -84
  109. package/engine/number-words.mjs +12 -1
  110. package/engine/pack-config.mjs +15 -25
  111. package/engine/pack-router.mjs +10 -16
  112. package/engine/pathnames.mjs +8 -22
  113. package/engine/pdf-build.mjs +773 -121
  114. package/engine/pdf-coverage.mjs +188 -0
  115. package/engine/pdf-index.mjs +54 -7
  116. package/engine/pdf-pages.mjs +247 -0
  117. package/engine/pdf-render.mjs +114 -4
  118. package/engine/pdf-toc.mjs +29 -32
  119. package/engine/project-init.mjs +46 -6
  120. package/engine/prose-config.mjs +1 -1
  121. package/engine/prose-lint.mjs +52 -88
  122. package/engine/readability-lint.mjs +9 -0
  123. package/engine/reckoning-markers.mjs +12 -1
  124. package/engine/reference-targets.mjs +7 -0
  125. package/engine/runtime-only-fields.mjs +5 -8
  126. package/engine/scene-levels.mjs +2 -2
  127. package/engine/scenes.mjs +1 -1
  128. package/engine/shortcode-references.mjs +7 -0
  129. package/engine/site-build.mjs +30 -16
  130. package/engine/site-index.mjs +10 -16
  131. package/engine/site-preview.mjs +6 -0
  132. package/engine/site-root.mjs +8 -1
  133. package/engine/social-tie-terms.mjs +12 -1
  134. package/engine/social-ties.mjs +3 -0
  135. package/engine/sql-tables.mjs +56 -20
  136. package/engine/standing-terms.mjs +11 -1
  137. package/engine/standings.mjs +11 -27
  138. package/engine/system-block.mjs +39 -141
  139. package/engine/system-vocabulary.mjs +6 -0
  140. package/engine/walk-files.mjs +69 -0
  141. package/engine/web-wikilinks.mjs +23 -3
  142. package/engine/wikilink-syntax.mjs +32 -21
  143. package/engine/wikilinks.mjs +27 -16
  144. package/engine/yaml-lint.mjs +7 -4
  145. package/githooks/attribution.mjs +8 -0
  146. package/githooks/commit-message.mjs +9 -0
  147. package/hm3/actors.mjs +9 -48
  148. package/hm3/default-item-art.mjs +9 -9
  149. package/hm3/item-fields.mjs +2 -9
  150. package/hm3/template-priority.mjs +6 -7
  151. package/hugo-theme/data/banner-kinds.yaml +78 -0
  152. package/hugo-theme/data/banners.yaml +2 -3
  153. package/hugo-theme/layouts/404.html +3 -3
  154. package/hugo-theme/layouts/partials/banner-url.html +31 -7
  155. package/hugo-theme/layouts/partials/hero-banner.html +13 -14
  156. package/hugo-theme/layouts/partials/related.html +1 -1
  157. package/hugo-theme/layouts/partials/warn-missing-banner.html +1 -1
  158. package/hugo-theme/static/css/style.css +1 -1
  159. package/manifest.mjs +11 -4
  160. package/markdownlint-config.mjs +1 -1
  161. package/package.json +6 -3
  162. package/release.mjs +83 -18
  163. package/sohl/actors.mjs +16 -27
  164. package/sohl/default-item-art.mjs +3 -3
  165. package/sohl/document-subtypes.mjs +1 -16
  166. package/sohl/infobox.mjs +2 -3
  167. package/sohl/item-builders.mjs +5 -8
  168. package/sohl/item-fields.mjs +5 -35
  169. package/sohl/items.mjs +0 -4
  170. package/sohl/kb-passes.mjs +3 -3
  171. package/types/container.d.mts +116 -5
  172. package/types/content-config.d.mts +83 -35
  173. package/types/deploy.d.mts +42 -6
  174. package/types/e2e.d.mts +35 -3
  175. package/types/engine/address-charset.d.mts +4 -17
  176. package/types/engine/address.d.mts +17 -8
  177. package/types/engine/art-fields.d.mts +7 -4
  178. package/types/engine/art-slots.d.mts +6 -0
  179. package/types/engine/base-compiler.d.mts +15 -17
  180. package/types/engine/content-address.d.mts +9 -12
  181. package/types/engine/content-embeds.d.mts +4 -2
  182. package/types/engine/content-format-check.d.mts +0 -64
  183. package/types/engine/content-index.d.mts +82 -3
  184. package/types/engine/content-links.d.mts +2 -4
  185. package/types/engine/culture-choice.d.mts +51 -8
  186. package/types/engine/data-keys.d.mts +2 -2
  187. package/types/engine/derived-fields.d.mts +1 -2
  188. package/types/engine/diagnostics.d.mts +27 -4
  189. package/types/engine/document-subtypes.d.mts +12 -31
  190. package/types/engine/event-views.d.mts +14 -2
  191. package/types/engine/exported-scene.d.mts +3 -2
  192. package/types/engine/field-spec.d.mts +13 -122
  193. package/types/engine/folder-notes.d.mts +0 -39
  194. package/types/engine/foreign-catalog.d.mts +2 -1
  195. package/types/engine/foundry-entries.d.mts +4 -5
  196. package/types/engine/frontmatter-lint.d.mts +2 -2
  197. package/types/engine/frontmatter.d.mts +19 -45
  198. package/types/engine/generate.d.mts +1 -1
  199. package/types/engine/helpers.d.mts +25 -42
  200. package/types/engine/homepage.d.mts +3 -10
  201. package/types/engine/ids.d.mts +0 -114
  202. package/types/engine/index-records.d.mts +6 -4
  203. package/types/engine/index.d.mts +0 -1
  204. package/types/engine/infobox-render.d.mts +7 -1
  205. package/types/engine/infobox.d.mts +3 -2
  206. package/types/engine/item-docs.d.mts +1 -1
  207. package/types/engine/item-registry.d.mts +2 -4
  208. package/types/engine/literature-notes.d.mts +1 -1
  209. package/types/engine/markdownlint-run.d.mts +40 -0
  210. package/types/engine/metadata-index.d.mts +9 -1
  211. package/types/engine/note-addresses.d.mts +27 -2
  212. package/types/engine/note-claims.d.mts +3 -5
  213. package/types/engine/note-ids.d.mts +2 -4
  214. package/types/engine/note-package.d.mts +1 -1
  215. package/types/engine/note-state.d.mts +1 -3
  216. package/types/engine/note-vocabulary.d.mts +28 -21
  217. package/types/engine/pack-config.d.mts +2 -6
  218. package/types/engine/pack-router.d.mts +4 -4
  219. package/types/engine/pathnames.d.mts +3 -3
  220. package/types/engine/pdf-build.d.mts +98 -12
  221. package/types/engine/pdf-coverage.d.mts +60 -0
  222. package/types/engine/pdf-index.d.mts +34 -5
  223. package/types/engine/pdf-pages.d.mts +143 -0
  224. package/types/engine/pdf-render.d.mts +45 -1
  225. package/types/engine/pdf-toc.d.mts +12 -14
  226. package/types/engine/prose-config.d.mts +1 -1
  227. package/types/engine/prose-lint.d.mts +11 -10
  228. package/types/engine/runtime-only-fields.d.mts +2 -2
  229. package/types/engine/site-build.d.mts +9 -5
  230. package/types/engine/site-index.d.mts +4 -5
  231. package/types/engine/site-root.d.mts +1 -1
  232. package/types/engine/sql-tables.d.mts +31 -2
  233. package/types/engine/standing-terms.d.mts +2 -1
  234. package/types/engine/standings.d.mts +7 -9
  235. package/types/engine/system-block.d.mts +10 -44
  236. package/types/engine/walk-files.d.mts +15 -0
  237. package/types/engine/wikilink-syntax.d.mts +18 -19
  238. package/types/engine/yaml-lint.d.mts +5 -4
  239. package/types/hm3/actors.d.mts +1 -2
  240. package/types/hm3/item-fields.d.mts +2 -4
  241. package/types/hm3/template-priority.d.mts +6 -7
  242. package/types/manifest.d.mts +3 -2
  243. package/types/release.d.mts +12 -7
  244. package/types/sohl/actors.d.mts +2 -1
  245. package/types/sohl/document-subtypes.d.mts +1 -2
  246. package/types/sohl/item-fields.d.mts +3 -7
  247. package/engine/retired-fields.mjs +0 -631
  248. package/types/engine/retired-fields.d.mts +0 -354
@@ -1,48 +1,9 @@
1
- /**
2
- * Whether a resolution read a field from the position it is being swept off.
3
- *
4
- * The sweep's progress signal, in one predicate so the compile-time report and
5
- * the frontmatter lint cannot disagree about what counts — the role
6
- * {@link module:engine/retired-fields.declaresRetiredAlias} plays for a renamed
7
- * field.
8
- *
9
- * **Only for a field that declares a `legacyKey`.** Every other field's
10
- * in-block position is simply where it lives; reporting those would put a
11
- * finding on every field of every note in every tree, which is the corpus
12
- * migration rather than a signal anyone could act on.
13
- *
14
- * @param {FieldSpec} field - The declaration.
15
- * @param {import("./system-block.mjs").FieldSource} from - Where
16
- * {@link resolveFieldValue} said the value came from.
17
- * @returns {boolean} True when the value came from the retiring position.
18
- */
19
- export function readsLegacyKey(field: FieldSpec, from: import("./system-block.mjs").FieldSource): boolean;
20
- /**
21
- * Whether a resolution read a field from the top-level key `data:` gathered it
22
- * off — the shared level's retiring position.
23
- *
24
- * {@link readsLegacyKey}'s sibling, and the same signal: a finding here counts
25
- * one note still on the pre-`data:` spelling, so the sweep has something to
26
- * count down instead of a corpus nobody has surveyed.
27
- *
28
- * The `from` tag already carries the whole answer — step 3b is the only thing
29
- * that produces it, and it produces it only for a `data.` source — so this is a
30
- * name for the question rather than a second test of it. Named all the same,
31
- * because a compile-time report and the frontmatter lint both ask it and must
32
- * not drift apart about what counts.
33
- *
34
- * @param {FieldSpec} field - The declaration.
35
- * @param {import("./system-block.mjs").FieldSource} from - Where
36
- * {@link resolveFieldValue} said the value came from.
37
- * @returns {boolean} True when the value came from the retiring top-level key.
38
- */
39
- export function readsRetiredTopLevel(field: FieldSpec, from: import("./system-block.mjs").FieldSource): boolean;
40
1
  /**
41
2
  * Read one declared field out of a note's frontmatter.
42
3
  *
43
4
  * The *position* is resolved by {@link resolveFieldValue} — `<system>.system`
44
- * first, then the legacy in-block key, then the declared shared source, then
45
- * the default. The **coercion** is applied here, once, wherever the value
5
+ * first, then the in-block key, then the declared shared source, then the
6
+ * default. The **coercion** is applied here, once, wherever the value
46
7
  * came from: a field's `read` is a statement about the field, not about where
47
8
  * an author happened to write it, so `weight: "7"` reads as `7` at every one of
48
9
  * those positions.
@@ -53,23 +14,10 @@ export function readsRetiredTopLevel(field: FieldSpec, from: import("./system-bl
53
14
  * @param {string} [options.block="sohl"] - Which system's block to resolve
54
15
  * against. The default is the one block every existing tree authors; a
55
16
  * second system passes its own.
56
- * @param {(field: FieldSpec) => void} [options.onLegacyKey] - Called with each
57
- * field read from the position it is being swept off. A callback
58
- * rather than a returned list because the caller is a compiler, which already
59
- * knows the note and how to locate a key in it; this module knows neither and
60
- * would have to invent a finding shape to say so.
61
- * @param {(field: FieldSpec) => void} [options.onRetiredTopLevel] - Called with
62
- * each field read from the top-level key `data:` gathered it off. The
63
- * shared level's counterpart to `onLegacyKey`, and a separate callback
64
- * because it is a separate position: a note may have moved one of the two and
65
- * not the other, and a caller that conflated them would tell its author to
66
- * fix the wrong line.
67
17
  * @returns {any} The value to emit.
68
18
  */
69
19
  export function readField(field: FieldSpec, fm: object, options?: {
70
20
  block?: string | undefined;
71
- onLegacyKey?: ((field: FieldSpec) => void) | undefined;
72
- onRetiredTopLevel?: ((field: FieldSpec) => void) | undefined;
73
21
  }): any;
74
22
  /**
75
23
  * The same read, reporting **where the value came from** as well.
@@ -90,17 +38,11 @@ export function readField(field: FieldSpec, fm: object, options?: {
90
38
  * @param {object} [options] - Options, as {@link readField} takes them.
91
39
  * @param {string} [options.block="sohl"] - Which system's block to resolve
92
40
  * against.
93
- * @param {(field: FieldSpec) => void} [options.onLegacyKey] - See
94
- * {@link readField}.
95
- * @param {(field: FieldSpec) => void} [options.onRetiredTopLevel] - See
96
- * {@link readField}.
97
41
  * @returns {{value: any, from: import("./system-block.mjs").FieldSource}} The
98
42
  * value to emit, and the position it was read from.
99
43
  */
100
- export function readFieldEntry(field: FieldSpec, fm: object, { block, onLegacyKey, onRetiredTopLevel }?: {
44
+ export function readFieldEntry(field: FieldSpec, fm: object, { block }?: {
101
45
  block?: string | undefined;
102
- onLegacyKey?: ((field: FieldSpec) => void) | undefined;
103
- onRetiredTopLevel?: ((field: FieldSpec) => void) | undefined;
104
46
  }): {
105
47
  value: any;
106
48
  from: import("./system-block.mjs").FieldSource;
@@ -137,18 +79,10 @@ export function isAuthored(from: import("./system-block.mjs").FieldSource, value
137
79
  * @param {string} [options.block="sohl"] - Which system's block the builder
138
80
  * reads. One declaration compiles against any block, which is what lets two
139
81
  * systems declare the same shared source and different destinations.
140
- * @param {(field: FieldSpec) => void} [options.onLegacyKey] - Passed through to
141
- * {@link readField}: called with each field the note authored at the position
142
- * it is being swept off.
143
- * @param {(field: FieldSpec) => void} [options.onRetiredTopLevel] - Passed
144
- * through to {@link readField}: called with each field the note authored at
145
- * the top-level key `data:` gathered it off.
146
82
  * @returns {(fm: object) => object} A `system`-block builder.
147
83
  */
148
- export function buildFromFields(fields: readonly FieldSpec[], { block, onLegacyKey, onRetiredTopLevel }?: {
84
+ export function buildFromFields(fields: readonly FieldSpec[], { block }?: {
149
85
  block?: string | undefined;
150
- onLegacyKey?: ((field: FieldSpec) => void) | undefined;
151
- onRetiredTopLevel?: ((field: FieldSpec) => void) | undefined;
152
86
  }): (fm: object) => object;
153
87
  /**
154
88
  * The fields of a declaration an author actually writes.
@@ -176,6 +110,7 @@ export function authoredFields(fields: readonly FieldSpec[]): FieldSpec[];
176
110
  * @returns {FieldSpec[]} Only the fields declaring `runtimeOnly`.
177
111
  */
178
112
  export function runtimeOnlyFields(fields: readonly FieldSpec[]): FieldSpec[];
113
+ export { setPath };
179
114
  /**
180
115
  * @typedef {object} FieldSpec
181
116
  * @property {string} to - Dotted path in the emitted `system` block — and,
@@ -190,29 +125,10 @@ export function runtimeOnlyFields(fields: readonly FieldSpec[]): FieldSpec[];
190
125
  * degenerate case where the shared source and the system destination happen
191
126
  * to share a name. They constantly do not — one shared `data.portrait` feeds
192
127
  * `sohl.system.portrait` *and* `hm3.system.bioImage` — so the source is
193
- * declared rather than matched by spelling. The in-block position is
194
- * still read, second, until the corpus moves off it — keyed on
195
- * `legacyKey` where the two spellings differ.
128
+ * declared rather than matched by spelling. A name that is not a path into
129
+ * `data:` is also read inside the system block, second.
196
130
  *
197
131
  * Absent means the value is not authored at all — see `value`.
198
- * @property {string} [legacyKey] - **The key this field is authored at inside
199
- * the system block** — the second position of the resolution order — when
200
- * that is not `name`. Absent, the position is keyed on `name`.
201
- *
202
- * As one property the two would hold only while a field's
203
- * shared source and its in-block key were the same word. `data:` ended
204
- * that: a shared source is a path into a container, so `data.species` and
205
- * `species` name two different places and no single value reached both.
206
- * `name: "species"` could not see `data.species`; `name: "data.species"`
207
- * could not see `hm3.species`; and each yielded the field's **default**
208
- * wherever only the other position was authored, with the note compiling and
209
- * the value simply gone.
210
- *
211
- * Declaring both restores the shape every other retirement in this package
212
- * uses — read both spellings, let the current one win, and *report* the
213
- * retiring one — so a field can move into `data:` while the corpus catches
214
- * up, instead of on a flag day across four repositories. A declaration that
215
- * names one is mid-sweep by construction; see {@link readsLegacyKey}.
216
132
  * @property {string} [topLevelMeans] - **Why the note's top-level key of this
217
133
  * name is not this field's source** — declared wherever that key's spelling
218
134
  * means something else at the note level, or the closed top-level
@@ -238,7 +154,7 @@ export function runtimeOnlyFields(fields: readonly FieldSpec[]): FieldSpec[];
238
154
  *
239
155
  * The exempted field is still authorable, at both of the positions that
240
156
  * describe the *document* rather than the note: `<system>.system.<to>` and
241
- * the legacy in-block `<system>.<name>`. Absent means the ordinary case —
157
+ * the in-block `<system>.<name>`. Absent means the ordinary case —
242
158
  * the top level is read, as the third step.
243
159
  *
244
160
  * **It is read from the other side too**, because the statement is symmetric:
@@ -340,7 +256,7 @@ export function runtimeOnlyFields(fields: readonly FieldSpec[]): FieldSpec[];
340
256
  * itself a valid world time, which is why such a field is nullable rather
341
257
  * than sentinelled and why a default cannot stand in for one.
342
258
  *
343
- * Nothing used to stop a note writing one. The three checks that might have
259
+ * Nothing else stops a note writing one. The three checks that might have
344
260
  * each declined for its own correct reason —
345
261
  * {@link module:engine/system-block.unknownBlockKeys} reads the block's top
346
262
  * level and never descends into `system:`;
@@ -439,34 +355,12 @@ export type FieldSpec = {
439
355
  * degenerate case where the shared source and the system destination happen
440
356
  * to share a name. They constantly do not — one shared `data.portrait` feeds
441
357
  * `sohl.system.portrait` *and* `hm3.system.bioImage` — so the source is
442
- * declared rather than matched by spelling. The in-block position is
443
- * still read, second, until the corpus moves off it — keyed on
444
- * `legacyKey` where the two spellings differ.
358
+ * declared rather than matched by spelling. A name that is not a path into
359
+ * `data:` is also read inside the system block, second.
445
360
  *
446
361
  * Absent means the value is not authored at all — see `value`.
447
362
  */
448
363
  name?: string | undefined;
449
- /**
450
- * - **The key this field is authored at inside
451
- * the system block** — the second position of the resolution order — when
452
- * that is not `name`. Absent, the position is keyed on `name`.
453
- *
454
- * As one property the two would hold only while a field's
455
- * shared source and its in-block key were the same word. `data:` ended
456
- * that: a shared source is a path into a container, so `data.species` and
457
- * `species` name two different places and no single value reached both.
458
- * `name: "species"` could not see `data.species`; `name: "data.species"`
459
- * could not see `hm3.species`; and each yielded the field's **default**
460
- * wherever only the other position was authored, with the note compiling and
461
- * the value simply gone.
462
- *
463
- * Declaring both restores the shape every other retirement in this package
464
- * uses — read both spellings, let the current one win, and *report* the
465
- * retiring one — so a field can move into `data:` while the corpus catches
466
- * up, instead of on a flag day across four repositories. A declaration that
467
- * names one is mid-sweep by construction; see {@link readsLegacyKey}.
468
- */
469
- legacyKey?: string | undefined;
470
364
  /**
471
365
  * - **Why the note's top-level key of this
472
366
  * name is not this field's source** — declared wherever that key's spelling
@@ -493,7 +387,7 @@ export type FieldSpec = {
493
387
  *
494
388
  * The exempted field is still authorable, at both of the positions that
495
389
  * describe the *document* rather than the note: `<system>.system.<to>` and
496
- * the legacy in-block `<system>.<name>`. Absent means the ordinary case —
390
+ * the in-block `<system>.<name>`. Absent means the ordinary case —
497
391
  * the top level is read, as the third step.
498
392
  *
499
393
  * **It is read from the other side too**, because the statement is symmetric:
@@ -634,7 +528,7 @@ export type FieldSpec = {
634
528
  * itself a valid world time, which is why such a field is nullable rather
635
529
  * than sentinelled and why a default cannot stand in for one.
636
530
  *
637
- * Nothing used to stop a note writing one. The three checks that might have
531
+ * Nothing else stops a note writing one. The three checks that might have
638
532
  * each declined for its own correct reason —
639
533
  * {@link module :engine/system-block.unknownBlockKeys} reads the block's top
640
534
  * level and never descends into `system:`;
@@ -668,7 +562,4 @@ export type FieldSpec = {
668
562
  */
669
563
  describe: string;
670
564
  };
671
- import { legacyKeyOf } from "./system-block.mjs";
672
- import { retiredTopLevelKey } from "./system-block.mjs";
673
565
  import { setPath } from "./system-block.mjs";
674
- export { legacyKeyOf, retiredTopLevelKey, setPath };
@@ -96,45 +96,6 @@ export function buildFolderNoteIndex(folders: FolderNote[]): {
96
96
  * @returns {object} The Folder document.
97
97
  */
98
98
  export function folderDocument(folder: FolderNote, parent: FolderNote | null, documentType: string, stats: object): object;
99
- /**
100
- * Refuse a note that declares the retired `folder:` spelling.
101
- *
102
- * `folder:` named a compendium folder by the raw Foundry id declared in a
103
- * per-pack `*-folders.yaml`. Both halves are retired together: the id
104
- * spelling has nothing left to resolve against once the YAML is gone, and the
105
- * YAML has no reader once the spelling is refused.
106
- *
107
- * **Presence is the whole test.** An empty `folder:` — which parses as `null`
108
- * — is still the field, and a value that happens to match a folder note's id
109
- * is still the retired spelling. There is no value that makes writing it
110
- * correct, so the message says what to write instead rather than which value
111
- * to change.
112
- *
113
- * **Both positions**, because notes wrote it both ways: top-level, and inside
114
- * the `sohl:` block. Checking only the more common one is how a sweep leaves
115
- * a tail behind.
116
- *
117
- * Refused rather than ignored, on the pattern `package:` set: a retired field
118
- * left ignored reads to its author as though it still works — the note says
119
- * one thing and the build does another, and nothing says so.
120
- *
121
- * @param {object|null|undefined} fm - Parsed frontmatter, or nothing when it
122
- * could not be parsed.
123
- * @param {object} [options] - Options.
124
- * @param {string} [options.file] - The note's path, named in the message. Omit
125
- * it where the caller emits through a diagnostic, which puts the locator at
126
- * the start of the line already — repeating it prints the path twice.
127
- * @param {string} [options.absPath] - The note's file on disk, read only on
128
- * the failing path to locate the offending line and column. The position
129
- * rides on the thrown error as `position`, for a caller that emits a
130
- * diagnostic.
131
- * @returns {void}
132
- * @throws {Error} When the note declares the field.
133
- */
134
- export function assertNoDeclaredFolder(fm: object | null | undefined, { file, absPath }?: {
135
- file?: string | undefined;
136
- absPath?: string | undefined;
137
- }): void;
138
99
  /**
139
100
  * The note type a folder is authored as.
140
101
  *
@@ -191,7 +191,8 @@ export function fetchMetadataFromPath(config: object, rel: {
191
191
  * Fetch every declared dependency's content index.
192
192
  *
193
193
  * A wider set than {@link fetchAllCatalogs}: an index is fetched for *every*
194
- * dependency, a catalogue only for those declaring `itemCatalog: true`. See
194
+ * dependency, plus every other relationship declaring `itemCatalog: true`; a
195
+ * catalogue only for those declaring `itemCatalog: true`. See
195
196
  * {@link metadataRelationships} for why the two sets differ.
196
197
  *
197
198
  * @param {object} config - The resolved build configuration.
@@ -44,11 +44,10 @@ export function entriesForNote(fm: object, name: string, address: string, body:
44
44
  * Every note this package publishes, as manifest entries.
45
45
  *
46
46
  * Every note in the tree is this package's, so nothing here selects by package:
47
- * the key's first segment is `contentPackage`. A note still declaring the
48
- * retired `package:` or `draft:` field **throws** rather than being skipped —
49
- * skipping one silently is how a whole tree came to be filtered out of a
50
- * manifest that then claimed the package published nothing, and it is what let
51
- * a drafted note's inbound links look like links to a note that never existed.
47
+ * the key's first segment is `contentPackage`. A note declaring `package:`
48
+ * **throws** rather than being skipped — skipping one silently is how a whole
49
+ * tree came to be filtered out of a manifest that then claimed the package
50
+ * published nothing.
52
51
  *
53
52
  * A note that has no address is **reported, not guessed** — the finding carries
54
53
  * the file and the reason, so a caller can print it or fail on it. Inventing an
@@ -112,10 +112,10 @@ export function lintFrontmatter(index: object, { schemas, vocabulary, packs, emi
112
112
  * declaration and both would otherwise be reported as unknown on every note
113
113
  * that uses them.
114
114
  *
115
- * - `folder` — the compendium folder the document is filed in.
115
+ * - `packFolder` — the folder note the document is filed in.
116
116
  * - `pack` — which compendium of the note's document type receives it, for a
117
117
  * repository shipping several.
118
- * - `archetype` — the archetype flag, read for any note by the walk itself.
118
+ * - `templatePriority` — which of several templates the Create dialog offers.
119
119
  * - `kbcat` — the knowledgebase category a note is grouped under.
120
120
  *
121
121
  * `kbcat` is the one that matters for what this check *is*. **The pack build is
@@ -26,44 +26,33 @@ export function sohlField(fm: object | null | undefined, key: string, defaultVal
26
26
  * `relations` and a projectile's impact die — because each validates a *shape
27
27
  * spread over several keys* rather than coercing one scalar.
28
28
  *
29
- * That was equivalent while every note authored inside the block, and stopped
30
- * being so once a note may author at `sohl.system.<to>` instead: {@link
31
- * sohlField} sees `sohl.<key>` and the top level, never inside `sohl.system`.
32
- * So those five read as unset and shipped their empty value — a missing
33
- * `subType` is a thrown build error, and the other four ship empty in silence,
34
- * which is the failure class the passthrough exists to prevent.
29
+ * {@link sohlField} sees `sohl.<key>` and the top level, never inside
30
+ * `sohl.system`, so this reads the destination first: a note may author at
31
+ * `sohl.system.<to>`, and a reader that could not see it would ship the
32
+ * field's empty value in silence.
35
33
  *
36
34
  * The **destination wins**, matching every other field's resolution order.
37
35
  *
38
- * `legacyKey` is for the one pair whose two positions are spelled differently:
39
- * a projectile authors `impact.die` and stores `impactBase.die`, so the reader
40
- * has to be told both. It is the same split `FieldSpec.name`/`legacyKey` makes,
41
- * for the same reason — one name cannot key two positions.
42
- *
43
- * `shared` is the field's declared shared source, and it completes the order the
44
- * three other positions are two thirds of. A field whose source is a path into
45
- * `data:` is authored there by the specification's own mapping tables, and a
46
- * reader that knew the destination, the block and the top level alone skipped
47
- * straight past the container: every authored value read as unset, and the
48
- * document shipped the field's default with nothing said. It sits **between**
49
- * the block and the top level, exactly where
50
- * {@link module:engine/system-block.resolveFieldValue} puts it — the block is the
51
- * position a tree still writes and wins while it does, and the bare top-level
52
- * key is the spelling `data:` gathered the fact off.
36
+ * `name` is for the one pair whose two positions are spelled differently: a
37
+ * projectile authors `impact.die` and stores `impactBase.die`, so the reader
38
+ * has to be told both.
39
+ *
40
+ * `shared` is the field's declared shared source when it is a path into
41
+ * `data:`. Such a field is authored in that container and nowhere else, so the
42
+ * block and the top level are not consulted.
53
43
  *
54
44
  * @param {object} fm - The note's frontmatter.
55
45
  * @param {string} to - The key at the destination, dotted for a nested one.
56
46
  * @param {any} [defaultValue] - What an unauthored field reads as.
57
47
  * @param {object} [options] - Options.
58
- * @param {string} [options.legacyKey] - The key the block still carries, when
59
- * it is not spelled `to`. Defaults to `to`.
48
+ * @param {string} [options.name] - The key the block carries, when it is not
49
+ * spelled `to`. Defaults to `to`.
60
50
  * @param {string} [options.shared] - The field's declared shared source, when it
61
- * is a path into a container (`data.relations`). Omitted by a field whose
62
- * shared position is the note's top level, which `sohlField` already reads.
51
+ * is a path into a container (`data.relations`).
63
52
  * @returns {any} The value.
64
53
  */
65
- export function sohlSystemField(fm: object, to: string, defaultValue?: any, { legacyKey, shared }?: {
66
- legacyKey?: string | undefined;
54
+ export function sohlSystemField(fm: object, to: string, defaultValue?: any, { name, shared }?: {
55
+ name?: string | undefined;
67
56
  shared?: string | undefined;
68
57
  }): any;
69
58
  /**
@@ -76,9 +65,6 @@ export function sohlSystemField(fm: object, to: string, defaultValue?: any, { le
76
65
  * so absent frontmatter must resolve to `null` — coercing it to `0` would ship
77
66
  * every item as an uncapped charge-user.
78
67
  *
79
- * A legacy `usesCharges` flag in authored frontmatter is ignored: it was inert
80
- * and has been dropped from the schema.
81
- *
82
68
  * @param {object} fm - The item frontmatter.
83
69
  * @returns {{value: number|null, max: number|null}} The persisted charges block.
84
70
  */
@@ -108,7 +94,7 @@ export function resolveCharges(fm: object): {
108
94
  */
109
95
  export function resolveSkillAptitudes(fm: object, ctx?: string): Record<string, number>;
110
96
  /**
111
- * Resolve an affiliation's `relation` map — the shortcode of another
97
+ * Resolve an affiliation's `relations` map — the shortcode of another
112
98
  * affiliation → this one's standing toward it.
113
99
  *
114
100
  * An unrecognized standing would fail the schema's `choices` validation at load
@@ -159,20 +145,8 @@ export function parseValueDesc(raw: unknown): Array<{
159
145
  *
160
146
  * `data.packFolder` is a folder note's **address**
161
147
  * (`folder-poisonsandtoxins`), resolved through the address index the whole
162
- * build shares. The `folder:` Foundry-id spelling this function once read
163
- * beside it, and the per-pack `*-folders.yaml` that id was resolved against,
164
- * are retired together — a note declaring `folder:` is refused by
165
- * {@link module:engine/folder-notes.assertNoDeclaredFolder} rather than
166
- * reaching here, so there is no second source for a value to come from.
167
- *
168
- * **`packFolder` was a path for one release** (`Possessions/Misc_Gear/Cooking`)
169
- * and is an address now. A path encoded the hierarchy *in the value*, so
170
- * reparenting a folder made every note naming it wrong — a structural edit
171
- * became a corpus-wide rewrite. An address is stable under reparenting, which
172
- * is exactly why a note is addressed by `(type, shortcode)` and never by
173
- * `file.path`. The path form is **removed rather than deprecated**: it had no
174
- * authors to migrate, which is the whole reason the change was cheap enough to
175
- * make.
148
+ * build shares. An address is stable under reparenting, which is exactly why a
149
+ * note is addressed by `(type, shortcode)` and never by `file.path`.
176
150
  *
177
151
  * @param {object|null|undefined} fm - Parsed frontmatter.
178
152
  * @returns {{value: string|null, isAddress: boolean}} The authored value, and
@@ -58,7 +58,7 @@ export function compilerFor(docType: string, system?: string | null): Function |
58
58
  * @param {string} type - The note's content type.
59
59
  * @returns {{document: string|null, art: readonly string[]}|null} What the type
60
60
  * compiles into and the art it carries there, or `null` where no claim can be
61
- * made — a retired type, which is reported as retired instead.
61
+ * made.
62
62
  */
63
63
  export function emittedArtFor(type: string): {
64
64
  document: string | null;
@@ -52,7 +52,7 @@ export function parseMarkdownFile(filePath: string, { addressContext }?: {
52
52
  *
53
53
  * The rule in one place, so every reader of the tree refuses the same
54
54
  * way and says so in the same words. It is shared rather than repeated because
55
- * the corpus is no longer read only by {@link walkMarkdownTree}: a pass that
55
+ * the corpus is read by more than {@link walkMarkdownTree}: a pass that
56
56
  * reads the content index instead is making the identical claim about which
57
57
  * files it is looking at, and must be held to the identical requirement — a
58
58
  * scope that quietly defaulted there would reintroduce exactly the second
@@ -130,12 +130,11 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories, addressCont
130
130
  * The template priority a note states, for a system that treats an unstated one
131
131
  * as "not a template" rather than as an authoring error.
132
132
  *
133
- * Reads exactly the positions {@link resolveTemplatePriority} reads, including
134
- * the retiring `archetype` spelling, and refuses the same contradiction — so
135
- * the two systems cannot disagree about what a note said. It differs only in
136
- * what silence means: SoHL requires the statement, while HM3 keeps the value in
137
- * a flag it simply omits, so there is no tri-state for an absent value to
138
- * corrupt and nothing to demand.
133
+ * Reads exactly the positions {@link resolveTemplatePriority} reads, so the two
134
+ * systems cannot disagree about what a note said. It differs only in what
135
+ * silence means: SoHL requires the statement, while HM3 keeps the value in a
136
+ * flag it simply omits, so there is no tri-state for an absent value to corrupt
137
+ * and nothing to demand.
139
138
  *
140
139
  * @param {object} fm Parsed frontmatter.
141
140
  * @param {string} label Human-readable context for error messages.
@@ -143,7 +142,6 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories, addressCont
143
142
  * @param {string} [options.block="sohl"] The system block being compiled.
144
143
  * @returns {number|null} The priority, or `null` when the note is not a
145
144
  * template or states nothing.
146
- * @throws {Error} When both spellings are present and disagree.
147
145
  */
148
146
  export function statedTemplatePriority(fm: object, label: string, { block }?: {
149
147
  block?: string | undefined;
@@ -158,23 +156,21 @@ export function statedTemplatePriority(fm: object, label: string, { block }?: {
158
156
  * silently assumed.
159
157
  *
160
158
  * Reads the positions {@link findTemplatePriority} lists: `data.templatePriority`
161
- * first — the specified home — then the system block and the top level, and
162
- * finally the retiring `archetype` spelling in the same two places.
159
+ * first — the specified home — then the system block and the top level.
163
160
  *
164
161
  * @param {object} fm Parsed frontmatter.
165
162
  * @param {string} label Human-readable context for error messages.
166
163
  * @param {object} [options] Options.
167
164
  * @param {string} [options.block="sohl"] The system block being compiled.
168
165
  * @returns {number|undefined} The template priority, or `undefined` when null.
169
- * @throws {Error} When the property is absent, is not a number/null, or both
170
- * spellings are present and disagree.
166
+ * @throws {Error} When the property is absent or is not a number/null.
171
167
  */
172
168
  export function resolveTemplatePriority(fm: object, label: string, { block }?: {
173
169
  block?: string | undefined;
174
170
  }): number | undefined;
175
171
  /**
176
172
  * The value a document's `system.templatePriority` carries, from the required
177
- * `templatePriority` frontmatter (`archetype` is the legacy spelling).
173
+ * `templatePriority` frontmatter.
178
174
  *
179
175
  * A **schema field**, so the tri-state is written out in full rather than
180
176
  * expressed by a key's presence: a number is a template at that priority, and
@@ -211,15 +207,11 @@ export function makeFilename(name: string, id: string): string;
211
207
  * and nothing else, and because the default a caller applies to an unset one is
212
208
  * domain-specific — actors default differently from items, and gear differently
213
209
  * again — so each compiler owns its default and applies it with **nullish**
214
- * coalescing: `resolveImg(fm.img) ?? <default>`. Not `||`, which would collapse
215
- * a deliberate blank back into the default.
210
+ * coalescing: `resolveImg(fm.img) ?? <default>`.
216
211
  *
217
- * **Two empties, and they mean opposite things.** `null` — or an absent key,
218
- * which reaches here as `undefined` — means _unset_: the note names no art and
219
- * the caller's default applies. `""` means _blank on purpose_: the note names
220
- * no art **and wants none**, so no default may replace it. Both come back
221
- * distinguishable, `null` and `""` respectively, and neither is invented from
222
- * the other.
212
+ * `null` — or an absent key, which reaches here as `undefined` — means _unset_:
213
+ * the note names no art and the caller's default applies. An empty string is
214
+ * not a pathname and is refused like any other value that names no file.
223
215
  *
224
216
  * **`title` does not follow this rule**, and must not be made to. On a
225
217
  * `type: affiliation` note `title` is a declared item field with its own
@@ -242,11 +234,11 @@ export function makeFilename(name: string, id: string): string;
242
234
  * @param {string | null | undefined} raw - The pathname from frontmatter.
243
235
  * @param {{assetRoot: string|null}} [config] - The resolved build configuration.
244
236
  * Defaults to this repository's.
245
- * @returns {string | null} the Foundry-relative path; `""` for a deliberate
246
- * blank, and `null` when the note names no art at all.
247
- * @throws {Error} When no Foundry address can be derived — a `documentation`
248
- * package, which Foundry installs nothing of, or a pathname naming a package
249
- * this build declares no relationship with.
237
+ * @returns {string | null} the Foundry-relative path, and `null` when the
238
+ * note names no art at all.
239
+ * @throws {Error} When the pathname is empty, or when no Foundry address can
240
+ * be derived — a `documentation` package, which Foundry installs nothing of,
241
+ * or a pathname naming a package this build declares no relationship with.
250
242
  */
251
243
  export function resolveImg(raw: string | null | undefined, config?: {
252
244
  assetRoot: string | null;
@@ -269,10 +261,8 @@ export function resolveName(fm: object | null | undefined, defaultValue?: string
269
261
  * every v14 build and so left all shipped content permanently eligible for
270
262
  * every v14 migration.
271
263
  *
272
- * **Read from the configuration, not from the shipped manifest.** It used to
273
- * open `paths.packageManifest` and take `compatibility.minimum` out of it,
274
- * because the configuration had no way to state the fact. Now that it does, the
275
- * manifest is generated *from* the configuration — so reading it back would be
264
+ * **Read from the configuration, not from the shipped manifest.** The
265
+ * manifest is generated *from* the configuration, so reading it back would be
276
266
  * a round trip through an artifact that need not exist yet: `build:db` can run
277
267
  * before the manifest is written.
278
268
  *
@@ -293,9 +283,7 @@ export function supportedCoreVersion(config?: {
293
283
  /**
294
284
  * Default `_stats` block for compiled compendium entries.
295
285
  *
296
- * Every stamped identity is configuration: four compilers used to pass
297
- * the same frozen `"0.6.0"` literal, and `systemId` / `lastModifiedBy` were
298
- * written into this function. `coreVersion` alone is *not* configuration — it
286
+ * Every stamped identity is configuration. `coreVersion` alone is *not* configuration — it
299
287
  * comes from {@link supportedCoreVersion}, the configured Foundry floor,
300
288
  * so a document never claims to predate the migrations that would rewrite it.
301
289
  *
@@ -344,11 +332,9 @@ export function statsForPack(packSystem: string | null | undefined, config?: obj
344
332
  /**
345
333
  * The `_stats` block every compiler stamps on an entry it emits, built once.
346
334
  *
347
- * Each compiler used to hoist `const STATS = buildStats()` at module scope,
348
- * which read the shipped package manifest the moment the module was imported —
349
- * so importing a compiler required a manifest to exist even when nothing was
350
- * going to be compiled. Deferred to first use and memoised here, the
351
- * cost and the identity are what they always were; only the moment moved.
335
+ * Built on first use and memoised, not at module scope: building it reads the
336
+ * shipped package manifest, and importing a compiler must not require a
337
+ * manifest to exist when nothing is going to be compiled.
352
338
  *
353
339
  * @returns {object} The default `_stats` block, shared by every compiler.
354
340
  */
@@ -364,10 +350,7 @@ export function defaultStats(): object;
364
350
  * would otherwise address every one of them as the first. A note whose
365
351
  * declaration is unroutable is indexed against the conventional name and left
366
352
  * for the compile pass to report — the index has no business failing a build,
367
- * and the pass fails it with a far better message. The one exception is a
368
- * **retired** content type: this walk is the first to see every
369
- * note together with its path, and unlike an unroutable declaration there is
370
- * no pass that would ever claim such a note and report it.
353
+ * and the pass fails it with a far better message.
371
354
  *
372
355
  * @param {string} contentBase - Root of the content tree.
373
356
  * @param {object} [router] - The pack router. Supplied by the calling pass so
@@ -25,8 +25,7 @@ export function isHomepage(fm: object | null | undefined): boolean;
25
25
  *
26
26
  * Each finding carries the `locator` key to position it at, because the two
27
27
  * things that would resolve one — the raw note text and the position helper —
28
- * belong to the caller. This mirrors {@link module:engine/retired-fields},
29
- * whose retired-field messages are likewise positioned by whoever reports them.
28
+ * belong to the caller.
30
29
  *
31
30
  * **A refused field must be one the note *wrote*.** `resolveNoteId` fills
32
31
  * `fm.id` **in place** so every downstream reader sees one derived value —
@@ -134,8 +133,7 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
134
133
  *
135
134
  * The note's own, plus the two derived values every emitted page carries: the
136
135
  * resolved `title`, and the package the build derived — no note declares one
137
- * (`package:` is retired) and the theme's breadcrumb partial reads
138
- * `.Params.package`.
136
+ * and the theme's breadcrumb partial reads `.Params.package`.
139
137
  *
140
138
  * **No `url` and no `slug`.** Hugo publishes the `home` kind at `baseURL`,
141
139
  * whose path is already where the package is served, so the page has no
@@ -144,8 +142,7 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
144
142
  *
145
143
  * An authored `aliases` is dropped for the same reason it is on every other
146
144
  * page: Hugo reads it as URL redirects, so passing it through would publish a
147
- * redirect stub at each one. The field is retired and refused before a
148
- * build reaches here, which makes this a guard rather than a working path.
145
+ * redirect stub at each one.
149
146
  *
150
147
  * `related` is derived like every other page's — what links to the homepage
151
148
  * is a fact about the rest of the tree — so an authored value is dropped and
@@ -251,10 +248,6 @@ export const HOMEPAGE_DESTINATION: string;
251
248
  * closed vocabulary, so repeating that judgement per type would be a second
252
249
  * answer to one question.
253
250
  *
254
- * `aliases` is deliberately not in the class: it is a **retired** field, refused
255
- * on every note whatever its type, so it is answered there rather than
256
- * here.
257
- *
258
251
  * @type {ReadonlyMap<string, string>}
259
252
  */
260
253
  export const HOMEPAGE_REFUSED_FIELDS: ReadonlyMap<string, string>;