@mhome/app-facade-protocol 1.85.0 → 1.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -81,10 +81,11 @@ are separate from this result envelope.
81
81
 
82
82
  App UI requests full and preserves the complete envelope through transport and SDK
83
83
  parsing. UI consumers explicitly read data and handle warnings and non-success outcomes.
84
- Essential YAML omits null-valued object fields recursively after domain projection.
85
- This shared rule also applies to error diagnostics and visible warning details.
86
- Full values, false, zero, strings (including empty strings), empty collections,
87
- array positions and a root null value are preserved. This is output-only; it
84
+ Essential YAML preserves all values selected by the domain projection, including
85
+ explicit business nulls in resource values, histories and diagnostic details.
86
+ Domains explicitly omit optional configuration fields when appropriate; the serializer
87
+ never guesses whether null means absence. False, zero, strings, empty collections and
88
+ array positions are preserved. This is output-only; it
88
89
  never changes request semantics, stored data or prepared actions.
89
90
 
90
91
  Agent and MCP public results use essential; internal workflow reads use full. The CLI
@@ -264,3 +265,6 @@ Cloud-owned content uses an object-store signed URL; local content is read from
264
265
  owning Hub on demand. External resolution creates no cloud replica and cannot
265
266
  make an offline local owner readable. The current inline Hub bridge size limit
266
267
  still applies. Local file download remains a client workflow over ordinary resolve.
268
+
269
+ Agent Profile Markdown image ownership and canonical read-back are specified in
270
+ [Agent Profile media](contract/agent-profile-media-v1.md).
@@ -8,15 +8,17 @@ identity come exclusively from authenticated ingress, never from domain input.
8
8
 
9
9
  ## Catalog and publication
10
10
 
11
- `/app/card-skins/catalog/*` always executes in Cloud. Catalog reads require a
11
+ `/app/ui/customize/cards/catalog/*` always executes in Cloud. Catalog reads require a
12
12
  User principal. Package creation reserves `packageId` in that tenant. Only the
13
13
  stored author may edit its display metadata or publish versions. Authorship is
14
- immutable. Package lookup is by known package ID; this is not a public marketplace
14
+ immutable. Creation requires `baseUi` (for example `default` or `default-pod`).
15
+ The package keeps this base across all versions; prepare and complete reject a
16
+ manifest with another base. Package lookup is by known package ID; this is not a public marketplace
15
17
  or a searchable cross-tenant catalog.
16
18
 
17
19
  | Target suffix | Input definition | Output |
18
20
  | --- | --- | --- |
19
- | `catalog/package/get` | `packageGet` | `{revision, value: {packageId, authorUserId, name, description, createdAt}}` |
21
+ | `catalog/package/get` | `packageGet` | `{revision, value: {packageId, baseUi, authorUserId, name, description, createdAt}}` |
20
22
  | `catalog/package/create` | `packageCreate` | `{}` |
21
23
  | `catalog/package/update` | `packageUpdate` | `{}` |
22
24
  | `catalog/release/get` | `releaseGet` | `{id, baseUrl, manifest}` |
@@ -34,35 +36,44 @@ only supported asset extensions with their exact media type, size and SHA-256.
34
36
  Schema draft-07, with only fragment `$ref`s, no `$async`, and at most 64 levels.
35
37
  Each config root must have `type: object`.
36
38
 
