@honua/mcp-server 0.1.14 → 0.1.15

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 (79) hide show
  1. package/README.md +12 -3
  2. package/certification/geospatial-mcp-schemas/PROVENANCE.md +14 -8
  3. package/certification/geospatial-mcp-schemas/common/controls.schema.json +81 -0
  4. package/certification/geospatial-mcp-schemas/common/exploration.schema.json +165 -0
  5. package/certification/geospatial-mcp-schemas/common/interactions.schema.json +263 -0
  6. package/certification/geospatial-mcp-schemas/index.json +155 -4
  7. package/certification/geospatial-mcp-schemas/resources/app-package.schema.json +17 -1
  8. package/certification/geospatial-mcp-schemas/resources/map-package.schema.json +17 -1
  9. package/certification/geospatial-mcp-schemas/resources/ops-findings.schema.json +61 -0
  10. package/certification/geospatial-mcp-schemas/resources/ops-health.schema.json +138 -0
  11. package/certification/geospatial-mcp-schemas/tools/add_control.schema.json +107 -0
  12. package/certification/geospatial-mcp-schemas/tools/alert_events.result.schema.json +49 -0
  13. package/certification/geospatial-mcp-schemas/tools/alert_events.schema.json +49 -0
  14. package/certification/geospatial-mcp-schemas/tools/bind_component_context.schema.json +60 -0
  15. package/certification/geospatial-mcp-schemas/tools/bind_interaction.schema.json +97 -0
  16. package/certification/geospatial-mcp-schemas/tools/create_exploration_context.schema.json +35 -0
  17. package/certification/geospatial-mcp-schemas/tools/deploy_operations.result.schema.json +109 -0
  18. package/certification/geospatial-mcp-schemas/tools/deploy_operations.schema.json +49 -0
  19. package/certification/geospatial-mcp-schemas/tools/operate_events.result.schema.json +67 -0
  20. package/certification/geospatial-mcp-schemas/tools/operate_events.schema.json +46 -0
  21. package/certification/geospatial-mcp-schemas/tools/ops_findings.schema.json +25 -0
  22. package/certification/geospatial-mcp-schemas/tools/ops_health.schema.json +10 -0
  23. package/certification/geospatial-mcp-schemas/tools/platform_release_status.result.schema.json +53 -0
  24. package/certification/geospatial-mcp-schemas/tools/platform_release_status.schema.json +10 -0
  25. package/certification/geospatial-mcp-schemas/tools/propose_operation.schema.json +7 -2
  26. package/certification/geospatial-mcp-schemas/tools/propose_rollback.schema.json +29 -0
  27. package/certification/geospatial-mcp-schemas/tools/remove_control.schema.json +114 -0
  28. package/certification/geospatial-mcp-schemas/tools/remove_exploration_context.schema.json +42 -0
  29. package/certification/geospatial-mcp-schemas/tools/remove_interaction.schema.json +99 -0
  30. package/dist/src/certification/certifier.d.ts +17 -2
  31. package/dist/src/certification/certifier.d.ts.map +1 -1
  32. package/dist/src/certification/certifier.js +69 -11
  33. package/dist/src/certification/cli.js +4 -1
  34. package/dist/src/certification/lifecycle.d.ts +2 -0
  35. package/dist/src/certification/lifecycle.d.ts.map +1 -1
  36. package/dist/src/certification/lifecycle.js +3 -0
  37. package/dist/src/certification/operator-catalog.d.ts.map +1 -1
  38. package/dist/src/certification/operator-catalog.js +0 -12
  39. package/dist/src/certification/report.d.ts.map +1 -1
  40. package/dist/src/certification/report.js +2 -1
  41. package/dist/src/certification/roster.d.ts +99 -0
  42. package/dist/src/certification/roster.d.ts.map +1 -0
  43. package/dist/src/certification/roster.js +233 -0
  44. package/dist/src/certification/round-trip-target.d.ts +45 -0
  45. package/dist/src/certification/round-trip-target.d.ts.map +1 -0
  46. package/dist/src/certification/round-trip-target.js +122 -0
  47. package/dist/src/certification/run.d.ts.map +1 -1
  48. package/dist/src/certification/run.js +7 -0
  49. package/dist/src/eval/northstar-corpus.d.ts.map +1 -1
  50. package/dist/src/eval/northstar-corpus.js +12 -10
  51. package/dist/src/eval/operator-corpus.d.ts.map +1 -1
  52. package/dist/src/eval/operator-corpus.js +11 -6
  53. package/dist/src/eval/proposal-tools.d.ts +14 -0
  54. package/dist/src/eval/proposal-tools.d.ts.map +1 -0
  55. package/dist/src/eval/proposal-tools.js +20 -0
  56. package/dist/src/index.d.ts.map +1 -1
  57. package/dist/src/index.js +12 -3
  58. package/dist/src/neutral/query.js +1 -1
  59. package/dist/src/release/zero-to-map-cli.d.ts.map +1 -1
  60. package/dist/src/release/zero-to-map-cli.js +8 -2
  61. package/dist/src/release/zero-to-map-provision.d.ts +6 -0
  62. package/dist/src/release/zero-to-map-provision.d.ts.map +1 -1
  63. package/dist/src/release/zero-to-map-provision.js +6 -0
  64. package/dist/src/tools/apply-style-preset.d.ts +1 -1
  65. package/dist/src/tools/apply-style-preset.d.ts.map +1 -1
  66. package/dist/src/tools/apply-style-preset.js +4 -2
  67. package/dist/src/tools/control-plane-command.d.ts +1 -1
  68. package/dist/src/tools/control-plane-command.d.ts.map +1 -1
  69. package/dist/src/tools/control-plane-command.js +3 -2
  70. package/dist/src/tools/docs-search.d.ts +2 -2
  71. package/dist/src/tools/docs-search.d.ts.map +1 -1
  72. package/dist/src/tools/docs-search.js +15 -4
  73. package/dist/src/tools/query-features.d.ts +10 -1
  74. package/dist/src/tools/query-features.d.ts.map +1 -1
  75. package/dist/src/tools/query-features.js +16 -4
  76. package/package.json +2 -2
  77. package/release/zero-to-map/configs/claude-code.mcp.json +1 -1
  78. package/release/zero-to-map/configs/claude-desktop.json +1 -1
  79. package/release/zero-to-map/configs/cursor.json +1 -1
