did-hosting-common 0.8.6

DID Hosting Common, Common methods and structs related to WebVH Content
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
//! Canonical Trust-Task URLs for every webvh-service operation.
//!
//! One `LazyLock<TrustTask>` per registered task — grep `TASK_` to
//! enumerate the full wire surface. Each URL is exact-match routed both
//! on REST (via the `Trust-Task:` header — see
//! [`crate::server::trust_task::TrustTaskRouter`]) and on DIDComm (via
//! the message `type` field — see
//! [`crate::server::trust_task::didcomm`]).
//!
//! ## Namespace
//!
//! Per `docs/multi-method-hosting-spec.md` §3:
//!
//! - `https://trusttasks.org/did-hosting/...` — method-agnostic ops:
//!   auth, DID provisioning lifecycle, hosting infrastructure
//!   (server-register, health, stats), domain management.
//! - `https://trusttasks.org/webvh/...` — webvh-protocol-specific ops:
//!   witness publish/confirm, sync update/delete. Future `did:webs` or
//!   `did:webplus` operations would register at `webs/...` /
//!   `webplus/...` paths.
//!
//! ## Versioning
//!
//! `{maj}.{min}` only per the canonical Trust-Tasks spec — no patch
//! component. Bumping requires registering a NEW const at a new URL;
//! the old URL keeps routing to its handler until removed in a future
//! release. The router does NOT do version-family matching — `1.0` and
//! `1.1` are completely separate identifiers.
//!
//! ## Cross-crate consistency
//!
//! T9 (the parity harness) and T51 (the client-crate URL invariant
//! test) will assert that every const here matches the client crate's
//! same-named const byte-for-byte. Edit both in lockstep.

use std::sync::LazyLock;

use crate::server::trust_task::TrustTask;

// ---------------------------------------------------------------------------
// Method-agnostic ops — `trusttasks.org/did-hosting/...`
// ---------------------------------------------------------------------------

/// `spec/auth/authenticate/0.1` — canonical cross-cutting authenticate.
/// (Was did-hosting/auth/authenticate/1.0; migrated to the framework
/// spec so VTA + VTC + did-hosting share one client surface.)
pub static TASK_AUTH_AUTHENTICATE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/authenticate/0.1").expect("static")
});

/// `spec/auth/authenticate/0.1#response` — the response variant of
/// the canonical authenticate. The framework now uses
/// `<type>#response` rather than a paired response-type URI; the
/// constant is retained for code that still references the dedicated
/// response identifier.
pub static TASK_AUTH_AUTHENTICATE_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/authenticate/0.1#response").expect("static")
});

/// `spec/auth/passkey/login/start/0.1` (step-up purpose) — request a
/// WebAuthn assertion to elevate the current session to aal2. Same
/// canonical spec as initial passkey login; handler dispatches on
/// `payload.purpose == "step-up"`.
#[deprecated(
    since = "0.8.0",
    note = "spec bumped to 0.2; use TASK_AUTH_STEP_UP_PASSKEY_START_0_2. \
            The 0.1 URI is still accepted on inbound for backwards compatibility."
)]
pub static TASK_AUTH_STEP_UP_PASSKEY_START_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/login/start/0.1").expect("static")
});

/// `spec/auth/passkey/login/start/0.2` (step-up purpose) — current
/// version of the step-up passkey-assertion request. The deprecated
/// [`TASK_AUTH_STEP_UP_PASSKEY_START_0_1`] form is still accepted on
/// inbound for backwards compatibility.
pub static TASK_AUTH_STEP_UP_PASSKEY_START_0_2: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/login/start/0.2").expect("static")
});

/// `spec/auth/passkey/login/finish/0.1` (step-up purpose) — submit
/// the assertion; the consumer elevates the existing session rather
/// than minting a new one.
#[deprecated(
    since = "0.8.0",
    note = "spec bumped to 0.2; use TASK_AUTH_STEP_UP_PASSKEY_FINISH_0_2. \
            The 0.1 URI is still accepted on inbound for backwards compatibility."
)]
pub static TASK_AUTH_STEP_UP_PASSKEY_FINISH_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/login/finish/0.1").expect("static")
});

