@flui-cloud/semantic-surface 0.1.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/LICENSE +202 -0
- package/README.md +106 -0
- package/docs/agent-surface/semantic-surface-core-v0.2.md +911 -0
- package/docs/agent-surface/semantic-surface-response-to-review-v0.2.md +1122 -0
- package/docs/agent-surface/semantic-surface.schema.json +257 -0
- package/lib/agent-surface/index.d.ts +8 -0
- package/lib/agent-surface/index.js +18 -0
- package/lib/agent-surface/semantic-surface.schema.json +257 -0
- package/lib/agent-surface/surface-digest.d.ts +47 -0
- package/lib/agent-surface/surface-digest.js +231 -0
- package/lib/agent-surface/surface-semantics.d.ts +30 -0
- package/lib/agent-surface/surface-semantics.js +137 -0
- package/lib/agent-surface/surface-validate.d.ts +29 -0
- package/lib/agent-surface/surface-validate.js +122 -0
- package/lib/agent-surface/surface.types.d.ts +86 -0
- package/lib/agent-surface/surface.types.js +16 -0
- package/package.json +49 -0
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
# Semantic Surface — Core Specification
|
|
2
|
+
|
|
3
|
+
**Version:** 0.2
|
|
4
|
+
**Status:** Normative draft — implementable, validatable, measurable. Not yet submitted as a public standard.
|
|
5
|
+
**Date:** 2026-08-05
|
|
6
|
+
**Schema:** [`semantic-surface.schema.json`](semantic-surface.schema.json)
|
|
7
|
+
**Supersedes:** the v0.1 draft, `vops-semantic-surface-spec.md`.
|
|
8
|
+
**Built from:** v0.1, the technical review of it (`semantic-surface-v0.1-review.md`) and the response decision record (`semantic-surface-response-to-review-v0.2.md`). Where they conflict, **this document prevails**. The v0.1 draft and its review were internal working documents of the sponsoring product and are not distributed here; they are cited for provenance only.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## How to read this document
|
|
13
|
+
|
|
14
|
+
The keywords **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT** and **MAY** carry their usual meaning in technical specifications: MUST is a conformance requirement, SHOULD is a strong recommendation that may be departed from with reason, MAY is optional.
|
|
15
|
+
|
|
16
|
+
Sections **§1–§13 are normative**. **Annex A and Annex B are informative** and do not contribute to conformance.
|
|
17
|
+
|
|
18
|
+
The contract of this specification is the **wire format** (§4): the serializable JSON form of a snapshot. The TypeScript interfaces are a convenient rendering of that same contract, not the contract itself. The runtime APIs (§10) are **not** required for conformance.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Purpose, definition and non-goals
|
|
23
|
+
|
|
24
|
+
### 1.1 Canonical definition
|
|
25
|
+
|
|
26
|
+
> **The Semantic Surface is a serializable, medium-independent representation of the user's current attention and of what the interface has actually presented to them.**
|
|
27
|
+
|
|
28
|
+
It answers exactly one question: **what is the user looking at right now, and what does "this" refer to?**
|
|
29
|
+
|
|
30
|
+
It does not answer — and MUST NOT attempt to answer — what the current truth of the domain is, which operations are available, who is authorised to perform them, or how they are executed.
|
|
31
|
+
|
|
32
|
+
### 1.2 What a Surface MAY describe
|
|
33
|
+
|
|
34
|
+
- current route or view, and active scopes;
|
|
35
|
+
- primary entity, selections, attention;
|
|
36
|
+
- active modes: tabs, filters, time window, sort order;
|
|
37
|
+
- open overlays;
|
|
38
|
+
- semantic state of the view: loading, error, no results, truncation;
|
|
39
|
+
- values **actually presented** to the user, with the time the underlying reading was taken;
|
|
40
|
+
- references (`resourceRef`) for retrieving authoritative data.
|
|
41
|
+
|
|
42
|
+
### 1.3 What a Surface MUST NOT become
|
|
43
|
+
|
|
44
|
+
- a replica of the application store;
|
|
45
|
+
- a dump of API responses;
|
|
46
|
+
- a complete domain model;
|
|
47
|
+
- a capability catalogue;
|
|
48
|
+
- an authorisation system;
|
|
49
|
+
- a representation of clicks, or a navigation model;
|
|
50
|
+
- a browser-automation engine;
|
|
51
|
+
- a second accessibility tree.
|
|
52
|
+
|
|
53
|
+
### 1.4 Positioning
|
|
54
|
+
|
|
55
|
+
The Surface describes **context**. Operational catalogues — MCP on the server side, and in-browser proposals such as WebMCP — describe and execute **actions**. They are complementary planes:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
Semantic Surface → what has been presented, and what "this" refers to
|
|
59
|
+
MCP / WebMCP → which tools exist, who may use them, how they run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This specification MUST NOT introduce proprietary browser APIs and MUST NOT define tools. The format is transport-independent (§11), so that it can also travel over channels standardised in the future.
|
|
63
|
+
|
|
64
|
+
### 1.5 Explicit non-goals
|
|
65
|
+
|
|
66
|
+
**Multimodality.** The term carries three readings; this specification normalises exactly one:
|
|
67
|
+
|
|
68
|
+
| Reading | Status |
|
|
69
|
+
|---|---|
|
|
70
|
+
| (a) The agent combines several input sources — semantics, pixels, DOM, APIs | **Out of scope, but anchorable.** It is a property of the host, not of the producer. The specification provides only the optional anchor of §11.4 |
|
|
71
|
+
| (b) Several user→agent channels: chat, voice, command, gesture | **Explicit non-goal.** It belongs to the assistant integration |
|
|
72
|
+
| (c) Several surfaces of the same product — web, SSR, CLI, TUI, voice — exposing the same attention model | **This is the reading being normalised.** A standard can only normalise what the producer controls |
|
|
73
|
+
|
|
74
|
+
**Agent→UI interaction.** v0.2 is **purely descriptive**: the Surface does not execute, navigate or prefill. The exclusion is deliberate and already bounded: a future extension registered as **SS-ATTENTION** (§13.3) MAY let an agent *request* that the user's attention be moved to, or marked on, an element the Surface names, and MUST NOT under any circumstance perform domain actions, submit forms or modify data.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 2. Conceptual model
|
|
79
|
+
|
|
80
|
+
### 2.1 The three planes
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
Semantic Surface
|
|
84
|
+
│ describes UI context (not authoritative)
|
|
85
|
+
▼
|
|
86
|
+
Assistant / Planner
|
|
87
|
+
│ interprets intent and selects a tool
|
|
88
|
+
▼
|
|
89
|
+
MCP Server / Backend
|
|
90
|
+
│ authorises, approves, validates, executes, audits (authoritative)
|
|
91
|
+
▼
|
|
92
|
+
Application Services / Infrastructure
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Preserving this separation is the primary condition for the project not to become a second UI framework, a browser-automation system, or a new state manager.
|
|
96
|
+
|
|
97
|
+
### 2.2 Roles
|
|
98
|
+
|
|
99
|
+
- **Producer** — the application that builds the snapshot. It is solely responsible for redaction (§8.2).
|
|
100
|
+
- **Consumer** — whoever reads the snapshot: assistant, planner, inspector, diagnostic tool.
|
|
101
|
+
- **Assistant host** — whoever composes the prompt and encapsulates the snapshot as untrusted data (§8.4).
|
|
102
|
+
|
|
103
|
+
### 2.3 Terms
|
|
104
|
+
|
|
105
|
+
- **Scope** — a semantically meaningful region: a page, a region, a selection, a list, a form, an overlay. It does not necessarily coincide with a UI component.
|
|
106
|
+
- **Attention** — the scope, and optionally the entity, the user's attention is directed at. It is a list ordered by salience.
|
|
107
|
+
- **Entity reference** — a stable reference to a domain entity (§5).
|
|
108
|
+
- **Observation** — a piece of information **presented** to the user (§3, §4.6).
|
|
109
|
+
- **Snapshot** — the JSON materialisation of the Surface at one instant (§4).
|
|
110
|
+
- **Revision** — a monotonic counter identifying the state of the Surface (§7).
|
|
111
|
+
|
|
112
|
+
### 2.4 Contribution rule
|
|
113
|
+
|
|
114
|
+
A component, region or page controller contributes to the Surface **only** when it: introduces or represents a domain entity; adds relevant observations; changes selection or attention; represents an independent operational region; or opens a transient context (an overlay).
|
|
115
|
+
|
|
116
|
+
Purely visual elements MUST NOT register anything.
|
|
117
|
+
|
|
118
|
+
A UI that **observes** the Surface — an assistant panel, an inspector, a debug overlay — MUST NOT contribute scopes either. It would describe itself into the description it is reading, and an open assistant would appear as the thing the user is attending to.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 3. Data admission rules
|
|
123
|
+
|
|
124
|
+
### 3.1 Principle: presented, not asserted
|
|
125
|
+
|
|
126
|
+
An observation in the Surface **does not assert** that a value is authoritative now. It asserts that it **was presented to the user in that form at that moment**. This is the distinction that allows backend-derived data to be included without duplicating the backend.
|
|
127
|
+
|
|
128
|
+
### 3.2 Admission criteria
|
|
129
|
+
|
|
130
|
+
Information MAY enter the Surface when it meets at least one of these criteria:
|
|
131
|
+
|
|
132
|
+
1. it exists only in the UI session: selection, attention, filter, mode, overlay;
|
|
133
|
+
2. it describes **how** a value was presented to the user;
|
|
134
|
+
3. it is required to resolve a deictic reference — "this server", "the selected one";
|
|
135
|
+
4. it states the semantic state of the view: loading, error, empty, truncated;
|
|
136
|
+
5. it carries a reference for retrieving the complete or current value from the authoritative source.
|
|
137
|
+
|
|
138
|
+
### 3.3 Exclusion criteria
|
|
139
|
+
|
|
140
|
+
Information MUST NOT enter when:
|
|
141
|
+
|
|
142
|
+
- it is better and unambiguously retrievable from an authoritative tool;
|
|
143
|
+
- it is not relevant to the current view or attention;
|
|
144
|
+
- it duplicates large backend structures;
|
|
145
|
+
- it contains secrets or unnecessary personal data;
|
|
146
|
+
- it was produced **solely to explain the page to the model**, and is not part of the product.
|
|
147
|
+
|
|
148
|
+
### 3.4 Anti-drift rule (normative)
|
|
149
|
+
|
|
150
|
+
There MUST NOT be two state structures kept in sync by hand:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
Application state
|
|
154
|
+
│
|
|
155
|
+
┌───────────────┴───────────────┐
|
|
156
|
+
UI rendering Surface materialisation
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
A semantic provider MUST read the same reactive state the rendering reads. It MUST NOT copy values into a parallel structure.
|
|
160
|
+
|
|
161
|
+
*Why this is normative:* it leaves only one possible kind of drift — **coverage** drift, a view with no semantic definition — which is mechanically detectable with a lint over the main routes and with the inspector. **Truth** drift — a snapshot asserting a value the UI no longer shows — would be invisible until it causes harm, and it is how artefacts of this kind historically rot.
|
|
162
|
+
|
|
163
|
+
### 3.5 Free text
|
|
164
|
+
|
|
165
|
+
| Case | Rule |
|
|
166
|
+
|---|---|
|
|
167
|
+
| Text actually shown to the user | **Allowed**, subject to a length limit and `source` |
|
|
168
|
+
| A summary produced by the backend, part of the product, tested, and also shown to the user | **Allowed with care**, with `source: "api"` |
|
|
169
|
+
| Text written by the semantic provider to steer the model | **FORBIDDEN** |
|
|
170
|
+
|
|
171
|
+
An example of what is forbidden: `"This server looks badly degraded and should be restarted."` — an instruction disguised as an observation, not part of the product, untested, feeding an agent's prompt directly.
|
|
172
|
+
|
|
173
|
+
Free text MUST respect: the length limits of §9, no HTML or arbitrary markup, no secrets, no operational instructions.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 4. Wire format (normative)
|
|
178
|
+
|
|
179
|
+
A snapshot MUST be serializable JSON and MUST validate against [`semantic-surface.schema.json`](semantic-surface.schema.json).
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
interface SurfaceSnapshot {
|
|
183
|
+
schemaVersion: "0.2";
|
|
184
|
+
|
|
185
|
+
app: {
|
|
186
|
+
id: string; // producer namespace, e.g. "vops"
|
|
187
|
+
version?: string;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
surface: {
|
|
191
|
+
id: string;
|
|
192
|
+
route?: string;
|
|
193
|
+
revision: number; // monotonically increasing integer
|
|
194
|
+
generatedAt: string; // ISO 8601, when the snapshot was produced
|
|
195
|
+
locale?: string;
|
|
196
|
+
truncated?: boolean; // true when the budget dropped something
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
attention: AttentionTarget[]; // ordered by salience, first is primary
|
|
200
|
+
scopes: SemanticScopeSnapshot[]; // flat list, hierarchy via parentId
|
|
201
|
+
|
|
202
|
+
extensions?: Record<string, unknown>; // namespaced keys, see §13.2
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
interface AttentionTarget {
|
|
206
|
+
scopeId: string;
|
|
207
|
+
entityRef?: string;
|
|
208
|
+
reason?: string; // reserved values in §6.3
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface SemanticScopeSnapshot {
|
|
212
|
+
id: string;
|
|
213
|
+
parentId?: string;
|
|
214
|
+
kind: string; // reserved values in §6.2
|
|
215
|
+
label?: string;
|
|
216
|
+
|
|
217
|
+
entities?: EntityReference[];
|
|
218
|
+
observations?: Observation[];
|
|
219
|
+
|
|
220
|
+
state?: {
|
|
221
|
+
loading?: boolean;
|
|
222
|
+
error?: boolean;
|
|
223
|
+
errorCode?: string; // a code, never the free-text error message
|
|
224
|
+
empty?: boolean;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
completeness?: {
|
|
228
|
+
shown: number;
|
|
229
|
+
total?: number;
|
|
230
|
+
filtered?: boolean;
|
|
231
|
+
truncated?: boolean;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
extensions?: Record<string, unknown>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
interface EntityReference {
|
|
238
|
+
ref: string; // the only identifying field, see §5
|
|
239
|
+
label?: string;
|
|
240
|
+
role?: "primary" | "selected" | "related";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
interface Observation {
|
|
244
|
+
key: string; // namespaced, see §6.1
|
|
245
|
+
|
|
246
|
+
presentedAs: { // what the user saw
|
|
247
|
+
value?: unknown;
|
|
248
|
+
unit?: string;
|
|
249
|
+
text?: string;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
source?: "ui" | "api" | "derived";
|
|
253
|
+
observedAt?: string; // when the reading was taken, see §7.3
|
|
254
|
+
resourceRef?: string; // where to fetch the authoritative value
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 4.1 `attention`
|
|
259
|
+
|
|
260
|
+
MUST be present; MAY be empty. A view with no defined attention is legitimate; inventing one is not. Every `scopeId` referenced MUST exist in `scopes`.
|
|
261
|
+
|
|
262
|
+
**Resolving `attention` is the producer's sole responsibility.** When several parts of an application contribute attention claims, the binding MUST merge them into the single ordered list **before serialisation**: the wire format never carries competing or unresolved claims, and a consumer never arbitrates. `attention[0]` expresses the producer's claim of primary salience — it is not a quantity comparable across producers. The merge mechanism is binding-specific and out of scope for the core.
|
|
263
|
+
|
|
264
|
+
Absent an application-specific rule, a producer SHOULD rank targets whose `reason` is `manual` or `overlay` above `selection`, and `selection` above `active-view` and `route`. A claim SHOULD be a pure function of observed state: an arrival of data, a re-registration or a re-render MUST NOT change the order when the observed state has not changed.
|
|
265
|
+
|
|
266
|
+
**Attention MUST NOT be derived from DOM or input focus.** Which control holds the caret is not what the user's words refer to — an assistant docked beside the page would otherwise steal the context the instant it is typed into. Attention follows route, selection, mode and overlay, which are state; focus is an event.
|
|
267
|
+
|
|
268
|
+
### 4.2 `scopes`
|
|
269
|
+
|
|
270
|
+
A **flat** list; hierarchy is expressed with `parentId`. A `parentId` MUST reference a scope present in the same snapshot.
|
|
271
|
+
|
|
272
|
+
**Core invariant:** *an inactive scope never appears in a snapshot.* This replaces the v0.1 criterion "destroying a component removes its scope", which was only verifiable in component frameworks. "Active" means *presented according to state*: an unselected tab, an untriggered lazy block, a cached-but-hidden view and a collapsed section are all application state, and their scopes MUST be absent.
|
|
273
|
+
|
|
274
|
+
**Identity is per instance, not per definition.** A scope `id` identifies one instance within this snapshot, not a component class or a template. A binding that instantiates one definition more than once — two panels side by side, a list of cards — MUST mint a distinct id per instance. Instance ids MUST be derived from the domain data that distinguishes the instances, and MUST NOT derive from mount order, counters, randomness or timestamps: those change across renders, and §7.1 requires a revision that only moves when content moves. `duplicate-scope-id` therefore remains an error, not a legitimate multi-instance case.
|
|
275
|
+
|
|
276
|
+
**`parentId` expresses semantic ownership**, never render position. A scope rendered elsewhere in the document — a portal, a teleport, an overlay attached to the body — keeps the parent that semantically owns it.
|
|
277
|
+
|
|
278
|
+
The **order of the `scopes` array carries no meaning**: a consumer MUST NOT infer salience from it. Salience lives only in `attention`.
|
|
279
|
+
|
|
280
|
+
### 4.3 `state`
|
|
281
|
+
|
|
282
|
+
Distinguishes three conditions that otherwise collapse into "empty scope": I am loading, I failed, there is nothing.
|
|
283
|
+
|
|
284
|
+
`errorCode` MUST be a namespaced code — `vops.host.unreachable`, `vops.metrics.timeout` — and MUST NOT carry the error text: a free-form message coming from a backend is an injection vector. A scope carrying `errorCode` MUST also set `error: true`.
|
|
285
|
+
|
|
286
|
+
The three flags are deliberately **not** mutually exclusive: a real UI keeps the previous content on screen while refreshing, and shows it again beside an error. Forcing exclusivity in the core would be stricter than reality. An application profile MAY raise `loading-and-error` or `loading-and-empty` as **warnings**; the core does not.
|
|
287
|
+
|
|
288
|
+
### 4.4 `completeness`
|
|
289
|
+
|
|
290
|
+
SHOULD be present on every scope representing a paginated, filtered or virtualised list. Without it, "restart the broken one" resolves against the rendered rows rather than against the set.
|
|
291
|
+
|
|
292
|
+
### 4.5 `EntityReference`
|
|
293
|
+
|
|
294
|
+
A single identifying field (`ref`). The v0.1 triple `ref` + `type` + `id` has been removed: two representations of one identity force every validator to define behaviour for the case where they disagree.
|
|
295
|
+
|
|
296
|
+
### 4.6 `Observation`
|
|
297
|
+
|
|
298
|
+
`presentedAs` is **required** and is the only container for the value: there is no bare `value` field beside it. One place where the datum lives, for the same reason as §4.5.
|
|
299
|
+
|
|
300
|
+
Three rules make it honest:
|
|
301
|
+
|
|
302
|
+
- `presentedAs` MUST carry `value` or `text`. A `unit` on its own presents nothing.
|
|
303
|
+
- `value` MUST be a **compact scalar** — number, boolean, null or a short string. Charts, series, objects and API structures MUST live behind `resourceRef` (§9). A `null` value is legitimate: it is how a UI showing "—" says the reading was unavailable.
|
|
304
|
+
- `text` MUST be accompanied by `source`, because free text is admitted only with provenance (§3.5).
|
|
305
|
+
|
|
306
|
+
The meaning of an observation is always: *"the interface presented this, referring to that moment."* Never: *"this is true now."*
|
|
307
|
+
|
|
308
|
+
### 4.7 Complete example
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"schemaVersion": "0.2",
|
|
313
|
+
"app": { "id": "vops", "version": "0.4.1" },
|
|
314
|
+
"surface": {
|
|
315
|
+
"id": "host-detail",
|
|
316
|
+
"route": "#host",
|
|
317
|
+
"revision": 42,
|
|
318
|
+
"generatedAt": "2026-08-05T16:09:41Z",
|
|
319
|
+
"locale": "en-GB"
|
|
320
|
+
},
|
|
321
|
+
"attention": [
|
|
322
|
+
{ "scopeId": "host-live-status", "entityRef": "vops://host/vmi3399032", "reason": "route" }
|
|
323
|
+
],
|
|
324
|
+
"scopes": [
|
|
325
|
+
{
|
|
326
|
+
"id": "host-detail",
|
|
327
|
+
"kind": "page",
|
|
328
|
+
"label": "Host detail",
|
|
329
|
+
"entities": [
|
|
330
|
+
{ "ref": "vops://host/vmi3399032", "label": "vmi3399032", "role": "primary" }
|
|
331
|
+
],
|
|
332
|
+
"observations": [
|
|
333
|
+
{
|
|
334
|
+
"key": "vops.host.provider",
|
|
335
|
+
"presentedAs": { "text": "contabo" },
|
|
336
|
+
"source": "api"
|
|
337
|
+
}
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"id": "host-live-status",
|
|
342
|
+
"parentId": "host-detail",
|
|
343
|
+
"kind": "region",
|
|
344
|
+
"label": "Live status",
|
|
345
|
+
"state": { "loading": false, "empty": false },
|
|
346
|
+
"observations": [
|
|
347
|
+
{
|
|
348
|
+
"key": "system.cpu.utilization",
|
|
349
|
+
"presentedAs": { "value": 92, "unit": "percent" },
|
|
350
|
+
"source": "api",
|
|
351
|
+
"observedAt": "2026-08-05T16:07:12Z",
|
|
352
|
+
"resourceRef": "vops://host/vmi3399032/metrics/cpu?window=1h"
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"key": "vops.host.window",
|
|
356
|
+
"presentedAs": { "text": "1h" },
|
|
357
|
+
"source": "ui"
|
|
358
|
+
}
|
|
359
|
+
],
|
|
360
|
+
"completeness": { "shown": 3, "total": 3 }
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 5. Entity references
|
|
369
|
+
|
|
370
|
+
### 5.1 Grammar
|
|
371
|
+
|
|
372
|
+
```text
|
|
373
|
+
<namespace>://<entity-type>/<entity-id>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Examples: `vops://host/vmi3399032`, `vops://server/srv_123`, `vops://app/nextcloud`.
|
|
377
|
+
|
|
378
|
+
The entity-id is a **single percent-encoded segment**: it MUST NOT contain `/`, `?` or `#`. A composite identity uses a separator inside the segment — `vops://app/vmi3399032:nextcloud` — rather than a nested path, so that every reference parses the same way with no ambiguity about where the type ends and the identity begins.
|
|
379
|
+
|
|
380
|
+
### 5.2 Requirements
|
|
381
|
+
|
|
382
|
+
A `ref` MUST be stable over time, serializable, interpretable by the producer, and passed **explicitly** to operational tools.
|
|
383
|
+
|
|
384
|
+
A `ref` MUST NOT be used as proof of authorisation (§8.1).
|
|
385
|
+
|
|
386
|
+
A consumer MAY extract type and identifier from it, but MUST NOT construct an entity other than the one expressed.
|
|
387
|
+
|
|
388
|
+
### 5.3 Obligation on the authoritative side
|
|
389
|
+
|
|
390
|
+
A server receiving a `ref` in a tool call MUST still resolve it, validate it, apply the tenant, verify authorisation and revalidate current state. No tool MUST exist in the form "act on the current entity": the implicit state of a browser tab is not a parameter.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## 6. Vocabulary and namespacing
|
|
395
|
+
|
|
396
|
+
This specification defines no universal ontology, and will not introduce one until several independent applications need to interoperate over the same terms. What it defines is a **grammar**, whose conformance cost is one regular expression.
|
|
397
|
+
|
|
398
|
+
### 6.1 Observation keys
|
|
399
|
+
|
|
400
|
+
Keys MUST be namespaced (at least one dot) or belong to a recognised vocabulary.
|
|
401
|
+
|
|
402
|
+
```text
|
|
403
|
+
system.cpu.utilization ← recognised vocabulary (OpenTelemetry)
|
|
404
|
+
system.memory.utilization
|
|
405
|
+
vops.host.status
|
|
406
|
+
vops.deployment.phase
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
For infrastructure metrics a producer SHOULD prefer names from the **OpenTelemetry Semantic Conventions** where applicable. This is an interoperability choice of the application profile, **not** a dependency of the core.
|
|
410
|
+
|
|
411
|
+
### 6.2 Scope kinds — reserved values
|
|
412
|
+
|
|
413
|
+
```text
|
|
414
|
+
page region selection list form overlay
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Extensions MUST be namespaced: `vops.terminal`, `flui.canvas`.
|
|
418
|
+
|
|
419
|
+
`overlay` covers dialogs and drawers: those are presentation words, and the core speaks of attention. A binding MAY distinguish them locally.
|
|
420
|
+
|
|
421
|
+
### 6.3 Attention reasons — reserved values
|
|
422
|
+
|
|
423
|
+
```text
|
|
424
|
+
route selection active-view overlay manual
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Namespaced extensions are allowed.
|
|
428
|
+
|
|
429
|
+
### 6.4 No registry
|
|
430
|
+
|
|
431
|
+
This version does **not** introduce a global registry, URI resolution, RDF, JSON-LD, or universal vocabulary governance. The consumer is a language model, not a reasoner: a vocabulary must be **readable**, not resolvable.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## 7. Revision, freshness and staleness
|
|
436
|
+
|
|
437
|
+
### 7.1 Revision
|
|
438
|
+
|
|
439
|
+
`surface.revision` MUST be a monotonically increasing integer for the lifetime of the Surface session. It changes when the content a snapshot would express changes.
|
|
440
|
+
|
|
441
|
+
Revision does **not** replace backend versions, ETags or revisions: it diagnoses UI context, it does not protect a mutation.
|
|
442
|
+
|
|
443
|
+
### 7.2 Revision on request and on response
|
|
444
|
+
|
|
445
|
+
The host SHOULD record the revision in force when the message was sent, and carry it back on the response:
|
|
446
|
+
|
|
447
|
+
```json
|
|
448
|
+
{ "message": "Why is CPU high?", "surfaceRevision": 42, "surface": { } }
|
|
449
|
+
{ "surfaceRevision": 42, "answer": "…" }
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
If the UI has since moved to revision 45, the client MAY show a "based on an earlier view" marker, avoid visually binding the answer to the new focus, or ask for a context refresh before an action. It MUST NOT automatically invalidate a historically correct answer.
|
|
453
|
+
|
|
454
|
+
*Why this matters:* an agent that reasons for forty seconds answers about an entity the user has already left. Without this link, that answer is indistinguishable from a current one.
|
|
455
|
+
|
|
456
|
+
### 7.3 `observedAt`
|
|
457
|
+
|
|
458
|
+
`surface.generatedAt` is when the snapshot was produced. `observation.observedAt` is when the underlying value was measured or received. **They are not equivalent.**
|
|
459
|
+
|
|
460
|
+
When `observedAt` is absent, a consumer MUST treat the age of the value as **unknown**. It MUST NOT assume it equals `generatedAt`: doing so would manufacture a freshness nobody measured.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## 8. Trust boundary, security and redaction
|
|
465
|
+
|
|
466
|
+
### 8.1 The Surface is never trusted
|
|
467
|
+
|
|
468
|
+
A Surface comes from the client and MAY be altered. It MUST NOT be trusted for authorisation, tenancy, permissions, risk level, approval, capability availability or current operational state.
|
|
469
|
+
|
|
470
|
+
All of those MUST be revalidated server-side on every operation.
|
|
471
|
+
|
|
472
|
+
### 8.2 Redaction before serialisation
|
|
473
|
+
|
|
474
|
+
There is no `sensitivity` field: security MUST NOT depend on a label a consumer is free to ignore.
|
|
475
|
+
|
|
476
|
+
> **Rule:** a value that must not leave the process building the snapshot MUST NOT be present in the snapshot.
|
|
477
|
+
|
|
478
|
+
The producer MUST redact **before** serialisation. The following MUST NOT appear: tokens, passwords, sensitive environment variables, keys, authentication headers, whole logs, unnecessary personal data.
|
|
479
|
+
|
|
480
|
+
### 8.3 Perimeter of this version
|
|
481
|
+
|
|
482
|
+
v0.2 declares itself:
|
|
483
|
+
|
|
484
|
+
```text
|
|
485
|
+
first-party · same-origin · single application trust domain
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
It does **not** claim to solve third-party iframes, untrusted widgets, browser extensions, cross-origin composition, provider signing, or provenance attestation.
|
|
489
|
+
|
|
490
|
+
The runtime MUST nevertheless prevent external code from registering scopes by default, absent an explicit integration.
|
|
491
|
+
|
|
492
|
+
### 8.4 Host-side encapsulation (prompt injection)
|
|
493
|
+
|
|
494
|
+
Resource names, logs, findings and any content originating from observed systems are **untrusted data**, even when the Surface itself was produced correctly.
|
|
495
|
+
|
|
496
|
+
The assistant host MUST:
|
|
497
|
+
|
|
498
|
+
1. wrap the snapshot in a block explicitly declared as untrusted data describing what the user is looking at;
|
|
499
|
+
2. never present Surface content as instructions;
|
|
500
|
+
3. truncate free text per §9;
|
|
501
|
+
4. prefer codes, structured values and `resourceRef` over prose.
|
|
502
|
+
|
|
503
|
+
It SHOULD further prefer a **compact, deterministic digest** of the snapshot in the prompt, exposing the full snapshot only on the model's explicit request through a tool — so the budget stays small and the full content arrives through the path the host already treats as tool output.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## 9. Budget and truncation
|
|
508
|
+
|
|
509
|
+
The core MUST prevent a snapshot from becoming an arbitrary dump. No universal limit is frozen in this version: the default will be chosen from pilot measurements.
|
|
510
|
+
|
|
511
|
+
A builder MUST support a configurable budget and expose its measurements:
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
createSurface(definitions, getState, {
|
|
515
|
+
maxBytes: 32_768,
|
|
516
|
+
maxScopes: 30,
|
|
517
|
+
maxObservationsPerScope: 20,
|
|
518
|
+
maxTextLength: 500,
|
|
519
|
+
});
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Measurements the builder MUST be able to report: JSON bytes, estimated tokens, number of scopes, observations and entity refs, fields truncated.
|
|
523
|
+
|
|
524
|
+
When the budget is exceeded, the truncation order MUST be:
|
|
525
|
+
|
|
526
|
+
1. free text first;
|
|
527
|
+
2. then the least salient observations;
|
|
528
|
+
3. **never** the entity references of the primary attention;
|
|
529
|
+
4. `surface.truncated` becomes `true`;
|
|
530
|
+
5. affected scopes expose `completeness.truncated`.
|
|
531
|
+
|
|
532
|
+
The following MUST NOT be serialised under any circumstance: full charts, long lists, whole logs, raw API responses, store objects.
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## 10. Runtime (not required for conformance)
|
|
537
|
+
|
|
538
|
+
### 10.1 Core: pure definitions
|
|
539
|
+
|
|
540
|
+
The model does **not** require component lifecycles. Its nucleus is a composition of pure definitions over application state:
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
interface SemanticScopeDefinition<S> {
|
|
544
|
+
id: string;
|
|
545
|
+
parentId?: string;
|
|
546
|
+
kind: string;
|
|
547
|
+
isActive(state: S): boolean;
|
|
548
|
+
read(state: S): ScopeContribution; // entities, observations, state, completeness
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function createSurface<S>(
|
|
552
|
+
definitions: SemanticScopeDefinition<S>[],
|
|
553
|
+
getState: () => S,
|
|
554
|
+
budget?: Budget,
|
|
555
|
+
): { snapshot(): SurfaceSnapshot };
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
This works with a single store (Alpine and similar), page controllers, SSR, multi-page applications, and tests without a browser.
|
|
559
|
+
|
|
560
|
+
*Note on multi-instance definitions:* the static `id` in this sketch is adequate only for definitions that produce one scope. A binding whose definitions instantiate more than once MUST mint an id per instance (§4.2) — copying this sketch literally is how a component binding builds itself an id collision. How instance keys are sourced (a React `key`, an Angular `trackBy`, a route parameter), how attention claims are merged, and whether the `scopes` array needs a canonical order are **binding concerns**, deliberately unspecified until a second, component-based implementation exists to exercise them.
|
|
561
|
+
|
|
562
|
+
### 10.2 Dynamic registration: an optional adapter
|
|
563
|
+
|
|
564
|
+
Component frameworks MAY add `register` / `invalidate` / `unregister` / `subscribe` on top of the core. These APIs belong to the binding, **not** to the wire format, and do not contribute to conformance.
|
|
565
|
+
|
|
566
|
+
### 10.3 Lazy materialisation
|
|
567
|
+
|
|
568
|
+
A runtime MUST NOT serialise continuously. It holds references to definitions, parent–child relations, activation state and a revision number, and builds a snapshot **only on request**.
|
|
569
|
+
|
|
570
|
+
A snapshot MUST derive from a **single coherent read of state**, never be accumulated across ticks: otherwise it describes a moment that never existed. A contribution whose parent is absent from that read MUST be omitted rather than emitted as an orphan — a transiently invalid snapshot is worse than a smaller one. A snapshot taken while something is still mounting is legitimate, not a race: what has not been presented does not appear.
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## 11. Transports
|
|
575
|
+
|
|
576
|
+
The format is **transport-independent**. A conforming producer MAY deliver it over any of these channels without the model changing.
|
|
577
|
+
|
|
578
|
+
### 11.1 Application request body
|
|
579
|
+
|
|
580
|
+
The pilot's case: the snapshot travels in an **optional** field of the message to the assistant. The receiver MUST validate it against the schema and MUST discard it silently if invalid — a defect in the Surface MUST NOT fail the user's request (§12.1, item 9).
|
|
581
|
+
|
|
582
|
+
### 11.2 MCP content block or resource
|
|
583
|
+
|
|
584
|
+
A snapshot MAY be exposed as the result of a read-only tool, or as a resource, so a model can read it on request instead of always receiving it in the prompt.
|
|
585
|
+
|
|
586
|
+
### 11.3 SSR and multi-page applications
|
|
587
|
+
|
|
588
|
+
```html
|
|
589
|
+
<script type="application/json" data-semantic-surface>
|
|
590
|
+
{ "schemaVersion": "0.2", "...": "..." }
|
|
591
|
+
</script>
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
A producer emitting **only** this tag is **fully conforming**. This is the test that keeps §10 honest: if a future version made this form impossible, the core would once again be contaminated by the API.
|
|
595
|
+
|
|
596
|
+
### 11.4 Layout anchoring (optional binding)
|
|
597
|
+
|
|
598
|
+
A web binding MAY mark the element rendering a scope:
|
|
599
|
+
|
|
600
|
+
```html
|
|
601
|
+
<section data-surface-scope="host-live-status"> … </section>
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
This attribute is not required, does not enter the wire format, carries no semantic data and does not replace the snapshot. It lets a host that has DOM and pixels compute bounding boxes **on demand**, instead of putting unstable coordinates — which change on every scroll — inside the snapshot.
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## 12. Conformance
|
|
609
|
+
|
|
610
|
+
### 12.1 Conforming producer
|
|
611
|
+
|
|
612
|
+
A producer conforms when it:
|
|
613
|
+
|
|
614
|
+
1. emits snapshots valid against the schema;
|
|
615
|
+
2. uses a single canonical `ref` per entity, with the grammar of §5.1;
|
|
616
|
+
3. namespaces observation keys and non-reserved scope kinds;
|
|
617
|
+
4. never includes an inactive scope;
|
|
618
|
+
5. distinguishes loading, error and empty;
|
|
619
|
+
6. declares the incompleteness of lists it truncates or filters;
|
|
620
|
+
7. redacts secrets **before** serialisation;
|
|
621
|
+
8. respects the configured budget or declares truncation;
|
|
622
|
+
9. **behaves identically when Surface production is disabled** — the Surface is never a prerequisite for the UI;
|
|
623
|
+
10. does not copy state into a parallel structure (§3.4).
|
|
624
|
+
|
|
625
|
+
### 12.2 Conforming consumer
|
|
626
|
+
|
|
627
|
+
A consumer conforms when it:
|
|
628
|
+
|
|
629
|
+
1. does not treat the Surface as authoritative for truth, permissions or availability (§8.1);
|
|
630
|
+
2. uses explicit `ref` values in tool calls, never the implicit state of a view;
|
|
631
|
+
3. treats the age of a value without `observedAt` as unknown;
|
|
632
|
+
4. encapsulates the Surface as untrusted data (§8.4);
|
|
633
|
+
5. revalidates data against the authoritative source before a diagnosis or an action.
|
|
634
|
+
|
|
635
|
+
### 12.3 Two levels of validation
|
|
636
|
+
|
|
637
|
+
JSON Schema cannot express several of the MUSTs in this specification: it validates one document's shape, and knows nothing about cross-references, history or producer behaviour. Validation is therefore defined at two levels, and an implementation MUST run both:
|
|
638
|
+
|
|
639
|
+
```text
|
|
640
|
+
validateSchema(snapshot) // structure
|
|
641
|
+
validateSemantics(snapshot, previousSnapshot?) // coherence
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
interface SemanticValidationIssue {
|
|
646
|
+
code: string;
|
|
647
|
+
path: string;
|
|
648
|
+
message: string;
|
|
649
|
+
severity: "error" | "warning";
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
interface SemanticValidationOptions {
|
|
653
|
+
previousSnapshot?: SurfaceSnapshot;
|
|
654
|
+
maxBytes?: number;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function validateSurfaceSemantics(
|
|
658
|
+
snapshot: SurfaceSnapshot,
|
|
659
|
+
options?: SemanticValidationOptions,
|
|
660
|
+
): SemanticValidationIssue[];
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Minimum checks on a single snapshot:
|
|
664
|
+
|
|
665
|
+
| Code | Severity |
|
|
666
|
+
|---|---|
|
|
667
|
+
| `duplicate-scope-id` | error |
|
|
668
|
+
| `missing-attention-scope` — an `attention.scopeId` absent from `scopes` | error |
|
|
669
|
+
| `missing-parent-scope` — a `parentId` absent from `scopes` | error |
|
|
670
|
+
| `cyclic-scope-hierarchy` | error |
|
|
671
|
+
| `duplicate-entity-ref` — the same `ref` twice in one scope | error |
|
|
672
|
+
| `attention-entity-not-in-scope` — an `entityRef` not present in the scope it points at | warning |
|
|
673
|
+
| `shown-exceeds-total` | error |
|
|
674
|
+
| `inconsistent-truncation` — a truncated scope with `surface.truncated` unset | error |
|
|
675
|
+
| `invalid-timestamp-value` — lexically valid but not a real instant | error |
|
|
676
|
+
| `budget-exceeded` — requires `options.maxBytes` | error |
|
|
677
|
+
|
|
678
|
+
Across snapshots, requiring `options.previousSnapshot`:
|
|
679
|
+
|
|
680
|
+
| Code | Severity |
|
|
681
|
+
|---|---|
|
|
682
|
+
| `invalid-revision` — revision not strictly increasing | error |
|
|
683
|
+
|
|
684
|
+
**On `invalid-timestamp-value`:** the schema's timestamp pattern constrains lexical shape and forces an explicit offset, but a well-formed impossible instant — `2026-99-45T29:81:75+84:92` — still satisfies it. Verifying that the instant exists belongs here. `Date.parse` alone is not sufficient: its behaviour on edge cases differs between runtimes, so an implementation SHOULD verify component ranges and round-trip the value, or use a strict ISO 8601 parser.
|
|
685
|
+
|
|
686
|
+
### 12.3.1 Producer conformance — outside both validators
|
|
687
|
+
|
|
688
|
+
Four obligations cannot be evidenced by any artefact, because they are properties of the code that produced it, not of the document. Naming them here with their own codes prevents a future implementer from trying to "solve" them in JSON Schema:
|
|
689
|
+
|
|
690
|
+
| Code | Obligation |
|
|
691
|
+
|---|---|
|
|
692
|
+
| `inactive-scope-emitted` | an inactive scope never appears (§4.2) |
|
|
693
|
+
| `secret-not-redacted` | redaction happens before serialisation (§8.2) |
|
|
694
|
+
| `parallel-semantic-state` | no state structure parallel to the UI's (§3.4) |
|
|
695
|
+
| `surface-disabled-changes-ui` | the UI behaves identically with Surface production off (§12.1, item 9) |
|
|
696
|
+
|
|
697
|
+
These are verified by the suite of §12.4.
|
|
698
|
+
|
|
699
|
+
*Implementation note:* JSON Schema validators silently ignore formats they do not know, so a validator SHOULD enable format validation (for Ajv: `ajv-formats`). Timestamps do not depend on it for their shape: they carry a regular-expression duplicate of the format and require an explicit offset, so a lexically malformed or ambiguous `generatedAt` is rejected even with formats disabled — an age computed from an ambiguous timestamp is worse than no age at all. Temporal validity is a separate matter, and belongs to `invalid-timestamp-value`.
|
|
700
|
+
|
|
701
|
+
### 12.4 Conformance suite
|
|
702
|
+
|
|
703
|
+
An implementation claims conformance by passing a suite that MUST include at least:
|
|
704
|
+
|
|
705
|
+
- valid and invalid wire-format fixtures;
|
|
706
|
+
- the ten semantic checks of §12.3, each with a failing case;
|
|
707
|
+
- snapshots during loading, during an error, and over a truncated list;
|
|
708
|
+
- absence of secrets in a case constructed to contain them;
|
|
709
|
+
- entity reference de-duplication;
|
|
710
|
+
- absence of inactive scopes;
|
|
711
|
+
- behaviour with Surface production disabled.
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## 13. Versioning and extensions
|
|
716
|
+
|
|
717
|
+
### 13.1 Version
|
|
718
|
+
|
|
719
|
+
`schemaVersion` is a **`major.minor` compatibility version**, not a full SemVer string: it names a compatibility family, so that non-breaking corrections to the schema can ship without invalidating snapshots already in the wild. It is independent of the vocabularies an application uses.
|
|
720
|
+
|
|
721
|
+
`minor` increases when fields are added or constraints are relaxed. `major` increases when a field is removed or renamed, when a constraint is tightened, or when the meaning of an existing field changes — all of which can invalidate a conforming producer.
|
|
722
|
+
|
|
723
|
+
### 13.2 Extensions
|
|
724
|
+
|
|
725
|
+
The `extensions` field — on the snapshot and on every scope — accepts namespaced keys (`vops.*`, `flui.*`). A consumer MUST ignore extensions it does not know. Every other object in the wire format is closed: an unexpected field is a validation error, not an extension.
|
|
726
|
+
|
|
727
|
+
### 13.3 Registered extensions
|
|
728
|
+
|
|
729
|
+
**SS-ATTENTION** *(not implemented; shape pre-committed)* — an agent→UI channel limited to attention:
|
|
730
|
+
|
|
731
|
+
```ts
|
|
732
|
+
type AttentionTarget = { scopeId: string } | { entityRef: string };
|
|
733
|
+
interface SurfaceIntent { kind: 'reveal' | 'highlight'; target: AttentionTarget; note?: string }
|
|
734
|
+
requestAttention(intent: SurfaceIntent): 'applied' | 'declined' | 'unknown-target';
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
Constraints already decided, should it ever be adopted: it is a **request**, and the UI MAY refuse it (`declined` is a legitimate outcome, and it is the antidote to focus stealing); it never submits, never focuses an input, never triggers a fetch; **every future `kind` must be paraphrasable as "move or mark attention", never as "do"**. A general layer of persistent agent annotations rendered inside the product UI will never enter the model: content written by an agent and rendered in the product is a phishing primitive.
|
|
738
|
+
|
|
739
|
+
### 13.4 Explicitly deferred
|
|
740
|
+
|
|
741
|
+
Public standardisation; a universal vocabulary registry; JSON-LD or RDF; provider signing; cross-origin composition; a universal capability registry; a navigation model; funnel automation; form prefilling; click automation; persistent annotations; cross-device sharing; Surface persistence; `requestAttention`; full equivalence across web, CLI, TUI and voice; full automatic generation from Flui.
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## 14. Decisions closed in this version
|
|
746
|
+
|
|
747
|
+
Three questions were left open by the decision record. They are closed here; each is reversible in one line, and the rationale is recorded so a future revision does not have to reconstruct it.
|
|
748
|
+
|
|
749
|
+
| # | Decision | Rationale |
|
|
750
|
+
|---|---|---|
|
|
751
|
+
| **1** | **`capabilityHints` are removed for good**, from the core and from the pilot | They live in the untrusted part of the system and duplicate an authoritative registry that already exists; an agent derives capabilities from entity type and intent. This is decided **on principle** (trust boundary), not pending a measurement: no experimental arm is planned to measure their uplift |
|
|
752
|
+
| **2** | **No package is extracted until a second binding exists** | The decision record deferred the document split while creating two packages with a single consumer: the same premature generalisation, applied to code. The implementation lives inside the pilot application; the JSON Schema is published as a versioned file from day one |
|
|
753
|
+
| **3** | **Bare `Observation.value` removed**: only `presentedAs` exists | Two fields for one truth is the same defect as the `ref`/`type`/`id` triple removed in this version. A producer would fill both, inconsistently |
|
|
754
|
+
|
|
755
|
+
One addition introduced here and absent from the earlier documents: the **`extensions`** field (§13.2), needed so that closing the wire-format objects does not make application extensions impossible.
|
|
756
|
+
|
|
757
|
+
### 14.1 Schema hardening
|
|
758
|
+
|
|
759
|
+
A review of the first schema draft found four constraints that were stated in prose but not enforced, and one inconsistency. All are now closed in both documents:
|
|
760
|
+
|
|
761
|
+
| Finding | Resolution |
|
|
762
|
+
|---|---|
|
|
763
|
+
| `presentedAs` accepted `{ "unit": "percent" }` — a unit presenting nothing | `value` or `text` is now required (§4.6) |
|
|
764
|
+
| `value` accepted arbitrarily large objects and strings; only arrays of 32+ items were blocked | `value` is a compact scalar; structures live behind `resourceRef` (§4.6) |
|
|
765
|
+
| Free text was admitted "with provenance", but `source` was optional | `source` is required whenever `text` is present (§4.6) |
|
|
766
|
+
| `errorCode` accepted a full sentence, contradicting its own description | `errorCode` is a namespaced code and implies `error: true` (§4.3) |
|
|
767
|
+
| `schemaVersion: "0.2"` was declared SemVer, which would be `0.2.0` | It is a `major.minor` compatibility version (§13.1) |
|
|
768
|
+
| `entityRef` claimed percent-encoding but accepted `%ZZ` and a trailing `%` | The pattern now admits only unreserved and sub-delimiter characters or valid `%XX` octets (§5.1) |
|
|
769
|
+
| The timestamp pattern was described as rejecting any malformed value, while it constrains lexical shape only | The claim is narrowed, and temporal validity becomes `invalid-timestamp-value` in the semantic validator (§12.3) |
|
|
770
|
+
|
|
771
|
+
### 14.2 Composition and attention (clarification pass)
|
|
772
|
+
|
|
773
|
+
A page is normally made of components, each declaring its own semantic fragment; someone must then **merge** those fragments and **decide which one holds attention**. The v0.2 wire format already answers the first question by construction — `scopes` is a flat array with `parentId`, so merging is concatenation rather than tree surgery — but it answered the second only by saying the list is "ordered by salience", without ever saying who orders it.
|
|
774
|
+
|
|
775
|
+
Leaving that unsaid was the real gap, and it is closed here **without adding a mechanism**: the pilot has one store and no components, so any arbitration algorithm would be designed against no case that could falsify it. What is added is the assignment of responsibility (§4.1), the rule that identity is per instance (§4.2), the fact that `parentId` is semantic rather than positional (§4.2), and the atomicity of a snapshot (§10.3) — each a consequence of rules the specification already contained. The default ranking of reasons is a SHOULD, and it is admissible only because vops genuinely exercises it: route, selection and overlay do compete on the same page.
|
|
776
|
+
|
|
777
|
+
The arbitration mechanism itself, instance keying, ownership and any canonical ordering of `scopes` wait for the second, component-based binding. A.9 now names them among the things the pilot does not prove.
|
|
778
|
+
|
|
779
|
+
Two further clarifications came from asking what happens when an assistant is docked beside the page: **attention is never DOM focus** (§4.1) and **a Surface-observing UI contributes no scopes** (§2.4). Both were already implied — a claim is a function of state, and an inspector is not part of the product — but an implementer holding `document.activeElement` would have made the obvious mistake, and the assistant would have stolen the context the moment it was used.
|
|
780
|
+
|
|
781
|
+
Neither the wire format nor the schema changed in this pass, so `schemaVersion` remains `0.2`.
|
|
782
|
+
|
|
783
|
+
### 14.3 Boundary of the schema
|
|
784
|
+
|
|
785
|
+
The schema review established the boundary of §12.3: a JSON Schema validates one document's shape and cannot see cross-references, history or producer behaviour. That is not a defect of the schema — it is the reason the semantic validator exists as a named, separate level rather than as an unstated expectation.
|
|
786
|
+
|
|
787
|
+
---
|
|
788
|
+
|
|
789
|
+
## Annex A — vops pilot *(informative)*
|
|
790
|
+
|
|
791
|
+
This annex is application-specific and translates the model onto the product's real objects. It does not contribute to conformance.
|
|
792
|
+
|
|
793
|
+
### A.1 Page
|
|
794
|
+
|
|
795
|
+
The pilot page is the **Host detail** view of vops, the sponsoring product. Its markup, its view state and its page logic live in that product's source tree, not in this repository, so nothing in this annex can be opened from here: it is recorded because a binding that was actually built is the only evidence a model of this kind survives contact with a real screen.
|
|
796
|
+
|
|
797
|
+
Correction relative to the earlier documents, verified against the code: there is **no "Server Details" page**; there is **no "active tab"** — the segmented control is a **time window** (`mon.range`, shared with the Monitoring page) — and there is **no "selected deployment"** on that page: installed apps live in a different view.
|
|
798
|
+
|
|
799
|
+
### A.2 Pilot scopes
|
|
800
|
+
|
|
801
|
+
| Scope | kind | Content |
|
|
802
|
+
|---|---|---|
|
|
803
|
+
| `host-detail` | `page` | primary host entity, provider, presented address, connection state |
|
|
804
|
+
| `host-live-status` | `region` | presented cpu/ram/disk with `observedAt`, active time window, `resourceRef` to the full series, `state` |
|
|
805
|
+
| `host-checks` | `list` | count and worst severity of findings, `completeness`; **never** the full texts |
|
|
806
|
+
| selection | `selection` | the opened finding, when one exists |
|
|
807
|
+
|
|
808
|
+
### A.3 Not to be exposed
|
|
809
|
+
|
|
810
|
+
Capability hints; permissions; risk level; approval policy; full metric series; whole logs; full finding texts; secrets; summaries invented for the model.
|
|
811
|
+
|
|
812
|
+
### A.4 Data path
|
|
813
|
+
|
|
814
|
+
1. the UI builds the snapshot from pure definitions over existing state;
|
|
815
|
+
2. the message to the assistant carries the **user's plain text** plus `surface` and `surfaceRevision` — the current `"About X:"` text prefix is retired, because it pollutes the stored conversation history;
|
|
816
|
+
3. the receiver validates against the schema, applies the cap, and silently discards an invalid snapshot;
|
|
817
|
+
4. towards the model: a **compact deterministic digest** in the prompt, encapsulated as untrusted data, plus a **read-only tool** returning the full snapshot on request;
|
|
818
|
+
5. only `{ surfaceId, revision, route, entityRefs }` is persisted to the audit trail — never the full snapshot.
|
|
819
|
+
|
|
820
|
+
### A.5 Positive questions
|
|
821
|
+
|
|
822
|
+
1. "Which host am I looking at?"
|
|
823
|
+
2. "How is it doing?"
|
|
824
|
+
3. "Why is this CPU high?"
|
|
825
|
+
4. "Show me the top consuming processes."
|
|
826
|
+
5. "What does this check say?"
|
|
827
|
+
6. "Restart this host."
|
|
828
|
+
|
|
829
|
+
### A.6 Negative controls
|
|
830
|
+
|
|
831
|
+
1. a question about a host that is not visible MUST NOT be resolved against the current host;
|
|
832
|
+
2. a view with no selection MUST NOT produce an invented selection;
|
|
833
|
+
3. a stale value MUST NOT be presented as current state without verification;
|
|
834
|
+
4. hostile text inside findings — which arrive from the remote machine over SSH — MUST NOT become an instruction: the test is run by seeding real hostile strings on the staging box;
|
|
835
|
+
5. an unavailable capability MUST NOT be inferred from the UI;
|
|
836
|
+
6. changing page during reasoning MUST produce an answer markable as based on an earlier view.
|
|
837
|
+
|
|
838
|
+
### A.7 Measurement
|
|
839
|
+
|
|
840
|
+
Two arms, holding model, system prompt, tools, authorisations, dataset and maximum turn count constant:
|
|
841
|
+
|
|
842
|
+
```text
|
|
843
|
+
A. assistant + authoritative tools, no Semantic Surface
|
|
844
|
+
B. assistant + authoritative tools, with Semantic Surface
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
Metrics, all from machine artefacts rather than a model judge: correct entity-ref resolution; first tool call carrying the correct identifier; number of calls before correct grounding; clarification requests; precision of the first capability chosen; deictic errors; bytes and tokens added; answers based on stale data; hostile-string containment; false associations on the negative questions.
|
|
848
|
+
|
|
849
|
+
**Thresholds MUST be fixed before the run.**
|
|
850
|
+
|
|
851
|
+
### A.8 Pilot acceptance criteria
|
|
852
|
+
|
|
853
|
+
1. the Surface validates against the schema;
|
|
854
|
+
2. the assistant identifies the current host without the user naming it;
|
|
855
|
+
3. changing selection changes the entity ref;
|
|
856
|
+
4. an inactive scope never appears in the snapshot;
|
|
857
|
+
5. loading, error and empty are distinguishable;
|
|
858
|
+
6. a truncated list declares its incompleteness;
|
|
859
|
+
7. no secret appears in the snapshot;
|
|
860
|
+
8. no capability hints are present;
|
|
861
|
+
9. mutations use explicit `ref` values in tool calls;
|
|
862
|
+
10. the authoritative side revalidates authorisation, state and approval;
|
|
863
|
+
11. the snapshot stays within budget or declares truncation;
|
|
864
|
+
12. the answer retains its originating revision;
|
|
865
|
+
13. the UI behaves identically with the Surface disabled;
|
|
866
|
+
14. arm B clears the uplift thresholds fixed before the test.
|
|
867
|
+
|
|
868
|
+
### A.9 What the pilot does NOT prove
|
|
869
|
+
|
|
870
|
+
| Particularity | Hypothesis left unverified | Second case required |
|
|
871
|
+
|---|---|---|
|
|
872
|
+
| No component lifecycle | register/unregister, cleanup, ownership, **arbitration of attention between competing components**, instance keying, whether `scopes` needs a canonical order | a component binding (Angular/Flui) |
|
|
873
|
+
| Assistant is a one-shot external process | mid-conversation context refresh | a persistent in-browser assistant |
|
|
874
|
+
| Pre-existing, strong governance | whether the Surface degrades security where tools are bare wrappers | an application with no action broker |
|
|
875
|
+
| Single user, local, one trust domain | §8.3, cross-tenant, staleness from concurrent actors | a multi-tenant application |
|
|
876
|
+
| Small surface, depth 2 | de-duplication, revision churn, performance | a page with dozens of scopes |
|
|
877
|
+
|
|
878
|
+
Claim permitted on success:
|
|
879
|
+
|
|
880
|
+
> The Semantic Surface improves contextual grounding for an assistant with authoritative tools on one single-page UI.
|
|
881
|
+
|
|
882
|
+
Claims **not** permitted: universal portability; cross-origin security; effectiveness on every framework; effectiveness on CLI, TUI or voice; suitability as a web standard; general cost reduction.
|
|
883
|
+
|
|
884
|
+
---
|
|
885
|
+
|
|
886
|
+
## Annex B — Positioning and prior art *(informative)*
|
|
887
|
+
|
|
888
|
+
| Item | Relationship to the Semantic Surface |
|
|
889
|
+
|---|---|
|
|
890
|
+
| **WebMCP** (W3C Web Machine Learning CG) | Standardises tool registration by the page (`document.modelContext.registerTool`, JS functions or `<form>` elements); **observed state is explicitly out of scope**. An early proposal, not a standard, without widespread implementation. It is the natural complement: the Surface is the context side of that action side. No dependency is created |
|
|
891
|
+
| **MCP** | The authoritative operational channel. It provides fresh data, capabilities, guardrails and execution; the Surface provides attention, references and presentation. `resourceRef` points naturally at its resources |
|
|
892
|
+
| **Accessibility tree / ARIA** | The closest existing thing, and not enough: per-widget, without domain entities, without salience, without freshness, and far more expensive in tokens. From it comes the lesson behind §11.4: declarative binding in the markup is what survives |
|
|
893
|
+
| **schema.org / JSON-LD** | From it comes the profile mechanism — adopted in minimal form in §6 — and the adoption lesson: a standard takes hold when an important consumer reads it and returns value. It does not cover the Surface: it describes published documents, not the runtime state of a view |
|
|
894
|
+
| **W3C Web of Things — Thing Description** | A close structural parallel: properties ≈ observations, actions ≈ capabilities, plus *events*, which are absent here and remain a future candidate |
|
|
895
|
+
| **OpenTelemetry Semantic Conventions** | Vocabulary reused for metrics (§6.1), and a governance model worth imitating |
|
|
896
|
+
|
|
897
|
+
### A note on adoption
|
|
898
|
+
|
|
899
|
+
This version has **one consumer, and it is the first party**. That is stated deliberately: it guards against over-designing for adopters who do not yet exist, and it keeps every portability claim honest until a second implementation — preferably SSR, the hardest test — has demonstrated it.
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## Provenance
|
|
904
|
+
|
|
905
|
+
| Document | Role |
|
|
906
|
+
|---|---|
|
|
907
|
+
| `vops-semantic-surface-spec.md` | v0.1, historical (Italian) — internal, not distributed |
|
|
908
|
+
| `semantic-surface-v0.1-review.md` | technical review, 24 recommendations, historical (Italian) — internal, not distributed |
|
|
909
|
+
| `semantic-surface-response-to-review-v0.2.md` | decision record, historical (Italian) |
|
|
910
|
+
| **`semantic-surface-core-v0.2.md`** | **current specification** |
|
|
911
|
+
| `semantic-surface.schema.json` | current validation schema |
|