package/README.md CHANGED
@@ -113,6 +113,15 @@ The tool requires `confirm: true`. Stop and remove this bootstrap server from
113
113
  the MCP client configuration after installation; the normal platform-free
114
114
  server remains read-only and cannot invoke the installer.
115
115
 
116
+ Choose a new directory for each installation. The installer checks Docker Engine
117
+ and Compose before writing files, refuses existing compose/bootstrap files it
118
+ cannot recognize, and preserves unrelated keys in an installer-owned private
119
+ `.env`. New installations keep an isolated Compose project identity in that file;
120
+ keep `.env` with the installation to retain its credentials and volumes. Existing
121
+ legacy installations keep their original project after verifying that its
122
+ containers belong to the chosen directory. Restore the original files when that
123
+ check refuses a conflicting or incomplete legacy installation.
124
+
116
125
  ## Environment Variables
117
126
 
118
127
  - `HONUA_BASE_URL` (required in normal mode): absolute base URL — a public ArcGIS folder
@@ -136,12 +145,12 @@ All read-only:
136
145
  - `honua_list_sources` — **protocol-neutral discovery.** Returns `<protocol>:<address>` source references plus each source's real capabilities. Every protocol family degrades independently with a reason.
137
146
  - `honua_list_services` — GeoServices-specific service catalog (prefer `honua_list_sources`)
138
147
  - `honua_describe_layer` — source schema, protocol, and advertised capabilities
139
- - `honua_query_features`
148
+ - `honua_query_features` — includes geometry by default; `resultOffset` and `outSrid` are aliases for `offset` and `outSr`. Pass one spelling of each field. Count-only and geometry-precision inputs are refused; use `honua_count_features` for counts.
140
149
  - `honua_count_features`
141
150
  - `honua_get_extent`
142
151
  - `honua_statistics`
143
152
  - `honua_explain_capability_gap`