/// `spec/auth/passkey/login/finish/0.2` (step-up purpose) — current
/// version of the step-up assertion submission. The deprecated
/// [`TASK_AUTH_STEP_UP_PASSKEY_FINISH_0_1`] form is still accepted on
/// inbound for backwards compatibility.
pub static TASK_AUTH_STEP_UP_PASSKEY_FINISH_0_2: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/login/finish/0.2").expect("static")
});

/// `did-hosting/auth/step-up-check/1.0` — demo sensitive op gated on
/// aal2. Stays under did-hosting/ namespace: the framework canonical
/// "is my session at AAL X?" is `auth/whoami/0.1` plus the client
/// reading `session.acr`. This route remains because the demo
/// scaffolding exercises a specific gating path.
pub static TASK_AUTH_STEP_UP_CHECK_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/auth/step-up-check/1.0").expect("static")
});

/// `spec/auth/step-up/approve-request/0.1` — RP asks the holder's
/// VTA/wallet to ratify an AAL elevation. Sent FROM did-hosting TO
/// the holder's VTA over DIDComm.
#[deprecated(
    since = "0.8.0",
    note = "spec bumped to 0.2; use TASK_AUTH_STEP_UP_VTA_START_0_2. \
            The 0.1 URI is still accepted on inbound for backwards compatibility."
)]
pub static TASK_AUTH_STEP_UP_VTA_START_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/step-up/approve-request/0.1").expect("static")
});

/// `spec/auth/step-up/approve-request/0.2` — current version of the
/// AAL-elevation ratification request. The deprecated
/// [`TASK_AUTH_STEP_UP_VTA_START_0_1`] form is still accepted on
/// inbound for backwards compatibility.
pub static TASK_AUTH_STEP_UP_VTA_START_0_2: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/step-up/approve-request/0.2").expect("static")
});

/// `spec/auth/step-up/approve-response/0.1` — VTA/wallet returns the
/// signed approval; the proof IS the cryptographic step-up gate.
#[deprecated(
    since = "0.8.0",
    note = "spec bumped to 0.2; use TASK_AUTH_STEP_UP_VTA_FINISH_0_2. \
            The 0.1 URI is still accepted on inbound for backwards compatibility."
)]
pub static TASK_AUTH_STEP_UP_VTA_FINISH_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/step-up/approve-response/0.1").expect("static")
});

/// `spec/auth/step-up/approve-response/0.2` — current version of the
/// signed-approval response. The deprecated
/// [`TASK_AUTH_STEP_UP_VTA_FINISH_0_1`] form is still accepted on
/// inbound for backwards compatibility.
pub static TASK_AUTH_STEP_UP_VTA_FINISH_0_2: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/step-up/approve-response/0.2").expect("static")
});

/// `spec/task-consent/request/0.1` — canonical RP→wallet/VTA consent
/// loop (the retired `confirm/request/0.1` is superseded by this).
/// did-hosting uses it for the "park a REST call, await a holder's
/// DIDComm decision" pattern that backs admin-initiated sensitive
/// operations: `effects: []` (no dry-run exists for a prose-described
/// admin action), `minApprovals: 1`, and the admin's action text
/// carried verbatim in the explicitly-untrusted `note` field.
pub static TASK_CONSENT_REQUEST_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/task-consent/request/0.1").expect("static")
});

/// `spec/task-consent/decision/0.1` — the wallet's signed answer to a
/// task-consent request. The Data Integrity proof on the decision — not
/// the transport session that carried it — is the authorization.
pub static TASK_CONSENT_DECISION_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/task-consent/decision/0.1").expect("static")
});

/// `spec/task-consent/decision/0.1#response` — the executor's
/// acknowledgement of a recorded decision (`granted` / `pending` /
/// `denied`), per the framework `<type>#response` convention.
pub static TASK_CONSENT_DECISION_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/task-consent/decision/0.1#response")
        .expect("static")
});

