@everfur/cli 0.1.0 → 0.2.1

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.
@@ -1,1335 +0,0 @@
1
- {
2
- "digest": {
3
- "algorithm": "sha256",
4
- "value": "40c1345cfcbff5102e770ae2c540a70e1d19090a6623cee3486110d49d7ec2c5"
5
- },
6
- "generatedAt": "2026-08-30T07:05:23Z",
7
- "kind": "everfur.contract-public",
8
- "projection": {
9
- "action-classes.yaml": {
10
- "action_classes": [
11
- {
12
- "default_exposure": "agent_allowed",
13
- "description": "Reads only material that is the same for every caller: the contract itself, an error registry, a capability list. No tenant data is touched and no state changes.",
14
- "id": "A0",
15
- "reaches_tenant_data": false,
16
- "reversible": true,
17
- "title": "Inert read"
18
- },
19
- {
20
- "default_exposure": "agent_with_confirmation",
21
- "description": "Creates or updates tenant state that the owner can inspect and undo: a pet profile, a consent row, a records request, a message turn. The write is visible to the owner and has a defined reversal.",
22
- "id": "A3",
23
- "reaches_tenant_data": true,
24
- "reversible": true,
25
- "title": "Reversible write"
26
- },
27
- {
28
- "default_exposure": "agent_with_confirmation",
29
- "description": "Reads tenant-scoped material that is not health content: a pet profile, a request status, a list of conversation titles. Wrong-caller risk is a metadata disclosure, not a health disclosure.",
30
- "id": "A1",
31
- "reaches_tenant_data": true,
32
- "reversible": true,
33
- "title": "Scoped read"
34
- },
35
- {
36
- "default_exposure": "never_agent",
37
- "description": "Changes the machinery that decides who may do any of the above: minting or revoking a credential, granting or denying a capability, flipping a runtime gate. A wrong invocation here is not one bad request, it is a changed posture for every subsequent request.",
38
- "id": "A5",
39
- "reaches_tenant_data": true,
40
- "reversible": false,
41
- "title": "Control-plane change"
42
- },
43
- {
44
- "default_exposure": "never_agent",
45
- "description": "Produces an effect that cannot be taken back: content handed to a third party, an export written to a downloadable artifact, a share link, an outbound send. Reversal changes future access only, never the copy the recipient already holds.",
46
- "id": "A4",
47
- "reaches_tenant_data": true,
48
- "reversible": false,
49
- "title": "Unrecallable effect"
50
- },
51
- {
52
- "default_exposure": "never_agent",
53
- "description": "Reads restricted content: a medical record projection, a conversation transcript, a classification derived from either. A read cannot be undone once it has been served, so this class carries the never_agent default even though nothing is written.",
54
- "id": "A2",
55
- "reaches_tenant_data": true,
56
- "reversible": true,
57
- "title": "Restricted-content read"
58
- }
59
- ],
60
- "exposures": [
61
- {
62
- "description": "An agent may invoke this without a human in the loop. Reserved for calls that read nothing tenant-scoped and change nothing.",
63
- "id": "agent_allowed",
64
- "rank": 0
65
- },
66
- {
67
- "description": "An agent may prepare the call, but a human must approve the specific invocation before it is sent. The confirmation must name the concrete effect, not the capability in the abstract.",
68
- "id": "agent_with_confirmation",
69
- "rank": 1
70
- },
71
- {
72
- "description": "No agent path exists. The call is reachable only from a first-party surface driven by the person the data belongs to. A never_agent action publishes its id, its exposure, and its class, and nothing else.",
73
- "id": "never_agent",
74
- "rank": 2
75
- }
76
- ],
77
- "schema_version": 1
78
- },
79
- "capabilities.yaml": {
80
- "packs": [
81
- {
82
- "actions": [
83
- {
84
- "action_class": "A1",
85
- "detail": "Polls the status of an asynchronous analysis job. Returns job state; the result payload it eventually carries is the analysis output, not the source media.",
86
- "exposure": "agent_with_confirmation",
87
- "id": "poll_analysis_job",
88
- "route": "GET /api/v1/analysis/jobs/{job_id}"
89
- },
90
- {
91
- "action_class": "A3",
92
- "exposure": "never_agent",
93
- "id": "submit_gait_analysis"
94
- }
95
- ],
96
- "data_class": "confidential",
97
- "entitlement": {
98
- "key": "video.gait.execute",
99
- "risk_tier": "standard"
100
- },
101
- "id": "video.gait.execute",
102
- "idempotent_operations": [],
103
- "required_disclaimer": {
104
- "confirmed_on_wire": false,
105
- "id": "none",
106
- "note": "No disclaimer copy is attached to the analysis surface in code. The analysis response carries interpretation text but no separate product-owned disclaimer field.",
107
- "text": null
108
- },
109
- "runtime_gate": {
110
- "fail_closed": true,
111
- "kind": "ddb_flag"
112
- },
113
- "support": "support@everfur.com",
114
- "surface": [
115
- "consumer_app"
116
- ],
117
- "title": "Run gait and mobility analysis on an uploaded video",
118
- "version": "1.0.0"
119
- },
120
- {
121
- "actions": [
122
- {
123
- "action_class": "A1",
124
- "detail": "Returns conversation containers with titles and cursors for the resolved user_ref. Titles are user-authored and may carry content, so this is scoped metadata rather than an inert read.",
125
- "exposure": "agent_with_confirmation",
126
- "id": "list_conversations",
127
- "route": "GET /api/v1/widget/v1/conversations"
128
- },
129
- {
130
- "action_class": "A2",
131
- "exposure": "never_agent",
132
- "id": "get_messages"
133
- }
134
- ],
135
- "data_class": "restricted",
136
- "entitlement": {
137
- "key": "chat.conversation.read",
138
- "risk_tier": "standard"
139
- },
140
- "id": "chat.conversation.read",
141
- "idempotent_operations": [],
142
- "required_disclaimer": {
143
- "confirmed_on_wire": false,
144
- "id": "entitlement_verdict_disclaimer",
145
- "note": "No chat-surface disclaimer copy exists in code. See chat.message.create.",
146
- "text": null
147
- },
148
- "runtime_gate": {
149
- "fail_closed": true,
150
- "key": "chat_enabled",
151
- "kind": "client_feature"
152
- },
153
- "support": "support@everfur.com",
154
- "surface": [
155
- "partner_widget"
156
- ],
157
- "title": "Read widget conversations and their transcripts",
158
- "version": "1.0.0"
159
- },
160
- {
161
- "actions": [
162
- {
163
- "action_class": "A1",
164
- "detail": "Returns one request's status. A request whose pet is not one of this caller's widget pets is reported as not found.",
165
- "exposure": "agent_with_confirmation",
166
- "id": "get_request",
167
- "route": "GET /api/v1/widget/v1/records/requests/{request_id}"
168
- },
169
- {
170
- "action_class": "A1",
171
- "detail": "Returns request status rows for the caller's own widget pets. Status and timestamps only; never document content.",
172
- "exposure": "agent_with_confirmation",
173
- "id": "list_requests",
174
- "route": "GET /api/v1/widget/v1/records/requests"
175
- },
176
- {
177
- "action_class": "A1",
178
- "detail": "Returns whether consent is granted, at which copy version, and when. Consent metadata, not record content.",
179
- "exposure": "agent_with_confirmation",
180
- "id": "get_consent_state",
181
- "route": "GET /api/v1/widget/v1/records/pets/{pet_ref}/consent"
182
- },
183
- {
184
- "action_class": "A2",
185
- "exposure": "never_agent",
186
- "id": "get_record"
187
- }
188
- ],
189
- "data_class": "restricted",
190
- "entitlement": {
191
- "key": "records.record.read",
192
- "risk_tier": "standard"
193
- },
194
- "id": "records.record.read",
195
- "idempotent_operations": [],
196
- "required_disclaimer": {
197
- "confirmed_on_wire": true,
198
- "id": "records_informational",
199
- "note": "Emitted on both the granted and the withheld projection, so the disclaimer is present even when the content is not.",
200
- "text": "Everfur summarizes what your vet recorded. It is informational, not diagnostic."
201
- },
202
- "runtime_gate": {
203
- "fail_closed": true,
204
- "kind": "ddb_flag"
205
- },
206
- "support": "support@everfur.com",
207
- "surface": [
208
- "partner_widget"
209
- ],
210
- "title": "Read the redacted records projection for a pet",
211
- "version": "1.0.0"
212
- },
213
- {
214
- "actions": [
215
- {
216
- "action_class": "A1",
217
- "detail": "Returns the reduced presentation state for the pet, including which breed art clip to play. Derived presentation data keyed to a pet the caller owns; it carries no health findings.",
218
- "exposure": "agent_with_confirmation",
219
- "id": "get_pet_state",
220
- "route": "GET /api/v1/me/pets/{pet_id}/state"
221
- }
222
- ],
223
- "data_class": "internal",
224
- "entitlement": {
225
- "key": "animations.state.read",
226
- "risk_tier": "standard"
227
- },
228
- "id": "animations.state.read",
229
- "idempotent_operations": [],
230
- "required_disclaimer": {
231
- "confirmed_on_wire": false,
232
- "id": "none",
233
- "note": "A presentation surface with no health claim, so no disclaimer applies.",
234
- "text": null
235
- },
236
- "runtime_gate": {
237
- "fail_closed": true,
238
- "kind": "ddb_flag"
239
- },
240
- "support": "support@everfur.com",
241
- "surface": [
242
- "consumer_app"
243
- ],
244
- "title": "Read the animated pet-state for a pet",
245
- "version": "1.0.0"
246
- },
247
- {
248
- "actions": [
249
- {
250
- "action_class": "A1",
251
- "detail": "Scores an image for usability before analysis. Returns a quality verdict, not a health finding.",
252
- "exposure": "agent_with_confirmation",
253
- "id": "precheck_image",
254
- "route": "POST /api/v1/analysis/image/precheck"
255
- },
256
- {
257
- "action_class": "A3",
258
- "exposure": "never_agent",
259
- "id": "analyze_image"
260
- },
261
- {
262
- "action_class": "A3",
263
- "exposure": "never_agent",
264
- "id": "analyze_image_from_s3"
265
- }
266
- ],
267
- "data_class": "confidential",
268
- "entitlement": {
269
- "key": "photo.checkup.execute",
270
- "risk_tier": "standard"
271
- },
272
- "id": "photo.checkup.execute",
273
- "idempotent_operations": [
274
- {
275
- "key": "Idempotency-Key header",
276
- "operation": "analyze_image",
277
- "semantics": "Explicit claim, run, complete. A replay of a completed key returns the recorded response; an in-flight key returns 409 IDEMPOTENCY_KEY_IN_FLIGHT; the same key with a different request body returns 409 IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST."
278
- }
279
- ],
280
- "required_disclaimer": {
281
- "confirmed_on_wire": false,
282
- "id": "none",
283
- "note": "As with video.gait.execute, no product-owned disclaimer field is attached to the analysis response.",
284
- "text": null
285
- },
286
- "runtime_gate": {
287
- "fail_closed": false,
288
- "key": null,
289
- "kind": "none"
290
- },
291
- "support": "support@everfur.com",
292
- "surface": [
293
- "consumer_app",
294
- "partner_widget"
295
- ],
296
- "title": "Run a photo checkup on a pet image",
297
- "version": "1.0.0"
298
- },
299
- {
300
- "actions": [
301
- {
302
- "action_class": "A2",
303
- "exposure": "never_agent",
304
- "id": "classify_urgency"
305
- },
306
- {
307
- "action_class": "A3",
308
- "detail": "Opens an empty conversation container bound to the resolved (client_id, user_ref). Carries no content of its own.",
309
- "exposure": "agent_with_confirmation",
310
- "id": "create_conversation",
311
- "route": "POST /api/v1/widget/v1/conversations"
312
- },
313
- {
314
- "action_class": "A3",
315
- "exposure": "never_agent",
316
- "id": "analyze_in_chat"
317
- },
318
- {
319
- "action_class": "A3",
320
- "exposure": "never_agent",
321
- "id": "send_message"
322
- }
323
- ],
324
- "data_class": "restricted",
325
- "entitlement": {
326
- "key": "chat.message.create",
327
- "risk_tier": "standard"
328
- },
329
- "id": "chat.message.create",
330
- "idempotent_operations": [],
331
- "required_disclaimer": {
332
- "confirmed_on_wire": false,
333
- "id": "entitlement_verdict_disclaimer",
334
- "note": "The entitlement verdict carries a product-owned disclaimer block, but its text and version both default to the empty string and no code path sets them. No chat-surface copy exists to quote.",
335
- "text": null
336
- },
337
- "runtime_gate": {
338
- "fail_closed": true,
339
- "key": "chat_enabled",
340
- "kind": "client_feature"
341
- },
342
- "support": "support@everfur.com",
343
- "surface": [
344
- "partner_widget"
345
- ],
346
- "title": "Send a message to the AI vet chat",
347
- "version": "1.0.0"
348
- },
349
- {
350
- "actions": [
351
- {
352
- "action_class": "A3",
353
- "detail": "Creates an owner-provided records request against a widget pet and materializes the consumer_pets shadow row the records FK needs. Carries no document content yet.",
354
- "exposure": "agent_with_confirmation",
355
- "id": "create_records_request",
356
- "route": "POST /api/v1/widget/v1/records/requests"
357
- },
358
- {
359
- "action_class": "A3",
360
- "exposure": "never_agent",
361
- "id": "complete_upload"
362
- },
363
- {
364
- "action_class": "A3",
365
- "exposure": "never_agent",
366
- "id": "grant_consent"
367
- },
368
- {
369
- "action_class": "A3",
370
- "exposure": "never_agent",
371
- "id": "initiate_upload"
372
- },
373
- {
374
- "action_class": "A3",
375
- "exposure": "never_agent",
376
- "id": "revoke_consent"
377
- }
378
- ],
379
- "data_class": "restricted",
380
- "entitlement": {
381
- "key": "records.upload.create",
382
- "risk_tier": "standard"
383
- },
384
- "id": "records.upload.create",
385
- "idempotent_operations": [
386
- {
387
- "key": "body.idempotency_key, scoped to the resolved user_id",
388
- "operation": "create_records_request",
389
- "semantics": "Partial unique index on (user_id, idempotency_key). A replay returns the existing row instead of inserting a second empty request."
390
- },
391
- {
392
- "key": "row grain (pet_id), not the supplied idempotency_key",
393
- "operation": "grant_consent",
394
- "semantics": "The consent write is a row-grain UPSERT and is therefore idempotent by construction. The body's idempotency_key is accepted and ignored; do not rely on it as the dedup token."
395
- }
396
- ],
397
- "required_disclaimer": {
398
- "confirmed_on_wire": true,
399
- "id": "records_informational",
400
- "note": "Sourced from the EverfurRecord field default so the partner surface can never drift from the owner-facing copy.",
401
- "text": "Everfur summarizes what your vet recorded. It is informational, not diagnostic."
402
- },
403
- "runtime_gate": {
404
- "fail_closed": true,
405
- "kind": "ddb_flag"
406
- },
407
- "support": "support@everfur.com",
408
- "surface": [
409
- "partner_widget"
410
- ],
411
- "title": "Create a records request and upload source documents onto it",
412
- "version": "1.0.0"
413
- },
414
- {
415
- "actions": [
416
- {
417
- "action_class": "A4",
418
- "exposure": "never_agent",
419
- "id": "export_record"
420
- }
421
- ],
422
- "data_class": "restricted",
423
- "entitlement": {
424
- "key": "records.record.export",
425
- "risk_tier": "high"
426
- },
427
- "id": "records.record.export",
428
- "idempotent_operations": [],
429
- "required_disclaimer": {
430
- "confirmed_on_wire": false,
431
- "id": "records_informational",
432
- "note": "The copy is the records disclaimer, but nothing emits it on this capability today because the capability has no route.",
433
- "text": "Everfur summarizes what your vet recorded. It is informational, not diagnostic."
434
- },
435
- "runtime_gate": {
436
- "fail_closed": true,
437
- "key": null,
438
- "kind": "none"
439
- },
440
- "support": "support@everfur.com",
441
- "surface": [
442
- "unrouted"
443
- ],
444
- "title": "Export a pet's records as a downloadable artifact",
445
- "version": "0.1.0"
446
- },
447
- {
448
- "actions": [
449
- {
450
- "action_class": "A4",
451
- "exposure": "never_agent",
452
- "id": "share_record"
453
- }
454
- ],
455
- "data_class": "restricted",
456
- "entitlement": {
457
- "key": "records.record.share",
458
- "risk_tier": "high"
459
- },
460
- "id": "records.record.share",
461
- "idempotent_operations": [],
462
- "required_disclaimer": {
463
- "confirmed_on_wire": false,
464
- "id": "records_informational",
465
- "note": "No route, so nothing emits it. See records.record.export.",
466
- "text": "Everfur summarizes what your vet recorded. It is informational, not diagnostic."
467
- },
468
- "runtime_gate": {
469
- "fail_closed": true,
470
- "key": null,
471
- "kind": "none"
472
- },
473
- "support": "support@everfur.com",
474
- "surface": [
475
- "unrouted"
476
- ],
477
- "title": "Share a pet's records with a third party",
478
- "version": "0.1.0"
479
- }
480
- ],
481
- "registry_kind": "authoritative",
482
- "registry_version": "2026.08.22",
483
- "schema_version": 1
484
- },
485
- "credentials.yaml": {
486
- "credentials": [
487
- {
488
- "body_charset": "hex",
489
- "body_length": 64,
490
- "description": "The publishable tenant key. Shipped inside partner apps and web pages, so it is public by construction and identifies the tenant rather than authorising a person. It resolves to a client_id and nothing else. It still carries a redaction pattern, because a key in a log is a key someone can enumerate tenants with; see the header for what does and does not consume that pattern today.",
491
- "hashed_at_rest": false,
492
- "holder": "partner_client",
493
- "id": "partner_publishable_key",
494
- "prefix": "pk_live_",
495
- "redaction_regex": "pk_live_[A-Za-z0-9]{8,}",
496
- "rotatable": true,
497
- "secret": false,
498
- "total_length": 72
499
- },
500
- {
501
- "body_charset": "hex",
502
- "body_length": null,
503
- "description": "RESERVED. A test-mode publishable prefix, registered so that a redactor would scrub it and the validator rejects it in a production credential slot before any such key is ever minted.",
504
- "hashed_at_rest": false,
505
- "holder": "partner_client",
506
- "id": "partner_publishable_key_test",
507
- "prefix": "pk_test_",
508
- "redaction_regex": "pk_test_[A-Za-z0-9]{8,}",
509
- "rotatable": true,
510
- "secret": false,
511
- "total_length": null
512
- },
513
- {
514
- "body_charset": "jwt",
515
- "body_length": null,
516
- "description": "The short-lived HS256 bearer a device carries after the mint. It has no literal prefix, so the redaction pattern matches the JWS compact serialisation instead. Not stored server-side at all, which is why it cannot be revoked directly; revocation works by deactivating the signing key_id it carries, which is re-checked live on every request. Publishable as a concept because every integrating partner must know it exists, but the token value itself is a secret.",
517
- "hashed_at_rest": false,
518
- "holder": "partner_device",
519
- "id": "partner_session_bearer",
520
- "prefix": null,
521
- "redaction_regex": "eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}(\\.[A-Za-z0-9_-]+)?",
522
- "rotatable": false,
523
- "secret": true,
524
- "total_length": null
525
- }
526
- ],
527
- "schema_version": 1
528
- },
529
- "decision-trees.yaml": {
530
- "schema_version": 1,
531
- "trees": [
532
- {
533
- "decides": "The host project's shape, and the verb that installs a package into it.",
534
- "entry_question_id": "project-shape",
535
- "principles": [
536
- {
537
- "doc_path": null,
538
- "id": "duplicate-react-native-is-silent",
539
- "statement": "Two copies of react-native in one dependency tree do not raise. The app builds, the bundle serves, and the screen comes up empty, because the renderer that mounted the component is not the renderer that owns the host view registry. Nothing in the stack reports it. If a surface that should show a component shows nothing at all, check for a second react-native before you debug the component, and hoist to a single copy rather than deduping by hand at the bundler.",
540
- "title": "A duplicate react-native renders a blank screen, not an error"
541
- },
542
- {
543
- "doc_path": null,
544
- "id": "expo-go-cannot-load-native",
545
- "statement": "The optional peer dependencies some capabilities bind ship native code. Expo Go carries a fixed native runtime, so a managed project that mounts one of those capabilities needs a development build before the capability can run at all. Installing the peer and reloading Expo Go is the failure that looks like an SDK bug.",
546
- "title": "Expo Go cannot load a native module, whatever the plan says"
547
- }
548
- ],
549
- "questions": [
550
- {
551
- "detectable": true,
552
- "id": "node-package-manager",
553
- "options": [
554
- {
555
- "id": "bun",
556
- "label": "Bun",
557
- "outcome": {
558
- "attributes": {
559
- "install_verb": "bun add",
560
- "package_manager_applies": true
561
- },
562
- "doc_paths": [],
563
- "kind": "terminal"
564
- },
565
- "when": "bun.lockb is the only lockfile, or package.json names bun in packageManager."
566
- },
567
- {
568
- "id": "npm",
569
- "label": "npm",
570
- "outcome": {
571
- "attributes": {
572
- "install_verb": "npm install",
573
- "package_manager_applies": true
574
- },
575
- "doc_paths": [],
576
- "kind": "terminal"
577
- },
578
- "when": "package-lock.json is the only lockfile, or package.json names npm in packageManager."
579
- },
580
- {
581
- "id": "pnpm",
582
- "label": "pnpm",
583
- "outcome": {
584
- "attributes": {
585
- "install_verb": "pnpm add",
586
- "package_manager_applies": true
587
- },
588
- "doc_paths": [],
589
- "kind": "terminal"
590
- },
591
- "when": "pnpm-lock.yaml is the only lockfile, or package.json names pnpm in packageManager."
592
- },
593
- {
594
- "id": "yarn",
595
- "label": "Yarn",
596
- "outcome": {
597
- "attributes": {
598
- "install_verb": "yarn add",
599
- "package_manager_applies": true
600
- },
601
- "doc_paths": [],
602
- "kind": "terminal"
603
- },
604
- "when": "yarn.lock is the only lockfile, or package.json names yarn in packageManager."
605
- }
606
- ],
607
- "prompt": "Which package manager installs into this project?",
608
- "supply_with": "add a \"packageManager\" field to package.json, or leave exactly one lockfile in the project root",
609
- "why_it_is_asked": "the package manager could not be identified, and the wrong install verb writes the wrong lockfile, so the next person to install gets a dependency tree nobody chose"
610
- },
611
- {
612
- "detectable": true,
613
- "id": "project-shape",
614
- "options": [
615
- {
616
- "id": "bare-react-native",
617
- "label": "Bare React Native app",
618
- "outcome": {
619
- "kind": "branch",
620
- "next_question_id": "node-package-manager"
621
- },
622
- "when": "package.json declares react-native and the native projects are committed."
623
- },
624
- {
625
- "id": "expo-managed",
626
- "label": "Expo managed React Native app",
627
- "outcome": {
628
- "kind": "branch",
629
- "next_question_id": "node-package-manager"
630
- },
631
- "when": "package.json declares expo and the project has no committed ios/ or android/ directory."
632
- },
633
- {
634
- "id": "flutter",
635
- "label": "Flutter application",
636
- "outcome": {
637
- "attributes": {
638
- "import_target": "package:everfur_sdk/everfur_sdk.dart",
639
- "install_commands": [
640
- "flutter pub add everfur_sdk",
641
- "flutter pub get"
642
- ],
643
- "install_style": "pub",
644
- "manifest": "pubspec.yaml",
645
- "package": "everfur_sdk",
646
- "package_manager_applies": false
647
- },
648
- "doc_paths": [],
649
- "kind": "terminal"
650
- },
651
- "when": "pubspec.yaml is present at the project root."
652
- },
653
- {
654
- "id": "web",
655
- "label": "Web application",
656
- "outcome": {
657
- "kind": "branch",
658
- "next_question_id": "node-package-manager"
659
- },
660
- "when": "package.json declares no react-native and no expo."
661
- }
662
- ],
663
- "prompt": "What shape is the host project?",
664
- "supply_with": "run from the application root, or pass --cwd <path> pointing at it",
665
- "why_it_is_asked": "the shape of the host project could not be identified, and the install verb, the native linking step and the import target all follow from it"
666
- }
667
- ],
668
- "schema_version": 1,
669
- "supply_with": "detected from the project; pass --cwd <path> to point at the application root",
670
- "title": "Which platform this integration is built on",
671
- "tree_id": "platform"
672
- },
673
- {
674
- "decides": "The set of SDK capability surfaces to mount, and for each one the import target and the optional native peers it binds.",
675
- "entry_question_id": "which-capability",
676
- "principles": [
677
- {
678
- "doc_path": "/.well-known/skills/everfur-integration.md#bundle-attach-narrows",
679
- "id": "entitlement-lattice-subtracts",
680
- "statement": "The entitlement lattice is born-full and SUBTRACTIVE. The granted set is the top-level grant universe intersected with every ceiling on the path, minus every live deny. Nothing below the top adds anything. Attaching a bundle materialises a ceiling whose allow list is exactly that bundle's contents, so reaching for a bundle to hand a tenant a capability is reaching for the one mechanism that can only restrict. To widen a tenant, widen or reset the ceiling that excludes the key, or lift the deny that matched it.",
681
- "title": "Attaching a bundle to grant a capability takes capabilities away"
682
- },
683
- {
684
- "doc_path": "/.well-known/skills/everfur-integration.md#empty-capability-verdict",
685
- "id": "empty-verdict-is-a-symptom",
686
- "statement": "A 200 with an empty capability map is not a denial. The same response covers a tenant with no entitlement scope provisioned, a live deny, a ceiling that excludes the key, a key absent from the grant universe, a runtime gate that is off, and a resolve that could not complete and degraded to the fail-closed empty verdict, which means UNKNOWN rather than denied. They are byte-identical on the wire by design, because a denial reason is not something a device should branch on. Run `everfur diagnose` against the same credential: it returns one machine-readable reason per capability, and that is the thing that separates the causes. A gate cause is an operator action no entitlement change will fix; a not-provisioned cause is a support request; a resolver-unavailable cause means retry; a denied cause is configuration somebody chose.",
687
- "title": "An empty capability verdict is a symptom, not a diagnosis"
688
- },
689
- {
690
- "doc_path": "/.well-known/skills/everfur-integration.md#wire-message-is-not-copy",
691
- "id": "wire-message-is-never-rendered",
692
- "statement": "Every capability surface renders errors, and the tempting field is the wrong one. The upstream text on an error is untrusted, unlocalised, health-adjacent and sanitised by nothing: it can carry provider internals or a URL into somebody's record. The SDK's only protection is structural, the field is non-enumerable so serialisers drop it, and reading it deliberately is what defeats that. Render the display copy the error registry publishes for the code, and never forward the upstream text to a third-party telemetry or crash sink. When a person needs something to quote, quote the request id from the response header.",
693
- "title": "The wire message is diagnostic text, never something a person sees"
694
- },
695
- {
696
- "doc_path": "/reference/capabilities.md",
697
- "id": "verdict-is-server-authoritative",
698
- "statement": "The capability list handed back at session mint is advisory and is stated to be advisory; the authoritative verdict comes from the entitlements route and the SDK renders it as received. Do not cache it across users, do not intersect it with a local list of what you think the tenant bought, and do not treat its absence as a denial. Two independent checks have to pass for a surface to work: the entitlement resolves granted, and the runtime gate is on. They fail differently and only one of them is visible in the verdict.",
699
- "title": "The verdict is resolved server side and never re-folded on the client"
700
- }
701
- ],
702
- "questions": [
703
- {
704
- "detectable": false,
705
- "id": "which-capability",
706
- "options": [
707
- {
708
- "id": "animations",
709
- "label": "Animated pet state",
710
- "outcome": {
711
- "capability_refs": [
712
- "animations.state.read"
713
- ],
714
- "kind": "blocked",
715
- "reason": "The animated pet state is surfaced to the Everfur consumer app rather than to the partner widget surface, and its runtime gate is a control-plane switch that is off by default and fails closed. A partner integration has no route to it today.",
716
- "request_path": "/reference/capabilities.md#capability-animations.state.read",
717
- "unblock_owner": "support@everfur.com"
718
- },
719
- "when": "The surface shows the animated pet avatar."
720
- },
721
- {
722
- "id": "chat",
723
- "label": "AI vet chat",
724
- "outcome": {
725
- "attributes": {
726
- "dart_library": "package:everfur_sdk/everfur_sdk.dart",
727
- "native_peers": [],
728
- "node_subpath": "@everfur/sdk/chat",
729
- "summary": "Streaming chat surface."
730
- },
731
- "capability_refs": [
732
- "chat.conversation.read",
733
- "chat.message.create"
734
- ],
735
- "doc_paths": [
736
- "/reference/capabilities.md#capability-chat.conversation.read",
737
- "/reference/capabilities.md#capability-chat.message.create"
738
- ],
739
- "kind": "terminal"
740
- },
741
- "when": "The surface shows a conversation with the Everfur assistant."
742
- },
743
- {
744
- "id": "entitlements",
745
- "label": "Entitlement gating for capability surfaces",
746
- "outcome": {
747
- "attributes": {
748
- "dart_library": "package:everfur_sdk/everfur_sdk.dart",
749
- "native_peers": [],
750
- "node_subpath": "@everfur/sdk/core",
751
- "summary": "Entitlement gating for capability surfaces."
752
- },
753
- "capability_refs": [],
754
- "doc_paths": [
755
- "/reference/capabilities.md"
756
- ],
757
- "kind": "terminal"
758
- },
759
- "when": "The integration hides or shows its own UI based on what the tenant is entitled to."
760
- },
761
- {
762
- "id": "photo",
763
- "label": "Photo checkup",
764
- "outcome": {
765
- "attributes": {
766
- "dart_library": "package:everfur_sdk/everfur_sdk.dart",
767
- "native_peers": [
768
- "expo-file-system",
769
- "expo-image-manipulator",
770
- "react-native-vision-camera"
771
- ],
772
- "node_subpath": "@everfur/sdk/photo",
773
- "summary": "Photo capture, resize, and upload."
774
- },
775
- "capability_refs": [
776
- "photo.checkup.execute"
777
- ],
778
- "doc_paths": [
779
- "/reference/capabilities.md#capability-photo.checkup.execute"
780
- ],
781
- "kind": "terminal"
782
- },
783
- "when": "The surface captures a skin or eye photo and shows an analysis."
784
- },
785
- {
786
- "id": "records",
787
- "label": "Health record upload and timeline",
788
- "outcome": {
789
- "capability_refs": [
790
- "records.record.export",
791
- "records.record.read",
792
- "records.record.share",
793
- "records.upload.create"
794
- ],
795
- "kind": "blocked",
796
- "reason": "The partner records surface is mounted behind a control-plane switch that is off by default and read strictly, so every route under it answers 404 rather than 403 until Everfur enables it for your client. A 404 is indistinguishable from a wrong URL, so an integration built against a dark surface reads to a partner as a broken SDK. The two record-movement packs are separately out of reach: export and share are not routed to the partner widget surface at all, and every action in both is never_agent, so no agent path to them exists even for an entitled tenant.",
797
- "request_path": "/reference/capabilities.md#capability-records.upload.create",
798
- "unblock_owner": "support@everfur.com"
799
- },
800
- "when": "The surface uploads vet documents or shows a pet's record history."
801
- },
802
- {
803
- "id": "video",
804
- "label": "Gait and mobility video analysis",
805
- "outcome": {
806
- "capability_refs": [
807
- "video.gait.execute"
808
- ],
809
- "kind": "blocked",
810
- "reason": "Gait analysis is surfaced to the Everfur consumer app and is not routed to the partner widget surface, and its runtime gate is a control-plane switch that is off by default and fails closed. Both have to change before a partner integration can reach it, and neither is a setting a partner can turn on.",
811
- "request_path": "/reference/capabilities.md#capability-video.gait.execute",
812
- "unblock_owner": "support@everfur.com"
813
- },
814
- "when": "The surface captures a walking video and shows a mobility result."
815
- }
816
- ],
817
- "prompt": "Which Everfur capability does this surface mount?",
818
- "supply_with": "--capabilities chat,entitlements,photo (name every surface you mount)",
819
- "why_it_is_asked": "which Everfur capabilities this integration uses cannot be read from the project; nothing on disk says which product surface somebody intends to build, and the import target, the optional native peers, and whether the surface is reachable at all differ per capability"
820
- }
821
- ],
822
- "schema_version": 1,
823
- "supply_with": "--capabilities chat,entitlements,photo (name every surface you mount)",
824
- "title": "Which capability surfaces this integration mounts",
825
- "tree_id": "capabilities"
826
- },
827
- {
828
- "decides": "Whether the integration asserts a partner-owned user reference, and therefore whether per-user data is reachable.",
829
- "entry_question_id": "how-users-are-named",
830
- "principles": [
831
- {
832
- "doc_path": "/.well-known/skills/everfur-integration.md#user-ref-case",
833
- "id": "user-ref-is-case-sensitive",
834
- "statement": "A user reference is an opaque, case-sensitive identifier resolved by exact match, and the session is keyed on the (client, user reference) pair with the case preserved. Lowercasing it anywhere on the path collapses two case-distinct references onto one internal user, which is a cross-user data bleed on read and a cross-user erasure on delete. This is not hypothetical: it happened, and the fix was to remove the case fold. Send the reference exactly as your system stores it, including in your own cache keys. If your identifiers are genuinely case-insensitive upstream, normalise them ONCE where they are minted, before they ever become a user reference.",
835
- "title": "user_ref is case sensitive, and folding it merges two people"
836
- },
837
- {
838
- "doc_path": "/reference/capabilities.md",
839
- "id": "ownership-fence-is-the-composite",
840
- "statement": "Every per-user read is fenced on the composite of the resolved user and the pet, not on the tenant alone. With no user reference asserted nothing owned by a specific end user is reachable, by construction rather than by policy. That is what makes the anonymous mode safe and what makes a guessable user reference dangerous: the reference IS the tenancy claim for everything below it, so it must be stable per person and must not be enumerable.",
841
- "title": "The ownership fence is the (user, pet) composite"
842
- }
843
- ],
844
- "questions": [
845
- {
846
- "detectable": false,
847
- "id": "how-users-are-named",
848
- "options": [
849
- {
850
- "id": "anonymous",
851
- "label": "No end user is asserted",
852
- "outcome": {
853
- "attributes": {
854
- "reaches_per_user_data": false,
855
- "session_mint": "Not applicable. A mint requires a non-empty user reference, so there is nothing to mint for a surface that asserts none.",
856
- "user_ref_header": null
857
- },
858
- "doc_paths": [
859
- "/reference/capabilities.md"
860
- ],
861
- "kind": "terminal"
862
- },
863
- "when": "The surface shows nothing owned by a specific person: a marketing page, a demo, or a logged-out state."
864
- },
865
- {
866
- "id": "partner-user-ref",
867
- "label": "The partner asserts its own stable user reference",
868
- "outcome": {
869
- "attributes": {
870
- "reaches_per_user_data": true,
871
- "session_mint": "A minted session is how the tenancy claim is made from your backend instead of from the device, and it is mandatory for a client configured to require partner sessions. Answer the session-mint-language tree next.",
872
- "user_ref_case_sensitive": true,
873
- "user_ref_forbidden_characters": "@",
874
- "user_ref_header": "x-everfur-user-ref",
875
- "user_ref_max_length": 128,
876
- "user_role_header": "x-everfur-user-role"
877
- },
878
- "doc_paths": [
879
- "/.well-known/skills/everfur-integration.md#user-ref-case"
880
- ],
881
- "kind": "terminal",
882
- "then_ask_tree_id": "session-mint-language"
883
- },
884
- "when": "The integration already has a signed-in person and a stable identifier for them, and the surface shows that person's pets, conversations or records."
885
- }
886
- ],
887
- "prompt": "How does this integration identify the person using it?",
888
- "supply_with": "--identity partner-user-ref | anonymous",
889
- "why_it_is_asked": "how this integration identifies end users cannot be read from the project, and the two answers reach different data; guessing wrong in one direction builds a surface that shows nothing, and in the other builds one that asserts an identity the partner never intended to assert"
890
- }
891
- ],
892
- "schema_version": 1,
893
- "supply_with": "--identity partner-user-ref | anonymous",
894
- "title": "How end users are named on the wire",
895
- "tree_id": "identity-mode"
896
- },
897
- {
898
- "decides": "Whether the integration mints sessions through the first-party server package or by calling the mint route directly.",
899
- "entry_question_id": "backend-language",
900
- "principles": [
901
- {
902
- "doc_path": "/.well-known/skills/everfur-integration.md#server-only-minter",
903
- "id": "minter-is-server-only",
904
- "statement": "The mint is the one request on the whole surface that legitimately carries both credentials at once, and a device must never hold the secret half. The `/server` subpath is what enforces that structurally: it declares no react-native export condition, so a mobile bundler cannot resolve it. Import the same function from the package root or a client subpath and the protection is gone, because the fence was the subpath, not the function. If a bundler ever resolves the server subpath into a device build, treat it as a leaked secret and rotate rather than shipping the build.",
905
- "title": "The session minter is imported only from the server subpath"
906
- },
907
- {
908
- "doc_path": "/.well-known/skills/everfur-integration.md#user-ref-case",
909
- "id": "mint-carries-the-identity",
910
- "statement": "The reference travels in the mint body, and the same validation rule applies there as on the per-request header: stripped, non-empty, at most 128 characters, printable, no `@`. The one difference is the empty case, which is a 400 at the mint because a session cannot be minted for nobody, while an empty header on the per-request path falls back to an anonymous identity. Do not build a mint call that sends an empty reference and expects anonymity: it is a rejection, not a fallback.",
911
- "title": "The mint body carries the user reference, and the rules are the same on both paths"
912
- },
913
- {
914
- "doc_path": "/reference/errors.md",
915
- "id": "mint-failures-are-one-opaque-401",
916
- "statement": "Wrong secret, wrong publishable key, a mismatched pair from two different tenants, an inactive client, a lookup timeout: all of them return the same 401 with no detail, on purpose, so the route is not an oracle. Do not build retry logic that branches on why the mint failed, because the wire does not tell you. Correlate with the request id and read the diagnostics route instead.",
917
- "title": "Every credential failure at the mint is one indistinguishable 401"
918
- }
919
- ],
920
- "questions": [
921
- {
922
- "detectable": false,
923
- "id": "backend-language",
924
- "options": [
925
- {
926
- "id": "go",
927
- "label": "Go",
928
- "outcome": {
929
- "kind": "branch",
930
- "next_question_id": "direct-mint-call"
931
- },
932
- "when": "The backend is a Go service."
933
- },
934
- {
935
- "id": "node",
936
- "label": "Node or TypeScript",
937
- "outcome": {
938
- "attributes": {
939
- "import_from_root_is_a_defect": true,
940
- "import_subpath": "@everfur/sdk/server",
941
- "package": "@everfur/sdk",
942
- "secret_source": "an environment variable or secret manager on the server, never a bundled constant"
943
- },
944
- "doc_paths": [
945
- "/.well-known/skills/everfur-integration.md#server-only-minter"
946
- ],
947
- "kind": "terminal"
948
- },
949
- "when": "The backend runs on Node and can install from the npm registry."
950
- },
951
- {
952
- "id": "other-language",
953
- "label": "Anything else",
954
- "outcome": {
955
- "kind": "branch",
956
- "next_question_id": "direct-mint-call"
957
- },
958
- "when": "The backend is any other language, or the mint lives in an edge function."
959
- },
960
- {
961
- "id": "python",
962
- "label": "Python",
963
- "outcome": {
964
- "kind": "branch",
965
- "next_question_id": "direct-mint-call"
966
- },
967
- "when": "The backend is a Python service."
968
- }
969
- ],
970
- "prompt": "What is the backend that will hold the secret and mint sessions written in?",
971
- "supply_with": "--backend node | python | go | other-language",
972
- "why_it_is_asked": "this integration asserts its own user reference, so a session has to be minted server side, and only one language has a first-party minter; the rest is the same HTTP call, and saying so is more useful than pretending a package exists"
973
- },
974
- {
975
- "detectable": false,
976
- "id": "direct-mint-call",
977
- "options": [
978
- {
979
- "id": "call-the-mint-route",
980
- "label": "Call the mint route from the backend",
981
- "outcome": {
982
- "attributes": {
983
- "body_fields": [
984
- "ttl_seconds",
985
- "user_ref"
986
- ],
987
- "failure_mode": "one opaque 401 for every credential failure",
988
- "publishable_header": "x-everfur-partner-key",
989
- "route": "POST /api/v1/widget/v1/sessions",
990
- "secret_header": "x-everfur-partner-secret-key",
991
- "secret_source": "an environment variable or secret manager on the server, never a bundled constant",
992
- "ttl_behaviour": "ttl_seconds is clamped server side to the configured range; an absent value takes the default"
993
- },
994
- "doc_paths": [
995
- "/.well-known/skills/everfur-integration.md#server-only-minter",
996
- "/reference/errors.md"
997
- ],
998
- "kind": "terminal"
999
- },
1000
- "when": "Always, for a language with no first-party minter."
1001
- }
1002
- ],
1003
- "prompt": "There is no first-party minter for this language. Confirm the direct call.",
1004
- "supply_with": null,
1005
- "why_it_is_asked": "three languages converge here because the answer is identical for all of them, and stating it once is what keeps the three from drifting apart"
1006
- }
1007
- ],
1008
- "schema_version": 1,
1009
- "supply_with": "--backend node | python | go | other-language",
1010
- "title": "Which language mints sessions on the partner backend",
1011
- "tree_id": "session-mint-language"
1012
- }
1013
- ]
1014
- },
1015
- "errors.yaml": {
1016
- "errors": [
1017
- {
1018
- "code": "AccessDeniedException",
1019
- "confirmed_on_wire": true,
1020
- "display": "You do not have access to that.",
1021
- "doc_url": "/errors#AccessDeniedException",
1022
- "envelope": "http_envelope",
1023
- "http_status": 403,
1024
- "naming_family": "pascal_exception",
1025
- "note": "Emitted by the report-page tier gate with extra fields current_tier, required_tier, page_id and upgrade_url, and as the status-map default for any other 403 raised with a string detail.",
1026
- "retryable": false,
1027
- "surfaces": [
1028
- "consumer_app"
1029
- ]
1030
- },
1031
- {
1032
- "code": "AuthorizationException",
1033
- "confirmed_on_wire": true,
1034
- "display": "Your session is not valid. Sign in again to continue.",
1035
- "doc_url": "/errors#AuthorizationException",
1036
- "envelope": "http_envelope",
1037
- "http_status": 401,
1038
- "naming_family": "pascal_exception",
1039
- "note": "The generic 401. The widget partner-key path emits this with a string detail; the bearer path emits authExpired instead.",
1040
- "retryable": false,
1041
- "surfaces": [
1042
- "consumer_app",
1043
- "partner_widget"
1044
- ]
1045
- },
1046
- {
1047
- "code": "ConflictException",
1048
- "confirmed_on_wire": true,
1049
- "display": "That is already running. Wait for it to finish, then try again.",
1050
- "doc_url": "/errors#ConflictException",
1051
- "envelope": "http_envelope",
1052
- "http_status": 409,
1053
- "naming_family": "pascal_exception",
1054
- "note": "On the widget streaming routes this is the single-flight fence: register_stream does an atomic check-and-set and rejects a second concurrent stream for the same conversation. Retryable once the first stream ends, not immediately.",
1055
- "retryable": true,
1056
- "surfaces": [
1057
- "partner_widget"
1058
- ]
1059
- },
1060
- {
1061
- "code": "DAILY_QUOTA_EXCEEDED",
1062
- "confirmed_on_wire": true,
1063
- "display": "You have used today's allowance for this. It resets tomorrow.",
1064
- "doc_url": "/errors#DAILY_QUOTA_EXCEEDED",
1065
- "envelope": "http_envelope",
1066
- "http_status": 429,
1067
- "naming_family": "screaming_snake",
1068
- "note": "The per-endpoint guardrail, a different counter from the tier-wide daily quota. Its envelope is the leaner {detail, code, endpoint, daily_limit}; note it uses `detail`, not `message`, and carries no request_id.",
1069
- "retryable": true,
1070
- "surfaces": [
1071
- "consumer_app"
1072
- ]
1073
- },
1074
- {
1075
- "code": "IDEMPOTENCY_KEY_IN_FLIGHT",
1076
- "confirmed_on_wire": true,
1077
- "display": "That is still processing. Give it a moment.",
1078
- "doc_url": "/errors#IDEMPOTENCY_KEY_IN_FLIGHT",
1079
- "envelope": "http_envelope",
1080
- "http_status": 409,
1081
- "naming_family": "screaming_snake",
1082
- "note": "The same Idempotency-Key is already claimed and running. Retry the SAME key after a short backoff to pick up the recorded response; do not mint a new key, which would duplicate the write. No widget router opts into the idempotent route class today, so this is not reachable on the partner surface.",
1083
- "retryable": true,
1084
- "surfaces": [
1085
- "consumer_app"
1086
- ]
1087
- },
1088
- {
1089
- "code": "IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST",
1090
- "confirmed_on_wire": true,
1091
- "display": "That request could not be repeated safely. Start it again.",
1092
- "doc_url": "/errors#IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST",
1093
- "envelope": "http_envelope",
1094
- "http_status": 409,
1095
- "naming_family": "screaming_snake",
1096
- "note": "The key was reused with a different request body. This is a client bug, not a transient fault. Mint a fresh key for the new body.",
1097
- "retryable": false,
1098
- "surfaces": [
1099
- "consumer_app"
1100
- ]
1101
- },
1102
- {
1103
- "code": "InternalFailureException",
1104
- "confirmed_on_wire": true,
1105
- "display": "Something went wrong on our side. Try again in a moment.",
1106
- "doc_url": "/errors#InternalFailureException",
1107
- "envelope": "http_envelope",
1108
- "http_status": 500,
1109
- "naming_family": "pascal_exception",
1110
- "note": "Also the fallback for any status with no entry in the map, so a 502 arrives under this code. The message is masked to a fixed string for every 5xx except 501, so it never carries diagnostic detail. Quote request_id when reporting.",
1111
- "retryable": true,
1112
- "surfaces": [
1113
- "consumer_app",
1114
- "partner_widget"
1115
- ]
1116
- },
1117
- {
1118
- "code": "NotImplementedException",
1119
- "confirmed_on_wire": true,
1120
- "display": "That is not available yet.",
1121
- "doc_url": "/errors#NotImplementedException",
1122
- "envelope": "http_envelope",
1123
- "http_status": 501,
1124
- "naming_family": "pascal_exception",
1125
- "note": "The one 5xx whose message is passed through unmasked, because it describes product state rather than a fault.",
1126
- "retryable": false,
1127
- "surfaces": [
1128
- "consumer_app"
1129
- ]
1130
- },
1131
- {
1132
- "code": "PaymentRequiredException",
1133
- "confirmed_on_wire": false,
1134
- "display": "This needs a plan upgrade.",
1135
- "doc_url": "/errors#PaymentRequiredException",
1136
- "envelope": "http_envelope",
1137
- "http_status": 402,
1138
- "naming_family": "pascal_exception",
1139
- "note": "Present in the status map, but every 402 traced in this pass is emitted by the typed entitlement handler as TIER_UPGRADE_REQUIRED, not as this generic code.",
1140
- "retryable": false,
1141
- "surfaces": [
1142
- "consumer_app"
1143
- ]
1144
- },
1145
- {
1146
- "code": "ResourceNotFoundException",
1147
- "confirmed_on_wire": true,
1148
- "display": "We could not find that.",
1149
- "doc_url": "/errors#ResourceNotFoundException",
1150
- "envelope": "http_envelope",
1151
- "http_status": 404,
1152
- "naming_family": "pascal_exception",
1153
- "note": "Load-bearing ambiguity, by design. A dark runtime gate answers 404 so a disabled surface is indistinguishable from a missing resource, and an ownership miss answers 404 so another tenant's resource is invisible rather than merely forbidden. A client must not infer existence from a 404.",
1154
- "retryable": false,
1155
- "surfaces": [
1156
- "consumer_app",
1157
- "partner_widget"
1158
- ]
1159
- },
1160
- {
1161
- "code": "ServiceUnavailableException",
1162
- "confirmed_on_wire": true,
1163
- "display": "That service is briefly unavailable. Try again shortly.",
1164
- "doc_url": "/errors#ServiceUnavailableException",
1165
- "envelope": "http_envelope",
1166
- "http_status": 503,
1167
- "naming_family": "pascal_exception",
1168
- "note": "The status-map default for 503. The widget dependencies raise the dict-detail code `unavailable` instead, so a widget client sees that one.",
1169
- "retryable": true,
1170
- "surfaces": [
1171
- "consumer_app"
1172
- ]
1173
- },
1174
- {
1175
- "code": "TIER_UPGRADE_REQUIRED",
1176
- "confirmed_on_wire": true,
1177
- "display": "This is part of a paid plan.",
1178
- "doc_url": "/errors#TIER_UPGRADE_REQUIRED",
1179
- "envelope": "http_envelope",
1180
- "http_status": 402,
1181
- "naming_family": "screaming_snake",
1182
- "note": "The store paywall gate. Extends the envelope with capability, current_tier, limit, used, paywall_cta and upgrade_url. Distinct from the 403 AccessDeniedException tier gate on report pages.",
1183
- "retryable": false,
1184
- "surfaces": [
1185
- "consumer_app"
1186
- ]
1187
- },
1188
- {
1189
- "code": "ThrottlingException",
1190
- "confirmed_on_wire": true,
1191
- "display": "You have reached the limit for now. Try again later.",
1192
- "doc_url": "/errors#ThrottlingException",
1193
- "envelope": "http_envelope",
1194
- "http_status": 429,
1195
- "naming_family": "pascal_exception",
1196
- "note": "The quota handler extends the envelope with daily_limit, used and resets_at, and adds tier when the exception carries one. resets_at is the only reliable retry signal; there is no Retry-After on this path.",
1197
- "retryable": true,
1198
- "surfaces": [
1199
- "consumer_app",
1200
- "partner_widget"
1201
- ]
1202
- },
1203
- {
1204
- "code": "ValidationException",
1205
- "confirmed_on_wire": true,
1206
- "display": "Something in that request was not right. Check the details and try again.",
1207
- "doc_url": "/errors#ValidationException",
1208
- "envelope": "http_envelope",
1209
- "http_status": 400,
1210
- "naming_family": "pascal_exception",
1211
- "note": "The default code for 400, 413, 415 and 422 alike, so payload-too-large and unsupported-media-type are indistinguishable from a field error by code. Branch on http_status as well as code.",
1212
- "retryable": false,
1213
- "surfaces": [
1214
- "consumer_app",
1215
- "partner_widget"
1216
- ]
1217
- },
1218
- {
1219
- "code": "accessDenied",
1220
- "confirmed_on_wire": true,
1221
- "display": "That request could not be authorised.",
1222
- "doc_url": "/errors#accessDenied",
1223
- "envelope": "http_envelope",
1224
- "http_status": 403,
1225
- "naming_family": "camel",
1226
- "note": "The caller presented BOTH a session bearer and a publishable partner key. Exactly one is correct per request. Retrying with both set will fail identically.",
1227
- "retryable": false,
1228
- "surfaces": [
1229
- "partner_widget"
1230
- ]
1231
- },
1232
- {
1233
- "code": "authExpired",
1234
- "confirmed_on_wire": true,
1235
- "display": "Your session expired. Reconnecting.",
1236
- "doc_url": "/errors#authExpired",
1237
- "envelope": "http_envelope",
1238
- "http_status": 401,
1239
- "naming_family": "camel",
1240
- "note": "The one 401 a client should respond to by minting a fresh session token and replaying the request exactly once. Emitted for a bad signature, an expired token, a wrong typ or issuer, a token missing a required claim, and for a bearer whose signing key has since been revoked. Do not loop: a second authExpired on the replay is terminal.",
1241
- "retryable": true,
1242
- "surfaces": [
1243
- "partner_widget"
1244
- ]
1245
- },
1246
- {
1247
- "code": "internal_error",
1248
- "confirmed_on_wire": true,
1249
- "display": "Something went wrong on our side.",
1250
- "doc_url": "/errors#sse-internal_error",
1251
- "envelope": "sse_frame",
1252
- "http_status": 200,
1253
- "naming_family": "snake",
1254
- "note": "Emitted with retryable false. The frame carries no request_id, so there is nothing for a client to quote in a support report; correlate by timestamp and conversation id instead. Everfur is tracking the gap.",
1255
- "retryable": false,
1256
- "surfaces": [
1257
- "partner_widget"
1258
- ]
1259
- },
1260
- {
1261
- "code": "invalidRequest",
1262
- "confirmed_on_wire": true,
1263
- "display": "This app needs to be updated to keep using Everfur.",
1264
- "doc_url": "/errors#invalidRequest",
1265
- "envelope": "http_envelope",
1266
- "http_status": 400,
1267
- "naming_family": "camel",
1268
- "note": "The SDK min-version and wire-contract gate. Deliberately 400 rather than 401, 403 or 426, so it neither trips refresh machinery nor lands as an unmapped status. The response carries a `reason` alongside the message so the client can say what specifically is out of date. Inert while the gate flag is off.",
1269
- "retryable": false,
1270
- "surfaces": [
1271
- "partner_widget"
1272
- ]
1273
- },
1274
- {
1275
- "code": "not_found",
1276
- "confirmed_on_wire": true,
1277
- "display": "We could not find that conversation.",
1278
- "doc_url": "/errors#sse-not_found",
1279
- "envelope": "sse_frame",
1280
- "http_status": 200,
1281
- "naming_family": "snake",
1282
- "note": "The stream had already returned 200 and its headers when the failure was discovered, so the HTTP status cannot express it. A client parsing only status codes will read this as success. Parse every data frame for an `error` key.",
1283
- "retryable": false,
1284
- "surfaces": [
1285
- "partner_widget"
1286
- ]
1287
- },
1288
- {
1289
- "code": "session_required",
1290
- "confirmed_on_wire": true,
1291
- "display": "This app needs to be updated to keep using Everfur.",
1292
- "doc_url": "/errors#session_required",
1293
- "envelope": "http_envelope",
1294
- "http_status": 403,
1295
- "naming_family": "snake",
1296
- "note": "An unsigned caller claimed an identity on a tenant that now requires signed sessions. Not retryable as sent: the integration must mint a session token through POST /widget/v1/sessions and carry the bearer instead.",
1297
- "retryable": false,
1298
- "surfaces": [
1299
- "partner_widget"
1300
- ]
1301
- },
1302
- {
1303
- "code": "timeout",
1304
- "confirmed_on_wire": true,
1305
- "display": "That took too long. Try again.",
1306
- "doc_url": "/errors#sse-timeout",
1307
- "envelope": "sse_frame",
1308
- "http_status": 200,
1309
- "naming_family": "snake",
1310
- "note": "Emitted with retryable true when the stream times out or is cancelled. A partial assistant turn may already have been persisted, so a retry can produce a visible duplicate; reconcile against the transcript rather than assuming nothing was written.",
1311
- "retryable": true,
1312
- "surfaces": [
1313
- "partner_widget"
1314
- ]
1315
- },
1316
- {
1317
- "code": "unavailable",
1318
- "confirmed_on_wire": true,
1319
- "display": "We are having trouble reaching that right now. Try again shortly.",
1320
- "doc_url": "/errors#unavailable",
1321
- "envelope": "http_envelope",
1322
- "http_status": 503,
1323
- "naming_family": "snake",
1324
- "note": "Deliberately a 503 and not a 401. It means the server could not decide, because the signing secret was unresolvable or the key-revocation check timed out. Answering 401 here would spin the client's refresh loop against a server fault, so a client must NOT refresh on this code; back off and retry the same request.",
1325
- "retryable": true,
1326
- "surfaces": [
1327
- "partner_widget"
1328
- ]
1329
- }
1330
- ],
1331
- "schema_version": 1
1332
- }
1333
- },
1334
- "schemaVersion": 1
1335
- }