37
- Each prepared session binds one manifest, author and immutable delivery directory.
38
- It expires after 15 minutes. Upload each file with HTTP PUT to its returned URL
39
- and the exact returned headers. Upload grants are bearer secrets; only their
40
- SHA-256 digests are persisted. The server verifies the actual body before writing
41
- it to a private bucket. S3 conditional create / OSS forbid-overwrite makes each
42
- verified object immutable; clients never receive an object-store write URL.
43
- A repeated identical PUT is safe. Different bytes, sizes or media types fail.
44
-
45
- Complete checks authorship, session expiry and all server-verified immutable
46
- object receipts before a create-only release write. Missing files never create a
47
- release. Two sessions racing for the same version have one winner. Retrying the
48
- winning completion returns that exact release, including after session expiry;
49
- a different session cannot replace it. Unknown storage outcomes are errors, not
50
- automatically replayed business operations. Reload the version to resolve them.
51
- A newer version requires another prepare/upload/complete cycle.
52
-
53
- Published `baseUrl` is a stable HTTPS directory. GET of a declared file checks the
54
- published release and its random delivery-directory ID, verifies content integrity,
55
- and serves the correct media type with CORS, `nosniff`, and immutable caching.
56
- There is no expiring signed URL in a release. Unpublished files cannot be fetched
57
- through delivery. A delivery URL is shareable: knowledge of it permits reading
58
- that release's executable bytes, without granting catalog or upload permissions.
59
- Do not put secrets in skins. Config values stay in the Space selection.
39
+ Each prepared session binds a manifest, authenticated author and immutable delivery directory.
40
+ It expires after 15 minutes. Each file's returned URL and exact headers authorize a direct
41
+ S3/OSS PUT to a private staging key, binding its size and forbidding overwrite. Upload grants
42
+ are temporary secrets and are not persisted in the publishing session. Staging is outside
43
+ CloudFront's readable origin prefix. No App HTTP endpoint relays upload or download bytes.
44
+
45
+ Complete checks ownership and expiry, then reads the actual staged bytes with the declared
46
+ size plus one byte as a hard bound. Size, SHA-256 and completeness must match the manifest;
47
+ uploader-supplied object metadata is not an integrity receipt. The exact verified bytes are
48
+ written with create-only semantics to a separate published prefix. Only after all files
49
+ succeed can a create-only release become visible. Concurrent sessions for one version have
50
+ one winner. Retrying that winning completion returns its exact release even after expiry.
51
+ A failed completion may leave unreferenced objects, never a partial installable release.
52
+
53
+ Published `baseUrl` is a stable HTTPS directory under the configured static origin, for
54
+ example `https://static.mhome.ai/ui/cards/<namespace>/<package>/<version>/<deliveryId>/`.
55
+ CloudFront reads only the `published/` storage prefix and supplies CORS, correct media types,
56
+ `nosniff` and immutable caching. Temporary uploads use `staging/`, which CDN readers cannot
57
+ access. There is no expiring download signature in a release. Possession of a delivery URL
58
+ permits reading its public executable bytes; catalog and upload authority remain separate.
59
+ Config values stay in the Space selection and secrets must not enter card packages.
60
+
61
+ Web loads compiled modules from the static origin. Native clients may resolve a release to
62
+ verified local package files through a host-only resource port. Such runtime resource URLs
63
+ are never persisted in Catalog or Space selection records and do not alter release identity.
64
+ Package caching does not install, select, upgrade, or grant additional device authority.
65
+ All platforms retain the renderer isolation requirement, including for cached files.
60
66
 
61
67
  The publication service validates metadata and bytes, not JavaScript behavior.
68
+ The renderer contract remains `apiVersion: 1` during development and changes in
69
+ place. Hosts and skins use that current contract without a compatibility layer.
62
70
  The authoring build verifies the bundled ESM structure; the browser host verifies
63
- renderer exports against the manifest before using them. Skins run in the host's
64
- JavaScript realm and are not sandboxed by the UI interface. Publication is not a
65
- claim of a malicious-code audit or cryptographic browser SRI enforcement.
71
+ renderer exports against the manifest before using them. Each base owns its
72
+ presentation adapter and renderer host; common card state and actions do not
73
+ require a common rendering technology. Hosts must enforce their execution
74
+ boundary before enabling external code. A manifest or publication grants no
75
+ general App privileges, SDK access or native bridge access. Publication is not a
76
+ claim of runtime isolation, a malicious-code audit or browser SRI enforcement.
66
77
 
67
78
  ## Space selection
68
79
 
@@ -86,19 +97,23 @@ been created; otherwise it must equal the latest read token. A stale token retur
86
97
  non-null revision. Mutation responses acknowledge the write; read the selection
87
98
  again for its latest state/revision, which may include another writer's edit.
88
99
 
89
- Install takes a known package ID and exact version; the service resolves the
100
+ Install requires the target `baseUi`, a known package ID and exact version; the service resolves the
90
101
  published release from the tenant catalog. Core uses its authenticated cloud
91
102
  bridge for that read. Neither accepts client-provided release IDs, manifests or