/// `did-hosting/admin-action/1.0` — service-local identifier for the
/// *pending task* a task-consent request concerns when the gated
/// operation is a prose-described, admin-initiated action with no
/// registered task of its own. Never routed; referenced only as the
/// `taskType` bound into a task-consent `payloadDigest`, so a decision
/// minted for an admin-action prompt can never authorize a registered
/// task whose payload happens to canonicalize identically.
pub static TASK_ADMIN_ACTION_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/admin-action/1.0").expect("static")
});

// -- DID provisioning lifecycle --------------------------------------------
//
// Canonical Trust-Task spec URIs only (Phase 3 of the cross-repo
// did-management migration, affinidi/affinidi-webvh-service#143): the
// historical `did-hosting/did/*/1.0` constants and their paired
// `*-confirm` / `*-offer` / `*-ack` response forms were removed in the
// clean cutover — the framework `<type>#response` convention (SPEC
// §4.4.1) replaces the pair-URL scheme, and every operation routes on
// its `spec/did-management/...` URI.

// -- DID-management Trust-Task spec URIs (canonical per dtgwg-trust-tasks-tf) -
//
// Used by:
//   - the dispatcher's match arms (which reference `MSG_*` constants
//     whose values now equal the canonical spec URI).
//   - `dispatch_did_op` response emission: when a request arrives under
//     a spec URI, the response uses the matching `#response` form
//     instead of the legacy paired-URL convention (MSG_DID_OFFER,
//     MSG_DID_REGISTER_CONFIRM, etc.) — keeping older clients on the
//     legacy responses they expect.

pub static TASK_DID_CHECK_NAME_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/check-name/0.1").expect("static")
});

pub static TASK_DID_CHECK_NAME_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/check-name/0.1#response")
        .expect("static")
});

pub static TASK_DID_REGISTER_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/register/0.1").expect("static")
});

pub static TASK_DID_REGISTER_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/register/0.1#response")
        .expect("static")
});

pub static TASK_DID_DELETE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/delete/0.1").expect("static")
});

pub static TASK_DID_DELETE_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/delete/0.1#response")
        .expect("static")
});

pub static TASK_DID_PROBLEM_REPORT_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/problem-report/0.1")
        .expect("static")
});

pub static TASK_DID_INFO_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/info/0.1").expect("static")
});

pub static TASK_DID_INFO_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/info/0.1#response")
        .expect("static")
});

pub static TASK_DID_LIST_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/list/0.1").expect("static")
});

pub static TASK_DID_LIST_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/list/0.1#response")
        .expect("static")
});

pub static TASK_DID_CHANGE_OWNER_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/change-owner/0.1")
        .expect("static")
});

pub static TASK_DID_CHANGE_OWNER_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/change-owner/0.1#response")
        .expect("static")
});

pub static TASK_ME_DOMAINS_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/me/domains/0.1").expect("static")
});

pub static TASK_ME_DOMAINS_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/me/domains/0.1#response")
        .expect("static")
});

/// `spec/did-management/did/set-state/0.1` — declarative hosting state
/// (`active` | `suspended`), the canonical replacement for the retired
/// `did/enable` + `did/disable` pair.
pub static TASK_DID_SET_STATE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/set-state/0.1").expect("static")
});

/// `spec/did-management/did/rollback/0.1` — canonical rollback task.
pub static TASK_DID_ROLLBACK_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/did/rollback/0.1").expect("static")
});

// Agent names — bind/release/park/resume a human-memorable `/@name` on a
// hosted DID, on their canonical spec URIs. `update` carries the
// declarative `state: active | parked` that replaced the retired
// set / enable / disable verb trio; `remove` stays its own task (the
// destructive release is separately auditable by design).
pub static TASK_AGENT_NAME_UPDATE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/agent-name/update/0.1")
        .expect("static")
});
pub static TASK_AGENT_NAME_REMOVE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/agent-name/remove/0.1")
        .expect("static")
});
pub static TASK_AGENT_NAME_CHECK_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/agent-name/check/0.1")
        .expect("static")
});
pub static TASK_AGENT_NAME_LIST_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/agent-name/list/0.1")
        .expect("static")
});

