endpoint-documentation-requirements.rst (57481B)
1 .. _endpoint-documentation-requirements: 2 3 ============================================== 4 Endpoint and Method Documentation Requirements 5 ============================================== 6 7 .. contents:: Table of Contents 8 :local: 9 :depth: 2 10 11 Status: proposed convention. Applies to: every per-endpoint ``.rst`` file 12 under ``taler-docs/core/`` (HTTP APIs: exchange, merchant, bank-\*, 13 corebank, challenger, taldir, mailbox, donau, auditor, sync, terminal, 14 ...) and every per-operation ``.rst`` file under 15 ``taler-docs/core/wallet-core/`` (wallet-core methods). 16 17 This document is the normative format specification. Design intent and 18 rationale, the diagnostic of the current corpus, the migration plan, and 19 worked examples live in :doc:`DD 105 20 <../design-documents/105-machine-readable-endpoint-documentation>`. 21 22 The format is language-agnostic. The shape layer describes wire-level 23 types (JSON objects exchanged between client and server) via the 24 historically named ``ts:def`` / ``ts:op`` directives; nothing in this 25 specification ties the documented semantics to any implementation 26 language, and generated artifacts may target any language. 27 28 29 1. Purpose 30 ========== 31 32 Each endpoint/method is documented in **exactly one** ``.rst`` file that is 33 **completely machine-readable**: every semantic claim a client, a doc lint, 34 or a use-case generator could need is expressed in a directive option, a 35 field, or a typed block — never only in prose. Prose remains for human 36 readers, but it is *rendering*, not *source of truth*. 37 38 The goal is that the following can be **derived** from the doc corpus 39 without reading prose: 40 41 * per-entity state tables (states, transitions, timers, failure exits), 42 with every transition naming its trigger and guard, 43 * per-actor observation projections (who can see which state, through 44 which endpoint field or notification channel), 45 * cross-component flows (which call creates an identifier, which calls 46 carry or consume it, which watched source moves an entity), 47 * sequence diagrams (happy-path messages from transition edges, 48 ``alt`` blocks from guards, ``loop`` blocks from timers). 49 50 The docs are **authoritative**: implementations conform to them. To make 51 that enforceable, the machine-readable shape layer (``ts:def`` types, 52 request/response schemas) should be used to generate as much source code 53 as possible (types, validators, stubs), with drift caught by CI in the 54 implementation repositories. 55 56 One design philosophy runs through everything below: **derive edges, 57 declare anchors.** Anything that is an *edge* (a transition, an arming, 58 an observation, a carriage of a value) is written once, on the edge 59 itself — the transition, the method, the effect. Anything that acts as 60 a *resolution anchor* (an entity, a timer name, an identifier, a 61 notification channel) is declared once, minimally, so that the edges can 62 be checked against it. If a fact can be derived from edges, it is never 63 also declared; if a name must be resolvable, it is never left implicit. 64 65 66 2. File organization 67 ==================== 68 69 R2.1 One file per method. No file documents two unrelated methods; no 70 method is documented in two files. Two declared exceptions exist: 71 72 * *Variant groups*: one logical method may be documented as 73 several directive instances in one file when the variants differ 74 only in a path parameter's value domain (per-record-type delete 75 endpoints, per-provider webhook entry points) or in long-poll 76 parameter dialect. The file is linted as one method with 77 variants. 78 * *Proxy stanzas*: wildcard delegation declarations 79 (``.. http:any::`` with wildcard paths, e.g. reverse-proxy 80 pass-throughs) are not methods; they carry no request/response 81 semantics and follow only the naming rules of R2.2. 82 83 R2.2 Naming (required): 84 85 * HTTP endpoints: ``<method>-<path-segments>.rst``, lowercase, 86 path variables in upper case without ``$`` 87 (``get-withdrawal-operation-WITHDRAWAL_ID.rst``), 88 placed in the per-API directory (``core/exchange/``, 89 ``core/merchant/``, ``core/bank-integration/``, ...). 90 * wallet-core operations: ``<op-name>.rst`` in kebab-case inside 91 the category directory (``core/wallet-core/balances/``). 92 93 R2.3 Chapter files (``api-*.rst``) keep version history, shared 94 conventions, and ``.. include::`` of per-method files. They carry 95 **no** per-method semantics that the method file itself lacks. 96 97 R2.4 Cross-method information lives in exactly two places outside the 98 method files: the **entity registry** (§4: entities, lifecycles, 99 timers, identifiers) and the **per-component notification pages** 100 (§5: real-time channels). Method files *reference* these; they 101 never restate them. 102 103 104 3. The method file format 105 ========================= 106 107 A compliant file contains the sections below **in this order**. 108 Requirement levels: **REQ** (must be present), **COND** (present when 109 applicable), **OPT**. 110 111 3.1 Method header — REQ 112 ----------------------- 113 114 The header is the method's machine-readable identity card. Every option 115 exists because a specific consumer needs it: the lint (vocab checks), 116 the use-case generator (persona closures, observation bindings), the 117 code generator (shape extraction), or the exclusion filter (drafts). 118 119 HTTP endpoints keep the existing identity directive, extended with new 120 options: 121 122 .. code-block:: rst 123 124 .. http:get:: /withdrawal-operation/$WITHDRAWAL_ID 125 :since: v1 126 :maturity: stable 127 :idempotency: readonly 128 :observes-state: withdrawal-operation.status 129 :visible-to: wallet-user, bank-customer (capability withdrawal-id) 130 :wait: long-poll(timeout_ms, old_state) 131 132 One-line summary usable standalone (no markup that breaks extraction). 133 134 Longer prose description... 135 136 wallet-core operations keep ``ts:op``, extended the same way: 137 138 .. code-block:: rst 139 140 .. ts:op:: getBalances 141 :read-only: 142 :since: v4 143 :maturity: stable 144 :idempotency: readonly 145 :visible-to: wallet-user 146 :wait: none 147 148 Options: 149 150 ``:since:`` (REQ) 151 Protocol/API version introducing the method, exactly ``v<N>``. 152 Needed so derived artifacts can be sliced by protocol version (an old 153 wallet talking to a new exchange sees a genuinely different machine). 154 155 ``:deprecated:`` (COND) 156 Version of deprecation, exactly ``v<N>``; presence implies 157 ``:maturity: deprecated`` unless ``:maturity:`` says otherwise. 158 159 ``:maturity:`` (REQ, default ``stable``) 160 One of: ``stable``, ``experimental``, ``draft-no-consumers``, 161 ``proposed-unimplemented``, ``deprecated``. 162 Methods marked ``draft-no-consumers`` or ``proposed-unimplemented`` 163 are excluded from use-case derivation (their files exist to document 164 intent, not behavior). Examples in the current corpus: wads and 165 reserve-control endpoints in the exchange API, ``/seed`` in donau, 166 the apns/account-directory/ebisync APIs. 167 (The option is named ``:maturity:`` and not ``:status:`` because the 168 HTTP domain already uses ``:status:`` as a field-name alias for 169 status codes.) 170 171 ``:idempotency:`` (REQ) 172 One of: ``readonly``, ``replay-safe``, ``keyed:<field>``, ``unsafe``. 173 See §3.5 and §7.1. 174 175 ``:observes-state: <entity>.<field>`` (COND) 176 The response field (dotted path into the response ``ts:def``) that 177 carries an entity's machine-readable state value 178 (e.g. ``withdrawal-operation.status``, ``order.order_status``, 179 ``transaction.state``). The field's type must be a string union or 180 an object with an enumerated discriminator — the lint resolves every 181 union member against the entity's lifecycle (§4). This is the 182 **observation binding**: it states that this method's response *is* a 183 view onto that entity's machine. Without it, the per-actor state 184 projections ("who can see which state, where") cannot be computed — 185 the generator would have to guess the binding from type names or 186 enum-member spelling, which collides across APIs (``pending`` 187 appears in half a dozen unions). 188 189 ``:visible-to:`` (REQ) 190 Comma-separated actor surfaces that may legitimately call or observe 191 through this method (§7.3). This is the single authorization fact of 192 the file: the actor value implies the credential mechanism (§7.3 193 table), so no separate ``:auth:`` field exists. Where access is 194 gated by an unguessable identifier rather than credentials, the 195 capability is named in parentheses: 196 ``merchant-staff, tax-auditor (capability wtid)``. 197 Persona projections are computed as closures over this field. 198 199 ``:timer-ref: <entity>.<timer>`` (COND, repeatable) 200 References a timer **declared at the entity** (§4.3), with a ``:role:`` 201 describing this method's relationship to it: 202 203 * ``arms`` — a successful call starts the clock, typically via a 204 request field named in ``:field:`` (``POST /private/orders`` arms 205 ``order.refund-deadline`` via ``refund_delay``). 206 * ``exposes`` — the response carries the timer's current value, in 207 the field named by ``:field:`` 208 (``ValidationChallengeInfo.solve_expiration``). 209 * ``affected-by`` — the method's behavior changes once the timer 210 fires (``.../challenge/$ID/confirm`` fails with 211 ``TAN_CHALLENGE_EXPIRED``). 212 213 The timer's semantics are declared once at the entity; the firing 214 edges live in the lifecycle's transitions. Method files only declare 215 their *relationship* to the clock. 216 217 ``:wait:`` (REQ) 218 ``none``, or ``long-poll(<param1>, <param2>, ...)`` listing this 219 method's long-poll request parameters. This acknowledges that the 220 endpoint can rendezvous with a state change. It stays at the method 221 because the parameter dialects are per-endpoint and differ 222 (``timeout_ms`` alone, ``timeout_ms`` + ``old_state``, the merchant's 223 ``lp_status``/``lp_not_etag`` algebra). Real-time *push* channels 224 are **not** declared here — they live in the component's notification 225 page (§5). 226 227 3.2 Request — REQ 228 ----------------- 229 230 **Why this section is strict:** the request block is what the code 231 generator turns into client stubs and validators, and what the lint 232 cross-checks ``:wait:`` parameters against. A parameter that exists 233 only in prose is invisible to both. 234 235 .. code-block:: rst 236 237 **Request:** 238 239 :query timeout_ms: 240 *Optional.* Timeout in milliseconds for :ref:`long-polling 241 <long-polling>`. Since protocol **v3**. 242 :header Taler-Purse-Signature: 243 *Conditionally required.* Purse signature over the request. 244 :body: 245 The request body must be a `WithdrawRequest` object. 246 247 .. ts:def:: WithdrawRequest 248 ... 249 250 Rules: 251 252 R3.2.1 Every parameter appears exactly once as ``:query:``, 253 ``:header:``, ``:path:``, or inside the ``ts:def`` body type. 254 Optionality (``*Optional.*``, ``*Conditionally required.*``) 255 starts the description; version annotations use ``@since`` / 256 ``@deprecated`` in the existing style. 257 258 R3.2.2 Every type referenced is defined in a ``ts:def`` block in this 259 file or in a reachable common file. No anonymous prose types. 260 261 3.3 Response — REQ 262 ------------------ 263 264 **Why the discipline:** HTTP status codes in this ecosystem do not 265 follow the naive "2xx = success, else failure" rule (see §3.4). The 266 response section declares the *shape* of each outcome; the **Errors** 267 section declares its *semantics*. Keeping the two apart is what lets 268 the generator distinguish a failure edge from a state report. 269 270 .. code-block:: rst 271 272 **Response:** 273 274 :http:statuscode:`200 OK`: 275 :schema: BankWithdrawalOperationStatus 276 The withdrawal operation is known to the bank... 277 :http:statuscode:`404 Not found`: 278 :error: 279 The operation was not found. 280 281 Rules: 282 283 R3.3.1 Every documented status code carries ``:schema: <TypeName>`` 284 (a success-shaped body defined in ``ts:def``), ``:error:`` 285 (standard `ErrorDetail` body), or both. "Both" happens for 286 non-2xx codes whose body reports state rather than failure — 287 those additionally get an **Errors** entry with 288 ``:temporality: progress`` (§3.4). 289 290 3.4 Errors — REQ (explicitly empty if none) 291 ------------------------------------------- 292 293 **Why this section exists:** error codes are overloaded across flows, 294 and the overloads are not inferable. A 404 on an unfunded reserve 295 means "wait, the wire hasn't arrived" (transient); a 404 on an expired 296 order means "gone forever" (terminal). A 402 on the order status page 297 is the *normal unpaid state* (progress); a 402 on purse merge is a 298 fixable rejection. Same codes, opposite meanings. The two fields per 299 entry — ``:temporality:`` (what kind of outcome this is) and 300 ``:recovery:`` (what moves the flow forward) — are what make the 301 failure and timer columns of derived state tables computable instead 302 of guessed. 303 304 Every non-2xx outcome gets a structured entry — including codes that 305 are **not failures** (progress signals such as 402-unpaid, 202-pending): 306 307 .. code-block:: rst 308 309 **Errors:** 310 311 :error 404 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN: 312 :temporality: transient-state 313 :recovery: long-poll 314 The reserve is unknown; the wire transfer may not have arrived. 315 The wallet should long-poll the reserve status and repeat the 316 exact same request. 317 :error 409 TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS: 318 :temporality: fixable 319 :recovery: retry-modified 320 The reserve balance is too low; retry withdrawing less. 321 :error 451 TALER_EC_EXCHANGE_KYC_REQUIRED: 322 :temporality: gate 323 :recovery: start-kyc 324 The account must pass KYC before this call can succeed. 325 :error 402 none: 326 :temporality: progress 327 :recovery: none 328 The order is unpaid; the body carries the ``taler_pay_uri`` the 329 wallet uses to proceed. This is the normal waiting state of the 330 flow, not a failure. 331 332 Rules: 333 334 R3.4.1 ``:temporality:`` (REQ per error) — one of ``transient-state``, 335 ``terminal``, ``fixable``, ``rate-limited``, ``gate``, 336 ``progress`` (§7.4). ``progress`` entries are how the 337 generator knows *not* to derive a failure edge: the code is the 338 state signal. 339 340 R3.4.2 ``:recovery:`` (REQ per error) — the client action that can 341 move the flow forward, from §7.5. For ``progress`` entries this 342 is usually ``none`` or ``poll-later``. 343 344 R3.4.3 The human-readable condition follows the fields. Multiple 345 error codes for one status get one entry each. Where the error 346 is caused by a timer firing, the condition references the 347 entity timer (``:timer-ref:`` in the header makes the method's 348 relationship explicit). 349 350 3.5 Idempotency — COND (mutating methods) 351 ----------------------------------------- 352 353 **Why this is a first-class field:** retries are the universal response 354 to network failure, and a request that arrived-but-lost-its-response is 355 indistinguishable from one that never arrived. Whether a verbatim 356 replay is *safe* therefore decides whether the client's failure branch 357 exists at all — and whether the derived sequence diagram draws a retry 358 loop or an abort. This is not guessable: ``POST /withdraw`` is 359 replay-safe by careful design (repeat the exact request, get the same 360 response, coins are never lost to a network cut), while 361 ``POST /private/orders`` without an explicit ``order_id`` creates a 362 duplicate. The label also flags design smells: an ``unsafe`` mutating 363 method in a system built on at-least-once networking deserves review. 364 365 Covered by the ``:idempotency:`` header option (§3.1, vocabulary in 366 §7.1). For ``keyed:<field>`` the file additionally documents the key 367 field's reuse semantics in prose (existing good examples: bank-wire 368 ``/transfer`` ``request_uid``, cashout ``request_uid``). The lint 369 requires: ``POST``/``PATCH``/``DELETE`` without 370 ``:idempotency: unsafe | replay-safe | keyed:*`` is an error. 371 372 3.6 Lifecycle linkage — COND 373 ---------------------------- 374 375 **Why two bindings and not one:** a method relates to entity machines 376 (§4) in two fundamentally different ways, and conflating them breaks 377 different consumers: 378 379 * **Mutators** declare their transitions in **Effects** (§3.7). The 380 lint diffs these against the entity's lifecycle: an effect naming an 381 undeclared transition is a doc bug in one of the two places. This is 382 the two-sided check — the lifecycle says "call X drives A→B", the 383 method file says "I drive A→B on entity E", and the disagreement 384 surfaces. One-sided declarations silently rot; that is the lesson 385 of every doc-versus-code audit on this corpus. 386 * **Readers** declare the observation binding via ``:observes-state:`` 387 (§3.1). This is what makes the per-persona "user sees" column of a 388 state table *generated* rather than authored. 389 390 R3.6.1 Every entity named in either place is registered in the entity 391 registry with: owner, transitions, terminal states, timers, 392 identifiers. 393 394 R3.6.2 State values named anywhere in the file resolve to the 395 entity's lifecycle states (the set inferred from its 396 transitions). 397 398 3.7 Effects — COND (mutating methods) 399 ------------------------------------- 400 401 **Why this section exists:** request/response documentation describes 402 what the *caller* gets back — it says nothing about what changed in the 403 *world*. ``POST /private/orders`` returns an order id; the state that 404 matters — "a wallet can now claim this order" — is a side effect 405 visible through a *different* endpoint by a *different actor*. A 406 mutation is not documented until you have said who else can now see 407 what. The Effects block is also the generator's source for the 408 *messages* of a sequence diagram: each effect edge is a signal to 409 another lifeline. 410 411 .. code-block:: rst 412 413 **Effects:** 414 415 :effect: 416 :entity: order 417 :transition: none -> unpaid 418 :observable-by: :http:post:`/orders/$ORDER_ID/claim` 419 Creates the order; a wallet can now claim it. 420 421 Field by field: 422 423 ``:entity:`` 424 The lifecycle entity whose machine moves (resolves to §4). 425 426 ``:transition:`` 427 The exact edge performed, in the lifecycle's own notation. Must 428 agree with the entity's declared transitions (the two-sided check 429 of §3.6). 430 431 ``:observable-by:`` 432 The endpoint through which another actor observes the change. This 433 is the join that lets a generator draw the *next* message in the 434 sequence diagram. 435 436 R3.7.1 One ``:effect:`` entry per world-change beyond the response. 437 438 R3.7.2 Real-time signals caused by the change (wallet notifications, 439 merchant webhooks) are **not** listed here; they follow from the 440 transition and are documented in the component's notification 441 page (§5). 442 443 3.8 Identifiers — COND 444 ---------------------- 445 446 **Why this section exists:** methods do not exist in isolation — a 447 value created by one call shows up in another component's API three 448 hops later. This block declares, per *identifier*, the method's role 449 in that value's life. Identifiers are the handles and join keys of the 450 system (``reserve_pub``, ``wtid``, ``order-id``, claim nonces); 451 ``order-id`` *names* an order entity, but the identifier itself is a 452 value, not a machine. 453 454 From the union of all method-side tags, the generator derives the 455 identifier's full life story (created-by / carried-by / read-by / 456 consumed-by), draws the cross-component value-flow graph, and the lint 457 gets its two-sided join checks. The registry (§4.4) holds only the 458 anchor: the name, the capability property, and prose. 459 460 .. code-block:: rst 461 462 **Identifiers:** 463 464 :identifier reserve_pub: 465 :role: carries 466 :registry: :ref:`identifier-reserve-pub` 467 Wallet-generated reserve key; delivered to the bank here, becomes 468 the wire subject of the funding transfer. 469 470 The four roles, from the method's perspective: 471 472 * ``creates`` — the value begins to exist here. ``POST 473 /private/orders`` creates ``order-id``; the wallet's withdrawal 474 preparation creates ``reserve_pub``. 475 * ``carries`` — transports a value created elsewhere: in the URL, body, 476 response, or wire subject. The bank-integration selection POST 477 carries ``reserve_pub`` from wallet to bank; ``POST 478 /private/transfers`` carries ``wtid`` from the bank statement into 479 the merchant backend. 480 * ``reads`` — uses the value as a lookup key. ``GET 481 /reserves/$RESERVE_PUB`` reads ``reserve_pub``; ``GET 482 /transfers/$WTID`` reads ``wtid``. 483 * ``consumes`` — validity ends here (single-use values): the 484 challenger's ``/token`` consumes the authorization grant; a 485 successful ``/orders/$ID/pay`` consumes the claim nonce. 486 487 One method can play several roles on several identifiers: ``POST 488 /orders/$ID/pay`` *reads* ``order-id``, *consumes* the claim nonce, and 489 *carries* ``h_contract_terms`` onward to the exchange. 490 491 R3.8.1 ``:role:`` vocabulary is §7.8. The ``:registry:`` ref must 492 resolve to the identifier's registry entry (§4.4). 493 494 3.9 Details/Notes — OPT 495 ----------------------- 496 497 Free prose for anything genuinely not expressible above (rationale, 498 privacy notes, examples). Must not introduce semantics that contradict 499 or extend the structured sections; the lint greps for state names and 500 error codes appearing *only* here. 501 502 503 4. Entities and lifecycles 504 ========================== 505 506 **Why a registry:** state lives *between* methods. A method file can 507 honestly describe its own request and response, but the machine the 508 methods collectively implement — the states, the transitions, the 509 clocks — is a property of the *entity*, not of any endpoint. The 510 registry (``core/entities.rst``, with per-API chapters linking in) is 511 where that machine is declared once. Per the design philosophy, the 512 registry declares anchors (names, owners, terminal states, timer names, 513 identifier properties) and the edges (transitions) carry their own 514 facts. 515 516 Each entity has exactly one declaration: 517 518 .. code-block:: rst 519 520 .. entity:: tan-challenge 521 :owner: corebank 522 523 A TAN challenge issued for multi-factor confirmation of a 524 corebank operation. 525 526 .. lifecycle:: 527 :terminal: solved, expired, exhausted 528 529 :transition none -> unsolved: 530 :trigger: call <any MFA-gated call returning 531 202 + ChallengeResponse> 532 The challenge is created as part of an MFA-gated operation. 533 :transition unsolved -> solved: 534 :trigger: call POST /accounts/$U/challenge/$ID/confirm 535 Correct TAN submitted. 536 :transition unsolved -> expired: 537 :trigger: timer tan-challenge.solve-expiration 538 The challenge was not solved in time. 539 :transition unsolved -> exhausted: 540 :trigger: call POST /accounts/$U/challenge/$ID/confirm 541 :if: the failed-confirmation counter reaches zero 542 Too many wrong TANs submitted. 543 544 .. timer:: solve-expiration 545 The clock by which the challenge must be solved. (Armed by 546 challenge creation — derived from the ``arms`` timer-refs of 547 the creating methods; exposed in 548 ``ValidationChallengeInfo.solve_expiration`` — derived from 549 ``exposes`` timer-refs; fires the ``unsolved -> expired`` 550 transition — derived from the lifecycle.) 551 552 .. timer:: retransmission-cooldown 553 Throttles TAN retransmission; calls while it is active fail 554 with 429 (derived from the ``affected-by`` timer-ref and the 555 error entry of the (re)send endpoint). 556 557 .. identifier:: challenge-id 558 :capability: no (owner-authenticated) 559 Identifies the challenge on the challenge endpoints. 560 561 4.1 Owner 562 --------- 563 564 ``:owner:`` (REQ) names the **component holding authoritative state and 565 executing the transitions** — not merely where the entity is documented 566 or displayed. Ownership determines which component's answer is truth; 567 every other component's view is a projection that may lag (the wallet's 568 balance is a projection of exchange-owned coin-spend state; the 569 wallet's order status display is a projection of merchant-owned order 570 state). An entity may be *exposed* through several APIs of the owner 571 (``withdrawal-operation``: bank-integration, corebank, terminal) — name 572 them in prose under the owner. Where state is genuinely shared across 573 components (KYC requirement and legitimization state is projected into 574 the exchange, the merchant, the bank, and the wallet), the owner is 575 the component whose answer is authoritative and the projections are 576 named as such; ownership is about who executes transitions, not about 577 which trigger kind dominates — a wallet transaction entity is owned by 578 wallet-core even though its transitions are driven by watches, timers, 579 and local user actions rather than incoming calls (§6). 580 581 4.2 Lifecycle 582 ------------- 583 584 The lifecycle is the transition list. **States are inferred**: the 585 state set is exactly the names appearing as transition sources or 586 targets (plus the pseudo-state ``none`` for pre-existence, which is 587 never a real state). ``:terminal:`` (REQ) is **declared**, not 588 inferred — it is the absorbing set: 589 590 * it anchors use-case success conditions ("done" = reaching a declared 591 terminal state); 592 * it makes the absorbing property checkable: a transition *leaving* a 593 declared terminal state is a lint error; 594 * it turns incompleteness into signal: a declared-non-terminal state 595 with no outgoing transitions is a lint warning (a silently truncated 596 machine becomes visible). 597 598 ``:final-reactivatable:`` (COND) declares states that anchor success the 599 same way but from which declared reactivation edges are legal (a paid 600 order later refunded; a ``done`` wallet transaction reactivated by a 601 refund query). Transitions leaving a ``final-reactivatable`` state are 602 permitted but must be listed explicitly. See §6 for the wallet 603 transaction entities that need this. 604 605 Each ``:transition: A -> B:`` has: 606 607 ``:trigger:`` (REQ, repeatable) 608 What causes the transition (§7.7). This is the causality layer — 609 the part of the system that no per-endpoint doc can see locally: 610 611 * ``call <method>`` — an external call to the owner's API 612 (resolvable to a method file). Example: the wallet's selection 613 POST drives ``pending -> selected``. 614 * ``timer <entity>.<timer>`` — a declared entity timer fires. 615 Example: ``solve-expiration`` drives ``unsolved -> expired``. 616 * ``watch <component>.<observable>`` — the owner watches an external 617 source (the exchange's wirewatch watching the bank's incoming 618 history; the merchant backend tracking exchange wire arrivals). 619 This is where **cross-component causality** lives: the transition 620 names the other component's observable, which is the join the old 621 prose never made. 622 * ``external <description>`` — a human or off-API action (bank-UI 623 2FA confirmation, cash inserted at a terminal, an offline key 624 ceremony). Non-API actors enter the machine here, honestly marked 625 as outside every API. 626 627 ``:if:`` (COND) 628 The guard: the config/data condition under which this transition 629 exists at all, referencing observable fields 630 (``:if: /keys.kyc_enabled and the amount crosses the withdrawal 631 threshold``). Guards are how context forks (KYC on/off, thresholds, 632 feature flags) enter the machine without multiplying machines — one 633 canonical machine, guarded edges, instead of a combinatorial family 634 of near-identical diagrams. 635 636 Prose (REQ) 637 One or two sentences of human explanation. 638 639 4.3 Timers (slim anchors) 640 ------------------------- 641 642 A timer is declared as a **name plus semantics in prose** — nothing 643 more, because everything structural is derivable from edges: 644 645 * *fires-to*: from the lifecycle transition whose ``:trigger:`` is this 646 timer; 647 * *armed-by*: from the method whose ``:timer-ref:`` has role ``arms``; 648 * *exposed-in*: from the method whose ``:timer-ref:`` has role 649 ``exposes`` (its ``:field:``); 650 * *gates*: from the ``429``/``rate-limited`` error entry of the method 651 whose ``:timer-ref:`` has role ``affected-by``. 652 653 The block survives at all for three reasons: it is the **lint anchor** 654 every ``:trigger: timer`` / ``:timer-ref:`` resolves against (free 655 strings across hundreds of files would drift); it is the home of 656 **config-armed** timers no method arms (``:armed-by:`` is present only 657 in that case — e.g. ``purse_timeout`` comes from ``GET /keys`` global 658 fees, not from any call); and it is the **gap marker**: an entity whose 659 timers are not yet specified records the gap here once (see the 660 ``withdrawal-operation`` example in DD 105), instead of silence 661 scattered across N method files. 662 663 4.4 Identifiers (slim anchors) 664 ------------------------------ 665 666 An identifier is declared as **name + ``:capability:`` + prose**: 667 668 .. code-block:: rst 669 670 .. identifier:: reserve_pub 671 :capability: yes 672 Reserve public key; possession authorizes reserve-status reads. 673 Wallet-generated; becomes the wire subject of the funding 674 transfer. 675 676 ``:capability:`` (REQ: ``yes``/``no``) records whether possession 677 alone authorizes (unguessable: ``wtid``, ``withdrawal-id``, purse/claim 678 links) — the property that distinguishes "public by design" (``GET 679 /keys``) from "public but unguessable" (``/transfers/$WTID`` is usable 680 by tax auditors *because* of it). It is a property of the *value*, not 681 of any method, so it has no other home; it is also what the 682 ``:visible-to:`` capability annotations resolve against (V3). 683 684 Everything else is derived from the method files' **Identifiers:** 685 blocks (§3.8): *created-by*, *carried-by*, *read-by*, *consumed-by*. 686 One exception needs prose: identifiers created **internally** (``wtid`` 687 is minted by the exchange's aggregator process — no method tags 688 ``creates``) get a created-by sentence here, mirroring config-armed 689 timers. The registry also serves as the canonical naming list — the 690 single place where ``reserve_pub`` is one concept, so that hundreds of 691 files of tags can be checked for spelling drift. 692 693 694 5. Per-component notification pages 695 =================================== 696 697 **Why these pages exist:** how an actor *learns* that a state changed 698 is as much a part of the use case as the state change itself — and it 699 was historically documented in prose scattered across chapters. The 700 doctrine of this system is *signals are hints; reads are truth*: a 701 notification tells you something changed, an authoritative read tells 702 you what is. The notification page is where a component declares its 703 channels, their delivery semantics, and — critically — the read that 704 backs each signal. 705 706 Every component that offers real-time signals documents them in a 707 single notification page: ``core/wallet-core/notifications.rst`` 708 (exists today as a flat list of notification types; to be restructured 709 into the channel form below), a merchant notification page (webhooks, 710 currently prose in ``api-merchant.rst``), an exchange notification page 711 (will honestly declare: none — pull + long-poll only), and so on. 712 713 Each channel declaration: 714 715 .. code-block:: rst 716 717 .. notification-channel:: webhook 718 :component: merchant 719 :registration: POST /private/webhooks 720 :delivery: at-least-once 721 :events: order_created, pay, refund, order_settled, 722 category_created, category_update, category_delete, 723 inventory_update, inventory_product_created, ... 724 725 Every event is a hint about an entity transition; the 726 authoritative read is named per event: 727 ``pay``/``refund``/``order_settled`` -> 728 ``GET /private/orders/$ORDER_ID``. 729 730 Rules: 731 732 R5.1 ``:delivery:`` is one of ``lossy-hint`` (no delivery guarantee; 733 re-read for truth — the wallet notification doctrine), 734 ``at-least-once`` (duplicates possible, recipient must 735 deduplicate), ``rendezvous`` (the channel itself blocks until the 736 state is available). If the implementation's delivery semantics 737 are not specified today, write ``unspecified`` — a visible gap, 738 not silence. 739 740 R5.2 Every event names its **authoritative read** (the endpoint whose 741 response is the truth). The lint rejects a channel without a 742 read reference per event family. 743 744 R5.3 Method files never declare push channels. The method whose 745 effect *causes* a signal is linked automatically: effect 746 transition (§3.7) + channel event mapping (here). 747 748 R5.4 Long-poll is **not** a notification channel; it is a property of 749 the read endpoint and is declared via ``:wait:`` (§3.1). A 750 long-poll response is authoritative state delivered late, not a 751 hint. 752 753 754 6. wallet-core method variant 755 ============================= 756 757 ``ts:op`` files follow the same requirements with this mapping: 758 759 .. list-table:: 760 :header-rows: 1 761 762 * - HTTP concept 763 - wallet-core op equivalent 764 * - ``:visible-to:`` 765 - ``wallet-user`` (local message protocol, no credentials) 766 * - ``:http:statuscode:`` responses 767 - **Response** ``ts:def`` + **Expected errors** 768 * - ``:error`` entries 769 - **Expected errors** entries, same ``:temporality:`` / 770 ``:recovery:`` fields 771 * - ``:wait:`` 772 - ``long-poll(...)`` for blocking ops (``waitTransactionState``, 773 ``waitExchangeReady``, ...), ``none`` otherwise; the wallet's 774 push channel is documented in ``notifications.rst`` (§5) 775 * - effects 776 - transitions on transaction-group entities 777 (``transaction-withdrawal``, ...) with states from the DD37 778 machine 779 780 ``TransactionState`` values used in ``:transition:`` fields must match 781 ``major[:minor][/working]`` notation of DD37 and resolve against 782 ``core/wallet-core/transactions/get-transaction-by-id.rst``. 783 784 Wallet transaction entities extend the lifecycle model of §4 in four 785 ways (rationale in DD 105): 786 787 * **Final-reactivatable states.** DD37's ``done`` is not absorbing 788 (refund queries, ``rebind-session``, and late KYC requirements can 789 reactivate a transaction). Transaction entities declare 790 ``:final-reactivatable:`` (§4.2) for such states and list the 791 reactivation edges explicitly. 792 * **Orthogonal modifiers instead of state explosion.** The DD37 793 ``working`` flag and the ``suspended`` / ``suspended-aborting`` / 794 ``suspended-finalizing`` shadow states are declared as modifiers 795 (same mechanism as ``:modifiers:`` on other entities), not as 796 literal states. A ``:standard-edges: suspend-retry`` declaration on 797 the lifecycle implies the suspend/resume/retry edge family on every 798 non-final state, so the inferred state and edge sets include them 799 without authoring each one. DD37 state *expressions* 800 (``pending:*``, ``pending:-``, ``/idle``, comma alternation) are 801 accepted wherever a state set is matched. 802 * **Absence as signal.** The ``deleted`` state exists only in 803 notifications; its observation is the absence of the record (a 804 not-found error from ``getTransactionById``). ``:observes-state:`` 805 bindings may name this explicitly (``transaction.state, 806 absence-signal: deleted``). 807 * **Actions are not derived.** ``txActions`` are computed per state 808 *and* context; no "possible actions" column is generated from the 809 lifecycle. 810 811 The registry entries for transaction entities are the canonical 812 machine-readable form of the DD37 machines; DD37's per-type state 813 tables are generated from the registry. 814 815 816 7. Controlled vocabularies 817 ========================== 818 819 Controlled vocabularies are where machine-readability lives or dies. 820 A field whose values come from a closed list can be checked, counted, 821 and closed over by tooling; a field with free text is prose wearing a 822 costume. Every value below is part of the contract: **adding a value 823 is a specification change** (tooling must learn its meaning), and using 824 a non-listed value is a lint error (M8.3). For each value we give the 825 definition, a real example from the corpus, and what the derivation 826 tooling does with it. 827 828 7.1 ``:idempotency:`` values 829 ---------------------------- 830 831 The question this field answers is the most frequent operational 832 question a client faces: *"the network ate my request or its response — 833 may I send the exact same bytes again?"* The answer determines whether 834 the failure branch of a use case exists at all, and what the sequence 835 diagram draws at every network boundary. 836 837 * ``readonly`` — the call mutates nothing: no entity transition, no 838 world-change. (Logging and caching on the server side do not count 839 as mutation.) Examples: ``GET /keys``, ``getBalances``. 840 *Consumers:* the generator expects no **Effects** section; the lint 841 warns if a ``readonly`` method declares one (V6's inverse). 842 843 * ``replay-safe`` — the server has recorded the request and its 844 response; a byte-identical repeat returns the same response with no 845 additional effect. Examples: ``POST /withdraw`` (repeating the exact 846 request yields the same blinded signatures — coins are never lost to 847 a network cut), donau batch-issue (keyed on the recorded request 848 hash). *Failure mode prevented:* value loss when the response is 849 lost after the server committed. *Consumers:* the generator draws 850 retry self-loops for free. Note that replay means *byte-identical*; 851 a modified request is a new operation, and conflicts there belong to 852 **Errors** (e.g. ``TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET``). 853 854 * ``keyed:<field>`` — the client supplies an idempotency key; reuse of 855 the key with the same body suppresses duplicate effects, reuse with a 856 *different* body is rejected (409). Examples: bank-wire 857 ``/transfer`` (``request_uid``), corebank cashouts (``request_uid``, 858 including the rule that a corrected quote needs a fresh nonce). 859 *Why it exists:* lets the client retry safely *and* lets the server 860 detect client bugs. §3.5 requires prose on the key's reuse 861 semantics. 862 863 * ``unsafe`` — retries may duplicate effects. Example: 864 ``POST /private/orders`` without an explicit ``order_id`` creates a 865 second order. Must be justified in prose. *Consumers:* derivation 866 tooling surfaces ``unsafe`` mutating methods prominently — in a 867 system built on at-least-once networking, an unsafe mutation is a 868 design smell. *Guidance:* prefer having the client supply the 869 identifier (``order_id``), which turns ``unsafe`` into 870 ``keyed:order_id`` by construction. 871 872 7.2 ``:maturity:`` values 873 ------------------------- 874 875 Why this field exists: derivation must know which methods describe 876 *behavior* and which describe *intent*. A documented-but-unimplemented 877 endpoint is not a fact about the system; deriving use cases from it 878 manufactures fiction. 879 880 * ``stable`` — implemented and conformant; fully derivable. 881 * ``experimental`` — implemented, but may change without a version 882 bump. Derivable, but derived artifacts are marked experimental. 883 Example: nearly all auditor diagnostic endpoints ("still 884 experimental" throughout ``api-auditor.rst``). 885 * ``draft-no-consumers`` — a spec nothing implements or consumes. 886 Examples: the apns relay, ebisync, the bank account directory (all 887 self-declared "nothing depends on this API"); sync in practice. 888 Excluded from derivation; renders with a banner. 889 * ``proposed-unimplemented`` — a documented proposal, explicitly not 890 implemented. Examples: wads, and the reserve-control endpoints 891 (``/reserves/$PUB/open|close|attest``) in the exchange API. Excluded 892 from derivation; kept for design reference. 893 * ``deprecated`` — still functional, but new derived artifacts must not 894 use it; the replacement should be named in prose (and 895 ``:deprecated:`` carries the version). 896 897 7.3 ``:visible-to:`` values (actor ⇒ implied credential) 898 -------------------------------------------------------- 899 900 This field is the file's single authorization fact. The design 901 decision: the *actor* is declared and the *mechanism* is implied by the 902 table below, because declaring both would duplicate one fact in two 903 drift-prone places. Persona projections are computed as closures over 904 this field: a persona is a set of actors; its visible surface is the 905 set of methods whose ``:visible-to:`` intersects it. 906 907 .. list-table:: 908 :header-rows: 1 909 910 * - Actor 911 - Implied mechanism 912 * - ``wallet-user`` 913 - wallet-core local message protocol (no credentials; "visibility" 914 is possession of the device running the wallet) 915 * - ``merchant-staff`` or ``merchant-staff:<scope>`` 916 - ``Authorization: Bearer secret-token:...`` with the named 917 permission scope (``orders-read``, ``transfers-write``, ...) 918 * - ``aml-officer`` / ``aml-officer:readonly`` 919 - ``Taler-AML-Officer-Signature`` header; the ``:readonly`` 920 variant maps to the officer's read-only access level (the 921 ``read_only`` flag in ``GET /aml/$OFFICER_PUB``) 922 * - ``exchange-operator`` 923 - master-key signature (management operations) 924 * - ``auditor`` 925 - auditor API authentication (see ``api-auditor.rst``) 926 * - ``tax-auditor`` 927 - none — capability identifiers only (``wtid``); this actor has 928 *no* credentials anywhere in the system 929 * - ``bank-customer`` 930 - external bank UI; on APIs, capability ``withdrawal-id`` 931 * - ``bank-admin`` 932 - corebank bearer token (admin scope) 933 * - ``kyc-provider`` 934 - provider webhook credentials 935 * - ``any-unauthenticated`` 936 - none — public by design (``GET /keys``). Do not confuse with 937 capability-gating: public-by-design means the *content* is 938 meant for everyone; capability-gated means the *identifier* is 939 the secret. 940 * - ``external`` 941 - outside any API (bank statements, PoS displays, offline 942 ceremonies) — the honest escape hatch for non-API actors 943 944 Capability-gated access is annotated in the field: 945 ``:visible-to: merchant-staff, tax-auditor (capability wtid)`` means 946 the listed actors hold the unguessable identifier; the lint resolves 947 the identifier against the registry (§4.4) and checks its 948 ``:capability: yes`` (V3). 949 950 7.4 ``:temporality:`` values 951 ---------------------------- 952 953 The classification of an outcome's *kind* — the input that decides 954 which column of the derived state table (happy / timer / failure) an 955 edge lands in, and whether a non-2xx is a failure at all. 956 957 * ``transient-state`` — the condition resolves without the client 958 changing anything; the identical request may succeed later. 959 Example: 404 ``RESERVE_UNKNOWN`` on an unfunded reserve. 960 *Derived as:* a wait/retry edge, not a failure exit. 961 * ``terminal`` — will never succeed; abandon or restart the flow. 962 Examples: 410 expired purse; 410 already-solved challenge. 963 *Derived as:* a failure exit to a terminal documentation state. 964 * ``fixable`` — a *modified* request can succeed. Examples: 409 965 insufficient funds (withdraw less); 400 parameter malformed. 966 *Derived as:* a failure edge with a return loop after modification. 967 * ``rate-limited`` — retry only after the documented delay. Examples: 968 mailbox 429 with the machine-readable ``retry_delay`` field; TAN 969 retransmission cooldown. *Derived as:* a retry edge annotated with 970 the delay's source field. 971 * ``gate`` — blocked on an action by the user or a third party, outside 972 this call. Examples: 451 KYC required; 202 MFA challenge; 402 973 payment-required at sync. *Derived as:* a gate edge into the 974 relevant sub-flow (KYC legitimization, MFA solving). 975 * ``progress`` — **not an error**: the non-2xx code is the happy-path 976 state signal. Examples: 402 unpaid + ``taler_pay_uri`` on the order 977 status endpoint; 202 pending on deposit tracking; 304 unchanged 978 during long-poll. *Derived as:* **no failure edge** — the code *is* 979 the waiting state's observation. 980 981 Classification guide (ask in order): Is it actually a failure? → 982 ``progress``. Can the caller fix the request? → ``fixable``. Does 983 the condition resolve by itself? → ``transient-state``. Does it only 984 need a delay? → ``rate-limited``. Does it need an outside action? 985 → ``gate``. Will it never succeed? → ``terminal``. 986 987 7.5 ``:recovery:`` values 988 ------------------------- 989 990 The client's forward action — the label on the edge that *leaves* this 991 outcome. ``:temporality:`` says what kind of outcome it is; 992 ``:recovery:`` says what to do about it. 993 994 * ``retry-same`` — safe verbatim replay (pairs with 995 ``:idempotency: replay-safe`` / ``keyed:``; pairing an unsafe method 996 with ``retry-same`` is a contradiction the lint should flag). 997 * ``retry-modified`` — change the request per the error's guidance 998 (lower the amount; add the missing field; set ``max_age`` per the 999 ``maximum_allowed_age`` hint). 1000 * ``retry-after`` — wait the documented delay, then ``retry-same`` 1001 (429 + ``retry_delay``). 1002 * ``long-poll`` — wait on the status endpoint's rendezvous parameters 1003 (reserve status long-poll). 1004 * ``poll-later`` — re-read after some time, without rendezvous (the 1005 sync backup cadence; usual companion of ``progress``). 1006 * ``refresh-config`` — re-fetch ``/keys`` or ``/config`` first 1007 (denomination unknown ⇒ outdated keys). 1008 * ``start-kyc`` — enter the legitimization sub-flow (451 responses 1009 carry the requirement). 1010 * ``user-action`` — the human must act outside this API (confirm in the 1011 bank app; insert cash). 1012 * ``abort`` — invoke the flow's own abort path (abort the withdrawal; 1013 delete the purse). 1014 * ``give-up`` — terminal; no forward action exists. 1015 * ``none`` — informational; nothing to recover from. 1016 1017 7.6 ``:timer-ref:`` roles 1018 ------------------------- 1019 1020 A method's relationship to an entity clock comes in three kinds, and 1021 each feeds a different derivation: 1022 1023 * ``arms`` — a successful call starts the clock; ``:field:`` names the 1024 arming request field (``POST /private/orders`` arms 1025 ``order.refund-deadline`` via ``refund_delay``). *Feeds:* the 1026 "armed when" column of timer tables. (Config-armed clocks are the 1027 exception — declared ``:armed-by:`` at the anchor, §4.3.) 1028 * ``exposes`` — the response carries the clock's current value; 1029 ``:field:`` names the response field 1030 (``ValidationChallengeInfo.solve_expiration``). *Feeds:* the 1031 observation layer — where an actor reads the time remaining. 1032 * ``affected-by`` — the method's behavior changes once the clock fires 1033 (challenge confirm fails with ``TAN_CHALLENGE_EXPIRED``). 1034 *Feeds:* the join between the timer's firing edge and this method's 1035 error table. 1036 1037 One method may reference several timers (one ``:timer-ref:`` each); one 1038 reference carries one role. 1039 1040 7.7 ``:trigger:`` kinds 1041 ----------------------- 1042 1043 A transition without a cause is narrative; the trigger is what makes 1044 the machine causal — and what makes the sequence diagram drawable, 1045 since every trigger kind maps to a diagram element (message, timer 1046 edge, external note). 1047 1048 * ``call <method>`` — an external call to the owner's API. Resolvable 1049 to a method file (V13). The most common kind. Example: the wallet's 1050 selection POST drives ``pending -> selected``. 1051 * ``timer <entity>.<timer>`` — a declared entity timer fires. 1052 Resolvable to the §4.3 anchor. Example: ``solve-expiration`` drives 1053 ``unsolved -> expired``. 1054 * ``watch <component>.<observable>`` — the owner watches an external 1055 source. This is where *cross-component* causality lives: the 1056 exchange's wirewatch watching ``bank.incoming-history``; the merchant 1057 backend tracking the exchange's outgoing wires. Naming form is 1058 ``component.observable`` so the join stays checkable. 1059 * ``external <description>`` — a human or off-API action: bank-UI 2FA 1060 confirmation, cash inserted at a terminal, an offline key ceremony. 1061 Non-API actors enter the machine here, honestly marked as outside 1062 every API. Deliberately not lint-resolvable. 1063 1064 Triggers are repeatable: one transition may have several (``selected -> 1065 confirmed`` via the confirm endpoint *or* the bank UI). 1066 1067 7.8 Identifier roles 1068 -------------------- 1069 1070 The method↔identifier relationship declared in **Identifiers:** 1071 (§3.8): ``creates`` (the value begins to exist here), ``carries`` 1072 (transports it onward in URL/body/response/wire subject), ``reads`` 1073 (uses it as a lookup key), ``consumes`` (its validity ends here). 1074 Definitions and examples are in §3.8. The roles are always from the 1075 *method's* perspective: the same ``reserve_pub`` is created by the 1076 wallet, carried by the bank-integration selection, read by the exchange 1077 — and the union of those tags is what V21 checks joins with (exactly 1078 one creator per carried/read value, or an internal-creation note at the 1079 anchor). 1080 1081 1082 8. Machine-readability rules 1083 ============================= 1084 1085 These rules are the contract between authors and tooling. Each exists 1086 because a specific derivation or lint check depends on it; breaking one 1087 does not merely make the docs sloppy — it makes a specific derived 1088 artifact silently wrong. Each rule below states the requirement, why 1089 it exists, a typical violation, and what checks it. 1090 1091 M8.1 One method per file, one identity directive per file (declared 1092 variant groups excepted, §2). 1093 1094 *Why:* the file is the unit of indexing — filename conventions 1095 (§2.2), lint scoping, and generated cross-references all assume 1096 the path↔method mapping is one-to-one. 1097 *Violation:* two endpoints documented in one file "because they 1098 are similar" — their semantics can no longer be addressed 1099 separately, and backfill tools cannot target them. 1100 *Checked by:* V1 (count identity directives per file). 1101 1102 M8.2 Every element in §3 marked REQ is present; COND is present or 1103 the absence is obvious from method kind (e.g. GET ⇒ no Effects). 1104 1105 *Why:* the REQ fields are the minimum derivation set — a missing 1106 REQ field means the generator must guess (the exact failure this 1107 specification exists to eliminate). The COND absence rule exists 1108 so the lint doesn't cry wolf on legitimately absent sections. 1109 *Violation:* a POST without ``:idempotency:``. 1110 *Checked by:* the REQ lint rules (V2, V3, V5, V11, ...). 1111 1112 M8.3 All field values come from the §7 vocabularies — free text is a 1113 lint error. 1114 1115 *Why:* a misspelled value (``rate_limited`` for 1116 ``rate-limited``) is invisible to a human reviewer but silently 1117 removes the entry from every derived table. 1118 *Violation:* ``:temporality: transient`` (missing ``-state``). 1119 *Checked by:* V2, V3, V5, V10, and the vocabulary checks. 1120 1121 M8.4 Every name that must resolve, resolves: entity names → registry; 1122 state values → lifecycle transitions (inferred set); ``:schema:`` 1123 types → ``ts:def`` blocks; ``:timer-ref:`` targets → entity timer 1124 anchors; ``:identifier:`` names → identifier anchors; 1125 ``:trigger: call`` refs → method files; ``:trigger: timer`` refs 1126 → entity timers; notification channels → the component's 1127 notification page. 1128 1129 *Why:* this rule is what turns the corpus from a pile of pages 1130 into a graph. Every derived artifact (state tables, persona 1131 projections, sequence diagrams) is a traversal of these edges; a 1132 dangling reference is a broken join that no amount of prose 1133 repairs. 1134 *Violation:* ``:observes-state: withdrawal-operation.state`` 1135 (the field is ``status``) — the persona projection for the bank 1136 customer silently loses the withdrawal states. 1137 *Checked by:* V7, V8, V9, V10, V12, V13, V21. 1138 1139 M8.5 State values and error codes appear in structured positions 1140 first; prose mentions are decoration. 1141 1142 *Why:* prose is unparseable. A state or error that exists only 1143 in a sentence is invisible to every consumer — the machine has a 1144 state the tooling cannot see. 1145 *Violation:* "returns 410 when the purse is gone" in the 1146 description, with no 410 entry in **Errors**. 1147 *Checked by:* V14 (grep prose for known state names and 1148 ``TALER_EC_*`` codes absent from the structured fields). 1149 1150 M8.6 Version annotations follow the single style ``@since **vN**`` / 1151 ``@deprecated **vN**``. 1152 1153 *Why:* derived artifacts must be sliceable by protocol version — 1154 an old wallet talking to a new exchange sees a genuinely 1155 different machine, and only uniform field-level versioning makes 1156 that computable. The style already exists as a convention; this 1157 rule makes it enforceable. 1158 *Violation:* "since v3" (unparseable variant), missing 1159 annotations on newer fields. 1160 *Checked by:* V16 (warn-only until backfilled). 1161 1162 M8.7 Draft APIs carry ``:maturity: draft-no-consumers`` and a visible 1163 banner; derivation tooling skips them. 1164 1165 *Why:* documented intent is not behavior. Deriving use cases 1166 from an API nothing implements manufactures fiction with the 1167 false authority of a generated artifact. 1168 *Violation:* a use-case table that includes apns device 1169 registration as if it were a live flow. 1170 *Checked by:* V17. 1171 1172 M8.8 The ``ts:def`` layer is the codegen source of truth: type 1173 definitions must be complete enough to generate client/server 1174 types and validators without consulting prose. 1175 1176 *Why:* the shape layer is what the spec-first model generates 1177 code from (§1). A field that exists only in a sentence ("the 1178 response also contains X when...") produces a generator-visible 1179 type that disagrees with the documented behavior — the 1180 documentation-drift disease, now with a compiler. 1181 *Violation:* optional fields described only in running text; 1182 union variants mentioned in prose but absent from the 1183 ``ts:def``. 1184 *Checked by:* partially by V4 (every outcome needs a schema); 1185 fully only by codegen dry-runs diffed against implementations. 1186 1187 1188 9. Validation rules (lint specification) 1189 ========================================= 1190 1191 A doc lint (to live in ``taler-docs/_exts/``) enforces, as build 1192 warnings/errors: 1193 1194 .. list-table:: 1195 :header-rows: 1 1196 1197 * - ID 1198 - Rule 1199 * - V1 1200 - Identity directive present exactly once per documented method 1201 (variant groups: one directive per variant, §2); options parse. 1202 * - V2 1203 - ``:since:`` / ``:deprecated:`` match ``v\d+``; ``:maturity:`` in 1204 vocab. 1205 * - V3 1206 - ``:visible-to:`` present; actors and capability annotations in 1207 vocab; capability identifiers resolve to identifier anchors. 1208 * - V4 1209 - Every ``:http:statuscode:`` has ``:schema:`` and/or ``:error:``; 1210 non-2xx codes with ``:schema:`` have a ``progress`` or ``gate`` 1211 entry in **Errors**. 1212 * - V5 1213 - Every non-2xx outcome has an entry with ``:temporality:`` and 1214 ``:recovery:`` in vocab. 1215 * - V6 1216 - Mutating methods have non-``readonly`` ``:idempotency:`` and at 1217 least one ``:effect:`` (or explicit "no observable effect" note). 1218 * - V7 1219 - Entity names (in Effects, ``:observes-state:``, 1220 ``:timer-ref:``) resolve to entity anchors; identifier names in 1221 **Identifiers:** blocks resolve to identifier anchors. 1222 * - V8 1223 - State values resolve to the named entity's inferred state set 1224 (including modifier-expanded sets for wallet transaction 1225 entities, §6). 1226 * - V9 1227 - ``:observes-state:`` path exists in the response ``ts:def`` and 1228 its type is an enumerated union. 1229 * - V10 1230 - ``:timer-ref:`` targets exist among the named entity's declared 1231 timers; roles in vocab; ``:field:`` present when the role is 1232 ``arms`` or ``exposes``; every declared timer has at least one 1233 ``arms`` reference or an ``:armed-by:`` in its anchor. 1234 * - V11 1235 - ``:wait:`` present; long-poll parameters match the method's 1236 documented ``:query:`` parameters. 1237 * - V12 1238 - Every real-time channel of a component is declared in that 1239 component's notification page; no push-channel metadata appears 1240 in method files. 1241 * - V13 1242 - Transition triggers resolve (``call`` → method file, ``timer`` 1243 → declared entity timer, ``watch`` → ``component.observable`` 1244 form); **Effects** transitions and lifecycle transitions agree 1245 (two-sided check); ``:observable-by:`` refs resolve. 1246 * - V14 1247 - No ``TALER_EC_*`` or state value appears only in prose. 1248 * - V15 1249 - Filename and location follow §2.2. 1250 * - V16 1251 - ``@since`` present on every ``ts:def`` field of public types 1252 (grandfathered: warn-only until backfilled). 1253 * - V17 1254 - ``draft-no-consumers`` / ``proposed-unimplemented`` methods 1255 render a banner and are excluded from derived artifacts. 1256 * - V18 1257 - wallet-core ``:transition:`` values match DD37 notation, resolve 1258 against the ``TransactionMajorState`` / ``TransactionMinorState`` 1259 enums documented in ``get-transaction-by-id.rst``, and edges 1260 resolve against the transaction entity lifecycles in the 1261 registry. 1262 * - V19 1263 - Lifecycle integrity: every declared terminal state is reachable 1264 from ``none`` and absorbing (no outgoing transitions); every 1265 ``final-reactivatable`` state's outgoing transitions are 1266 explicitly declared reactivation edges; every inferred 1267 non-terminal state has at least one outgoing transition; every 1268 inferred state is reachable from ``none``. 1269 * - V20 1270 - ``:if:`` guards reference documented config/data fields 1271 (best-effort resolvability: ``/keys`` fields, request fields, 1272 documented config). 1273 * - V21 1274 - Identifier joins: every identifier with a ``carries`` / 1275 ``reads`` tag has exactly one ``creates`` across the corpus or 1276 an internal-creation note in its anchor; every ``creates`` is 1277 carried or read somewhere.