92
- URLs. Installation does not automatically enable pattern overrides. Each package
103
+ URLs. The release base must equal the requested base. Installation records include
104
+ that base and must match their pinned release. Installation does not
105
+ automatically enable pattern overrides. Each package
93
106
  has one installation per Space; installing another version uses `upgrade`.
94
107
 
95
- Set override selects an installed renderer declaring that pattern and validates
96
- the supplied config against its schema. Reset removes just that pattern's
97
- override. Uninstall removes the installation, its pinned descriptor and every
108
+ Set override requires `baseUi` and selects an installation of that base with a renderer declaring that pattern and validates
109
+ the supplied config against its schema. Reset requires `baseUi` and removes only that base and pattern's override.
110
+ The unique selection key is `(Space, baseUi, patternId)`; the same pattern can
111
+ have independent overrides in different bases. Uninstall removes the installation, its pinned descriptor and every
98
112
  override referencing it in one commit. Reset/uninstall/config edits use local
99
113
  pinned metadata; they do not need a live catalog connection.
100
114
 
101
- Upgrade keeps the installation ID and all existing overrides. Every affected
115
+ Upgrade keeps the installation ID, UI base and all existing overrides. A release
116
+ with another base is rejected before any mutation. Every affected
102
117
  renderer must still exist, declare exactly the same pattern/contract ID/version,
103
118
  and accept the existing config. A different catalog identity is never silently
104
119
  substituted after Space transfer. Any incompatibility rejects the entire write;
@@ -113,12 +128,15 @@ publication does not assume every MeowLink client supports the same contracts.
113
128
 
114
129
  ## Deployment and boundaries
115
130
 
116
- The cloud service needs a private `card-skins.storage.bucket` on its configured
117
- AWS/OSS provider and an HTTPS `card-skins.delivery.base-url` reaching the binary
118
- controller. Object-store credentials must not grant end users overwrite/delete
119
- access to skin objects. Existing release directories and URLs must be preserved
120
- across deployment. A trusted reverse proxy/CDN may cache successful delivery;
121
- upload and error responses are `no-store`.
131
+ The cloud service needs a private `ui.customize.cards.storage.bucket` on its configured
132
+ AWS/OSS provider and the platform-wide HTTPS origin `resources.static.base-url` (normally
133
+ `https://static.mhome.ai`, without a path). CloudFront maps its origin to `published/`;
134
+ its S3 access policy must exclude `staging/`. End users receive only bounded, expiring
135
+ create-only staging writes. They receive no published-object overwrite/delete authority.
136
+ This development cutover removes the old binary routes without a compatibility alias.
137
+ Existing development releases/selections containing old delivery URLs require explicit
138
+ recreation; startup and ordinary reads never rewrite or erase them.
139
+
122
140
 
123
141
  Unfinished publishing sessions expire but their draft records and unreferenced
124
142
  objects are currently retained. This version provides no automatic garbage
@@ -136,3 +154,7 @@ a future local execution node. These source capabilities require their matching
136
154
  published artifacts and deployed services. See the frontend's
137
155
  [card guide](../../../../pallas-cat/docs/cards/README.md) for the authoring tools
138
156
  and management flow.
157
+
158
+ The UI-base fields are mandatory. There is no legacy-base inference or automatic
159
+ startup conversion. Existing incompatible development documents require explicit
160
+ recreation; ordinary reads and startup must not rewrite them.
@@ -1,8 +1,9 @@
1
1
  # Host provisioning v1
2
2
 
3
- Embedded Linux Hosts without a working network get Wi-Fi over BLE from a native
4
- Client (desktop/CLI Clientd, Android, iOS). Desktop, Docker and Android Hosts are
5
- already online and never advertise. Provisioning connects the Host to the LAN
3
+ BLE-capable embedded Linux Hosts get Wi-Fi or recover an unfinished first claim
4
+ over BLE from a native Client (desktop/CLI Clientd, Android, iOS). Desktop, Docker
5
+ and Android Hosts use LAN discovery rather than advertising BLE setup.
6
+ Provisioning connects the Host to the LAN
6
7
  and hands the provisioner a one-time claim token; ownership is then established