// Domain management on canonical spec URIs. `set-state` carries the
// declarative `state: active | disabled` that replaced the retired
// `domain/enable` + `domain/disable` pair. `domain/list` has no registry
// spec yet and stays on its legacy identifier below.
pub static TASK_DOMAIN_CREATE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/domain/create/0.1").expect("static")
});
pub static TASK_DOMAIN_UPDATE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/domain/update/0.1").expect("static")
});
pub static TASK_DOMAIN_SET_STATE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/domain/set-state/0.1")
        .expect("static")
});
pub static TASK_DOMAIN_SET_DEFAULT_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/domain/set-default/0.1")
        .expect("static")
});
pub static TASK_DOMAIN_PURGE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/domain/purge/0.1").expect("static")
});
pub static TASK_DOMAIN_ASSIGN_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/domain/assign/0.1").expect("static")
});
pub static TASK_DOMAIN_UNASSIGN_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/domain/unassign/0.1")
        .expect("static")
});

/// `spec/did-management/server/register/0.1` — canonical server
/// self-registration with the control plane.
pub static TASK_SERVER_REGISTER_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/did-management/server/register/0.1")
        .expect("static")
});

// -- Hosting infrastructure (server registration, health, stats) ------------

// -- Domain management (new in multi-domain release) -----------------------
//
// Wired by T17 (REST endpoints) and T33 (Trust-Task dispatch). Listed
// here as the source of truth so handlers don't string-literal the URL.

pub static TASK_DOMAIN_LIST_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/domain/list/1.0").expect("static")
});

// -- T8b: REST-only operations that don't have a DIDComm equivalent ---------
//
// The DIDComm protocol carries auth challenges, DID lifecycle, server
// registration etc. The REST surface also exposes admin / observability
// endpoints (passkey enrolment, ACL CRUD, stats, time-series, registry
// management) that have no DIDComm twin. Registering Trust-Task URLs for
// them gates the wire shape uniformly: every authed REST call carries
// (or can carry, in permissive mode during the v0.7→v0.8 migration) a
// canonical task identifier.

// Auth — canonical cross-cutting specs from trusttasks-tf.
pub static TASK_AUTH_CHALLENGE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/challenge/0.1").expect("static")
});
pub static TASK_AUTH_REFRESH_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/refresh/0.1").expect("static")
});
pub static TASK_AUTH_PASSKEY_ENROLL_START_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/enroll/start/0.1").expect("static")
});
pub static TASK_AUTH_PASSKEY_ENROLL_FINISH_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/enroll/finish/0.1").expect("static")
});
#[deprecated(
    since = "0.8.0",
    note = "spec bumped to 0.2; use TASK_AUTH_PASSKEY_LOGIN_START_0_2. \
            The 0.1 URI is still accepted on inbound for backwards compatibility."
)]
pub static TASK_AUTH_PASSKEY_LOGIN_START_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/login/start/0.1").expect("static")
});
/// `spec/auth/passkey/login/start/0.2` — current version of the
/// passkey-login assertion request. The deprecated
/// [`TASK_AUTH_PASSKEY_LOGIN_START_0_1`] form is still accepted on
/// inbound for backwards compatibility.
pub static TASK_AUTH_PASSKEY_LOGIN_START_0_2: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/login/start/0.2").expect("static")
});
#[deprecated(
    since = "0.8.0",
    note = "spec bumped to 0.2; use TASK_AUTH_PASSKEY_LOGIN_FINISH_0_2. \
            The 0.1 URI is still accepted on inbound for backwards compatibility."
)]
pub static TASK_AUTH_PASSKEY_LOGIN_FINISH_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/login/finish/0.1").expect("static")
});
/// `spec/auth/passkey/login/finish/0.2` — current version of the
/// passkey-login assertion submission. The deprecated
/// [`TASK_AUTH_PASSKEY_LOGIN_FINISH_0_1`] form is still accepted on
/// inbound for backwards compatibility.
pub static TASK_AUTH_PASSKEY_LOGIN_FINISH_0_2: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/login/finish/0.2").expect("static")
});
pub static TASK_AUTH_PASSKEY_INVITE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/auth/passkey/enroll/invite/0.1").expect("static")
});