144
- - `honua_get_style`, `honua_apply_style_preset` — structured-unavailable on a plain FeatureServer
153
+ - `honua_get_style`, `honua_apply_style_preset` — structured-unavailable on a plain FeatureServer. Preset resolution is read-only and returns a stylesheet for client-side application; persisted `serviceId`/`layerId` binding arguments are refused.
145
154
  - `honua_docs_search` — answer a Honua documentation question from the local versioned docs corpus (`llms-full.txt`), with a source-file and release-version citation per hit. Each citation is pinned to the exact commit the corpus bytes come from, so it keeps naming the text that was searched after `trunk` moves; when that commit cannot be established (an installed package with no checkout, or a locally regenerated corpus) the citation reports `sourceRevision: null` and links to `trunk` rather than claiming a pin it cannot back. Offline: reads committed files, never the network, and reports structured-unavailable when the corpus is not present in the installation. Point `HONUA_DOCS_CORPUS_PATH` at a corpus file (any name) or at a directory holding `llms-full.txt` to supply the corpus explicitly; `HONUA_SOURCE_REVISION` declares the commit when git is unavailable.
146
155
 
147
156
  ### Protocol-neutral tool contract
@@ -260,7 +269,7 @@ Embedding the server in your own Node.js host needs the packages as local
260
269
  dependencies of that host project:
261
270
 
262
271
  ```bash
263
- npm install @honua/mcp-server@0.1.13 @honua/sdk-js@0.1.13
272
+ npm install @honua/mcp-server @honua/sdk-js
264
273
  ```
265
274
 
266
275
  The map runtime, model callback, approval verifier, receipt signer, and
@@ -5,16 +5,22 @@ machine-readable schemas from the open **geospatial-mcp** standard.
5
5
 
6
6
  - **Source repo:** https://github.com/honua-io/geospatial-mcp
7
7
  - **Source path:** `spec/schemas/`
8
- - **Source commit:** `eb53989cc61c856261cf017b4b5a8e721317dc41`
9
- (`feat: direct geoprocessing verbs (analysis profile) + geometryPrecision/maxInlineBytes (#55)`)
10
- - **Schema index date:** `2026-07-06`
8
+ - **Source commit:** `4fac81c2f93a110921bb62638bc5ab599ef5e626`
9
+ (`Declare release component versions for the nightly resolver (#92)`, trunk HEAD on 2026-10-08)
10
+ - **Schema index date:** `2026-08-12`
11
11
  - **Dialect:** JSON Schema draft 2020-12
12
12
 