7
8
  by the existing Host claim over the LAN, which requires that token (section 4).
8
9
 
@@ -13,14 +14,16 @@ same characteristic UUID fallback.
13
14
 
14
15
  ## 1. Advertising
15
16
 
16
- - The Host advertises only in first provisioning, inside a re-provision window,
17
- or after a factory reset. A Host on a working network does not advertise.
17
+ - A BLE-capable Host advertises during first network provisioning, while awaiting
18
+ its first claim (including with a working network), or inside an explicit
19
+ re-provision window. Restart never reopens first-claim authority on an already
20
+ claimed Host.
18
21
  - Flags: bit 0 commissionable (set while advertising, except during a code
19
- lockout), bit 1 set when the Host still has a saved network (re-provision
20
- window).
22
+ lockout), bit 1 set when the Host has a saved network, including unclaimed
23
+ recovery and re-provision windows.
21
24
  - `shortId`: the first 4 bytes of the SHA-256 of the Host bootstrap public key,
22
- which survives factory reset. The bootstrap key only names the Host (`shortId`,
23
- `fingerprint`); it signs nothing in provisioning or claim.
25
+ which is replaced after factory reset. The bootstrap key only names the Host
26
+ (`shortId`, `fingerprint`); it signs nothing in provisioning or claim.
24
27
  - Local name: the Host name, at most 20 bytes.
25
28
 
26
29
  ## 2. Pairing code
@@ -84,18 +87,23 @@ Fallback characteristic UUIDs: the standard endpoints as for pods, `host-info`
84
87
  `productId` is optional. `onboardingRequired` reports whether this Host still
85
88
  awaits its first claim. `networkOnline` reports current LAN readiness. Both
86
89
  are optional for older Hosts. A securely paired Host with `networkOnline: true`
87
- and `onboardingRequired: true` can finish without resubmitting Wi-Fi. `finish` issues a fresh first-claim token after checking authoritative
88
- Host ownership. Already claimed Hosts never issue a new first-claim token.
89
- - `{"op":"finish"}` → `{"ok":true,"claimToken":"…"}`. After a successful join
90
- during first provisioning the response carries the claim token: 32 random
91
- bytes as 43 unpadded base64url characters. It is created when the join
92
- succeeds, the same token is returned by every `finish` until the Host closes
93
- BLE, and it is absent when no join succeeded and in a re-provision window
94
- (the Host already has an owner). The Host then closes
95
- BLE and continues its lifecycle (starts managed services, announces itself
96
- over mDNS). Without `finish` the Host closes BLE 30 seconds after a successful
97
- join, or when the central disconnects; the commissioner retries `finish`
98
- within that window.
90
+ and `onboardingRequired: true` can finish without resubmitting Wi-Fi. `finish`
91
+ issues a fresh first-claim token after checking authoritative Host ownership.
92
+ Already claimed Hosts never issue a new first-claim token.
93
+ - `{"op":"finish"}` → `{"ok":true,"claimToken":"…"}`. After a successful
94
+ first-provisioning join, or secure recovery with an already working network,
95
+ the response carries a claim token: 32 random bytes as 43 unpadded base64url
96
+ characters. A new join persists its token hash before applying Wi-Fi and
97
+ exposes the token only after success. Recovery issues a fresh token after
98
+ checking that the Host is still unclaimed. Repeated `finish` requests in the
99
+ same session return the same token. A claimed Host's re-provision window
100
+ carries no first-claim token.
101
+ `finish` ends the central's setup session. Without it, the Host disconnects the
102
+ central 30 seconds after a successful join; the commissioner retries `finish`
103
+ within that window. Network readiness and mDNS announcement do not establish
104
+ ownership. An unclaimed Host keeps its BLE recovery surface available;
105
+ a claimed Host closes a re-provision window
106
+ after the join.
99
107
 
100
108
  Every request and every encrypted response is at most 512 bytes; the response
101
109
  is read with offset reads. `prov-scan` start blocks while the Host scans and
@@ -157,9 +165,13 @@ Native `host.claim` passes it to `POST /v1/auth/claim`, alongside the environmen
157
165
  cloud proof and direct Client exchange secret (`core-api::host::access::ClaimRequest`).