// ACL admin operations.
pub static TASK_ACL_LIST_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/acl/list/1.0").expect("static")
});
pub static TASK_ACL_CREATE_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/acl/create/1.0").expect("static")
});
pub static TASK_ACL_UPDATE_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/acl/update/1.0").expect("static")
});
pub static TASK_ACL_DELETE_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/acl/delete/1.0").expect("static")
});

// DID management — REST-specific helpers (the DIDComm-paired ones are
// above).
pub static TASK_DID_LOG_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/did/log/1.0").expect("static")
});
pub static TASK_DID_RAW_LOG_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/did/raw-log/1.0").expect("static")
});

// Agent names — bind/release/park/resume a human-memorable `/@name` on a
// hosted DID. The DIDComm/TSP-paired framework payloads live in
// `did-hosting-control::trust_tasks_did`; these are the REST-surface task
// identifiers.
/// DID → names, the reverse of the `/@name` redirect. Batched, because its
/// callers are display surfaces holding several DIDs at once and a per-DID
/// round-trip would make a name cost more than the identifier it replaces.
pub static TASK_AGENT_NAME_RESOLVE_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/agent-name/resolve/1.0").expect("static")
});

// Observability / config.
pub static TASK_STATS_SERVER_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/stats/server/1.0").expect("static")
});
pub static TASK_STATS_DID_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/stats/did/1.0").expect("static")
});
pub static TASK_TIMESERIES_SERVER_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/timeseries/server/1.0").expect("static")
});
pub static TASK_TIMESERIES_DID_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/timeseries/did/1.0").expect("static")
});
pub static TASK_SERVICES_OVERVIEW_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/services/overview/1.0").expect("static")
});
pub static TASK_CONFIG_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/config/1.0").expect("static")
});

// Registry admin operations. Distinct from `TASK_SERVER_REGISTER_1_0`,
// which is the *server's* self-registration; these are the *admin's*
// CRUD over the registry table.
pub static TASK_REGISTRY_LIST_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/registry/list/1.0").expect("static")
});
pub static TASK_REGISTRY_ADMIN_REGISTER_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/registry/admin-register/1.0")
        .expect("static")
});
pub static TASK_REGISTRY_GET_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/registry/get/1.0").expect("static")
});
pub static TASK_REGISTRY_DEREGISTER_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/registry/deregister/1.0").expect("static")
});
pub static TASK_REGISTRY_HEALTH_1_0: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/did-hosting/registry/health/1.0").expect("static")
});

// ---------------------------------------------------------------------------
// webvh-protocol-specific ops — `trusttasks.org/webvh/...`
// ---------------------------------------------------------------------------
//
// Witness + sync are protocol features of did:webvh's append-only log.
// did:web has no analog (single did.json, no log, no witness signature).
// Future per-method protocol ops live under `webs/...` / `webplus/...`.

// -- webvh Trust-Task spec URIs (canonical per dtgwg-trust-tasks-tf) ---------
//
// Same dual-URI scheme as the did-management family (see the
// `_0_1` / `_RESPONSE_0_1` block above): each operation has a legacy
// `webvh/did/<op>/1.0` constant paired with `*_ACK_1_0` /
// `*_CONFIRM_1_0` (above) and a canonical spec URI under
// `spec/webvh/<sub>/<op>/0.1` (here). The dispatcher accepts both
// forms; the response dialect mirrors whichever form the request used.
// Slug structure differs between legacy and spec (`webvh/did/<op>` vs
// `spec/webvh/<sub>/<op>`) so the two `LazyLock`s sit side by side
// with the `_1_0` / `_0_1` suffix as the disambiguator.

pub static TASK_WEBVH_WITNESS_PUBLISH_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/webvh/witness/publish/0.1").expect("static")
});

pub static TASK_WEBVH_WITNESS_PUBLISH_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/webvh/witness/publish/0.1#response")
        .expect("static")
});

pub static TASK_WEBVH_SYNC_UPDATE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/webvh/sync/update/0.1").expect("static")
});

pub static TASK_WEBVH_SYNC_UPDATE_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/webvh/sync/update/0.1#response").expect("static")
});

pub static TASK_WEBVH_SYNC_DELETE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/webvh/sync/delete/0.1").expect("static")
});