13
- > The pin is deliberately held at `eb53989` (pre geospatial-mcp#58): the #58
14
- > platform-ops schemas are marked implemented in the manifest but honua-server
15
- > does not serve those tools yet, so re-vendoring past #58 would introduce
16
- > conformance failures. The post-#58 bump is owned by honua-server#2555/#2566,
17
- > which implement the new tools and vendor their schemas together.
13
+ > Re-vendored at trunk HEAD for the 2026.1 rc.3 certification fix (unit K2).
14
+ > The earlier hold at `eb53989` (pre geospatial-mcp#58) is over: honua-server
15
+ > now serves the #58 platform-ops tools (`honua_ops_health`, `honua_ops_findings`,
16
+ > `honua_alert_events`, `honua_operate_events`, `honua_platform_release_status`,
17
+ > `honua_deploy_operations`, `honua_propose_rollback`).
18
+ >
19
+ > Known upstream lag: this index still maps `propose_operation` to the retired
20
+ > `honua_propose_operation` as `implemented`. Marking it `known-gap` is
21
+ > geospatial-mcp unit K4, which had not landed at this commit. Until it does, the
22
+ > certifier records `propose_operation` as a standard-tool known gap (reference
23
+ > tool not advertised), which is recorded, not failed. Re-pin once K4 merges.
18
24
 
19
25
  ## Why vendored
20
26
 
@@ -0,0 +1,81 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://geospatial-mcp.honua.io/spec/schemas/common/controls.schema.json",
4
+ "title": "Composition controls",
5
+ "description": "Standard-owned `controls` collection for composition documents (MapPackage/AppPackage). A control is an input affordance the user operates; it emits the `change` event of the ADR-0030 interaction vocabulary and is the target a `control:{id}` reference resolves against. The kind vocabulary is closed — extending it requires a standard ADR — and deliberately excludes a feature-editing draw control (ADR-0028). Controls declare presentation and data binding only; behaviour is wired declaratively through the `interactions` block (common/interactions.schema.json). Controls never appear in `layout`: grid placement is restricted to `map` and `widget:{id}` by that schema's layoutRef. See docs/adr/0031-composition-controls.md.",
6
+ "type": "object",
7
+ "$defs": {
8
+ "controlKind": {
9
+ "type": "string",
10
+ "enum": [
11
+ "navigation",
12
+ "scale",
13
+ "fullscreen",
14
+ "geolocate",
15
+ "search",
16
+ "measure",
17
+ "timeSlider",
18
+ "filterSelect",
19
+ "filterSlider",
20
+ "filterDateRange",
21
+ "bookmarks",
22
+ "opacity",
23
+ "attribution",
24
+ "basemapSwitcher"
25
+ ],
26
+ "description": "Closed control vocabulary. Map affordances: navigation (zoom/pan/compass), scale, fullscreen, geolocate, attribution, basemapSwitcher, bookmarks, measure, search. Data-binding affordances that emit `change`: timeSlider, filterSelect, filterSlider, filterDateRange, opacity. There is deliberately no feature-editing draw kind — autonomous agent mutation of source records stays excluded (ADR-0028); an annotation-only draw control may be admitted later by its own ADR. Extending this set requires a standard ADR (ADR-0031)."
27
+ },
28
+ "control": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": [
32
+ "id",
33
+ "kind"
34
+ ],
35
+ "properties": {
36
+ "id": {
37
+ "type": "string",
38
+ "minLength": 1,
39
+ "maxLength": 200,
40
+ "description": "Stable identifier, unique within the document's controls block. `control:{id}` references in the **interactions** block resolve against this value; add_control replaces an existing control with the same id. Controls are deliberately NOT layout grammar — ADR-0031 treats them as chrome the host positions, and `layoutItem.ref` is constrained to `map` and `widget:{id}` so a control can never claim a grid cell."
41
+ },
42
+ "kind": {
43
+ "$ref": "#/$defs/controlKind",
44
+ "description": "What the control is. The kind fixes the affordance's semantics, not its skin — hosts render each kind in their own idiom."
45
+ },
46
+ "title": {
47
+ "type": "string",
48
+ "maxLength": 200,
49
+ "description": "Human-readable label shown by the host. Optional; hosts fall back to a per-kind default label."
50
+ },
51
+ "sourceId": {
52
+ "type": "string",
53
+ "minLength": 1,
54
+ "description": "Identifier of the layer or datasource the control reads its domain from (e.g. the layer whose field values populate a filterSelect, or the layer a timeSlider ranges over). Data-binding kinds normally set it; presentation-only kinds (navigation, scale, fullscreen, attribution) omit it. Resolution of the identifier within the document is a validation-gate responsibility, as for layer references."
55
+ },
56
+ "config": {
57
+ "type": "object",
58
+ "description": "Per-kind configuration (e.g. the field a filterSelect filters on, a slider's bounds and step, a bookmark list). Host-interpreted and intentionally open, matching the widget entry's `config`; it carries no expression language and grants no capability the composition surface does not already have."
59
+ },
60
+ "explorationContextId": {
61
+ "$ref": "exploration.schema.json#/$defs/componentBinding/properties/explorationContextId",
62
+ "description": "Exploration context this control joins (ADR-0032). A data-binding control bound to a context contributes its state to every component in that context, which is how a filter dropdown narrows peer widgets without an interactions edge per target."
63
+ },
64
+ "role": {
65
+ "$ref": "exploration.schema.json#/$defs/componentBinding/properties/role",
66
+ "description": "Role this control plays in its exploration context (ADR-0032); data-binding kinds normally bind as `filter`."
67
+ }
68
+ },
69
+ "description": "One control entry. The shape mirrors the composition widget entry ({ id, kind, title?, sourceId?, config? }) so agents author both collections the same way."
70
+ }
71
+ },
72
+ "properties": {
73
+ "controls": {
74
+ "type": "array",
75
+ "items": {
76
+ "$ref": "#/$defs/control"
77
+ },
78
+ "description": "Input affordances declared by this composition document. Optional and additive: a document without controls is valid. Ids must be unique within the collection (validation-gate check)."
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,165 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://geospatial-mcp.honua.io/spec/schemas/common/exploration.schema.json",
4
+ "title": "Composition exploration contexts",
5
+ "description": "Standard-owned `explorationContexts` collection for composition documents (MapPackage/AppPackage). An exploration context is a named unit of shared interaction state — filters, spatial filter, extent, selection, sort, page, visible fields, grouping, aggregation — that components bind to. Components that name the same context share that state, so cross-filtering is context membership rather than an event→action edge; this is the primary interaction model, and the `interactions` block (common/interactions.schema.json) is retained for one-shot gestures. Propagation between bound components is governed by a closed preset vocabulary keyed on the component's role. Contexts are data, never code: there is no expression language and no $event.* substitution here, because a context's state is not produced by an event. Reference resolution (context ids, source ids) is a validation-gate responsibility. See docs/adr/0032-shared-exploration-contexts.md.",
6
+ "type": "object",
7
+ "$defs": {
8
+ "presetName": {
9
+ "type": "string",
10
+ "enum": [
11
+ "globalLinked",
12
+ "mapDriven",
13
+ "gridDriven",
14
+ "chartDriven",
15
+ "decoupled"
16
+ ],
17
+ "description": "Closed linked-view preset vocabulary, naming which bound components propagate state outward. globalLinked: every bound component contributes and receives every shared slice. mapDriven: only the map's extent and selection propagate outward, though filter components still contribute filter state. gridDriven: only the grid's selection, sort, and page propagate outward. chartDriven: only the chart's grouping, aggregation, and selection propagate outward. decoupled: nothing propagates and each component is independent. Extending this set requires a standard ADR (ADR-0032)."
18
+ },
19
+ "viewRole": {
20
+ "type": "string",
21
+ "enum": [
22
+ "map",
23
+ "grid",
24
+ "chart",
25
+ "filter",
26
+ "detail",
27
+ "form",
28
+ "custom"
29
+ ],
30
+ "description": "Coarse role a bound component plays in its context. The preset's propagation policy is keyed on this value, not on the component's kind, so two chart widgets of different kinds behave identically when both are bound as `chart`. `form` is retained for compatibility with older detail/filter integrations; new documents should prefer the more precise `filter` and `detail` roles."
31
+ },
32
+ "explorationStateSnapshot": {
33
+ "type": "object",
34
+ "additionalProperties": true,
35
+ "required": [
36
+ "version"
37
+ ],
38
+ "properties": {
39
+ "version": {
40
+ "type": "integer",
41
+ "const": 1,
42
+ "description": "Snapshot format version. Pinned to 1: this ADR defines only the initial format and gives implementations no semantics for any later value, so admitting `version: 2` would produce a schema-valid document no runtime could interpret portably. Later versions join through explicit versioned branches in a follow-on ADR."
43
+ },
44
+ "filters": {
45
+ "type": "object",
46
+ "additionalProperties": true,
47
+ "description": "Shared filter clauses keyed by clause id."
48
+ },
49
+ "spatialFilter": {
50
+ "type": "object",
51
+ "additionalProperties": true,
52
+ "description": "Shared spatial filter (geometry plus relationship)."
53
+ },
54
+ "extent": {
55
+ "type": "object",
56
+ "additionalProperties": true,
57
+ "description": "Shared viewport extent."
58
+ },
59
+ "selection": {
60
+ "type": "array",
61
+ "items": {
62
+ "type": [
63
+ "string",
64
+ "number",
65
+ "object"
66
+ ]
67
+ },
68
+ "description": "Shared feature selection. Raw ids are permitted for single-source documents; multi-source documents should use source-qualified targets."
69
+ },
70
+ "sort": {
71
+ "type": "array",
72
+ "items": {
73
+ "type": "object",
74
+ "additionalProperties": true
75
+ },
76
+ "description": "Shared sort order."
77
+ },
78
+ "page": {
79
+ "type": "object",
80
+ "additionalProperties": true,
81
+ "description": "Shared pagination offset and limit."
82
+ },
83
+ "visibleFields": {
84
+ "type": "array",
85
+ "items": {
86
+ "type": "string"
87
+ },
88
+ "description": "Shared field visibility for tabular and form components. An empty array means all fields."
89
+ },
90
+ "grouping": {
91
+ "type": "array",
92
+ "items": {
93
+ "type": "string"
94
+ },
95
+ "description": "Shared group-by keys for chart and pivot components."
96
+ },
97
+ "aggregation": {
98
+ "type": "object",
99
+ "additionalProperties": true,
100
+ "description": "Shared aggregation specification."
101
+ }
102
+ },
103
+ "description": "Optional versioned snapshot of a context's shared state at document load. Static JSON only — the same data-never-code posture ADR-0030 established for interaction arguments, minus the $event.* substitution, which has no meaning outside an event payload."
104
+ },
105
+ "explorationContext": {
106
+ "type": "object",
107
+ "additionalProperties": false,
108
+ "required": [
109
+ "id"
110
+ ],
111
+ "properties": {
112
+ "id": {
113
+ "type": "string",
114
+ "minLength": 1,
115
+ "maxLength": 200,
116
+ "description": "Stable identifier, unique within the document's explorationContexts block. A component's explorationContextId resolves against this value; create_exploration_context replaces an existing context with the same id."
117
+ },
118
+ "sourceIds": {
119
+ "type": "array",
120
+ "items": {
121
+ "type": "string",
122
+ "minLength": 1
123
+ },
124
+ "description": "Identifiers of the sources whose records this context's shared state addresses. Each must resolve within the document; resolution is a validation-gate responsibility, as for layer references."
125
+ },
126
+ "preset": {
127
+ "$ref": "#/$defs/presetName",
128
+ "default": "globalLinked",
129
+ "description": "Propagation policy for components bound to this context. Defaults to globalLinked, which is the behaviour an author means by 'link these together'."
130
+ },
131
+ "initialState": {
132
+ "$ref": "#/$defs/explorationStateSnapshot",
133
+ "description": "Optional starting shared state. Omit it and the context starts empty."
134
+ }
135
+ },
136
+ "description": "One exploration context. Components bind to it by id; components sharing an id share its state."
137
+ },
138
+ "componentBinding": {
139
+ "type": "object",
140
+ "additionalProperties": true,
141
+ "properties": {
142
+ "explorationContextId": {
143
+ "type": "string",
144
+ "minLength": 1,
145
+ "maxLength": 200,
146
+ "description": "Identifier of the exploration context this component binds to. It must resolve against the document's explorationContexts block (validation-gate check). A component binds to at most one context; omitting the property leaves the component unbound and independent."
147
+ },
148
+ "role": {
149
+ "$ref": "#/$defs/viewRole",
150
+ "description": "The role this component plays in its context, which the preset's propagation policy is keyed on. Meaningful only alongside explorationContextId."
151
+ }
152
+ },
153
+ "description": "The two optional properties a bindable component (the map, or an entry in layers, widgets, or controls) carries to join an exploration context. Cross-filtering is identity of explorationContextId: there is no edge to author, no ordering between bindings, and no fan-out to cap."
154
+ }
155
+ },
156
+ "properties": {
157
+ "explorationContexts": {
158
+ "type": "array",
159
+ "items": {
160
+ "$ref": "#/$defs/explorationContext"
161
+ },
162
+ "description": "Units of shared interaction state declared by this composition document. Optional and additive: a document without exploration contexts is valid, and one whose components are all unbound behaves exactly as it did before this block existed. Ids must be unique within the collection, and a declared context with no bound components is valid but should warn (validation-gate checks)."
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,263 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://geospatial-mcp.honua.io/spec/schemas/common/interactions.schema.json",
4
+ "title": "Composition interactions and layout",
5
+ "description": "Standard-owned declarative blocks for composition documents (MapPackage/AppPackage): `interactions` wires component events to bounded action verbs; `layout` places widgets on a grid. Bindings are data, never code: the only dynamic element is $event.* path substitution in string argument values. Actions never emit events (only user gestures do), so binding graphs cannot cycle by construction. Reference resolution and the per-(ref,event) fan-out cap (recommended 8) are validation-gate responsibilities. See docs/adr/0030-declarative-interactions-and-layout.md.",
6
+ "type": "object",
7
+ "$defs": {
8
+ "componentRef": {
9
+ "type": "string",
10
+ "pattern": "^(map|layer:.+|widget:.+|control:.+)$",
11
+ "description": "Reference to a component declared in the same composition document: `map`, `layer:{id}`, `widget:{id}`, or `control:{id}`. Must resolve within the document (validation-gate check, not a schema check)."
12
+ },
13
+ "eventName": {
14
+ "type": "string",
15
+ "enum": [
16
+ "featureSelect",
17
+ "featureHover",
18
+ "selection",
19
+ "change",
20
+ "viewportChange"
21
+ ],
22
+ "description": "Closed event set: featureSelect/featureHover (layer sources), selection (widget sources), change (control sources), viewportChange (map source). Extending the set requires a standard ADR (ADR-0030)."
23
+ },
24
+ "actionVerb": {
25
+ "type": "string",
26
+ "enum": [
27
+ "setFilter",
28
+ "setViewport",
29
+ "selectFeature",
30
+ "runWidgetQuery",
31
+ "setVisibility"
32
+ ],
33
+ "description": "Closed verb set drawn from the existing composition vocabulary. Verbs mutate presentation/exploration state only, never source records (ADR-0028 unaffected). Extending the set requires a standard ADR (ADR-0030)."
34
+ },
35
+ "interaction": {
36
+ "type": "object",
37
+ "additionalProperties": false,
38
+ "required": [
39
+ "id",
40
+ "on",
41
+ "do"
42
+ ],
43
+ "properties": {
44
+ "id": {
45
+ "type": "string",
46
+ "minLength": 1,
47
+ "maxLength": 200,
48
+ "description": "Stable identifier, unique within the document's interactions block. bind_interaction replaces an existing interaction with the same id."
49
+ },
50
+ "on": {
51
+ "$ref": "#/$defs/interactionEvent"
52
+ },
53
+ "do": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": [
57
+ "ref",
58
+ "verb"
59
+ ],
60
+ "properties": {
61
+ "ref": {
62
+ "$ref": "#/$defs/componentRef"
63
+ },
64
+ "verb": {
65
+ "$ref": "#/$defs/actionVerb"
66
+ },
67
+ "args": {
68
+ "type": "object",
69
+ "description": "Static JSON arguments for the verb. A string value beginning with `$event.` is replaced at dispatch time by the value at that path in the event payload (e.g. $event.featureId, $event.value, $event.bbox). No expression language: no arithmetic, no conditionals, no function calls."
70
+ }
71
+ },
72
+ "description": "The action. Verb-driven state changes MUST NOT re-enter the interaction dispatcher (actions never emit events)."
73
+ },
74
+ "disabled": {
75
+ "type": "boolean",
76
+ "default": false,
77
+ "description": "Authored-but-inactive binding; retained in the document, skipped at dispatch."
78
+ }
79
+ }
80
+ },
81
+ "layoutItem": {
82
+ "type": "object",
83
+ "additionalProperties": false,
84
+ "required": [
85
+ "ref",
86
+ "x",
87
+ "y",
88
+ "w",
89
+ "h"
90
+ ],
91
+ "properties": {
92
+ "ref": {
93
+ "$ref": "#/$defs/layoutRef"
94
+ },
95
+ "x": {
96
+ "type": "integer",
97
+ "minimum": 0,
98
+ "description": "Grid column of the item's left edge (0-based)."
99
+ },
100
+ "y": {
101
+ "type": "integer",
102
+ "minimum": 0,
103
+ "description": "Grid row of the item's top edge (0-based)."
104
+ },
105
+ "w": {
106
+ "type": "integer",
107
+ "minimum": 1,
108
+ "description": "Width in grid columns."
109
+ },
110
+ "h": {
111
+ "type": "integer",
112
+ "minimum": 1,
113
+ "description": "Height in grid rows."
114
+ }
115
+ },
116
+ "description": "Grid placement for one widget (or the map). The ref must resolve within the document — same validation gate as interactions."
117
+ },
118
+ "layoutRef": {
119
+ "type": "string",
120
+ "pattern": "^(map|widget:.+)$",
121
+ "description": "Reference to a component that can be placed on the layout grid: `map` or `widget:{id}`. Layers and controls are deliberately excluded — a layer is drawn inside the map and a control is an affordance the host positions, so neither has grid placement. This is narrower than `componentRef` on purpose; reusing that definition would let `layer:parcels` validate into a grid cell whose rendering is undefined."
122
+ },
123
+ "interactionEvent": {
124
+ "type": "object",
125
+ "additionalProperties": false,
126
+ "required": [
127
+ "ref",
128
+ "event"
129
+ ],
130
+ "properties": {
131
+ "ref": {
132
+ "$ref": "#/$defs/componentRef"
133
+ },
134
+ "event": {
135
+ "$ref": "#/$defs/eventName"
136
+ }
137
+ },
138
+ "allOf": [
139
+ {
140
+ "if": {
141
+ "properties": {
142
+ "ref": {
143
+ "const": "map"
144
+ }
145
+ },
146
+ "required": [
147
+ "ref"
148
+ ]
149
+ },
150
+ "then": {
151
+ "properties": {
152
+ "event": {
153
+ "const": "viewportChange"
154
+ }
155
+ }
156
+ },
157
+ "description": "The map emits only viewportChange."
158
+ },
159
+ {
160
+ "if": {
161
+ "properties": {
162
+ "ref": {
163
+ "pattern": "^layer:"
164
+ }
165
+ },
166
+ "required": [
167
+ "ref"
168
+ ]
169
+ },
170
+ "then": {
171
+ "properties": {
172
+ "event": {
173
+ "enum": [
174
+ "featureSelect",
175
+ "featureHover"
176
+ ]
177
+ }
178
+ }
179
+ },
180
+ "description": "Layers emit only featureSelect and featureHover."
181
+ },
182
+ {
183
+ "if": {
184
+ "properties": {
185
+ "ref": {
186
+ "pattern": "^widget:"
187
+ }
188
+ },
189
+ "required": [
190
+ "ref"
191
+ ]
192
+ },
193
+ "then": {
194
+ "properties": {
195
+ "event": {
196
+ "const": "selection"
197
+ }
198
+ }
199
+ },
200
+ "description": "Widgets emit only selection."
201
+ },
202
+ {
203
+ "if": {
204
+ "properties": {
205
+ "ref": {
206
+ "pattern": "^control:"
207
+ }
208
+ },
209
+ "required": [
210
+ "ref"
211
+ ]
212
+ },
213
+ "then": {
214
+ "properties": {
215
+ "event": {
216
+ "const": "change"
217
+ }
218
+ }
219
+ },
220
+ "description": "Controls emit only change."
221
+ }
222
+ ],
223
+ "description": "The event-source half of an interaction. The allOf branches encode the normative source mapping so a schema-valid binding cannot name an event its component kind never emits — previously `{ \"ref\": \"map\", \"event\": \"featureSelect\" }` validated despite having no defined dispatch semantics."
224
+ }
225
+ },
226
+ "properties": {
227
+ "interactions": {
228
+ "type": "array",
229
+ "items": {
230
+ "$ref": "#/$defs/interaction"
231
+ },
232
+ "description": "Declarative event→action bindings between components of this composition document."
233
+ },
234
+ "layout": {
235
+ "type": "object",
236
+ "additionalProperties": false,
237
+ "properties": {
238
+ "grid": {
239
+ "type": "object",
240
+ "additionalProperties": false,
241
+ "properties": {
242
+ "columns": {
243
+ "type": "integer",
244
+ "minimum": 1,
245
+ "maximum": 24,
246
+ "default": 12,
247
+ "description": "Number of grid columns; 12 is the default."
248
+ }
249
+ },
250
+ "description": "Grid geometry. Rows grow as needed; only columns are declared."
251
+ },
252
+ "items": {
253
+ "type": "array",
254
+ "items": {
255
+ "$ref": "#/$defs/layoutItem"
256
+ },
257
+ "description": "Widget placements. A composition with widgets and no layout is valid; hosts choose a default flow."
258
+ }
259
+ },
260
+ "description": "Presentation-only grid placement for the document's widgets."
261
+ }
262
+ }
263
+ }