158
166
  There is no Core-proxied first-claim route. The Host rejects a missing or incorrect
159
167
  provisioning token with 403 `HOST_CLAIM_TOKEN_REQUIRED`. The token remains valid
160
- until claim succeeds or factory reset. The App claims automatically after
161
- `completed`. Hosts that were not provisioned over BLE use the ordinary direct
162
- LAN claim flow. See the Foundation Host management authorization contract.
168
+ until claim succeeds, another setup issues a replacement token, or factory reset.
169
+ BLE `completed` confirms the network/handoff step, not Host ownership. The App's
170
+ Host onboarding operation waits for explicit ownership confirmation, then claims
171
+ over the LAN. It completes only after the existing claim API confirms cloud
172
+ registration. It does not install Core or Plugins. Hosts that were not provisioned
173
+ over BLE use the ordinary direct LAN claim flow. See the Foundation Host
174
+ management authorization contract.
163
175
 
164
176
  Error codes are the pod session codes without the credential ones:
165
177
  `bluetooth_unavailable`, `device_busy`, `disconnected`, `code_rejected` (retry
@@ -185,5 +197,6 @@ The schema is `schema/host-provision.v1.schema.json`.
185
197
  are kept. Closes after a successful join, on `meowhostd provision close`, or
186
198
  after 10 minutes.
187
199
  - Factory reset: from the local interface (`meowhostd factory-reset`). Stops
188
- services, forgets saved Wi-Fi, erases owners and the Host security store, keeps
189
- the bootstrap key, and returns to first provisioning.
200
+ services, forgets saved Wi-Fi on BLE provisioning appliances, erases owners,
201
+ the Host security store and bootstrap key, and returns to first provisioning.
202
+ Machine identity and installed software are retained.
@@ -0,0 +1,56 @@
1
+ # Now attention
2
+
3
+ `/app/now/attention/list` is a Space-read Facade routed by Space execution mode.
4
+ Input is an empty object inside `FacadeCall.input`. Identity comes from the
5
+ verified caller, never an input user ID. Existing Dialog, Twin and reconnect events invalidate the view. Time boundaries
6
+ also refresh it while the page is visible.
7
+
8
+ All eligible items use one score:
9
+ `100 * relation * time * (0.625 * impact + 0.375 * necessity)`.
10
+ There are no source or severity tiers. Personal owner: 1; household: 0.8;
11
+ other personal owner: 0. Round scores to four decimal places, then sort descending
12
+ and by stable ID ascending for ties. The display threshold is 20.
13
+
14
+ Recipients control visibility and are not ownership. Without explicit business
15
+ ownership, shared household facts remain household facts, even if delivered to
16
+ one user. Personal ownership must be declared by the business source.
17
+
18
+ Decay uses the original occurrence time and a configured half-life; retries,
19
+ reads and redelivery must not reset it. Window relevance rises from zero at
20
+ start to one at peak, remains one until deadline, and is zero at deadline.
21
+ Persistent relevance stays one until the underlying condition is cleared.
22
+ Unknown observations do not clear a previously active condition. Expiration of a
23
+ request invalidates that request, but does not resolve a separate device issue.
24
+
25
+ The policy manifest and conformance fixtures are canonical across Rust and Java.
26
+ Unknown reasons and invalid time models have zero score. Policy updates do not
27
+ change business lifecycle state. Ordinary running, recent activity, scheduled
28
+ execution and notification priority alone do not establish attention eligibility.
29
+
30
+ The output contains items, evaluatedAt, nextRefreshAt, policyVersion and
31
+ sourceErrors. Source failures must not be represented as an authoritative empty
32
+ view. Actions continue through their business Facades with fresh permission and
33
+ state checks. Only confirmed completion removes an item; accepting a device
34
+ command is not confirmation that the condition was resolved.
35
+
36
+ Current adapters read pending Dialogs from their authoritative store, applying the
37
+ current audience, expiry and status on each read. Optional `attentionContext`
38
+ can declare `dialog.pending`, `task.userDecision` or `schedule.decisionRequired`.
39
+ Without it, pending Dialogs use household association and a 15-minute half-life.
40
+ Task failure, notification priority and schedule trigger times are not inferred
41
+ as user-decision facts.
42
+
43
+ Standard device facts currently cover `state.water` (`wet` / `dry`) and
44
+ `state.smoke` (`detected` / `clear`). Other readings, including unknown/testing,
45
+ do not create an incident and do not resolve an existing incident. The small
46
+ retained device projection preserves the episode across restarts and Space
47
+ execution-mode migration. Its revision is owned by Attention, independent of
48
+ rebuildable Twin cache counters. Each reconciliation reads current canonical
49
+ Twin state, including retained-history recovery, rather than replaying event
50
+ payloads. Device metadata and retained episodes cover cold/missing caches.
51
+
52
+ All timestamps are epoch milliseconds. `sourceUpdatedAt` is the underlying
53
+ state-change timestamp, not a heartbeat or proof of current connectivity.
54
+ `verificationRequired` marks a retained issue whose current reading is unknown.
55
+ Only one execution owner serves a Space; local and cloud candidates are not
56
+ merged into a second queue. Source errors remain visible alongside usable items.
@@ -0,0 +1,263 @@
1
+ [
2
+ {
3
+ "name": "personal",
4
+ "score": 62.5,
5
+ "reasonCode": "dialog.pending",
6
+ "eligible": true,
7
+ "association": {
8
+ "kind": "personal",
9
+ "ownerUserId": "me"
10
+ },
11
+ "userId": "me",
12
+ "time": {
13
+ "mode": "persistent"
14
+ },
15
+ "now": 1000000
16
+ },
17
+ {
18
+ "name": "household",
19
+ "score": 50,
20
+ "reasonCode": "dialog.pending",
21
+ "eligible": true,
22
+ "association": {
23
+ "kind": "household"
24
+ },
25
+ "userId": "me",
26
+ "time": {
27
+ "mode": "persistent"
28
+ },
29
+ "now": 1000000
30
+ },
31
+ {
32
+ "name": "not mine",
33
+ "score": 0,
34
+ "reasonCode": "dialog.pending",
35
+ "eligible": true,
36
+ "association": {
37
+ "kind": "personal",
38
+ "ownerUserId": "me"
39
+ },
40
+ "userId": "other",
41
+ "time": {
42
+ "mode": "persistent"
43
+ },
44
+ "now": 1000000
45
+ },
46
+ {
47
+ "name": "shared screen excludes personal",
48
+ "score": 0,
49
+ "reasonCode": "dialog.pending",
50
+ "eligible": true,
51
+ "association": {
52
+ "kind": "personal",
53
+ "ownerUserId": "me"
54
+ },
55
+ "userId": null,
56
+ "time": {
57
+ "mode": "persistent"
58
+ },
59
+ "now": 1000000
60
+ },
61
+ {
62
+ "name": "ineligible",
63
+ "score": 0,
64
+ "reasonCode": "dialog.pending",
65
+ "eligible": false,
66
+ "association": {
67
+ "kind": "personal",
68
+ "ownerUserId": "me"
69
+ },
70
+ "userId": "me",
71
+ "time": {
72
+ "mode": "persistent"
73
+ },
74
+ "now": 1000000
75
+ },
76
+ {
77
+ "name": "unknown reason",
78
+ "score": 0,
79
+ "reasonCode": "unknown",
80
+ "eligible": true,
81
+ "association": {
82
+ "kind": "personal",
83
+ "ownerUserId": "me"
84
+ },
85
+ "userId": "me",
86
+ "time": {
87
+ "mode": "persistent"
88
+ },
89
+ "now": 1000000
90
+ },
91
+ {
92
+ "name": "half life",
93
+ "score": 31.25,
94
+ "reasonCode": "dialog.pending",
95
+ "eligible": true,
96
+ "association": {
97
+ "kind": "personal",
98
+ "ownerUserId": "me"
99
+ },
100
+ "userId": "me",
101
+ "time": {
102
+ "mode": "decay",
103
+ "occurredAt": 100000,
104
+ "halfLifeMs": 900000
105
+ },
106
+ "now": 1000000
107
+ },
108
+ {
109
+ "name": "one hour",
110
+ "score": 3.9063,
111
+ "reasonCode": "dialog.pending",
112
+ "eligible": true,
113
+ "association": {
114
+ "kind": "personal",
115
+ "ownerUserId": "me"
116
+ },
117
+ "userId": "me",
118
+ "time": {
119
+ "mode": "decay",
120
+ "occurredAt": 100000,
121
+ "halfLifeMs": 900000
122
+ },
123
+ "now": 3700000
124
+ },
125
+ {
126
+ "name": "invalid decay",
127
+ "score": 0,
128
+ "reasonCode": "dialog.pending",
129
+ "eligible": true,
130
+ "association": {
131
+ "kind": "personal",
132
+ "ownerUserId": "me"
133
+ },
134
+ "userId": "me",
135
+ "time": {
136
+ "mode": "decay",
137
+ "occurredAt": 100000,
138
+ "halfLifeMs": 0
139
+ },
140
+ "now": 1000000
141
+ },
142
+ {
143
+ "name": "future creation",
144
+ "score": 0,
145
+ "reasonCode": "dialog.pending",
146
+ "eligible": true,
147
+ "association": {
148
+ "kind": "personal",
149
+ "ownerUserId": "me"
150
+ },
151
+ "userId": "me",
152
+ "time": {
153
+ "mode": "decay",
154
+ "occurredAt": 1000001,
155
+ "halfLifeMs": 900000
156
+ },
157
+ "now": 1000000
158
+ },
159
+ {
160
+ "name": "window before start",
161
+ "score": 0,
162
+ "reasonCode": "dialog.pending",
163
+ "eligible": true,
164
+ "association": {
165
+ "kind": "personal",
166
+ "ownerUserId": "me"
167
+ },
168
+ "userId": "me",
169
+ "time": {
170
+ "mode": "window",
171
+ "start": 100,
172
+ "peak": 200,
173
+ "deadline": 300
174
+ },
175
+ "now": 99
176
+ },
177
+ {
178
+ "name": "window rising",
179
+ "score": 31.25,
180
+ "reasonCode": "dialog.pending",
181
+ "eligible": true,
182
+ "association": {
183
+ "kind": "personal",
184
+ "ownerUserId": "me"
185
+ },
186
+ "userId": "me",
187
+ "time": {
188
+ "mode": "window",
189
+ "start": 100,
190
+ "peak": 200,
191
+ "deadline": 300
192
+ },
193
+ "now": 150
194
+ },
195
+ {
196
+ "name": "window plateau",
197
+ "score": 62.5,
198
+ "reasonCode": "dialog.pending",
199
+ "eligible": true,
200
+ "association": {
201
+ "kind": "personal",
202
+ "ownerUserId": "me"
203
+ },
204
+ "userId": "me",
205
+ "time": {
206
+ "mode": "window",
207
+ "start": 100,
208
+ "peak": 200,
209
+ "deadline": 300
210
+ },
211
+ "now": 299
212
+ },
213
+ {
214
+ "name": "window deadline",
215
+ "score": 0,
216
+ "reasonCode": "dialog.pending",
217
+ "eligible": true,
218
+ "association": {
219
+ "kind": "personal",
220
+ "ownerUserId": "me"
221
+ },
222
+ "userId": "me",
223
+ "time": {
224
+ "mode": "window",
225
+ "start": 100,
226
+ "peak": 200,
227
+ "deadline": 300
228
+ },
229
+ "now": 300
230
+ },
231
+ {
232
+ "name": "invalid window",
233
+ "score": 0,
234
+ "reasonCode": "dialog.pending",
235
+ "eligible": true,
236
+ "association": {
237
+ "kind": "personal",
238
+ "ownerUserId": "me"
239
+ },
240
+ "userId": "me",
241
+ "time": {
242
+ "mode": "window",
243
+ "start": 200,
244
+ "peak": 100,
245
+ "deadline": 300
246
+ },
247
+ "now": 150
248
+ },
249
+ {
250
+ "name": "persistent old hazard",
251
+ "score": 74,
252
+ "reasonCode": "device.waterDetected",
253
+ "eligible": true,
254
+ "association": {
255
+ "kind": "household"
256
+ },
257
+ "userId": "me",
258
+ "time": {
259
+ "mode": "persistent"
260
+ },
261
+ "now": 9000000000000
262
+ }
263
+ ]