pub static TASK_WEBVH_SYNC_DELETE_RESPONSE_0_1: LazyLock<TrustTask> = LazyLock::new(|| {
    TrustTask::new("https://trusttasks.org/spec/webvh/sync/delete/0.1#response").expect("static")
});

#[cfg(test)]
mod tests {
    use super::*;

    /// Every registered URL must validate as a `TrustTask`. A broken URL
    /// in a `LazyLock::expect` would only surface on first access; this
    /// test forces every const to deref so the assertion runs at test
    /// time instead of in production.
    #[test]
    // The deprecated `_0_1` auth consts are intentionally still listed
    // — they remain accepted on inbound for backwards compatibility, so
    // their URLs must keep validating alongside the current `_0_2` forms.
    #[allow(deprecated)]
    fn every_registered_url_validates() {
        // List every const here. Adding a new TASK_* without adding it to
        // this list is the kind of drift the cross-crate invariant
        // test (T9) will catch; for now this list is the local proof.
        let all: &[&LazyLock<TrustTask>] = &[
            &TASK_AUTH_AUTHENTICATE_0_1,
            &TASK_AUTH_AUTHENTICATE_RESPONSE_0_1,
            &TASK_AUTH_STEP_UP_PASSKEY_START_0_1,
            &TASK_AUTH_STEP_UP_PASSKEY_START_0_2,
            &TASK_AUTH_STEP_UP_PASSKEY_FINISH_0_1,
            &TASK_AUTH_STEP_UP_PASSKEY_FINISH_0_2,
            &TASK_AUTH_STEP_UP_CHECK_1_0,
            &TASK_AUTH_STEP_UP_VTA_START_0_1,
            &TASK_AUTH_STEP_UP_VTA_START_0_2,
            &TASK_AUTH_STEP_UP_VTA_FINISH_0_1,
            &TASK_AUTH_STEP_UP_VTA_FINISH_0_2,
            &TASK_CONSENT_REQUEST_0_1,
            &TASK_CONSENT_DECISION_0_1,
            &TASK_CONSENT_DECISION_RESPONSE_0_1,
            &TASK_ADMIN_ACTION_1_0,
            &TASK_DID_CHECK_NAME_0_1,
            &TASK_DID_CHECK_NAME_RESPONSE_0_1,
            &TASK_DID_REGISTER_0_1,
            &TASK_DID_REGISTER_RESPONSE_0_1,
            &TASK_DID_DELETE_0_1,
            &TASK_DID_DELETE_RESPONSE_0_1,
            &TASK_DID_PROBLEM_REPORT_0_1,
            &TASK_DID_INFO_0_1,
            &TASK_DID_INFO_RESPONSE_0_1,
            &TASK_DID_LIST_0_1,
            &TASK_DID_LIST_RESPONSE_0_1,
            &TASK_DID_CHANGE_OWNER_0_1,
            &TASK_DID_CHANGE_OWNER_RESPONSE_0_1,
            &TASK_ME_DOMAINS_0_1,
            &TASK_ME_DOMAINS_RESPONSE_0_1,
            &TASK_DID_SET_STATE_0_1,
            &TASK_DID_ROLLBACK_0_1,
            &TASK_AGENT_NAME_UPDATE_0_1,
            &TASK_AGENT_NAME_REMOVE_0_1,
            &TASK_AGENT_NAME_CHECK_0_1,
            &TASK_AGENT_NAME_LIST_0_1,
            &TASK_DOMAIN_CREATE_0_1,
            &TASK_DOMAIN_UPDATE_0_1,
            &TASK_DOMAIN_SET_STATE_0_1,
            &TASK_DOMAIN_SET_DEFAULT_0_1,
            &TASK_DOMAIN_PURGE_0_1,
            &TASK_DOMAIN_ASSIGN_0_1,
            &TASK_DOMAIN_UNASSIGN_0_1,
            &TASK_SERVER_REGISTER_0_1,
            &TASK_DOMAIN_LIST_1_0,
            &TASK_AUTH_CHALLENGE_0_1,
            &TASK_AUTH_REFRESH_0_1,
            &TASK_AUTH_PASSKEY_ENROLL_START_0_1,
            &TASK_AUTH_PASSKEY_ENROLL_FINISH_0_1,
            &TASK_AUTH_PASSKEY_LOGIN_START_0_1,
            &TASK_AUTH_PASSKEY_LOGIN_START_0_2,
            &TASK_AUTH_PASSKEY_LOGIN_FINISH_0_1,
            &TASK_AUTH_PASSKEY_LOGIN_FINISH_0_2,
            &TASK_AUTH_PASSKEY_INVITE_0_1,
            &TASK_ACL_LIST_1_0,
            &TASK_ACL_CREATE_1_0,
            &TASK_ACL_UPDATE_1_0,
            &TASK_ACL_DELETE_1_0,
            &TASK_DID_LOG_1_0,
            &TASK_DID_RAW_LOG_1_0,
            &TASK_AGENT_NAME_RESOLVE_1_0,
            &TASK_STATS_SERVER_1_0,
            &TASK_STATS_DID_1_0,
            &TASK_TIMESERIES_SERVER_1_0,
            &TASK_TIMESERIES_DID_1_0,
            &TASK_SERVICES_OVERVIEW_1_0,
            &TASK_CONFIG_1_0,
            &TASK_REGISTRY_LIST_1_0,
            &TASK_REGISTRY_ADMIN_REGISTER_1_0,
            &TASK_REGISTRY_GET_1_0,
            &TASK_REGISTRY_DEREGISTER_1_0,
            &TASK_REGISTRY_HEALTH_1_0,
            &TASK_WEBVH_WITNESS_PUBLISH_0_1,
            &TASK_WEBVH_WITNESS_PUBLISH_RESPONSE_0_1,
            &TASK_WEBVH_SYNC_UPDATE_0_1,
            &TASK_WEBVH_SYNC_UPDATE_RESPONSE_0_1,
            &TASK_WEBVH_SYNC_DELETE_0_1,
            &TASK_WEBVH_SYNC_DELETE_RESPONSE_0_1,
        ];
        for lock in all {
            let _t = lock.as_str(); // force deref; expect() inside LazyLock
            assert!(
                lock.as_str().starts_with("https://trusttasks.org/"),
                "URL must be under trusttasks.org: {}",
                lock.as_str()
            );
        }
    }

    #[test]
    fn method_agnostic_urls_under_did_hosting() {
        for url in [
            TASK_DOMAIN_LIST_1_0.as_str(),
            TASK_DID_LOG_1_0.as_str(),
            TASK_REGISTRY_LIST_1_0.as_str(),
        ] {
            assert!(
                url.starts_with("https://trusttasks.org/did-hosting/"),
                "expected /did-hosting/ namespace: {url}"
            );
        }
    }

    #[test]
    fn webvh_specific_urls_under_spec_webvh() {
        for url in [
            TASK_WEBVH_WITNESS_PUBLISH_0_1.as_str(),
            TASK_WEBVH_SYNC_UPDATE_0_1.as_str(),
        ] {
            assert!(
                url.starts_with("https://trusttasks.org/spec/webvh/"),
                "expected /spec/webvh/ namespace: {url}"
            );
        }
    }

    #[test]
    fn every_url_ends_in_a_maj_min_version() {
        let all: &[&LazyLock<TrustTask>] = &[
            &TASK_AUTH_AUTHENTICATE_0_1,
            &TASK_DID_CHECK_NAME_0_1,
            &TASK_DOMAIN_LIST_1_0,
            &TASK_WEBVH_SYNC_UPDATE_0_1,
        ];
        for lock in all {
            let url = lock.as_str();
            let tail = url.rsplit('/').next().unwrap();
            // Must look like {digit}.{digit} — no patch component per
            // the canonical Trust-Tasks spec.
            let parts: Vec<&str> = tail.split('.').collect();
            assert_eq!(parts.len(), 2, "version must be maj.min only: {url}");
            assert!(
                parts[0].chars().all(|c| c.is_ascii_digit())
                    && parts[1].chars().all(|c| c.is_ascii_digit()),
                "version components must be digits: {url}"
            );
        }
    }
}