Individual Submission M. Petkov Internet-Draft A. Panitch Intended status: Standards Track Effortel Technologies SA Expires: 21 March 2027 17 September 2026 Pulse: Real-Time Online Charging for AI Services draft-effortel-pulse-00 Abstract This document specifies Pulse, version 1.1 — the AI online charging protocol: the interface between an AI Gateway (the client) and the Charging Server (the server) by which AI service usage is authorised in real time, supervised under a granted budget, and settled. The spend guarantee is precisely stated: *settled spend never exceeds the reported meter or the plan's ceiling*, and serving exposure is bounded by the granted pool plus the completion of Calls already in flight when a stop lands. The protocol consists of two request/ response operations over HTTPS with JSON bodies: *Authorise* and *Report*. It follows the reserve-then-settle discipline of telecom online charging (cf. Diameter Credit-Control, RFC 4006) applied to AI workloads. Status of This Memo This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79. Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet- Drafts is at https://datatracker.ietf.org/drafts/current/. Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress." This Internet-Draft will expire on 21 March 2027. Copyright Notice Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved. Petkov & Panitch Expires 21 March 2027 [Page 1] Internet-Draft The Pulse Protocol September 2026 This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/ license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 4 1.1. Motivation . . . . . . . . . . . . . . . . . . . . . . . 4 1.2. Relationship to Diameter Credit-Control . . . . . . . . . 4 1.3. Scope . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.4. Version-1 scope and deferred items (named) . . . . . . . 4 2. Conventions and Terminology . . . . . . . . . . . . . . . . . 5 3. Protocol Overview . . . . . . . . . . . . . . . . . . . . . . 7 3.1. Message flow . . . . . . . . . . . . . . . . . . . . . . 8 3.2. The two-message contract . . . . . . . . . . . . . . . . 8 3.3. Flat-priced Actions . . . . . . . . . . . . . . . . . . . 8 3.4. Division of responsibility . . . . . . . . . . . . . . . 9 3.5. The Action state machine (normative) . . . . . . . . . . 9 4. Transport and Encoding . . . . . . . . . . . . . . . . . . . 14 4.1. Endpoints . . . . . . . . . . . . . . . . . . . . . . . . 14 4.2. Authentication . . . . . . . . . . . . . . . . . . . . . 15 4.3. Encoding rules and JSON types (normative) . . . . . . . . 15 4.4. Limits (normative) . . . . . . . . . . . . . . . . . . . 16 5. Schema Versioning . . . . . . . . . . . . . . . . . . . . . . 17 6. The Authorise Operation . . . . . . . . . . . . . . . . . . . 18 6.1. Request . . . . . . . . . . . . . . . . . . . . . . . . . 19 6.2. Processing . . . . . . . . . . . . . . . . . . . . . . . 20 6.3. Response — GRANT . . . . . . . . . . . . . . . . . . . . 20 6.3.1. Client obligations under a grant . . . . . . . . . . 23 6.4. Response — REFUSE . . . . . . . . . . . . . . . . . . . . 24 6.5. Idempotency, request identity, and identifier scopes . . 27 7. The Report Operation . . . . . . . . . . . . . . . . . . . . 28 7.1. Request . . . . . . . . . . . . . . . . . . . . . . . . . 28 7.2. Cumulative semantics (normative) . . . . . . . . . . . . 29 7.3. Response . . . . . . . . . . . . . . . . . . . . . . . . 30 7.4. stopReason registry (version 1) . . . . . . . . . . . . . 32 7.5. Lapse boundary (normative) . . . . . . . . . . . . . . . 34 7.6. Late reports and the late window . . . . . . . . . . . . 35 7.7. Reporting cadence requirements . . . . . . . . . . . . . 35 8. Timing Summary . . . . . . . . . . . . . . . . . . . . . . . 36 8.1. Protocol constants and configurables . . . . . . . . . . 36 9. Child Actions . . . . . . . . . . . . . . . . . . . . . . . . 46 10. Time Bands . . . . . . . . . . . . . . . . . . . . . . . . . 47 Petkov & Panitch Expires 21 March 2027 [Page 2] Internet-Draft The Pulse Protocol September 2026 11. Draining . . . . . . . . . . . . . . . . . . . . . . . . . . 48 12. Shared Bounds and Fair Usage . . . . . . . . . . . . . . . . 48 12.1. Sponsor caps . . . . . . . . . . . . . . . . . . . . . . 48 12.2. Capacity pools . . . . . . . . . . . . . . . . . . . . . 49 12.3. Degradation before refusal (fair usage) . . . . . . . . 49 12.4. Report promptness . . . . . . . . . . . . . . . . . . . 50 13. Error Handling . . . . . . . . . . . . . . . . . . . . . . . 50 13.1. HTTP status mapping . . . . . . . . . . . . . . . . . . 50 13.2. errorCode registry (400 family, version 1) . . . . . . . 52 13.3. Client retry guidance . . . . . . . . . . . . . . . . . 52 13.4. Charging-plane unavailability — the client state machine . . . . . . . . . . . . . . . . . . . . . . . . 52 13.5. Operational expectations (informative) . . . . . . . . . 54 14. Conformance Requirements Summary (client) . . . . . . . . . . 54 15. Security Considerations . . . . . . . . . . . . . . . . . . . 55 15.1. Transport and credentials . . . . . . . . . . . . . . . 55 15.2. Tenant binding . . . . . . . . . . . . . . . . . . . . . 56 15.3. Gateway ownership of Actions . . . . . . . . . . . . . . 56 15.4. Abuse limits . . . . . . . . . . . . . . . . . . . . . . 56 15.5. API-level protection . . . . . . . . . . . . . . . . . . 56 15.6. Clock authority and posture . . . . . . . . . . . . . . 57 15.7. Trust model and privacy . . . . . . . . . . . . . . . . 57 16. Implementation Status . . . . . . . . . . . . . . . . . . . . 58 17. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 58 18. References . . . . . . . . . . . . . . . . . . . . . . . . . 60 18.1. Normative References . . . . . . . . . . . . . . . . . . 60 18.2. Informative References . . . . . . . . . . . . . . . . . 61 Appendix A. Examples (illustrative, not normative; abbreviated) . . . . . . . . . . . . . . . . . . . . . . 61 A.1. Authorise → grant . . . . . . . . . . . . . . . . . . . . 62 A.2. Report → continue . . . . . . . . . . . . . . . . . . . . 62 A.3. Final report → settled . . . . . . . . . . . . . . . . . 63 A.4. Stop obligation . . . . . . . . . . . . . . . . . . . . . 63 A.5. Authorise → refuse metadata . . . . . . . . . . . . . . . 64 A.6. Stale report → CURRENT . . . . . . . . . . . . . . . . . 64 A.7. Error envelope . . . . . . . . . . . . . . . . . . . . . 65 Appendix B. Message schemas (CDDL, provisional) . . . . . . . . 66 B.1. Schema notes — ambiguities found . . . . . . . . . . . . 78 Appendix C. Worked arithmetic (illustrative) . . . . . . . . . . 80 C.1. Authorise and opening grant . . . . . . . . . . . . . . . 80 C.2. Cycle 1 — threshold crossing, settlement, and re-grant . 81 C.3. Stale, out-of-order report — zero merge and CURRENT . . . 83 C.4. Cycle 2 — the baseline moves again . . . . . . . . . . . 83 C.5. Final report — CLOSE and terminal settlement . . . . . . 84 Appendix D. Vocabulary mapping (informative) . . . . . . . . . . 85 D.1. The framework's terms . . . . . . . . . . . . . . . . . . 86 D.2. Additional implementation and telecom terms . . . . . . . 90 Appendix E. Change history (informative) . . . . . . . . . . . . 92 Petkov & Panitch Expires 21 March 2027 [Page 3] Internet-Draft The Pulse Protocol September 2026 Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 92 1. Introduction 1.1. Motivation Providers and resellers of mass-market AI services cannot today guarantee that an individual subscriber will not overspend, nor apply a hard per-subscriber budget. Pulse provides that guarantee the way telecom charging does: no chargeable work proceeds without a prior authorisation carrying a budget, and all consumption is reported back and settled against it. 1.2. Relationship to Diameter Credit-Control Pulse is not a Diameter application, but deliberately reuses proven Diameter Credit-Control concepts from [RFC4006]: session-based credit control with granted quota (here: the _grant_), reporting of used units, and credit pooling — a single abstract unit pool with per- unit-type multipliers (Section 6.3), mirroring the Diameter G-S-U- Pool-Reference mechanism. [RFC8506], which obsoletes [RFC4006], is the current specification. The name reflects the protocol's own rhythm — supervision happens on a regular pulse of reports (heartbeat reports, reporting cadence, settlement cycles). 1.3. Scope This memo specifies the *charging plane* only: the two operations, their semantics, timing, idempotency, errors, and security requirements. The management plane (tenant onboarding, plan authoring, subscriber provisioning, credential administration, reporting) is a separate surface and is out of scope here. 1.4. Version-1 scope and deferred items (named) Version 1 targets *bounded synchronous and streaming Actions* operated against *one logical charging domain per tenant*; an Action MUST be operated against the domain that opened it, and HTTP redirection (3xx) is not used. Quiet periods inside an Action are covered by heartbeat reports (Section 7.1). The following are deliberately deferred, not accidentally missing — each is named so an implementer never mistakes silence for support: * *Batch / asynchronous work* (a result returning hours later) has no contract in version 1. * *Cross-region failover* semantics (retrying against a different charging domain). Petkov & Panitch Expires 21 March 2027 [Page 4] Internet-Draft The Pulse Protocol September 2026 * *Pre-grants / bulk authorisation* for latency amortisation (Section 13.5 states version 1's posture). * *Multi-Action batch reporting / multiplexed transport* (Section 13.5). * *An interoperable call-signature profile* (Section 7.2 — the field is reserved). * *The unit-vocabulary catalogue contract* (distribution of Action- Type unit definitions is a management-plane artefact; version 1 assumes out-of-band onboarding). * *Serving-system provenance* (which model or system actually served an Action — a self-attested, evidence-only telemetry block on reports). No version-1 contract; its design belongs with the reference-model phase, alongside the evidence-grade question any self-attested provenance raises. * *A single owner's cap across several of its own operators* — one company operating multiple tenants under a jurisdiction-mandated joint bound (the shape of, e.g., German gambling regulation). In version 1 a relation-scoped bound ranges within one tenant (Section 12.1); a cross-tenant bound has no contract. 2. Conventions and Terminology The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. +==============+===================================================+ | Term | Meaning | +==============+===================================================+ | *Charging | The OCS instance implementing this protocol. | | Server* | | | ("the | | | server") | | +--------------+---------------------------------------------------+ | *Gateway* | The authenticated system in the AI usage path | | ("the | that requests authorisation and reports | | client") | consumption. | +--------------+---------------------------------------------------+ | *Tenant* | The commercial customer of the platform; every | | | credential is bound to one or more tenants. | +--------------+---------------------------------------------------+ Petkov & Panitch Expires 21 March 2027 [Page 5] Internet-Draft The Pulse Protocol September 2026 | *Subscriber* | An end user provisioned under a tenant, | | | identified by accountRef. | +--------------+---------------------------------------------------+ | *Action* | The chargeable unit of work (e.g. one agent task, | | | one conversation exchange), identified by a | | | client-asserted actionId. An Action contains one | | | or more Calls. | +--------------+---------------------------------------------------+ | *Call* | One inference invocation within an Action, | | | identified by callId. | +--------------+---------------------------------------------------+ | *Grant* | The server's authorisation to consume up to a | | | stated quantity of pooled units before reporting | | | back, valid until expiresAt. Exactly one grant | | | is current per live Action; grants carry a | | | *strictly increasing* generation number | | | (grantSeq, +1 per mint — the ending itself mints | | | the final generation, Section 3.5), and a client | | | discards any grant older than one it has already | | | seen. | +--------------+---------------------------------------------------+ | *Envelope* | The always-fresh part of a response — serverTime | | | and maxSchemaVersion on every response (error | | | envelopes included); actionLive, currentGrantSeq, | | | currentGrantId, and per-attempt unattributable on | | | every 200 that identifies an opened Action | | | (defined-absent on refusals of unopened Actions | | | and on error envelopes). Computed at each | | | processing, replays included; the client MUST | | | obey the envelope over a replayed body. | | | actionLive is monotonic — once false, never true | | | again — and *scoped precisely*: false forbids | | | lifecycle serving under any grant; a flat | | | Action's work is authorised by its settled GRANT | | | itself (Section 3.3), which is a record, not a | | | serving grant subject to the stale-discard rule. | +--------------+---------------------------------------------------+ | *Cycle* | One Report exchange: the server settles the | | | reported consumption and, if the Action | | | continues, issues the next grant. | +--------------+---------------------------------------------------+ | *Action | The declared, catalogue-defined kind of Action; | | Type* | it selects the price entry (the "law") the Action | | | is rated under. | +--------------+---------------------------------------------------+ | *Ending* | The terminal transition of an Action: CLOSE | | | (client-declared final report), STOP (server- | | | directed stop), or LAPSE (grant validity lapsed | Petkov & Panitch Expires 21 March 2027 [Page 6] Internet-Draft The Pulse Protocol September 2026 | | without a report). | +--------------+---------------------------------------------------+ | *Late | The period after an ending — until max(endedAt + | | window* | 24 h, expiresAt + 1 h) (Section 3.5, Section 7.6) | | | — during which the server retains the Action's | | | terminal state, honours retries, and can still | | | settle late-arriving consumption. | +--------------+---------------------------------------------------+ | *Sponsor | An aggregate spend bound a sponsoring party | | cap* | declares across a whole population of subscribers | | | (e.g. a reseller capping the total cost of a | | | bundled AI benefit). Enforced at authorisation, | | | attributed to the sponsor in refusals | | | (Section 12.1). | +--------------+---------------------------------------------------+ | *Capacity | A tenant-declared provisioned serving rate that | | pool* | Actions of given types draw on. Finite and | | | perishable: what is not admitted now is gone. | | | Enforced at admission (Section 12.2). | +--------------+---------------------------------------------------+ | *Meter* | The server's stored per-call cumulative high- | | | water totals for an Action — the authoritative | | | record of reported consumption. Settlement never | | | exceeds the meter. | +--------------+---------------------------------------------------+ | *Ceiling / | The ceiling is the maximum total charge an Action | | headroom* | (or Action family, Section 9) may accumulate | | | under its plan; headroom is what remains of it. | +--------------+---------------------------------------------------+ | *Minimum | The plan-declared serve rate below which | | rate* | degradation (Section 12.3) is not attempted and | | | the honest answer is NO_CAPACITY. | +--------------+---------------------------------------------------+ | *Integration | The tested record of what a given gateway | | profile* | integration actually does (authorise-before- | | | serving, stop obedience, report promptness, …), | | | established by the conformance suite | | | (Section 12.4). | +--------------+---------------------------------------------------+ Table 1 All normative statements about server behaviour ("the server MUST …") are guarantees a client may rely on; statements about client behaviour are conformance requirements for gateways. 3. Protocol Overview Petkov & Panitch Expires 21 March 2027 [Page 7] Internet-Draft The Pulse Protocol September 2026 3.1. Message flow Gateway Charging Server | | |--- POST /ocs/ai/v1/authorizations --------->| Authorise: may |<-- GRANT {grantId, grantedUnits, | this Action | reportAt, expiresAt, unitWeights} | proceed, and on | | what budget? | (gateway serves AI calls, metering | | consumption against the granted pool) | | | |--- POST /ocs/ai/v1/grants/{id}/reports ---->| Report: usage |<-- CONTINUE {next grant} or | so far. Settle | STOP {stopReason} or | and re-grant, | SETTLED | stop, or settle | | finally. | ... repeat cycles until an ending ... | An Action opens with exactly one successful Authorise and ends exactly once (CLOSE, STOP, or LAPSE). Between opening and ending, the gateway reports at the server-stated thresholds and the server answers each report with the next directive. 3.2. The two-message contract The protocol is deliberately two messages and no more: 1. *Authorise* — the client asks; the server answers GRANT or REFUSE. A refusal means no chargeable work may proceed; a grant means work up to the granted pool may proceed. 2. *Report* — the client reports cumulative consumption; the server settles and answers CONTINUE (with the next grant), STOP (a *stop obligation*: the client MUST cease serving the Action's traffic promptly), or SETTLED (final settlement acknowledged). There is no server-initiated push channel in version 1. The stop obligation is delivered on the response to a report; grant lapse (expiresAt) bounds how long an unreported Action can run. 3.3. Flat-priced Actions An Action whose type is flat-priced settles at open: the Authorise response carries decision: GRANT with settled: true and a grantId for correlation, and no reports are expected. *A flat Action ends at authorisation — its ending is CLOSE, its endedAt is the response's serverTime, and its late window anchors there.* Its envelope therefore carries actionLive: false with terminal generation 2 — Petkov & Panitch Expires 21 March 2027 [Page 8] Internet-Draft The Pulse Protocol September 2026 which is correct and does not countermand the work: the settled GRANT itself is the complete authorisation for the flat Action's work (Section 2, Envelope). The opening settlement carries a receipt of settlementId alone (no units existed to accept). A Report addressed to its grantId never settles — an identity match replays; anything else receives the terminal directive, with unattributable: true if units were carried (Section 3.5). The report-borne controls (call limits, runtime, signatures, inflation) do not apply to flat Actions by construction — their controls are the authorisation-time checks (Section 6.2). All other price forms follow the report lifecycle. 3.4. Division of responsibility * *The client admits Calls; the server settles money.* The client MUST NOT knowingly serve consumption beyond the granted pool (checking each next Call against the remaining pool, which it can compute from grantedUnits and unitWeights, is the expected mechanism) and MUST report when the reportAt threshold is crossed. The server clamps every settlement at the reported meter and at the Action's ceiling; overshoot exposure is bounded to one final report. The permitted *tail*: consumption whose serving began before the report was dispatched may complete under the old grant and is settled on the next cycle. * *The server's clock is authoritative* for lapse boundaries and time-band selection. A client MUST NOT assume its own clock decides either. * *One in-flight Report per Action.* The client MUST serialise its Reports: at most one Report (including its retries) is outstanding per Action at any time, and *while a Report is outstanding the client MUST NOT admit new Calls* — work already begun (the tail, first bullet above) completes. The server's convergence rules (Section 3.5) remain the safety net for the distributed-gateway case where two workers overlap despite this rule. 3.5. The Action state machine (normative) This section is the authority for every exchange; the prose of Section 6 and Section 7 restates it and never overrides it. Petkov & Panitch Expires 21 March 2027 [Page 9] Internet-Draft The Pulse Protocol September 2026 *The gates — evaluated before the table, in this order, each state- neutral (Section 13.1) and carrying no Action-state envelope:* transport (wrong method → 405, wrong content type → 415) → authentication (401) → gateway ownership (403 — including a parentActionId owned by another gateway, Section 9) → rate limiting (429) → parse and validation (400 — Section 4.3, Section 4.4, Section 5) → identity resolution (404 — an unknown actionId or grantId, or a path grantId that does not belong to the body's Action). Only a request that passes every gate reaches the table. *Row evaluation:* within a state, rows are evaluated in the listed order and the first matching row applies — the table is a function from (state, input) to one outcome. *States:* NONE (the actionId is unclaimed) · LIVE(Gn) (open; one current grant, generation _n_) · ENDED(e) (ended as _e_ ∈ {CLOSE, STOP, LAPSE}; recognised) · FORGOTTEN. A flat-priced Action passes through LIVE for zero time: a successful flat Authorise opens *and ends* it (ENDED(CLOSE)) in one transition. *Generations:* *every stored outcome mints a generation* (grantSeq/outcome generation, strictly +1) — cycles, the terminal transition, and late adjustments alike — so envelope ordering (Section 6.3) and receipt ordering (Section 7.3) are total, and terminal state is sticky (actionLive never returns to true). *In state NONE* (a Report cannot reach NONE — the identity gate returns 404): +===+============+==========================+=================+ | # | Input | Outcome | Next state | +===+============+==========================+=================+ | 1 | Authorise, | GRANT (lifecycle: G1; | LIVE(G1); flat: | | | admissible | flat: settled + endedAt) | ENDED(CLOSE) | +---+------------+--------------------------+-----------------+ | 2 | Authorise, | 200 REFUSE + reason (no | NONE | | | refused | identity retained) | | +---+------------+--------------------------+-----------------+ Table 2 *In state LIVE(Gn):* +===+================+===============================+==============+ | # | Input | Outcome | Next state | +===+================+===============================+==============+ | 1 | Authorise, | re-presentation of Gn + | LIVE(Gn) | | | identity = | its baseline receipt | | | | opening | | | Petkov & Panitch Expires 21 March 2027 [Page 10] Internet-Draft The Pulse Protocol September 2026 | | identity | | | +---+----------------+-------------------------------+--------------+ | 2 | Authorise, | 200 REFUSE / | LIVE(Gn) | | | other identity | DUPLICATE_ACTION | | +---+----------------+-------------------------------+--------------+ | 3 | Report whose | that stored outcome | LIVE(Gn) | | | identity | replayed + fresh | | | | matches a | envelope | | | | retained | | | | | outcome | | | +---+----------------+-------------------------------+--------------+ | 4 | Report, final: | the *closing cycle | ENDED(CLOSE) | | | true | against the current | | | | (addressed to | state*: merge → settle | | | | any grant of | the full unsettled | | | | this Action) | meter delta → SETTLED + | | | | | receipt (a close mints | | | | | no serving grant; the | | | | | outcome mints its | | | | | generation) | | +---+----------------+-------------------------------+--------------+ | 5 | Report | the *fresh cycle* (the | LIVE(Gn+1) · | | | addressed to | primary operation): | ENDED(STOP) | | | the current | merge → *settle the | | | | grant Gn | full unsettled meter | | | | (calls[] MAY | delta* → evaluate | | | | be empty — a | ending conditions in | | | | heartbeat) | precedence order | | | | | (Section 7.4): none | | | | | hold → CONTINUE(Gn+1); | | | | | one holds → | | | | | STOP(reason) + receipt | | +---+----------------+-------------------------------+--------------+ | 6 | Report | meters max-merge; then | LIVE(Gn) · | | | addressed to a | ending conditions are | ENDED(STOP) | | | superseded | evaluated (one raised | | | | grant (non- | by the merge ends the | | | | final, no | Action per precedence, | | | | identity | settling at the meter → | | | | match) | STOP path); none hold → | | | | | *CURRENT*: the standing | | | | | grant Gn re-presented + | | | | | baselineCumulative | | | | | (Gn's cycle baseline) + | | | | | meterCumulative (the | | | | | post-merge meter). No | | | | | settlement, no mint. | | +---+----------------+-------------------------------+--------------+ Petkov & Panitch Expires 21 March 2027 [Page 11] Internet-Draft The Pulse Protocol September 2026 | 7 | Server event: | *settle at the current | ENDED(LAPSE | | | grace-end | meter*, then the | or STOP) | | | without a | terminal transition | | | | report (now ≥ | (mints) | | | | expiresAt + 5 | | | | | s), parent | | | | | cascade, | | | | | administrative | | | | | stop | | | +---+----------------+-------------------------------+--------------+ Table 3 *In state ENDED(e) (while recognised):* Petkov & Panitch Expires 21 March 2027 [Page 12] Internet-Draft The Pulse Protocol September 2026 +===+==============+===================================+==========+ | # | Input | Outcome | Next | | | | | state | +===+==============+===================================+==========+ | 1 | Authorise, | terminal acknowledgement | ENDED(e) | | | identity = | (decision: GRANT, settled where | | | | opening | applicable, ending, endedAt, | | | | identity | final receipt) | | +---+--------------+-----------------------------------+----------+ | 2 | Authorise, | 200 REFUSE / DUPLICATE_ACTION | ENDED(e) | | | other | | | | | identity | | | +---+--------------+-----------------------------------+----------+ | 3 | Report whose | that stored outcome replayed | ENDED(e) | | | identity | *verbatim* + fresh envelope | | | | matches a | | | | | retained | | | | | outcome | | | +---+--------------+-----------------------------------+----------+ | 4 | Report to a | terminal directive (SETTLED) — | ENDED(e) | | | *flat* | never settles; unattributable: | | | | Action | true if units were carried | | | | | (recorded, never charged) | | +---+--------------+-----------------------------------+----------+ | 5 | Report, | late adjustment: terminal | ENDED(e) | | | merge adds | directive + adjustment: true + | | | | new | new settlementId + receipt (mints | | | | consumption, | its outcome generation) | | | | settleable | | | +---+--------------+-----------------------------------+----------+ | 6 | Report, | terminal directive + | ENDED(e) | | | merge adds | unattributable: true, no | | | | new | settlementId; the unsettled range | | | | consumption, | *marks the Action* — every later | | | | not | zero-merge response also carries | | | | settleable | unattributable: true until it | | | | | settles or recognition lapses | | +---+--------------+-----------------------------------+----------+ | 7 | Report, | terminal directive + current | ENDED(e) | | | merge adds | receipt (no adjustment; sticky | | | | zero | unattributable where marked) | | +---+--------------+-----------------------------------+----------+ Table 4 *The terminal directive* applies to every ENDED-state response *except row 3's verbatim replays*, and is a pure function of the ending: CLOSE → SETTLED · STOP → STOP with the original stopReason Petkov & Panitch Expires 21 March 2027 [Page 13] Internet-Draft The Pulse Protocol September 2026 (and limitOwner where it applied) · LAPSE → STOP (LAPSED) — always with endedAt. *The ENDED envelope:* currentGrantSeq = the latest outcome generation; currentGrantId = the *last issued grant's id* (the address late reports use). A flat Action: generation 2 and the settled grant's id. *Arbitration:* processing is serialised; the applicable rules are those of the state at processing time. The "final beats stop" precedence (Section 7.5) applies to conditions evaluated _within one report's processing_ — never to transitions that already committed: a final report processed after an ending is late handling under the terminal directive. *Retention (uniform):* the terminal outcome plus the *last 4 non- terminal outcomes* (cycles and late adjustments share the ring), each carrying its generation. A retry whose outcome has aged out takes its state's no-match row — safe by construction (Section 7.2's absorption), though its original settlementId is then no longer re- presentable (Section 7.6). *Recognition* (duplicate detection and the terminal directive) is unconditional until *max(endedAt + 24 h, expiresAt + 1 h)* — an unseen asynchronous stop can never outlive the window of the grant that hid it. *When recognition ends:* ENDED → FORGOTTEN — the identity is discarded. Reports → 404, unattributable (Section 7.6); an Authorise on the id is evaluated *fresh* (this is the documented reopen residual of Section 6.5). 4. Transport and Encoding 4.1. Endpoints +===========+==========================================+ | Operation | Method and path | +===========+==========================================+ | Authorise | POST /ocs/ai/v1/authorizations | +-----------+------------------------------------------+ | Report | POST /ocs/ai/v1/grants/{grantId}/reports | +-----------+------------------------------------------+ Table 5 Requests and responses are application/json bodies, in the JSON format of [RFC8259], carried over HTTP [RFC9110] on TLS. TLS 1.2 [RFC5246] or later is REQUIRED (TLS 1.3 [RFC8446] RECOMMENDED; configuration per [RFC9325]); plaintext HTTP MUST be refused. A non- POST method on these paths → 405; a non-JSON content type → 415. These two paths are the only public charging surface. Petkov & Panitch Expires 21 March 2027 [Page 14] Internet-Draft The Pulse Protocol September 2026 4.2. Authentication Every request MUST carry a per-gateway bearer API key (Authorization: Bearer …). Keys are issued per gateway, support dual-active rotation, and are revocable with effect within seconds. Mutual TLS is a deployment option. OAuth2 client-credentials is planned for a later protocol version and is not part of version 1. 4.3. Encoding rules and JSON types (normative) Every field has exactly one JSON type; a value of the wrong type is malformed (MALFORMED_REQUEST / MALFORMED_REPORT): * *Decimal quantities* — unit quantities, estimate values, grantedUnits, reportAt values, unitWeights, quantumUnits, minRate, and every other decimal — are *JSON strings in canonical decimal rendering*: plain notation, no exponent, no trailing zeros, no trailing decimal point, no leading zeros (a bare 0 integer part is the only zero prefix), no leading +, no -0, non-negative unless stated otherwise, scale ≤ 6. Exactly one spelling of every value is valid; any other spelling is malformed — there is no equivalence class of spellings anywhere in this protocol. (JSON numbers are IEEE-754 doubles and cannot carry scale-6 decimals faithfully; single-spelling strings also make canonicalization exact, Section 6.5.) Money never appears on this wire (Section 15.7). * *Identifiers* (actionId, callId, grantId, currentGrantId, parentActionId, tenantRef, accountRef, actionType, scope, timeBandId, settlementId): JSON strings, ≤ 128 characters from [A- Za-z0-9._:-]. * *Display labels* (limitOwner): UTF-8 JSON strings, ≤ 64 characters of printable text — no control or bidirectional-control characters; clients render them as plain text, never as markup. Presentation text declared with the relation, not an identifier. * *Unit-type names* (map keys in estimate, units, unitWeights, receipt maps): strings matching [a-z0-9_]{1,64}. * *Timestamps* (expiresAt, serverTime, grantIssuedAt, endedAt, gatewayTime): JSON strings in the [RFC3339] profile YYYY-MM- DDThh:mm:ss.sssZ — uppercase T and Z, exactly three fractional digits, years 1970–9999, no leap seconds. Petkov & Panitch Expires 21 March 2027 [Page 15] Internet-Draft The Pulse Protocol September 2026 * *Counts and versions* (schemaVersion, maxSchemaVersion, grantSeq, currentGrantSeq, lawVersion, retryAfter, maxCallsPerAction, maxRuntimeSec, maxConcurrentActions, enforcementWindowSec): JSON integers ≥ 0 and < 2⁵³. * *Numeric domains (closed under the arithmetic):* request quantities lie in [0, 10^9] and weights in [0, 10^6], scale ≤ 6; *derived pooled quantities* (grantedUnits, reportAt, quantumUnits, cyclePooledUnits, baselineCumulative/meterCumulative pooled values) have their own domain [0, 10¹⁷), scale ≤ 6 — closed under 16 unit types × 10⁹ × 10⁶. Response maps are bounded by the 16- unit-type limit; acceptedCalls by the 256-entry limit. *The 10⁹ per-unit-per-Call cap is a hard operational bound:* a gateway MUST terminate or segment work before any Call's cumulative value reaches it — it is not merely a parser limit. * *Flags* (final, settled, retryable, rateReduced, adjustment, clamped, actionLive, unattributable, signaturesRequired): JSON booleans. * *Maps*: JSON objects whose keys are unit-type names and whose values are decimal strings as above. * *Duplicate JSON keys* anywhere in a body are malformed. 4.4. Limits (normative) +============================+==========+====================+ | Limit | Value | On violation | +============================+==========+====================+ | Request body size | ≤ 256 KB | 400 LIMIT_EXCEEDED | +----------------------------+----------+--------------------+ | calls[] entries per report | ≤ 256 | 400 LIMIT_EXCEEDED | +----------------------------+----------+--------------------+ | Unit-map entries per Call | ≤ 16 | 400 LIMIT_EXCEEDED | +----------------------------+----------+--------------------+ | Per-call unit quantity | ≤ 10⁹ | 400 LIMIT_EXCEEDED | +----------------------------+----------+--------------------+ | Cumulative per-unit-type | ≤ 10⁹ | 400 LIMIT_EXCEEDED | | total per Action | | | +----------------------------+----------+--------------------+ Table 6 Petkov & Panitch Expires 21 March 2027 [Page 16] Internet-Draft The Pulse Protocol September 2026 5. Schema Versioning Every request MUST carry schemaVersion (integer ≥ 1); every response echoes it and additionally carries maxSchemaVersion. Two version layers with distinct roles: the URL path (/v1/) changes only for *incompatible* protocol generations; schemaVersion evolves *compatibly* within a generation, and a server MUST support every schema version from 1 through its maxSchemaVersion within the path generation it serves. * Fields belong to schema versions. A client MUST NOT send a field beyond the version it declares; the server rejects fields not in the declared version (strict parse per version — unknown members are rejected, never ignored). * An unknown schemaVersion → 400 UNSUPPORTED_SCHEMA. If schemaVersion itself is missing or unparseable, the error response carries maxSchemaVersion but cannot echo a version (the one exception to echoing). * The server-side value sets (refusalReason, stopReason, errorCode, decision, directive, ending, grade, reduceCause) are closed per version, but clients MUST tolerate unknown strings in these fields — treating an unknown refusalReason as a refusal, an unknown stopReason as a stop obligation, an unknown errorCode as the error class its HTTP status indicates, an unknown directive as a stop obligation, an unknown decision as a refusal, an unknown ending as STOP, an unknown grade as BOUNDED, and an unknown reduceCause as CAPACITY. * *Responses are version-bounded too:* a server sends only fields belonging to the echoed schemaVersion; a client reads responses tolerantly, acting on the fields of its declared version and ignoring unknown members (response reading is tolerant; request parsing stays strict). * The reason/code registries are owned by this document; new values are allocated only by a new schema version of this specification (no private-use values in version 1). While the series is unpublished, a draft MAY rename a registry value, recording the rename in its change history; once published, a renamed value is a new value. The registries and their allocation policy are stated in Section 17. Petkov & Panitch Expires 21 March 2027 [Page 17] Internet-Draft The Pulse Protocol September 2026 *Protocol versions are cited major.minor.* The major is the path generation (/v1/); the minor identifies one compatible addition set within it, and each minor version binds to exactly one wire schemaVersion. The wire carries only the integer — the dotted form is the human citation, and "version 1" unqualified means the generation. +==========+===============+===============================+ | Protocol | Wire | Surface | | version | schemaVersion | | +==========+===============+===============================+ | 1.0 | 1 | The base contract (the draft- | | | | effortel-circ-05/-06 | | | | surface). | +----------+---------------+-------------------------------+ | 1.1 | 2 | Adds the GROUP_VELOCITY | | | | refusal reason, the refusal | | | | fields automatedDecision and | | | | reviewContact (Section 6.4), | | | | and the per-call startedAt | | | | evidence field (Section 7.2). | +----------+---------------+-------------------------------+ Table 7 *Fields versus values under version skew.* A new *field* never crosses a version boundary: a client MUST NOT send a field beyond its declared schemaVersion (strict parse rejects it), and a server sends only the fields of the echoed version — a version-1.0 client therefore never sees automatedDecision or reviewContact. A new *value* in an existing closed set MAY reach an older client: the tolerance fallbacks above exist for exactly this skew, and the server always refuses with the true reason rather than mislabelling it under an older value — a version-1.0 client refused for a group-velocity bound receives refusalReason: GROUP_VELOCITY and treats it as the generic refusal the fallback prescribes. 6. The Authorise Operation POST /ocs/ai/v1/authorizations Petkov & Panitch Expires 21 March 2027 [Page 18] Internet-Draft The Pulse Protocol September 2026 6.1. Request +================+=============+===================================+ | Field | Requirement | Meaning | +================+=============+===================================+ | schemaVersion | REQUIRED | See Section 5. | +----------------+-------------+-----------------------------------+ | actionId | REQUIRED | Client-asserted Action identity. | | | | SHOULD be asserted by the | | | | application originating the work; | | | | a gateway-fabricated id is | | | | permitted but downgrades | | | | attribution quality (known per | | | | contract). *Never legally | | | | reusable* (Section 6.5). | +----------------+-------------+-----------------------------------+ | tenantRef | REQUIRED | The tenant under which the | | | | subscriber is provisioned. MUST | | | | be within the credential's bound | | | | tenant set. | +----------------+-------------+-----------------------------------+ | accountRef | REQUIRED | The subscriber identity under | | | | that tenant. | +----------------+-------------+-----------------------------------+ | actionType | REQUIRED | The declared Action Type. | | | | Undeclared/unknown types are | | | | refused (NO_LAW) — the plan's | | | | vocabulary is a whitelist; there | | | | is no default. | +----------------+-------------+-----------------------------------+ | estimate | RECOMMENDED | Expected consumption (units per | | | | unit type). Validated like a | | | | report's units: keys MUST be unit | | | | types declared by the Action | | | | Type, ≤ 16 entries (else | | | | MALFORMED_REQUEST). Used to size | | | | the opening reservation; a good | | | | estimate reduces both refusal | | | | risk and over-reservation. | +----------------+-------------+-----------------------------------+ | scope | OPTIONAL | An opaque funding-scope label. | | | | Funding buckets provisioned with | | | | a matching scope become eligible; | | | | exclusive-scope buckets fund only | | | | matching Actions and never fall | | | | through to general funds. Values | | | | are plan-defined. | +----------------+-------------+-----------------------------------+ Petkov & Panitch Expires 21 March 2027 [Page 19] Internet-Draft The Pulse Protocol September 2026 | parentActionId | OPTIONAL | Declares this Action a child of | | | | an open parent Action of the same | | | | subscriber (Section 9). | +----------------+-------------+-----------------------------------+ | gatewayTime | OPTIONAL | The gateway's clock at the *first | | | | attempt* — preserved verbatim on | | | | retries (it is part of the | | | | request identity). Recorded as | | | | evidence, never enforced against | | | | (Section 15.6). | +----------------+-------------+-----------------------------------+ Table 8 6.2. Processing The server resolves the subscriber, verifies tenancy (Section 15), selects the price entry for the declared Action Type from the subscriber's plan (pinning the plan version — the Action is rated under that law for its entire life), resolves the applicable time band if the type is banded (Section 10). It then verifies budget headroom (the opening reservation covers the estimated charge, any contractual minimum, and rounding), verifies the shared bounds (any sponsor cap and the applicable capacity pool, Section 12), applies the trust limits (Section 15.4), and answers. 6.3. Response — GRANT +==================+====================+===========================+ | Field | Present | Meaning | +==================+====================+===========================+ | decision | always | GRANT or REFUSE. | +------------------+--------------------+---------------------------+ | grantId | every grant | Correlates the | | | | lifecycle; on a flat- | | | | priced Action it | | | | correlates the settled | | | | Action. | +------------------+--------------------+---------------------------+ | grantSeq | every grant | Monotonic generation | | | | number per Action. A | | | | client MUST discard any | | | | grant whose grantSeq is | | | | lower than one it has | | | | already seen for the | | | | Action. | +------------------+--------------------+---------------------------+ | grantIssuedAt | every lifecycle | The grant's issue | Petkov & Panitch Expires 21 March 2027 [Page 20] Internet-Draft The Pulse Protocol September 2026 | | grant | instant — replay- | | | | stable; the base of the | | | | capacity-rate window | | | | (Section 12.2). | +------------------+--------------------+---------------------------+ | grantedUnits | lifecycle grants | The granted pool, in | | | | abstract pooled units. | +------------------+--------------------+---------------------------+ | reportAt | lifecycle grants | The consumption | | | | threshold at which the | | | | client MUST report. | +------------------+--------------------+---------------------------+ | expiresAt | lifecycle grants | Grant validity instant | | | | (Section 8). | +------------------+--------------------+---------------------------+ | unitWeights | every lifecycle | Pool multipliers per | | | grant (all price | unit type — the | | | forms, including | *complete* map, | | | free) | constant for the | | | | Action's whole life | | | | (pinned with the law): | | | | consuming quantity _q_ | | | | of unit type _u_ draws | | | | q × weight(u) from the | | | | pool. Weights are | | | | positive decimals, | | | | scale ≤ 6; each product | | | | is rounded HALF_UP at | | | | scale 6, and products | | | | are summed exactly. | | | | Ratios, not prices. | +------------------+--------------------+---------------------------+ | quantumUnits | quantised Action | The block size in | | | Types | pooled units: | | | | consumption is charged | | | | in whole blocks; the | | | | client SHOULD expect | | | | pool draw-down in block | | | | increments. | +------------------+--------------------+---------------------------+ | timeBandId | banded Action | The time band fixed at | | | Types (including | opening (Section 10). | | | flat) | | +------------------+--------------------+---------------------------+ | serverTime | every response — | The server clock at | | | *envelope*, always | this processing — the | | | fresh, never | authoritative time | | | replayed | reference (Section 8). | Petkov & Panitch Expires 21 March 2027 [Page 21] Internet-Draft The Pulse Protocol September 2026 +------------------+--------------------+---------------------------+ | actionLive, | every Action- | The live truth: whether | | currentGrantSeq, | identifying 200 — | the Action is open | | currentGrantId | *envelope*, always | (monotonic — once | | | fresh | false, never true), the | | | | current generation, and | | | | the current grant's | | | | opaque address | | | | (currentGrantId — the | | | | recovery handle: a | | | | restarted client | | | | reports against it). | | | | On any response, | | | | replays included, the | | | | client MUST obey the | | | | envelope over the | | | | stored body: | | | | actionLive: false means | | | | stop; currentGrantSeq | | | | above a replayed | | | | grant's grantSeq means | | | | that grant is stale. | +------------------+--------------------+---------------------------+ | lawVersion | every lifecycle | The pinned plan/price- | | | grant | book version (integer) | | | | this Action rates under | | | | — for client-side | | | | correlation with the | | | | tenant's published | | | | catalogue; opaque | | | | otherwise. | +------------------+--------------------+---------------------------+ | limits | every lifecycle | The enforceable caps: | | | grant | maxCallsPerAction (int, | | | | always), maxRuntimeSec | | | | (int, *always* — plan | | | | value or platform | | | | default; measured from | | | | the Action's opening | | | | grantIssuedAt, exceeded | | | | when now > open + | | | | maxRuntimeSec; also | | | | bounds heartbeat-only | | | | renewal), | | | | maxConcurrentActions | | | | (int, always — | | | | subscriber-level), | | | | signaturesRequired | Petkov & Panitch Expires 21 March 2027 [Page 22] Internet-Draft The Pulse Protocol September 2026 | | | (bool, where | | | | configured), minRate | | | | (decimal string, pooled | | | | units *per second*, | | | | where a capacity pool | | | | applies), | | | | enforcementWindowSec | | | | (int, where a capacity | | | | pool applies — the | | | | window over which | | | | consumed rate is | | | | accounted, | | | | Section 12.2). | | | | Disclosed so a gateway | | | | can plan work instead | | | | of discovering limits | | | | through stops. | +------------------+--------------------+---------------------------+ | rateReduced | when capacity | true, with reduceCause: | | | pressure reduced | "CAPACITY" | | | this grant | (Section 12.3). | | | (opening or | | | | continuation) | | +------------------+--------------------+---------------------------+ | settled | flat-priced | true: charged at open; | | | Actions | no reports, no | | | | validity. | +------------------+--------------------+---------------------------+ Table 9 *Grant invariants (normative):* every issued grant satisfies grantedUnits > 0, 0 < reportAt ≤ grantedUnits, quantumUnits > 0 where present, expiresAt > grantIssuedAt, and *reachability*: reportAt MUST be attainable within the Action's cumulative caps under the grant's weights. A response violating them is a server defect; the client treats it as a 5xx-equivalent and retries the identical request. 6.3.1. Client obligations under a grant The client MAY serve Calls until the earliest of: pool exhaustion, the cycle delta reaching reportAt (≥, Section 7.7), or expiresAt. It MUST report at reportAt; it MUST NOT knowingly serve consumption beyond the granted pool other than the tail defined in Section 3.4. Petkov & Panitch Expires 21 March 2027 [Page 23] Internet-Draft The Pulse Protocol September 2026 6.4. Response — REFUSE refusalReason registry (version 1): +========================+=========================================+ | Reason | Meaning | +========================+=========================================+ | NO_HEADROOM | The opening reservation cannot be fully | | | funded. Partial funding is never | | | granted. | +------------------------+-----------------------------------------+ | CEILING_PREEMPT | The Action's ceiling (or its family's, | | | Section 9) leaves no room. | +------------------------+-----------------------------------------+ | ACCOUNT_VELOCITY | Account-level velocity limit engaged. | +------------------------+-----------------------------------------+ | BALANCE_VELOCITY | Balance-level velocity limit engaged. | +------------------------+-----------------------------------------+ | GROUP_VELOCITY | Group-level velocity limit engaged: a | | | rate bound across a declared group of | | | related accounts. Version 1.1 | | | (Section 5). | +------------------------+-----------------------------------------+ | MAX_CONCURRENT_ACTIONS | The subscriber's concurrent-Action cap | | | is reached. | +------------------------+-----------------------------------------+ | PARENT_STOPPED | The declared parent Action is not open. | +------------------------+-----------------------------------------+ | NO_LAW | No applicable price entry: unknown | | | Action Type, no plan, or unknown | | | identities on a declared tenant. | +------------------------+-----------------------------------------+ | DRAINING | The service is draining (an operator- | | | controlled, temporary state): new | | | Actions are refused. Always retryable, | | | with retryAfter (Section 11). | +------------------------+-----------------------------------------+ | BLOCKED_TARIFF | The plan blocks this Action Type for | | | this subscriber. | +------------------------+-----------------------------------------+ | SERVICE_BARRED | The subscriber's state bars service. | +------------------------+-----------------------------------------+ | DUPLICATE_ACTION | The actionId belongs to an *opened* | | | Action and the request identity differs | | | (Section 6.5). | +------------------------+-----------------------------------------+ | EXPOSURE_CAP | A sponsor's aggregate bound across its | | | population is reached; limitOwner names | Petkov & Panitch Expires 21 March 2027 [Page 24] Internet-Draft The Pulse Protocol September 2026 | | the sponsor (Section 12.1). | +------------------------+-----------------------------------------+ | NO_CAPACITY | The applicable capacity pool cannot | | | admit the Action at or above the | | | declared minimum rate (Section 12.2). | | | Always retryable, with retryAfter. | +------------------------+-----------------------------------------+ | PREEMPTED | A capacity commitment was reclaimed | | | under the declared pressure policy | | | before the grant could stand | | | (Section 12.2). Always retryable. | +------------------------+-----------------------------------------+ | CLASS_BLOCKED | Reserved; never emitted in version 1. | +------------------------+-----------------------------------------+ Table 10 *Every refusal additionally carries:* +===================+==============+==============================+ | Field | Present | Meaning | +===================+==============+==============================+ | retryable | always on | Whether the identical | | | refuse | request MAY succeed later | | | | with nothing changed but | | | | time. Version-1 retryable | | | | reasons — exactly seven: the | | | | transient-scarcity six | | | | (NO_CAPACITY, PREEMPTED, | | | | ACCOUNT_VELOCITY, | | | | BALANCE_VELOCITY, | | | | GROUP_VELOCITY, | | | | MAX_CONCURRENT_ACTIONS) plus | | | | DRAINING. Every other | | | | reason is retryable: false. | +-------------------+--------------+------------------------------+ | retryAfter | REQUIRED | Seconds to wait before | | | when | retrying the identical | | | retryable: | request. | | | true | | +-------------------+--------------+------------------------------+ | grade | always on | The evidence behind the | | | refuse | reason: PROVEN (the server's | | | | own arithmetic on its own | | | | records), ATTESTED (an | | | | operator process decided), | | | | or BOUNDED (true within a | | | | stated window, no stronger). | Petkov & Panitch Expires 21 March 2027 [Page 25] Internet-Draft The Pulse Protocol September 2026 | | | A client MUST NOT present | | | | more certainty to the end | | | | user than the grade holds. | +-------------------+--------------+------------------------------+ | limitOwner | on a party- | Names whose limit was | | | scoped bound | reached (e.g. the | | | | sponsor's), so an end user | | | | is never shown a generic | | | | "insufficient funds" for | | | | someone else's cap. | +-------------------+--------------+------------------------------+ | automatedDecision | always on | true in version 1: the | | | refuse, from | refusal was decided by | | | version 1.1 | machine, with no human in | | | (Section 5) | the loop (Section 15.7). | | | | Carried on the decision | | | | itself so the notice travels | | | | with the refusal it | | | | describes. | +-------------------+--------------+------------------------------+ | reviewContact | when the | A human-review channel — a | | | tenant | URI or plain-text contact — | | | configures | through which the refused | | | one, from | party can have the decision | | | version 1.1 | re-examined by a person. | | | (Section 5) | Display text under the | | | | limitOwner presentation | | | | rules (printable characters | | | | only, rendered as plain | | | | text, never markup); tenant- | | | | configured and echoed by the | | | | server. A client SHOULD | | | | present it alongside the | | | | refusal it accompanies. | +-------------------+--------------+------------------------------+ Table 11 Version-1 grades: NO_CAPACITY and PREEMPTED are BOUNDED; SERVICE_BARRED and DRAINING are ATTESTED; every other reason is PROVEN. Petkov & Panitch Expires 21 March 2027 [Page 26] Internet-Draft The Pulse Protocol September 2026 A refusal is a complete outcome: no budget is held, nothing is charged, and the client MUST NOT serve the work. *A refusal retains no identity*: no Action opens, the actionId remains unclaimed, and any later Authorise carrying it — identical or different — is evaluated fresh. DUPLICATE_ACTION applies only against Actions that actually opened. Concurrent identical Authorises are serialised per subscriber; if one opens the Action, the other lands on live-Action replay (Section 6.5). 6.5. Idempotency, request identity, and identifier scopes *Request identity* is SHA-256 over the JSON Canonicalization Scheme (JCS) [RFC8785] canonicalization of the strict-parsed request body (duplicate or unknown members are rejected before hashing, Section 5); for Reports, the path grantId is appended to the canonical bytes before hashing. Because every decimal travels as a canonical string (Section 4.3), JCS needs no number-rendering caveats. Two requests with equal canonical form are the same request. Test vectors accompany the conformance suite. *Identifier scopes:* actionId is unique within its (tenant, account) pair — two tenants can never collide; callId is unique within its Action; grantId is server-issued, opaque, and unique within the charging domain. Server recognition retention is the late window (Section 7.6). *Authorise always answers in the Authorise shape* — a replay re- presents facts, it never returns a stored Report body: * Retry of a live Action (same actionId, matching identity) → decision: GRANT re-presenting the *current standing grant* (current grantSeq and parameters) *plus its cycle-baseline receipt* (acceptedCumulative at the settlement that issued it), so a restarted gateway recovers both the address and the remaining pool — with a fresh envelope. * Retry of an ended Action within the late window (matching identity) → an Authorise-shaped terminal acknowledgement: decision: GRANT, settled where applicable, the ending, endedAt, and the final receipt; a flat-priced Action's retry re-receives GRANT/settled: true, never a refusal. * Same actionId as an *opened* Action, different identity → 200 REFUSE / DUPLICATE_ACTION (a charging refusal like any other — identity conflicts are not HTTP errors). Petkov & Panitch Expires 21 March 2027 [Page 27] Internet-Draft The Pulse Protocol September 2026 * An actionId that opened an Action is never legally reusable, including after the Action ends. Beyond the late window the server no longer guarantees replay; a client MUST NOT rely on replay after it. Clients MUST serialise a retry as the identical logical request: JCS removes JSON key-order differences, and nothing else — there is no equivalence class of spellings (Section 4.3 admits exactly one spelling of every value; any other is malformed before identity is ever computed). Any byte difference that survives canonicalization is a different request. 7. The Report Operation POST /ocs/ai/v1/grants/{grantId}/reports 7.1. Request +===============+=============+====================================+ | Field | Requirement | Meaning | +===============+=============+====================================+ | schemaVersion | REQUIRED | See Section 5. | +---------------+-------------+------------------------------------+ | actionId | REQUIRED | The Action being reported. | +---------------+-------------+------------------------------------+ | tenantRef, | REQUIRED | Identity resolution; MUST match | | accountRef | | the Action's. | +---------------+-------------+------------------------------------+ | calls[] | REQUIRED | Per-call cumulative totals | | | | (Section 7.2). *MAY be empty*: an | | | | empty calls[] with final: false is | | | | a *heartbeat* — an ordinary fresh | | | | cycle (Section 3.5) that settles | | | | whatever unsettled meter delta | | | | exists (normally zero) and renews | | | | the grant — the lease-renewal | | | | mechanism for quiet periods; it | | | | passes through the same stop- | | | | condition precedence as any cycle. | +---------------+-------------+------------------------------------+ | final | REQUIRED | true declares this the closing | | | | report (the CLOSE ending). | +---------------+-------------+------------------------------------+ | gatewayTime | OPTIONAL | As in Section 6.1 — recorded, | | | | never enforced against | | | | (Section 15.6). | +---------------+-------------+------------------------------------+ Petkov & Panitch Expires 21 March 2027 [Page 28] Internet-Draft The Pulse Protocol September 2026 Table 12 7.2. Cumulative semantics (normative) Each calls[] entry is {callId, units, startedAt?, signature?} where units is a map of unit type → *cumulative total for that Call since the Call began* — not a delta; values are canonical decimal strings (Section 4.3). calls[] is a *keyed patch*, not a snapshot: only Calls with changed totals need appear; an omitted callId reads as unchanged; a callId never seen before initialises all its declared units at zero baseline; *two entries with the same callId in one request are malformed*. The calls-per-Action limit counts *distinct callIds* over the Action's life. Unit keys MUST be among those declared by the pinned Action Type (undeclared keys are malformed); declared keys absent from an entry read as unchanged. signature is *reserved in version 1*: an opaque string, recorded verbatim. Its verification profile is bilateral and out of band, and SIGNATURES_REQUIRED (Section 7.4) applies only where such a profile is agreed. startedAt (version 1.1, Section 5) is OPTIONAL evidence: the instant the Call began at the serving system (timestamp, Section 4.3). Like gatewayTime it is recorded and never enforced against (Section 15.6), and it can never re-price anything — the Action's time band is pinned at open (Section 10), so no client- reported timestamp changes a charge. Its value is that the moment consumption _occurred_, not merely the moment it was _reported_, survives into the audit record. It SHOULD carry the same value on every entry for a given callId; the server retains it under the audit retention rules (Section 15.7). *Monotonicity and out-of-order delivery:* a reported value lower than the server's stored high-water for that Call and unit is _absorbed_: the meter is the per-value maximum, so a stale, reordered, or replayed report charges zero and is never an error — this is what makes the arithmetic immune to retries, duplicates, and delivery order. (Within one request there is no shrinking case to define: duplicate callId entries are malformed outright.) Cumulative reporting is what makes the protocol retry-safe: a replayed report or replayed call entry moves the cumulative meter by zero and therefore charges zero. The server settles each cycle as the _increment of the rated cumulative consumption_ under the pinned law — never rating the same consumption twice, for any price form. *Pool arithmetic (normative — one cumulative pipeline, cadence- independent):* everything is computed on *Action-cumulative* quantities, and every charge or threshold is a *delta of cumulatives* — never a rounded delta. Per unit type _u_: cumulativePooled_u = HALF_UP₆(weight_u × cumulativeTotal_u); the Action's pooled total is Petkov & Panitch Expires 21 March 2027 [Page 29] Internet-Draft The Pulse Protocol September 2026 the exact sum over unit types. For quantised forms, blocks apply to that cumulative pooled total — blocksCharged = ceil(cumulativePooled / quantumUnits) — and each cycle charges the newly completed blocks (the cumulative-rated rule). reportAt and pool exhaustion compare *raw* (unquantised) pooled-total deltas against the cycle baseline. Because rounding and quantisation are applied only to cumulative values, report segmentation provably cannot change any charge. 7.3. Response +===========+=======================================================+ | Directive | Meaning | +===========+=======================================================+ | CONTINUE | Settlement done; the next grant is included | | | (grantId, grantedUnits, reportAt, expiresAt, | | | unitWeights, serverTime, and the other grant fields | | | of Section 6.3). The tail (Section 3.4) is served | | | under the old grant and settled on the next cycle. | | | Under capacity pressure the next grant MAY be | | | reduced (Section 12.3), flagged rateReduced: true. | +-----------+-------------------------------------------------------+ | STOP | Settlement done; *stop obligation* — the client | | | MUST promptly cease serving the Action's traffic. | | | stopReason per Section 7.4; endedAt states the | | | ending instant (the late window's start). | | | Consumption already in flight is reportable | | | afterwards (Section 7.6 / Section 8). | +-----------+-------------------------------------------------------+ | SETTLED | Final settlement acknowledged. Returned for a | | | final: true close and for every non-replay terminal | | | response of a *CLOSE-ended* Action (the terminal | | | directive is a function of the ending, Section 3.5: | | | STOP-ended Actions answer STOP, LAPSE-ended answer | | | STOP (LAPSED)); late adjustments are flagged | | | adjustment: true. Carries endedAt. | +-----------+-------------------------------------------------------+ | CURRENT | No settlement occurred (Section 3.5 LIVE row 6): | | | the response to a stale, non-final, unrecognised | | | report — the standing grant re-presented (its full | | | Section 6.3 grant fields) plus baselineCumulative | | | (that grant's cycle baseline) and meterCumulative | | | (the post-merge meter). The client resynchronises: | | | pool draw-down = weighted(meterCumulative − | | | baselineCumulative). | +-----------+-------------------------------------------------------+ Table 13 Petkov & Panitch Expires 21 March 2027 [Page 30] Internet-Draft The Pulse Protocol September 2026 This distributed-worker convergence flow is illustrative; it does not relax the one-in-flight requirement of Section 3.4. The CURRENT branch shown is the no-ending-condition branch of Section 3.5 LIVE row 6: Gateway worker A Charging Server Gateway worker B | | | |--- Report on G1 --->| | |<-- CONTINUE G2 -----| | | |<-- stale non-final -| | | G1; no retained- | | | id match | | | max-merge meter; | | | evaluate ending | | | conditions | | |--- CURRENT G2 + --->| | | baseline + | | | post-merge meter | | | no settlement, | | | no mint | | | | B resynchronizes *Cycle baseline (normative):* grantedUnits and reportAt are *cycle- scoped*, and the baseline is a _meter state_, not an instant: a cycle's consumption is the weighted pooled delta (Section 7.2 arithmetic) between *the accepted meter at the settlement that issued the grant* — the acceptedCumulative of that response; zero for the first grant — and the current meter. Both sides can compute it exactly: the client from its last receipt, the server from its stored meter. Report _values_ remain Action-cumulative (Section 7.2). Tail consumption lands in whichever cycle's report first carries it — the cumulative arithmetic makes double-counting impossible. *Settlement receipt:* every settling response (CONTINUE, STOP, SETTLED) carries settlementId (stable per settlement; a replay returns the same id), acceptedCumulative (the server's post-merge Action totals — a *complete* map over the declared unit types), *acceptedCalls* (an *array* of {callId, units} entries — the per-call accepted cumulative values for exactly the Calls present in this request, ≤ 256 entries), and cyclePooledUnits (the *raw* pooled delta settled this cycle, pre-quantisation), plus where applicable clamped: true (limited by the meter or a ceiling) and adjustment: true (a late settlement). A client holding several receipts trusts the one from the highest-generation outcome. Receipts are denominated in *units only* — no balance, price, or currency ever crosses this wire. Petkov & Panitch Expires 21 March 2027 [Page 31] Internet-Draft The Pulse Protocol September 2026 *Response replay and convergence:* governed entirely by the state machine (Section 3.5) — the server serialises all of one Action's operations; only a fresh Report on the current grant runs a cycle and mints; identity-matched retries replay their stored outcome under a fresh envelope; non-matching reports converge (a final: true close is always honoured against the current state; non-final ones max-merge and answer CURRENT); retention is the terminal outcome plus the last 4 non-terminal outcomes — an aged retry converges safely, though its original settlementId is then no longer re-presentable (Section 7.6). A lost CONTINUE is therefore always recoverable: retry the identical request (replay), or report against currentGrantId from any response's envelope. acceptedCalls appears on Report responses only. The lost-response path below illustrates the request identity and replay rules of Section 6.5 and this section. R includes the Report body and its path grantId; S is the stable settlement receipt. The retry remains the same logical Report for Section 3.4: Gateway Charging Server | | |--- Report R against current Gn --->| | | merge, settle, and | | atomically store | | CONTINUE G(n+1) + S |<-- CONTINUE G(n+1) + receipt S --X | response lost | | |--- identical retry of R ---------->| | | retained identity | | matches |<-- stored CONTINUE + receipt S ----| body replay; fresh | | envelope; no | | settlement, no mint 7.4. stopReason registry (version 1) +=====================+=========================================+ | Reason | Meaning | +=====================+=========================================+ | CEILING_REACHED | The Action (or family) ceiling is | | | exhausted. | +---------------------+-----------------------------------------+ | NO_HEADROOM | Funds cannot cover a further grant. | +---------------------+-----------------------------------------+ | CALL_LIMIT | The calls-per-Action limit was reached | | | (always enforced; the settlement of the | | | limit-crossing report is completed | | | first). | +---------------------+-----------------------------------------+ Petkov & Panitch Expires 21 March 2027 [Page 32] Internet-Draft The Pulse Protocol September 2026 | MAX_RUNTIME | The Action exceeded its maximum | | | runtime. | +---------------------+-----------------------------------------+ | INFLATION | A consumption-inflation trust wire | | | tripped. | +---------------------+-----------------------------------------+ | SIGNATURES_REQUIRED | The Action Type requires call | | | signatures and they were absent. | +---------------------+-----------------------------------------+ | PARENT_STOPPED | The parent Action ended; children are | | | stopped (Section 9). | +---------------------+-----------------------------------------+ | ADMIN_FORCED | Operator intervention. | +---------------------+-----------------------------------------+ | DRAINING | The service is draining: settlement | | | completed, no re-grants are issued. | +---------------------+-----------------------------------------+ | NO_CAPACITY | Capacity fell below the declared | | | minimum serve rate; continuing usefully | | | is impossible, so the server stops | | | honestly rather than degrading past use | | | (Section 12.3). | +---------------------+-----------------------------------------+ | EXPOSURE_CAP | The sponsor's aggregate bound was | | | exhausted mid-Action; limitOwner names | | | the sponsor (Section 12.1). | +---------------------+-----------------------------------------+ | LAPSED | The grant's validity lapsed — returned | | | for the grace-window close | | | (Section 7.5), and as the terminal | | | directive of every LAPSE-ended Action's | | | later responses (Section 3.5). | +---------------------+-----------------------------------------+ Table 14 *Precedence (normative):* final: true beats every server stop condition → SETTLED (Section 7.5), draining included. For a non- final report on which several stop conditions hold at once, the reported stopReason is the first that applies in this order: ADMIN_FORCED · DRAINING · PARENT_STOPPED · LAPSED · CEILING_REACHED · EXPOSURE_CAP · NO_HEADROOM · CALL_LIMIT · MAX_RUNTIME · INFLATION · SIGNATURES_REQUIRED · NO_CAPACITY — and the stored terminal outcome records that same reason, so replays agree. Petkov & Panitch Expires 21 March 2027 [Page 33] Internet-Draft The Pulse Protocol September 2026 7.5. Lapse boundary (normative) With now being the server clock at processing: * now < expiresAt → normal processing; CONTINUE is possible. * expiresAt ≤ now < expiresAt + 5 s → the report is accepted as the *closing report*: consumption is settled and the directive is SETTLED when final: true, else STOP (LAPSED) — never CONTINUE. * now ≥ expiresAt + 5 s → the Action is ended as LAPSE (if it wasn't already — the transition instant is the same either way, Section 3.5) and the report is processed as a late report (Section 7.6). The following diagram illustrates those branches; Section 3.5 and the rules above remain authoritative, including the stored-outcome replay exception: Gateway Charging Server (`now`) | | |--- Report ------------------------>| | +-- now < expiresAt | | normal processing; | | CONTINUE is possible | | | +-- expiresAt <= now | | < expiresAt + 5 s | | final=true: SETTLED | | final=false: | | STOP (LAPSED) | | | `-- now >= expiresAt + 5 s | ENDED(LAPSE), with | endedAt=expiresAt+5 s; |<-- response under selected branch -- late handling | (section 7.6) *Precedence:* on a final: true report, the client's close wins over any concurrent server stop condition — the response is SETTLED (the stop obligation is moot once the client has ended the Action). A late report can never revive a grant. A client that wants to continue past expiresAt MUST have reported before it. Petkov & Panitch Expires 21 March 2027 [Page 34] Internet-Draft The Pulse Protocol September 2026 *The LAPSE instant (normative):* an Action that ends as LAPSE has *endedAt = expiresAt + 5 s* — by definition, whether the ending is physically performed by a timer, a sweep, or the first post-grace report. Both sides can compute the late window's anchor from the grant alone. CLOSE and STOP endings record their processing instant as endedAt. 7.6. Late reports and the late window While recognised — until max(endedAt + 24 h, expiresAt + 1 h) (Section 3.5; endedAt is carried on every terminal response) — the server: * *MUST retain recognition*: replay of stored responses to recognised retries (matching request identity) and duplicate- actionId detection are *unconditional* for the full window — this guarantee does not degrade. * *Settles genuinely new consumption* reported late: the cumulative meters are merged upward, and the increment of rated consumption — floored at zero, clamped by the remaining ceiling — is charged under the Action's pinned law and returned with *the ending's terminal directive* (Section 3.5), adjustment: true, and a receipt (Section 7.3). Contractual minimums are never applied a second time. Repeated late reports charge each unit at most once. Late-*settlement* capability (unlike recognition) is best-effort under extreme retention pressure — an exceptional, server-metered condition; a late report the server recognises but can no longer settle is answered with the stored terminal outcome plus *unattributable: true* and *no new settlementId* — the response says plainly that the new units were not settled (they are recorded unattributable). A non-matching terminal report whose merge adds zero receives the terminal outcome with the current receipt and no adjustment flag. Beyond the late window, reports against unknown identities receive 404 and the traffic is recorded as unattributable — recorded, never charged. 7.7. Reporting cadence requirements A client MUST report when the cycle's pooled consumption delta reaches reportAt (comparator: *≥*), and SHOULD report promptly on its own initiative when an Action ends client-side (with final: true). Batching beyond reportAt increases the client's overshoot exposure, which the protocol bounds to one final report but does not eliminate. Petkov & Panitch Expires 21 March 2027 [Page 35] Internet-Draft The Pulse Protocol September 2026 *On receiving STOP*, the client MUST stop admitting new Calls immediately, SHOULD cancel cancellable in-flight work, and MUST flush the final meter — dispatching the first flush report *within 30 seconds* (a hard per-event deadline, distinct from the statistical promptness profile of Section 12.4); when more than 256 Calls changed, the flush spans serialised 256-call chunks continued back- to-back. *The flush obligation fires once per ending*: a STOP directive received in response to the flush itself, a replay, or a late response does not re-trigger it. Uncancellable work may complete, and its consumption reports late (Section 7.6). 8. Timing Summary +=================+==========+=====================================+ | Instant | Source | Effect | +=================+==========+=====================================+ | reportAt | grant | Consumption threshold: the client | | | | MUST report on crossing it. | +-----------------+----------+-------------------------------------+ | expiresAt | grant | Grant validity end. Reports within | | | | a 5 s grace settle as closing; | | | | beyond it the Action ends as LAPSE. | +-----------------+----------+-------------------------------------+ | max(endedAt + | terminal | Late-window end: recognition | | 24 h, expiresAt | response | unconditional until it; late | | + 1 h) | + last | consumption settleable within it | | | grant | (Section 7.6). | +-----------------+----------+-------------------------------------+ Table 15 All boundaries are evaluated on the server clock. Every response carries serverTime (Section 6.3), the authoritative reference: the client SHOULD track the offset between serverTime and its own clock plus the measured round-trip, and dispatch its pre-lapse report with at least that margin in hand — a report sent "before the lapse" by the client's clock but processed after it lands in the closing or late branch. 8.1. Protocol constants and configurables This table is a descriptive index to the defining prose, not a second source of rules. Where a value is a recommendation or a configured value, the table says so; where two statements are not fully harmonised, it preserves both without choosing between them. "Deployment-configurable" includes plan-, catalogue-, integration-, client-, and server-policy values. Petkov & Panitch Expires 21 March 2027 [Page 36] Internet-Draft The Pulse Protocol September 2026 *Fixed in protocol.* +==================+===============================+============+ | Name | Value | Defining | | | | section | +==================+===============================+============+ | Protocol | Version 1 (/v1/) | Section | | generation | | 1.4, | | | | Section | | | | 4.1, | | | | Section 5 | +------------------+-------------------------------+------------+ | Charging | Exactly two: Authorise and | Section | | operation count | Report | 3.2 | +------------------+-------------------------------+------------+ | Authorise | POST /ocs/ai/v1/ | Section | | endpoint | authorizations | 4.1 | +------------------+-------------------------------+------------+ | Report endpoint | POST /ocs/ai/v1/ | Section | | | grants/{grantId}/reports | 4.1 | +------------------+-------------------------------+------------+ | Media and | application/json over HTTPS; | Section | | transport | plaintext refused | 4.1 | +------------------+-------------------------------+------------+ | Request | A per-gateway bearer API key | Section | | authentication | in every request; OAuth2 is | 4.2 | | | not in version 1 | | +------------------+-------------------------------+------------+ | Redirect posture | HTTP 3xx is not used | Section | | | | 1.4 | +------------------+-------------------------------+------------+ | Charging-domain | One logical charging domain | Section | | topology | per tenant in version 1 | 1.4 | +------------------+-------------------------------+------------+ | Action lifecycle | One successful Authorise; one | Section | | cardinality | ending | 3.1 | +------------------+-------------------------------+------------+ | Current grant | Exactly one per live Action | Section 2 | | cardinality | | | +------------------+-------------------------------+------------+ | Outstanding | At most one logical Report | Section | | Report | per Action | 3.4 | | cardinality | | | +------------------+-------------------------------+------------+ | Opening and mint | Lifecycle opening is G1; each | Section 2, | | generations | mint is exactly +1; a flat | Section | | | terminal envelope is | 3.3, | | | generation 2 | Section | Petkov & Panitch Expires 21 March 2027 [Page 37] Internet-Draft The Pulse Protocol September 2026 | | | 3.5 | +------------------+-------------------------------+------------+ | Outcome | Terminal outcome plus the | Section | | retention ring | last 4 non-terminal outcomes; | 3.5, | | | cycles and late adjustments | Section | | | share the ring | 7.3 | +------------------+-------------------------------+------------+ | Lapse grace | 5 s | Section | | | | 3.5, | | | | Section | | | | 7.5, | | | | Section 8 | +------------------+-------------------------------+------------+ | LAPSE instant | endedAt = expiresAt + 5 s | Section | | | | 7.5 | +------------------+-------------------------------+------------+ | Recognition / | Until max(endedAt + 24 h, | Section 2, | | late window | expiresAt + 1 h) | Section | | | | 3.5, | | | | Section | | | | 7.6, | | | | Section 8 | +------------------+-------------------------------+------------+ | Decimal scale | Canonical decimal JSON | Section | | and rendering | string; scale at most 6; the | 4.3 | | | lexical exclusions are in | | | | Section 4.3 | | +------------------+-------------------------------+------------+ | Request-quantity | [0, 10^9], scale at most 6 | Section | | domain | | 4.3 | +------------------+-------------------------------+------------+ | Per-call and | Each is at most 10^9 | Section | | Action/unit | | 4.4 | | parser caps | | | +------------------+-------------------------------+------------+ | Per-call | Terminate or segment before a | Section | | operational | Call's cumulative value | 4.3 | | segmentation | reaches 10^9 | | | point | | | +------------------+-------------------------------+------------+ | Weight domain | [0, 10^6], scale at most 6; | Section | | | issued weights are positive | 4.3, | | | | Section | | | | 6.3 | +------------------+-------------------------------+------------+ | Derived pooled | [0, 10^17), scale at most 6, | Section | | domain | for the fields named in | 4.3 | | | Section 4.3 | | Petkov & Panitch Expires 21 March 2027 [Page 38] Internet-Draft The Pulse Protocol September 2026 +------------------+-------------------------------+------------+ | Identifier | At most 128 characters from | Section | | syntax | [A-Za-z0-9._:-] | 4.3 | +------------------+-------------------------------+------------+ | Display-label | At most 64 printable UTF-8 | Section | | syntax | characters; no control or | 4.3 | | | bidirectional-control | | | | characters | | +------------------+-------------------------------+------------+ | Unit-type syntax | [a-z0-9_]{1,64} | Section | | | | 4.3 | +------------------+-------------------------------+------------+ | Timestamp | YYYY-MM-DDThh:mm:ss.sssZ; 3 | Section | | profile | fractional digits; years | 4.3 | | | 1970-9999; no leap seconds | | +------------------+-------------------------------+------------+ | Count/version | JSON integer 0 <= n < 2^53; | Section | | integer domain | schemaVersion >= 1 | 4.3, | | | | Section 5 | +------------------+-------------------------------+------------+ | Compatible | Every version from 1 through | Section 5 | | schema support | maxSchemaVersion | | +------------------+-------------------------------+------------+ | Request-body | At most 256 KB; the memo does | Section | | limit | not define the KB base | 4.4 | +------------------+-------------------------------+------------+ | Report patch | At most 256 calls[] entries | Section | | size | | 4.4 | +------------------+-------------------------------+------------+ | Unit-map size | At most 16 entries per Call; | Section | | | estimate and response maps | 4.3, | | | share the 16-unit bound | Section | | | | 4.4, | | | | Section | | | | 6.1 | +------------------+-------------------------------+------------+ | Receipt call | acceptedCalls has at most 256 | Section | | count | entries and covers exactly | 4.3, | | | the Calls in the settling | Section | | | Report | 7.3 | +------------------+-------------------------------+------------+ | Grant | grantedUnits > 0; 0 < | Section | | inequalities | reportAt <= grantedUnits; | 6.3 | | | quantumUnits > 0 where | | | | present; expiresAt > | | | | grantIssuedAt | | +------------------+-------------------------------+------------+ | Pooled product | HALF_UP at scale 6 per | Section | Petkov & Panitch Expires 21 March 2027 [Page 39] Internet-Draft The Pulse Protocol September 2026 | rounding | cumulative unit-type product; | 6.3, | | | exact sum | Section | | | | 7.2 | +------------------+-------------------------------+------------+ | Quantised rating | ceil(cumulativePooled / | Section | | | quantumUnits); charge block | 7.2 | | | deltas | | +------------------+-------------------------------+------------+ | Threshold and | Raw, unquantised pooled-total | Section | | exhaustion basis | delta from the cycle baseline | 7.2 | +------------------+-------------------------------+------------+ | Report threshold | Dispatch when the cycle delta | Section | | comparator | is >= reportAt | 7.7 | +------------------+-------------------------------+------------+ | Threshold / | Section 7.7 requires | Section | | closing cadence | threshold dispatch and | 7.7, | | | recommends a prompt client | Section | | | close; Section 12.4 says both | 12.4 | | | are due immediately outside | | | | the profile bound | | +------------------+-------------------------------+------------+ | STOP flush | First chunk within 30 s, once | Section | | | per ending; chunks of at most | 7.7, | | | 256 changed Calls back-to- | Section 14 | | | back | | +------------------+-------------------------------+------------+ | Retry backoff | Initial 1 s, factor 2, full | Section | | recommendation | jitter, cap 30 s | 13.3 | | | (recommendation) | | +------------------+-------------------------------+------------+ | Client attempt | 2 s per attempt | Section | | deadline | (recommendation) | 13.5 | | recommendation | | | +------------------+-------------------------------+------------+ | TLS versions | TLS 1.2 or later required; | Section | | | TLS 1.3 recommended; | 4.1, | | | [RFC9325] configuration | Section | | | | 15.1 | +------------------+-------------------------------+------------+ | Request-identity | SHA-256 over JCS [RFC8785]; | Section | | algorithm | append path grantId for | 6.5 | | | Report | | +------------------+-------------------------------+------------+ | Explicit HTTP | 200, 400, 401, 403, 404, 405, | Section | | status mappings | 415, 429, and 503 as mapped | 3.5, | | | | Section | | | | 4.1, | | | | Section | Petkov & Panitch Expires 21 March 2027 [Page 40] Internet-Draft The Pulse Protocol September 2026 | | | 13.1 | +------------------+-------------------------------+------------+ | Additional | INTERNAL is status-mirroring; | Section | | server-error | retry guidance covers 5xx | 13.1, | | class | generally, whose exact | Section | | | statuses are not enumerated | 13.3 | +------------------+-------------------------------+------------+ | HTTP retry | Retry-After on 429 and 503 | Section | | header | | 13.1 | +------------------+-------------------------------+------------+ | HTTP connection | HTTP/2 connection reuse is | Section | | posture | assumed (operational | 13.5 | | | assumption) | | +------------------+-------------------------------+------------+ | Current protocol | 1.1 — generation 1, wire | Section 5 | | version | schemaVersion 2; each minor | | | | binds to one schemaVersion | | +------------------+-------------------------------+------------+ | Retryable | Exactly seven reasons from | Section 5, | | refusal set | version 1.1 (six in 1.0), | Section | | | enumerated in Section 6.4 | 6.4 | +------------------+-------------------------------+------------+ | Closed per- | Decision, refusal reason, | Section 5, | | version value | stop reason, error code, | Section 17 | | sets | ending, directive, grade, and | | | | reduce cause; initial values | | | | in Section 17; additions bind | | | | to minor versions per | | | | Section 5's version table | | +------------------+-------------------------------+------------+ | API-key rotation | Dual-active rotation; | Section | | and revocation | revocation effective "within | 4.2, | | posture | seconds" | Section | | | | 15.1 | +------------------+-------------------------------+------------+ Table 16 *Deployment-configurable.* +=============================+==========================+==========+ | Name | Value | Defining | | | | section | +=============================+==========================+==========+ | Refusal human-review | Tenant-configured; | Section | | channel (reviewContact) | absent unless | 6.4, | | | configured | Section | | | | 15.7 | Petkov & Panitch Expires 21 March 2027 [Page 41] Internet-Draft The Pulse Protocol September 2026 +-----------------------------+--------------------------+----------+ | Sponsor-cap accounting | Part of the sponsor's | Section | | window | declaration (credits | 12.1 | | | per period); boundary | | | | mechanics deployment- | | | | profile in version 1 | | +-----------------------------+--------------------------+----------+ | maxSchemaVersion | Highest compatible | Section | | | schema supported; 1 <= | 4.3, | | | n < 2^53 | Section | | | | 5 | +-----------------------------+--------------------------+----------+ | grantedUnits | Server-selected per | Section | | | cycle within the | 4.3, | | | pooled domain and | Section | | | grant inequalities | 6.3, | | | | Section | | | | 12.2 to | | | | Section | | | | 12.3 | +-----------------------------+--------------------------+----------+ | reportAt | Server-selected per | Section | | | cycle; 0 < reportAt <= | 6.3, | | | grantedUnits | Section | | | | 7.7 | +-----------------------------+--------------------------+----------+ | Grant validity / expiresAt | Server-selected | Section | | | instant after | 6.3, | | | grantIssuedAt; no | Section | | | default duration | 8, | | | stated | Section | | | | 12.3 | +-----------------------------+--------------------------+----------+ | Action-Type vocabulary | Catalogue-defined, | Section | | | whitelisted, and | 1.4, | | | distributed out of | Section | | | band | 6.1 | +-----------------------------+--------------------------+----------+ | unitWeights | Positive plan/law | Section | | | ratios, at most 10^6, | 4.3, | | | pinned for the Action | Section | | | | 6.3 | +-----------------------------+--------------------------+----------+ | Price form and lawVersion | Catalogue/plan choice; | Section | | | the selected law | 3.3, | | | version is pinned | Section | | | | 6.2 to | | | | Section | Petkov & Panitch Expires 21 March 2027 [Page 42] Internet-Draft The Pulse Protocol September 2026 | | | 6.3 | +-----------------------------+--------------------------+----------+ | Quantisation / quantumUnits | Action-Type choice; | Section | | | positive pooled | 6.3, | | | decimal where used | Section | | | | 7.2 | +-----------------------------+--------------------------+----------+ | Time-band schedule / | Tenant-published full- | Section | | timeBandId | week schedule; band | 6.3, | | | selected once at | Section | | | opening | 10 | +-----------------------------+--------------------------+----------+ | Action or family ceiling | Plan-defined internal | Section | | | ceiling; no monetary | 2, | | | value appears on the | Section | | | wire | 6.2, | | | | Section | | | | 9, | | | | Section | | | | 15.7 | +-----------------------------+--------------------------+----------+ | Contractual minimum | Plan-defined; applied | Section | | | as described at open | 6.2, | | | and not a second time | Section | | | late | 7.6 | +-----------------------------+--------------------------+----------+ | Funding scope and buckets | Plan-defined scope, | Section | | | eligibility, and | 6.1 | | | exclusivity | | +-----------------------------+--------------------------+----------+ | limits.maxCallsPerAction | Always disclosed; | Section | | | integer in the count | 4.3, | | | domain; counts | Section | | | distinct Calls over | 6.3, | | | the Action | Section | | | | 7.2 | +-----------------------------+--------------------------+----------+ | limits.maxRuntimeSec | Section 6.3 says | Section | | | always disclosed as a | 4.3, | | | plan value or platform | Section | | | default and exceeded | 6.3, | | | when now > open + | Section | | | value; Section 15.4 | 15.4 | | | calls maximum runtime | | | | optional per | | | | deployment/plan | | +-----------------------------+--------------------------+----------+ | limits.maxConcurrentActions | Always disclosed | Section | Petkov & Panitch Expires 21 March 2027 [Page 43] Internet-Draft The Pulse Protocol September 2026 | | subscriber-level | 4.3, | | | integer cap | Section | | | | 6.3, | | | | Section | | | | 15.4 | +-----------------------------+--------------------------+----------+ | limits.signaturesRequired | Boolean where a | Section | | | bilateral signature | 6.3, | | | profile is configured | Section | | | | 7.2, | | | | Section | | | | 15.4 | +-----------------------------+--------------------------+----------+ | Call-signature profile | Bilateral and out of | Section | | | band; no field- | 1.4, | | | specific size cap is | Section | | | stated | 7.2 | +-----------------------------+--------------------------+----------+ | limits.minRate | Capacity-only pooled | Section | | | units per second; | 4.3, | | | exact upper numeric | Section | | | domain is not stated | 6.3, | | | | Section | | | | 12.2 to | | | | Section | | | | 12.3 | +-----------------------------+--------------------------+----------+ | limits.enforcementWindowSec | Capacity-only integer | Section | | | seconds, greater than | 4.3, | | | 0; exact algorithm | Section | | | provisional | 6.3, | | | | Section | | | | 12.2 | +-----------------------------+--------------------------+----------+ | Sponsor cap and owner label | Sponsor-defined | Section | | | aggregate internal | 12.1, | | | bound and | Section | | | presentation-safe | 15.7 | | | label | | +-----------------------------+--------------------------+----------+ | Capacity pool | Tenant-defined | Section | | | provisioned rate and | 12.2 | | | named Action-Type | | | | membership | | +-----------------------------+--------------------------+----------+ | Capacity pressure / | Deployment-declared; | Section | | reclamation policy | no numeric default | 6.4, | | | stated | Section | Petkov & Panitch Expires 21 March 2027 [Page 44] Internet-Draft The Pulse Protocol September 2026 | | | 12.2 to | | | | Section | | | | 12.3 | +-----------------------------+--------------------------+----------+ | Integration promptness | Agreed per | Section | | bound | integration; | 12.4 | | | provisional version-1 | | | | default is 30 s at p95 | | +-----------------------------+--------------------------+----------+ | Refusal-body retryAfter | Server-selected | Section | | | integer seconds, | 4.3, | | | required when | Section | | | retryable: true | 6.4 | +-----------------------------+--------------------------+----------+ | Error-envelope retry | retryable and | Section | | members | retryAfter are | 13.1 | | | independently | | | | optional; no status | | | | coupling or numeric | | | | default is stated | | +-----------------------------+--------------------------+----------+ | HTTP Retry-After value | Server-selected on 429 | Section | | | and 503; Pulse states | 13.1 | | | no minimum or maximum | | +-----------------------------+--------------------------+----------+ | Client pre-lapse margin | Measured server-clock | Section | | | offset plus round-trip | 8 | | | margin; no fixed value | | +-----------------------------+--------------------------+----------+ | Velocity limits | Optional account/ | Section | | | balance controls; | 6.4, | | | thresholds and windows | Section | | | not stated | 15.4 | +-----------------------------+--------------------------+----------+ | Consumption-inflation wire | Optional control; | Section | | | threshold/window not | 7.4, | | | stated | Section | | | | 15.4 | +-----------------------------+--------------------------+----------+ | Per-credential API rate | Token-bucket rate/ | Section | | limit | capacity; no numeric | 13.1, | | | default stated | Section | | | | 15.5 | +-----------------------------+--------------------------+----------+ | Draining state | Operator-controlled | Section | | | temporary state; delay | 6.4, | | | returned in retryAfter | Section | | | | 11 | Petkov & Panitch Expires 21 March 2027 [Page 45] Internet-Draft The Pulse Protocol September 2026 +-----------------------------+--------------------------+----------+ | Mutual TLS | Optional deployment | Section | | | choice | 4.2 | +-----------------------------+--------------------------+----------+ | Credential tenant set | One or more bound | Section | | | tenants; exact | 2, | | | cardinality not stated | Section | | | | 15.2 | +-----------------------------+--------------------------+----------+ | API-key material and exact | Deployment credential | Section | | revocation SLA | configuration; no | 4.2, | | | exact seconds bound is | Section | | | stated | 15.1 | +-----------------------------+--------------------------+----------+ | Persistent clock-skew | Integration trust | Section | | tolerance | threshold; no value | 15.6 | | | stated | | +-----------------------------+--------------------------+----------+ | Audit, gatewayTime, and | Operator retention | Section | | startedAt retention | policy, apart from the | 15.7 | | | fixed late window | | +-----------------------------+--------------------------+----------+ Table 17 No row defines an Action boundary: the protocol does not define what constitutes one Action — the actionId is asserted by the application originating the work (Section 6.1). No row exposes a monetary amount (Section 15.7). 9. Child Actions Where enabled, an Authorise MAY declare parentActionId, nesting the new Action under an open parent of the *same subscriber*, opened by *a credential of the same owning gateway* — a parentActionId referencing another gateway's Action is refused 403 before any charging evaluation (Section 15.3). Children share the parent's family ceiling: sibling headroom reflects every family settlement as it happens. Any parent ending cascades: open children are stopped (PARENT_STOPPED) before the parent concludes. *Cascade timing (normative):* children are stopped server-side at the parent's ending instant — the server serialises all of one subscriber's operations, so a concurrent parent ending and child report have a definite order. A stopped child's grant is no longer eligible for CONTINUE; the child gateway learns at its next exchange, whose response is STOP (PARENT_STOPPED) after settling the reported consumption. Consumption already in flight when the cascade lands Petkov & Panitch Expires 21 March 2027 [Page 46] Internet-Draft The Pulse Protocol September 2026 remains reportable and settleable under the late rules; the notification gap is bounded by the child's grant validity, exactly like any other silent period. Late child consumption continues to settle against the family ceiling during the late window. The following CLOSE example illustrates the cascade for any parent ending. It also shows that version 1 has no server push: the child gateway learns the stop at its next exchange. Parent gateway Charging Server Child gateway | | | |--- final Report --->| | | | serialize subscriber state | | | stop open child at parent's | | | ending instant: PARENT_STOPPED| | | conclude parent | |<-- SETTLED ---------| | | | (no server push) | | |<----- child's next Report ----| | | merge, settle reported units | | |--- STOP (PARENT_STOPPED) ---->| 10. Time Bands A banded Action Type prices differently by time of day/week, per the tenant's published schedule. Normative rules: * The band is resolved *once, at authorisation, on the server clock* — never from any client-asserted time — and disclosed as timeBandId in the response. * The entire Action rates in its opening band: *the band never switches mid-Action*, however long the Action runs or when its reports arrive. (Deliberate divergence from telecom voice charging; exposure is bounded by grant validity and maximum runtime.) * Published schedules always cover every instant (the platform validates full-week coverage before publication), so band resolution cannot fail. Petkov & Panitch Expires 21 March 2027 [Page 47] Internet-Draft The Pulse Protocol September 2026 11. Draining When the service is draining (operator-controlled, temporary): new Actions are refused DRAINING (retryable, with retryAfter); replays of live Actions are still answered; every *non-final* report on a live Action settles normally and answers STOP (DRAINING) — no re-grants are issued — while a final: true report settles as SETTLED per Section 7.5's precedence. Terminal semantics (Section 7.6) remain in force until no live state remains. 12. Shared Bounds and Fair Usage The bounds in Section 6 to Section 7 are per subscriber. Two further bounds range over populations, and both surface on the wire only through the vocabulary above — no new operations, no new fields beyond the refusal metadata of Section 6.4. 12.1. Sponsor caps A sponsoring party (typically a reseller funding an AI benefit) MAY declare an aggregate spend bound across a population of subscribers. It is enforced at authorisation and at each cycle like any other headroom check. Its refusals and stops are *attributed*: EXPOSURE_CAP always carries limitOwner, and a client MUST surface the distinction to the end user — from a single account's viewpoint the sponsor's cap and the user's own limit are otherwise indistinguishable, and telling a user their own money ran out when the sponsor's did is untrue. The cap's accounting basis is *exposure* — credits committed to open grants plus credits settled, per sponsor relation; an open grant is spend the sponsor is already standing behind, so a cap that counted settled spend alone could be overshot by in-flight work. The bound is declared *per period*: the accounting window (a calendar month, a rolling interval) is part of the sponsor's declaration, not a protocol constant, and is disclosed to the sponsor and its members out of band — the wire carries only the refusals and stops the cap produces. Exact period-boundary mechanics (the reset instant, the treatment of exposure spanning the boundary) are deployment-profile in version 1, pending the same trace validation as Section 12.2 to Section 12.4. Petkov & Panitch Expires 21 March 2027 [Page 48] Internet-Draft The Pulse Protocol September 2026 12.2. Capacity pools A tenant MAY declare provisioned serving capacity — a rate — that named Action Types draw on. Admission then answers two questions: may this be paid for, and can it be served. A grant is a rate authorisation: *its rate is grantedUnits over the interval from grantIssuedAt to expiresAt* — so capacity enforcement changes grant _sizing_, not the message set. When a pool cannot admit at or above the declared minimum rate, authorisation refuses NO_CAPACITY — always retryable with retryAfter — graded BOUNDED, because capacity keeps no ledger. Capacity refusals say something about the service's provisioned room, never about the customer's money; clients MUST NOT render them as balance problems. *Enforcement is server-side and window-aware:* when sizing each grant, the server accounts the Action's _consumed_ rate over the enforcement window (enforcementWindowSec > 0, disclosed in limits) — so reporting early and cycling fast confers no rate advantage (the window remembers, and smaller grants follow). The client has *no pacing obligation*; pacing to the grant rate is optional smoothness, not conformance. *Shared bounds are linearizable:* admissions against a capacity pool — and against a sponsor cap (Section 12.1) — are evaluated as if serially against the aggregate; two concurrent admissions can never both observe and consume the same remaining headroom. _(Provisional: the window's exact algorithm and reproducibility are completed by the capacity conformance traces — see Status.)_ 12.3. Degradation before refusal (fair usage) Under capacity pressure the server degrades before it refuses: a grant (opening or continuation) MAY be reduced — *always by lowering grantedUnits, never by shortening the validity window* (a shorter window with the same units would _raise_ the implied rate) — and never below the declared minimum rate. Such a response carries rateReduced: true and reduceCause: "CAPACITY"; the client can compute the allowed rate exactly from grantedUnits, grantIssuedAt, and expiresAt. Three guarantees travel with it: * *The price never changes with the rate.* A slowdown is never a surcharge. * *The cause is always capacity.* Commercial throttling has no code path in this protocol — a margin decision is a tariff term to be declared in the plan, never an operational throttle. Petkov & Panitch Expires 21 March 2027 [Page 49] Internet-Draft The Pulse Protocol September 2026 * *It is disclosed.* The reduction is flagged on the wire and recorded per party, so "what was reduced, when, and why" is answerable per customer, not only per pool. Degradation lands at exchange boundaries only — there is no push channel — which is one reason validity windows are short. Below the minimum rate the server answers honestly instead: STOP (NO_CAPACITY) at a report boundary, refusal at authorisation. 12.4. Report promptness Rate enforcement is defeated by stale reports: a gateway batching every thirty seconds defeats a per-second pool while passing every other check. Promptness — measured as the elapsed time from a unit of consumption occurring to dispatch of the report that first carries it, evaluated at the 95th percentile — is a tested dimension of an integration's profile (alongside authorise-before-serving and stop obedience): a bound the conformance suite measures, never a documentation claim. The bound is agreed per integration at onboarding; *the version-1 default profile bound is 30 s at p95* _(provisional — subject to ratification with the rate-enforcement window design; integrations should treat the mechanism as fixed and the number as negotiable)_. Threshold-triggered reports (Section 7.7) and the closing report are due immediately regardless of the profile bound. 13. Error Handling 13.1. HTTP status mapping +=============+===================================================+ | Status | Meaning | +=============+===================================================+ | 200 | Charging outcome — including refusals and stop | | | obligations; inspect decision/directive. | +-------------+---------------------------------------------------+ | 400 | Malformed or out-of-limit request; machine | | | errorCode included (Section 13.2). | +-------------+---------------------------------------------------+ | 401 | Missing/invalid credential. | +-------------+---------------------------------------------------+ | 403 | Credential valid but not authorised: tenant not | | | in the bound set, the Action belongs to another | | | gateway, or a parentActionId references another | | | gateway's Action (Section 9, Section 15.3). | +-------------+---------------------------------------------------+ | 404 | Unknown or mismatched identity: an actionId/ | | | grantId the server does not recognise (never | Petkov & Panitch Expires 21 March 2027 [Page 50] Internet-Draft The Pulse Protocol September 2026 | | opened, or past the late window, Section 7.6), a | | | grantId that does not belong to the body's | | | actionId, or tenantRef/accountRef that do not | | | match the Action's — traffic is unattributable. | +-------------+---------------------------------------------------+ | 405 / 415 | Wrong method / wrong content type (Section 4.1). | +-------------+---------------------------------------------------+ | 429 | Per-credential rate limit exceeded | | | (Section 15.5). MUST carry Retry-After. When | | | rate-limited, the client prioritises mandatory | | | reports (threshold crossings, STOP flushes, pre- | | | lapse) over new Authorises. | +-------------+---------------------------------------------------+ | 503 + | The server's own condition — transient contention | | Retry-After | or overload — on its own channel. *Overload is | | | never expressed as a refusal reason*: no decision | | | about the caller was reached, nothing is charged, | | | and a shed request is not counted as a refusal. | | | The client SHOULD retry after the indicated delay | | | with the identical request. | +-------------+---------------------------------------------------+ Table 18 Charging refusals are *not* HTTP errors: a REFUSE or STOP arrives as 200 — including DUPLICATE_ACTION (an identity conflict is a charging refusal, not an HTTP error). Clients MUST NOT treat 200 as permission without inspecting the body. *Error envelope:* every non-200 response carries a JSON body {errorCode, message, serverTime, maxSchemaVersion, retryable?, retryAfter?} — errorCode from Section 13.2 for the 400 family or a status-mirroring code otherwise (UNAUTHORIZED, FORBIDDEN, NOT_FOUND, METHOD_NOT_ALLOWED, UNSUPPORTED_MEDIA, RATE_LIMITED, UNAVAILABLE, INTERNAL); schemaVersion is echoed when it was parseable (Section 5). Error envelopes carry no Action-state fields (Section 2, Envelope). *Atomicity (normative):* request processing is all-or-nothing. Full validation precedes any state mutation, and *every non-200 response is state-neutral* — nothing merged, counted, or settled. Settlement and the stored exchange outcome persist atomically; a crash between processing and durable storage means the request was not processed, and the identical retry is safe. Petkov & Panitch Expires 21 March 2027 [Page 51] Internet-Draft The Pulse Protocol September 2026 13.2. errorCode registry (400 family, version 1) +====================+==================================+ | Code | Meaning | +====================+==================================+ | UNSUPPORTED_SCHEMA | Unknown schemaVersion, or fields | | | beyond the declared version. | +--------------------+----------------------------------+ | LIMIT_EXCEEDED | A Section 4.4 limit violated. | +--------------------+----------------------------------+ | MALFORMED_REPORT | Bad unit value, undeclared unit | | | key, duplicate callId entries, | | | duplicate JSON keys. | +--------------------+----------------------------------+ | MALFORMED_REQUEST | Any other syntactic/semantic | | | malformation. | +--------------------+----------------------------------+ Table 19 13.3. Client retry guidance Idempotency (Section 6.5, Section 7.2) makes retries safe: on timeout or 5xx, the client SHOULD retry the identical request with *bounded, jittered exponential backoff* (RECOMMENDED: initial 1 s, factor 2, full jitter, cap 30 s). *The serialisation unit of Section 3.4 is the logical Report*: identical retries are the same unit, and overlapping identical attempts (a retry racing its timed-out original) are permitted — replay makes them harmless. The admission freeze ends on a charging outcome (any 200); state-neutral errors (4xx/429/503) leave it in place while retries continue, until an outcome arrives or the grant expires. The client MUST NOT mint a fresh actionId to retry an Action whose authorisation outcome is unknown — that risks double authorisation; retry the original request instead, or abandon the actionId permanently. Retrying a Report remains useful up to the late window; retrying an Authorise past the point the work would still be served is pointless — abandon it; if the work is re- attempted later, that is a new Action. 13.4. Charging-plane unavailability — the client state machine When the server is slow or unreachable, the existing mechanisms compose into a complete, bounded posture; a conforming gateway follows it: 1. *Mid-Action:* per Section 3.4's single-writer rule, once the report is dispatched *no new Calls are admitted* until a response arrives — retrying included. Work already begun (the tail) Petkov & Panitch Expires 21 March 2027 [Page 52] Internet-Draft The Pulse Protocol September 2026 completes within the remaining pool of the last received grant; consumption from uncancellable streams is reported when connectivity returns (late, if need be — the late window exists for this). 2. *At pool exhaustion with no response:* stop admitting new Calls. Serving beyond the pool is non-conformant regardless of the outage. 3. *At expiresAt with no successful report:* the gateway MUST stop serving the Action — validity is the hard bound, and the server will settle at the reported meter via the lapse ending. Consumption reported late still settles while late-settlement capability holds (Section 7.6). Its exceptional degradation is the one bounded exception, and it is flagged unattributable, never silent. 4. *New Actions:* when Authorise is unreachable, a conforming gateway does not serve — *fail-closed is the only conformance posture this protocol defines.* _(Informative: a tenant may contractually accept unauthorized serving during outages; such traffic is outside this protocol — never authorised, never reported, never settled here — and is accounted between the parties as declared loss. Pulse neither forbids the business arrangement nor provides a wire path for it.)_ 5. *Recovery:* retry identical requests (Section 6.5/Section 7.3 replay makes every lost response recoverable), or report against the envelope's currentGrantId; reconcile via receipts (settlementId, acceptedCumulative, acceptedCalls). This post-grace, late-settleable example illustrates the end-to-end posture. The gateway's local hard bounds remain the received grant's remaining pool and expiresAt; new Authorises fail closed during the outage. Other recovery branches remain as specified above. Petkov & Panitch Expires 21 March 2027 [Page 53] Internet-Draft The Pulse Protocol September 2026 Gateway Charging Server | | | outage begins; a grant is live | | serve only within remaining pool | |--- Report R at reportAt --------X | unreachable | no new Calls; the tail finishes | |--- identical retries of R ------X | | at expiresAt: stop serving | | | | connectivity recovers at | | now >= expiresAt + 5 s | |--- identical retry of R ---------->| | | LAPSE and late | | handling |<-- STOP (LAPSED) + receipt --------| | | |--- final-meter flush ------------->| within 30 s of | (tail included) | STOP |<-- STOP (LAPSED) + receipt --------| The exposure of the outage posture is: *the remaining granted pool, plus the consumption of Calls already in flight when the outage began* — each such Call bounded by the call-level caps (Section 4.4 magnitudes; limits.maxRuntimeSec) and settling late. Uncancellable in-flight work is the one component the pool does not cap; the protocol bounds it per Call and prices it under the pinned law, never silently. 13.5. Operational expectations (informative) Authorise and Report sit on the serving hot path; the server is designed to answer from hot state and the RECOMMENDED client deadline is *2 s* per attempt. For latency-critical cheap actions, the flat price form (Section 3.3) is the minimal-overhead shape (one exchange, no lifecycle). Control-plane request rate scales with Actions × cycles; the server controls the cycle rate via reportAt/grantedUnits sizing, and HTTP/2 connection reuse is assumed. Pre-grants, bulk authorisation, and multi-Action batch reporting are deferred, named (Section 1.4). 14. Conformance Requirements Summary (client) 1. Send canonical, versioned, within-limits bodies (Section 4, Section 5). 2. Never serve lifecycle work without a live grant (a flat Action's work is authorised by its settled GRANT itself, Section 3.3); honour stop obligations promptly (Section 3.2). Petkov & Panitch Expires 21 March 2027 [Page 54] Internet-Draft The Pulse Protocol September 2026 3. Report cumulatively, never by delta; never shrink a cumulative (Section 7.2). 4. Report on reportAt crossing (MUST) and SHOULD report promptly at client-side endings with final: true (Section 7.7). 5. Retry with canonically identical requests; never reuse an actionId (Section 6.5, Section 13.3). 6. Tolerate unknown reason/code strings (Section 5). 7. Never knowingly exceed the granted pool; admit Calls against it using unitWeights (Section 3.4), and follow the Section 13.4 posture when the charging plane is unreachable. 8. Treat the server clock as authoritative for lapse and bands (Section 3.4, Section 10). 9. Report promptly — within the integration's tested promptness bound (Section 12.4); batched, stale reports defeat rate enforcement and downgrade the integration's profile. 10. Render refusals honestly: no more certainty than grade holds, capacity refusals never as balance problems, and attributed limits (limitOwner) never as the user's own (Section 6.4, Section 12). 11. Obey the envelope over any stored body: discard grants with a stale grantSeq, treat actionLive: false as a stop, and converge on currentGrantSeq (Section 2, Section 6.3, Section 7.3). 12. On STOP: admit nothing new, cancel what is cancellable, and flush the final meter — first chunk within 30 seconds, once per ending (Section 7.7). 13. One in-flight Report per Action, and no new Call admissions while one is outstanding (Section 3.4). 15. Security Considerations 15.1. Transport and credentials TLS 1.2+ is REQUIRED (1.3 RECOMMENDED; [RFC9325] configuration). API keys are secrets: gateways MUST store them in a secrets facility, MUST NOT place them in URLs, and MUST redact them from logs and traces; the server never echoes a key and audits by credential id only. Dual-active rotation rolls keys without downtime. Compromise of a key is bounded by revocation (effective within seconds), tenant Petkov & Panitch Expires 21 March 2027 [Page 55] Internet-Draft The Pulse Protocol September 2026 binding, gateway ownership, and the per-credential rate limit. *Revocation and outstanding grants:* a revoked credential's requests fail 401 from the revocation instant; Actions its gateway opened continue only to their grants' lapse, ending and settling at their reported meters — the residual exposure is the remaining validities. Because ownership is *gateway-level*, a replacement credential of the same gateway MAY report and close them; nothing strands. 15.2. Tenant binding Every request is authorised as: credential enabled → tenantRef within the credential's bound set (else 403) → subscriber belongs to that tenant (else NO_LAW). A credential can never operate on another tenant's subscribers. 15.3. Gateway ownership of Actions Every Action records the opening credential's gateway. Reports and Authorise-replays are accepted *only* from credentials of that gateway (else 403) — a sibling gateway of the same tenant cannot operate another gateway's Actions, and a child Action MUST be opened by the parent's owning gateway (Section 9; checked before any charging evaluation). Identifiers are correlators, not secrets; authority derives from credential + binding + ownership, so sequential or guessable ids confer no authority. *Acknowledged residual:* the 403-vs-404 distinction lets an authenticated gateway of the same tenant probe whether an identifier exists — existence only, never content or authority; accepted and documented. 15.4. Abuse limits Always enforced per subscriber: a concurrent-Actions cap and a calls- per-Action limit. Optionally (per deployment/plan): velocity limits, consumption-inflation wires, maximum runtime, and required call signatures. These stop Actions; only operator action bars a subscriber. 15.5. API-level protection A per-credential request rate limit (token bucket; 429 on excess) protects the platform independently of charging-level velocity. Every request is audit-recorded by credential id; the key itself never appears in records. Petkov & Panitch Expires 21 March 2027 [Page 56] Internet-Draft The Pulse Protocol September 2026 15.6. Clock authority and posture Band selection and lapse are computed exclusively from the server clock (Section 3.4), removing client-asserted-time gaming. The gateway MAY report its own clock (gatewayTime) and per-call start instants (startedAt, Section 7.2); both are recorded as evidence and never enforced against — a wrong clock is a fact about the gateway's estate, not a verdict about the customer whose call it carries. Persistent skew beyond tolerance is a trust signal on the integration's profile, never a refusal. 15.7. Trust model and privacy *The gateway is a trusted metering point.* Its reports are _attestations_ of consumption, not proofs: the protocol bounds what a dishonest or compromised gateway can cost (grants, ceilings, caps, tenant binding, gateway ownership, revocation, rate limits) but cannot verify token counts it did not observe. The residual — systematic under- or mis-reporting — is addressed outside the request path: integration grading by the conformance suite, and slow-clock observation of the executor. Stated plainly so no one mistakes the boundary: a tenant's spend guarantee holds against its subscribers; the _operator's_ protection against a dishonest metering point is detection and contract, not cryptography, in version 1. *Every version-1 decision is automated.* No human participates in the request path — for a grant, a refusal, or a stop. A refusal says so on the wire (automatedDecision, Section 6.4) and MAY carry a tenant- configured human-review channel (reviewContact), so a subscriber facing an adverse automated decision has a route to a person; the notice travels with the decision it describes rather than living only in documentation. *No money crosses outward.* Grants and receipts are denominated in work units only; a gateway never learns a balance, a price, or a currency. This is deliberate — a commercial and privacy boundary, not an omission. *Privacy.* accountRef SHOULD be a pseudonymous, tenant-scoped identifier (never a raw MSISDN, e-mail, or name); the server treats it as an opaque key. limitOwner is a presentation-safe display label declared by the sponsor with its relation — never a legal identity the tenant did not choose to show. gatewayTime, startedAt, and per- request audit records are retained under the operator's retention policy; the protocol itself mandates retention only for the late window. Refusal metadata deliberately reveals coarse state (which kind of limit bound); tenants choosing per-subscriber secrecy beyond that boundary configure presentation, not the wire. Petkov & Panitch Expires 21 March 2027 [Page 57] Internet-Draft The Pulse Protocol September 2026 16. Implementation Status This section records the implementation status of the protocol described in this document, as encouraged by [RFC7942]. It is meant to be removed before publication as an RFC. At the time of writing, one server-side implementation of this protocol exists, built into a commercial telecom online charging system, together with a conformance suite exercising the state machine of Section 3.5, the cumulative reporting rules of Section 7.2, and the lapse boundary of Section 7.5. No independent interoperating implementation exists. Readers should treat the capacity-enforcement semantics of Section 12.2 to Section 12.4 as provisional on that basis. 17. IANA Considerations This document establishes the registries below. For each registry, the placeholder registration policy mirrors Section 5: the registries are owned by this document, and new values are allocated only by a new schema version of this specification — a Standards-Action-style posture in the [RFC8126] taxonomy, not Specification Required. The cited sections, not this summary, define the initial values and their meanings. Petkov & Panitch Expires 21 March 2027 [Page 58] Internet-Draft The Pulse Protocol September 2026 +===========+=========================================+==========+ | Registry | Initial contents | Defining | | | | section | +===========+=========================================+==========+ | Pulse | GRANT, REFUSE | Section | | Decision | | 6.3, | | | | Section | | | | 6.4 | +-----------+-----------------------------------------+----------+ | Pulse | NO_HEADROOM, CEILING_PREEMPT, | Section | | Refusal | ACCOUNT_VELOCITY, BALANCE_VELOCITY, | 6.4 | | Reason | GROUP_VELOCITY (version 1.1), | | | | MAX_CONCURRENT_ACTIONS, PARENT_STOPPED, | | | | NO_LAW, DRAINING, BLOCKED_TARIFF, | | | | SERVICE_BARRED, DUPLICATE_ACTION, | | | | EXPOSURE_CAP, NO_CAPACITY, PREEMPTED, | | | | CLASS_BLOCKED (reserved in version 1) | | +-----------+-----------------------------------------+----------+ | Pulse | CEILING_REACHED, NO_HEADROOM, | Section | | Stop | CALL_LIMIT, MAX_RUNTIME, INFLATION, | 7.4 | | Reason | SIGNATURES_REQUIRED, PARENT_STOPPED, | | | | ADMIN_FORCED, DRAINING, NO_CAPACITY, | | | | EXPOSURE_CAP, LAPSED | | +-----------+-----------------------------------------+----------+ | Pulse | UNSUPPORTED_SCHEMA, LIMIT_EXCEEDED, | Section | | Error | MALFORMED_REPORT, MALFORMED_REQUEST, | 13.1, | | Code | UNAUTHORIZED, FORBIDDEN, NOT_FOUND, | Section | | | METHOD_NOT_ALLOWED, UNSUPPORTED_MEDIA, | 13.2 | | | RATE_LIMITED, UNAVAILABLE, INTERNAL | | +-----------+-----------------------------------------+----------+ | Pulse | CLOSE, STOP, LAPSE | Section | | Ending | | 2, | | | | Section | | | | 3.5 | +-----------+-----------------------------------------+----------+ | Pulse | CONTINUE, CURRENT, SETTLED, STOP | Section | | Directive | | 7.3 | +-----------+-----------------------------------------+----------+ | Pulse | PROVEN, ATTESTED, BOUNDED | Section | | Grade | | 6.4 | +-----------+-----------------------------------------+----------+ | Pulse | CAPACITY | Section | | Reduce | | 12.3 | | Cause | | | +-----------+-----------------------------------------+----------+ Table 20 Petkov & Panitch Expires 21 March 2027 [Page 59] Internet-Draft The Pulse Protocol September 2026 Formal IANA text, including final registry names and the formal [RFC8126] policy designations, is completed at publication. 18. References 18.1. Normative References [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . [RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002, . [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, DOI 10.17487/RFC5246, August 2008, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . [RFC8259] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017, . [RFC8446] Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018, . [RFC8610] Birkholz, H., Vigano, C., and C. Bormann, "Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures", RFC 8610, DOI 10.17487/RFC8610, June 2019, . [RFC8785] Rundgren, A., Jordan, B., and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, DOI 10.17487/RFC8785, June 2020, . Petkov & Panitch Expires 21 March 2027 [Page 60] Internet-Draft The Pulse Protocol September 2026 [RFC9110] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, . [RFC9325] Sheffer, Y., Saint-Andre, P., and T. Fossati, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)", BCP 195, RFC 9325, DOI 10.17487/RFC9325, November 2022, . 18.2. Informative References [RFC4006] Hakala, H., Mattila, L., Koskinen, J., Stura, M., and J. Loughney, "Diameter Credit-Control Application", RFC 4006, DOI 10.17487/RFC4006, August 2005, . [RFC7942] Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, DOI 10.17487/RFC7942, July 2016, . [RFC8126] Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, June 2017, . [RFC8506] Bertz, L., Ed., Dolson, D., Ed., and Y. Lifshitz, Ed., "Diameter Credit-Control Application", RFC 8506, DOI 10.17487/RFC8506, March 2019, . Appendix A. Examples (illustrative, not normative; abbreviated) Except for A.7, the 200 responses below omit, for brevity only: serverTime, actionLive, currentGrantSeq, currentGrantId, unattributable where applicable, grantIssuedAt, lawVersion, limits, and settlement receipts where a settling response requires one — all present in real responses under Section 2/Section 6.3/Section 7.3. A refusal of an unopened Action (A.5) and an error envelope (A.7) carry no Action-state fields; A.7 intentionally shows serverTime. maxSchemaVersion is shown throughout. Every field shown conforms to the Section 4.3 type rules, except A.7's deliberately malformed estimate — a JSON number where Section 4.3 requires a canonical decimal string, the defect that example exists to show. Petkov & Panitch Expires 21 March 2027 [Page 61] Internet-Draft The Pulse Protocol September 2026 A.1. Authorise → grant POST /ocs/ai/v1/authorizations { "schemaVersion": 1, "actionId": "app-7f3e:conv-2091:task-4", "tenantRef": "4102", "accountRef": "subscriber-889201", "actionType": "chat.frontier", "estimate": { "tokens_in": "12000", "tokens_out": "4000" } } 200 { "schemaVersion": 1, "maxSchemaVersion": 1, "decision": "GRANT", "grantId": "g-000117", "grantSeq": 1, "grantedUnits": "50000", "reportAt": "40000", "expiresAt": "2026-09-01T10:15:00.000Z", "unitWeights": { "tokens_in": "1", "tokens_out": "4" }, "timeBandId": "offpeak" } A.2. Report → continue Petkov & Panitch Expires 21 March 2027 [Page 62] Internet-Draft The Pulse Protocol September 2026 POST /ocs/ai/v1/grants/g-000117/reports { "schemaVersion": 1, "actionId": "app-7f3e:conv-2091:task-4", "tenantRef": "4102", "accountRef": "subscriber-889201", "final": false, "calls": [ { "callId": "c1", "units": { "tokens_in": "9100", "tokens_out": "7800" } } ] } 200 { "schemaVersion": 1, "maxSchemaVersion": 1, "directive": "CONTINUE", "grantId": "g-000118", "grantSeq": 2, "grantedUnits": "50000", "reportAt": "40000", "expiresAt": "2026-09-01T10:30:00.000Z", "unitWeights": { "tokens_in": "1", "tokens_out": "4" }, "timeBandId": "offpeak" } A.3. Final report → settled POST /ocs/ai/v1/grants/g-000118/reports { "schemaVersion": 1, "actionId": "app-7f3e:conv-2091:task-4", "tenantRef": "4102", "accountRef": "subscriber-889201", "final": true, "calls": [ { "callId": "c1", "units": { "tokens_in": "9100", "tokens_out": "9950" } } ] } 200 { "schemaVersion": 1, "maxSchemaVersion": 1, "directive": "SETTLED", "endedAt": "2026-09-01T10:19:42.114Z" } A.4. Stop obligation 200 { "schemaVersion": 1, "maxSchemaVersion": 1, "directive": "STOP", "stopReason": "CEILING_REACHED", "endedAt": "2026-09-01T10:12:03.551Z" } Petkov & Panitch Expires 21 March 2027 [Page 63] Internet-Draft The Pulse Protocol September 2026 A.5. Authorise → refuse metadata The two abbreviated outcomes below show the distinct conditional metadata rules of Section 6.4: retryAfter accompanies a retryable refusal, while the sponsor's party-scoped bound carries limitOwner. Both carry the automated-decision notice; the second additionally shows a tenant-configured human-review channel. This is a version- 1.1 exchange (schemaVersion: 2, Section 5) — the disclosure fields belong to schema version 2; the other examples show version-1.0 exchanges. They are alternatives for separate Authorise attempts, not two responses to one request. POST /ocs/ai/v1/authorizations { "schemaVersion": 2, "actionId": "app-7f3e:capacity-check", "tenantRef": "4102", "accountRef": "subscriber-889201", "actionType": "chat.frontier" } 200 (capacity refusal) { "schemaVersion": 2, "maxSchemaVersion": 2, "decision": "REFUSE", "refusalReason": "NO_CAPACITY", "retryable": true, "retryAfter": 5, "grade": "BOUNDED", "automatedDecision": true } 200 (sponsor-cap refusal) { "schemaVersion": 2, "maxSchemaVersion": 2, "decision": "REFUSE", "refusalReason": "EXPOSURE_CAP", "retryable": false, "grade": "PROVEN", "limitOwner": "Example AI Benefit Sponsor", "automatedDecision": true, "reviewContact": "support.example-tenant.com/spend-review" } A.6. Stale report → CURRENT This exchange happens between A.2 and A.3, while the Action is still LIVE — after A.3's close, the same stale body would instead draw the terminal directive (Section 3.5, ENDED rows). This non-final body differs from the retained report for g-000117, so it is not an identity replay. Both values are below the stored high-water after A.2; the max-merge adds zero, no ending condition holds, and the standing grant is re-presented (Section 3.5, Section 7.3). The map Petkov & Panitch Expires 21 March 2027 [Page 64] Internet-Draft The Pulse Protocol September 2026 rendering follows Section 7.3's weighted-difference description; Appendix B, schema note 5, records the conflicting domain wording and keeps this representation provisional. POST /ocs/ai/v1/grants/g-000117/reports { "schemaVersion": 1, "actionId": "app-7f3e:conv-2091:task-4", "tenantRef": "4102", "accountRef": "subscriber-889201", "final": false, "calls": [ { "callId": "c1", "units": { "tokens_in": "9000", "tokens_out": "7700" } } ] } 200 { "schemaVersion": 1, "maxSchemaVersion": 1, "directive": "CURRENT", "grantId": "g-000118", "grantSeq": 2, "grantedUnits": "50000", "reportAt": "40000", "expiresAt": "2026-09-01T10:30:00.000Z", "unitWeights": { "tokens_in": "1", "tokens_out": "4" }, "timeBandId": "offpeak", "baselineCumulative": { "tokens_in": "9100", "tokens_out": "7800" }, "meterCumulative": { "tokens_in": "9100", "tokens_out": "7800" } } A.7. Error envelope Here an Authorise estimate uses a JSON number instead of the canonical decimal string required by Section 4.3, so the request fails state-neutrally with the Section 13.1 envelope. Petkov & Panitch Expires 21 March 2027 [Page 65] Internet-Draft The Pulse Protocol September 2026 POST /ocs/ai/v1/authorizations { "schemaVersion": 1, "actionId": "app-7f3e:conv-2091:task-bad", "tenantRef": "4102", "accountRef": "subscriber-889201", "actionType": "chat.frontier", "estimate": { "tokens_in": 12000 } } 400 { "schemaVersion": 1, "errorCode": "MALFORMED_REQUEST", "message": "estimate values must be canonical decimal strings", "serverTime": "2026-09-01T10:20:11.006Z", "maxSchemaVersion": 1 } Appendix B. Message schemas (CDDL, provisional) *PROVISIONAL — HAND-WRITTEN:* These schemas are hand-written from the prose of this memo. They are to be regenerated from the reference implementation's validated model, as Status of This Memo already specifies for the field-presence matrix. On any conflict, the prose is authoritative. The schemas use CDDL [RFC8610] to describe the JSON bodies. They do not define an additional wire format. Settlement-receipt members are top-level response members; there is no nested receipt object. Regular expressions encode the principal lexical restrictions; comments retain the calendar and character-class details not usefully repeated in the message shapes. The schemas describe the current protocol version (1.1, schemaVersion 2); members and values whose comment says version 1.1 belong to schema version 2 (Section 5) — a schema-version-1 exchange omits those fields, while the new value may still arrive under Section 5's tolerance rule. ; ---------- Lexical and numeric types (section 4.3) ---------- schema-version = 1..9007199254740991 count = 0..9007199254740991 generation = 1..9007199254740991 positive-count = 1..9007199254740991 ; At most 128 characters from [A-Za-z0-9._:-]. identifier = tstr .regexp "[A-Za-z0-9._:-]{0,128}" Petkov & Panitch Expires 21 March 2027 [Page 66] Internet-Draft The Pulse Protocol September 2026 ; [a-z0-9_]{1,64}. unit-type = tstr .regexp "[a-z0-9_]{1,64}" ; The expression fixes the wire layout. Year range and calendar ; validity remain ; subject to section 4.3. timestamp = tstr .regexp "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z" ; At most 64 printable UTF-8 characters, excluding control and bidi ; controls. display-label = tstr ; Each decimal below is a canonical decimal JSON string with scale at ; most 6. request-decimal = tstr .regexp "1000000000|(0|[1-9][0-9]{0,8})(\\.[0-9]{0,5}[1-9])?" pooled-decimal = tstr .regexp "(0|[1-9][0-9]{0,16})(\\.[0-9]{0,5}[1-9])?" positive-pooled-decimal = tstr .regexp "0\\.[0-9]{0,5}[1-9]|[1-9][0-9]{0,16}(\\.[0-9]{0,5}[1-9])?" positive-weight = tstr .regexp "1000000|0\\.[0-9]{0,5}[1-9]|[1-9][0-9]{0,5}(\\.[0-9]{0,5}[1-9])?" canonical-decimal = tstr .regexp "(0|[1-9][0-9]*)(\\.[0-9]{0,5}[1-9])?" request-unit-map = { *16 unit-type => request-decimal } cumulative-unit-map = { *16 unit-type => request-decimal } weight-map = { *16 unit-type => positive-weight } ; The prose leaves this as a representation ambiguity (schema note ; 5), but ; both members of one response have the same representation. current-meter-fields = ( baselineCumulative: cumulative-unit-map, meterCumulative: cumulative-unit-map ) // ( baselineCumulative: pooled-decimal, meterCumulative: pooled-decimal ) ; ---------- Closed value sets (sections 5, 6.4, 7.4) ---------- ending = "CLOSE" / "STOP" / "LAPSE" directive = "CONTINUE" / "CURRENT" / "SETTLED" / "STOP" Petkov & Panitch Expires 21 March 2027 [Page 67] Internet-Draft The Pulse Protocol September 2026 grade = "PROVEN" / "ATTESTED" / "BOUNDED" reduce-cause = "CAPACITY" error-code = "UNSUPPORTED_SCHEMA" / "LIMIT_EXCEEDED" / "MALFORMED_REPORT" / "MALFORMED_REQUEST" / "UNAUTHORIZED" / "FORBIDDEN" / "NOT_FOUND" / "METHOD_NOT_ALLOWED" / "UNSUPPORTED_MEDIA" / "RATE_LIMITED" / "UNAVAILABLE" / "INTERNAL" stop-reason-without-owner = "CEILING_REACHED" / "NO_HEADROOM" / "CALL_LIMIT" / "MAX_RUNTIME" / "INFLATION" / "SIGNATURES_REQUIRED" / "PARENT_STOPPED" / "ADMIN_FORCED" / "DRAINING" / "NO_CAPACITY" / "LAPSED" stop-fields = ( stopReason: "EXPOSURE_CAP", limitOwner: display-label ) // ( stopReason: stop-reason-without-owner ) ; ---------- Shared response envelope (sections 2, 5, 6.3) ---------- versioned-success = ( schemaVersion: schema-version, maxSchemaVersion: schema-version, serverTime: timestamp ) Petkov & Panitch Expires 21 March 2027 [Page 68] Internet-Draft The Pulse Protocol September 2026 action-envelope = ( actionLive: bool, currentGrantSeq: generation, currentGrantId: identifier, unattributable: bool ) live-action-envelope = ( actionLive: true, currentGrantSeq: generation, currentGrantId: identifier, unattributable: bool ) ended-action-envelope = ( actionLive: false, currentGrantSeq: generation, currentGrantId: identifier, unattributable: bool ) ended-unattributable-envelope = ( actionLive: false, currentGrantSeq: generation, currentGrantId: identifier, unattributable: true ) ; serverTime and maxSchemaVersion occur on every response. The four ; Action ; fields occur on every 200 that identifies an opened Action. They ; are absent ; from error envelopes and refusals of unopened Actions. ; ---------- Shared lifecycle-grant fields (section 6.3) ---------- limits = { maxCallsPerAction: count, maxRuntimeSec: count, maxConcurrentActions: count, ? signaturesRequired: bool, ? capacity-limit-fields } capacity-limit-fields = ( minRate: canonical-decimal, enforcementWindowSec: positive-count ) Petkov & Panitch Expires 21 March 2027 [Page 69] Internet-Draft The Pulse Protocol September 2026 reduced-grant-fields = ( rateReduced: true, reduceCause: reduce-cause ) lifecycle-grant-fields = ( grantId: identifier, grantSeq: generation, grantIssuedAt: timestamp, grantedUnits: positive-pooled-decimal, reportAt: positive-pooled-decimal, expiresAt: timestamp, unitWeights: weight-map, ? quantumUnits: positive-pooled-decimal, ? timeBandId: identifier, lawVersion: count, limits: limits, ? reduced-grant-fields ) ; ---------- Settlement receipts (sections 3.3, 6.5, 7.3) ---------- accepted-call = { callId: identifier, units: request-unit-map } receipt-core = ( settlementId: identifier, acceptedCumulative: cumulative-unit-map, cyclePooledUnits: pooled-decimal, ? clamped: true ) ; Required on a settling Report response. acceptedCalls has exactly ; the Calls ; present in that Report request. fresh-report-receipt = ( receipt-core, acceptedCalls: [*256 accepted-call] ) late-report-receipt = ( receipt-core, acceptedCalls: [*256 accepted-call], adjustment: true ) Petkov & Panitch Expires 21 March 2027 [Page 70] Internet-Draft The Pulse Protocol September 2026 terminal-settlement-receipt = fresh-report-receipt // late-report-receipt ; Used for the "current receipt" on a zero-merge terminal response. terminal-current-receipt = ( receipt-core, ? acceptedCalls: [*256 accepted-call] ) ; acceptedCalls appears on Report responses only. authorise-settlement-receipt = ( receipt-core, ? adjustment: true ) ; The accepted baseline is the only member explicitly named for a ; live ; Authorise re-presentation; the remaining receipt members are ; provisional. authorise-baseline-receipt = ( acceptedCumulative: cumulative-unit-map, ? settlementId: identifier, ? cyclePooledUnits: pooled-decimal, ? clamped: true ) ; A flat opening has no units to accept. flat-opening-receipt = ( settlementId: identifier ) ; ---------- Authorise request (section 6.1) ---------- authorise-request = { schemaVersion: schema-version, actionId: identifier, tenantRef: identifier, accountRef: identifier, actionType: identifier, ? estimate: request-unit-map, ? scope: identifier, ? parentActionId: identifier, ? gatewayTime: timestamp } ; ---------- Authorise GRANT responses (sections 3.3, 6.3, 6.5) ; ---------- Petkov & Panitch Expires 21 March 2027 [Page 71] Internet-Draft The Pulse Protocol September 2026 authorise-grant-response = authorise-live-grant-response / authorise-flat-grant-response / authorise-terminal-grant-response authorise-live-grant-response = { versioned-success, live-action-envelope, decision: "GRANT", lifecycle-grant-fields, ? authorise-baseline-receipt } authorise-flat-grant-response = { versioned-success, ended-action-envelope, decision: "GRANT", grantId: identifier, grantSeq: generation, ? timeBandId: identifier, settled: true, endedAt: timestamp, ? ending: "CLOSE", flat-opening-receipt } last-grant-reference = ( grantId: identifier, grantSeq: generation ) authorise-terminal-grant-response = { versioned-success, ended-action-envelope, decision: "GRANT", ? last-grant-reference, ? settled: true, ending: ending, endedAt: timestamp, authorise-settlement-receipt } ; ---------- Authorise REFUSE response (section 6.4) ---------- unopened-refusal-fields = ( refusalReason: "NO_CAPACITY" / "PREEMPTED", retryable: true, Petkov & Panitch Expires 21 March 2027 [Page 72] Internet-Draft The Pulse Protocol September 2026 retryAfter: count, grade: "BOUNDED", ? limitOwner: display-label ) // ( refusalReason: "DRAINING", retryable: true, retryAfter: count, grade: "ATTESTED" ) // ( refusalReason: "ACCOUNT_VELOCITY" / "BALANCE_VELOCITY" / "GROUP_VELOCITY" ; v1.1 (schemaVersion 2) / "MAX_CONCURRENT_ACTIONS", retryable: true, retryAfter: count, grade: "PROVEN", ? limitOwner: display-label ) // ( refusalReason: "SERVICE_BARRED", retryable: false, ? retryAfter: count, grade: "ATTESTED" ) // ( refusalReason: "EXPOSURE_CAP", retryable: false, ? retryAfter: count, grade: "PROVEN", limitOwner: display-label ) // ( refusalReason: "NO_HEADROOM" / "CEILING_PREEMPT", retryable: false, ? retryAfter: count, grade: "PROVEN", ? limitOwner: display-label ) Petkov & Panitch Expires 21 March 2027 [Page 73] Internet-Draft The Pulse Protocol September 2026 // ( refusalReason: "PARENT_STOPPED" / "NO_LAW" / "BLOCKED_TARIFF", retryable: false, ? retryAfter: count, grade: "PROVEN" ) duplicate-refusal-fields = ( refusalReason: "DUPLICATE_ACTION", retryable: false, ? retryAfter: count, grade: "PROVEN" ) authorise-refuse-response = authorise-unopened-refuse-response / authorise-duplicate-refuse-response authorise-unopened-refuse-response = { versioned-success, decision: "REFUSE", automatedDecision: true, ; v1.1 (schemaVersion 2) ? reviewContact: display-label, ; v1.1 (schemaVersion 2) unopened-refusal-fields } authorise-duplicate-refuse-response = { versioned-success, decision: "REFUSE", automatedDecision: true, ; v1.1 (schemaVersion 2) ? reviewContact: display-label, ; v1.1 (schemaVersion 2) duplicate-refusal-fields, ? action-envelope } ; CLASS_BLOCKED is reserved and never emitted in version 1. ; ---------- Report request (sections 7.1, 7.2) ---------- report-call = { callId: identifier, units: request-unit-map, ? startedAt: timestamp, ; v1.1 (schemaVersion 2) ? signature: tstr } report-request = { Petkov & Panitch Expires 21 March 2027 [Page 74] Internet-Draft The Pulse Protocol September 2026 schemaVersion: schema-version, actionId: identifier, tenantRef: identifier, accountRef: identifier, calls: [*256 report-call], final: bool, ? gatewayTime: timestamp } ; The path grantId participates in request identity but is not a body ; member. ; ---------- Report responses (sections 3.5, 7.3, 7.6) ---------- report-response = report-continue-response / report-current-response / report-stop-response / report-settled-response ; actionLive can be false when a retained CONTINUE outcome is ; replayed after ; the Action has ended; the fresh envelope overrides the stored ; grant. report-continue-response = { versioned-success, action-envelope, directive: "CONTINUE", lifecycle-grant-fields, fresh-report-receipt } report-current-response = { versioned-success, live-action-envelope, directive: "CURRENT", lifecycle-grant-fields, current-meter-fields } report-stop-response = report-stop-settlement-response / report-stop-current-response / report-stop-unattributable-response report-stop-settlement-response = { versioned-success, ended-action-envelope, Petkov & Panitch Expires 21 March 2027 [Page 75] Internet-Draft The Pulse Protocol September 2026 directive: "STOP", stop-fields, endedAt: timestamp, terminal-settlement-receipt } report-stop-current-response = { versioned-success, ended-action-envelope, directive: "STOP", stop-fields, endedAt: timestamp, terminal-current-receipt } report-stop-unattributable-response = { versioned-success, ended-unattributable-envelope, directive: "STOP", stop-fields, endedAt: timestamp, ? terminal-current-receipt } report-settled-response = report-settled-settlement-response / report-settled-current-response / report-flat-settled-response / report-settled-unattributable-response report-settled-settlement-response = { versioned-success, ended-action-envelope, directive: "SETTLED", endedAt: timestamp, terminal-settlement-receipt } report-settled-current-response = { versioned-success, ended-action-envelope, directive: "SETTLED", endedAt: timestamp, terminal-current-receipt } report-flat-settled-response = { versioned-success, Petkov & Panitch Expires 21 March 2027 [Page 76] Internet-Draft The Pulse Protocol September 2026 ended-action-envelope, directive: "SETTLED", endedAt: timestamp, ? settlementId: identifier } report-settled-unattributable-response = { versioned-success, ended-unattributable-envelope, directive: "SETTLED", endedAt: timestamp, ? terminal-current-receipt } ; A retained terminal replay has the same stored directive body and ; receipt ; as the original response, but versioned-success and action-envelope ; are ; recomputed. A replayed CONTINUE can therefore carry actionLive: ; false. terminal-stored-outcome-replay = report-continue-response / report-stop-response / report-settled-response ; ---------- Error envelope (sections 5, 13.1, 13.2) ---------- error-envelope = { ? schemaVersion: count, errorCode: error-code, message: any, serverTime: timestamp, maxSchemaVersion: schema-version, ? retryable: bool, ? retryAfter: count } ; schemaVersion is present only when parseable. Error envelopes never ; carry ; actionLive, currentGrantSeq, currentGrantId, or unattributable. Petkov & Panitch Expires 21 March 2027 [Page 77] Internet-Draft The Pulse Protocol September 2026 B.1. Schema notes — ambiguities found 1. Section 6.5 requires a live Authorise replay to include the standing grant's "cycle-baseline receipt", but explicitly identifies only acceptedCumulative. Section 7.3 defines the remaining receipt fields for settling Report responses, while the first grant has no preceding settlement. The schema therefore makes the baseline receipt optional and makes its other members optional. 2. An Authorise terminal acknowledgement explicitly contains decision, ending, endedAt, and the final receipt, with settled "where applicable". It does not say whether grantId and grantSeq are repeated, or enumerate the applicability of settled. The schema makes the last-grant reference and settled optional. 3. The initial flat-GRANT state-machine row names settled and endedAt, while an ended-Action Authorise replay names ending. It is not explicit whether the initial flat response also carries ending: "CLOSE"; the schema makes it optional. 4. The shared-envelope definition requires Action-state fields on every 200 that identifies an opened Action and excludes them on refusals of unopened Actions. It is unclear whether REFUSE / DUPLICATE_ACTION counts as identifying the already-opened Action. The unopened REFUSE shape excludes the envelope; only the duplicate-Action branch optionally permits the complete envelope. 5. Sections 3.5 and 7.3 describe baselineCumulative and meterCumulative as meter states whose difference is weighted. Section 4.3 groups their "pooled values" with derived quantities in [0, 10^17). The examples provisionally use per-unit maps because the weighted-difference operation needs them; current- meter-fields permits either a map/map or pooled-scalar/pooled- scalar representation because the exact JSON shape and value domain require adjudication. 6. A zero-merge terminal response carries the "current receipt", but Section 7.3 defines acceptedCalls relative to the Calls present in the request that caused a settlement. It is unclear what acceptedCalls contains for a non-settling zero-merge request, so it is optional in terminal-current-receipt. 7. For recognised new consumption that can no longer be settled, Section 3.5 says there is no settlementId, while Section 7.6 says there is no _new_ settlementId and also refers to the Petkov & Panitch Expires 21 March 2027 [Page 78] Internet-Draft The Pulse Protocol September 2026 stored terminal outcome. It is unclear whether an old current receipt remains present; the unattributable terminal shapes therefore permit it optionally. 8. A Report to a flat Action receives SETTLED and never settles. The prose does not say whether the response re-presents the flat opening receipt's settlementId; the flat Report shape therefore makes it optional. 9. retryAfter is required when an Authorise refusal has retryable: true, but its presence is not expressly forbidden or defined when retryable: false. The false variants therefore model it as optional. The error envelope separately leaves retryable and retryAfter independently optional and does not couple them to an HTTP status. 10. The JSON type, length, and character domain of error-envelope message are not stated in Section 4.3 or Section 13.1. The provisional schema uses any; if a JSON string is intended, an authoritative field rule is needed before regeneration. 11. Section 7.3 does not state whether each acceptedCalls[].units map is complete over the declared unit types or contains only the keys relevant to that Call or request. The schema permits any within-limit unit map. 12. The exact trigger for clamped: true is not fully mechanical: Section 7.3 says "limited by the meter or a ceiling", although every settlement is bounded by the meter. The schema preserves the stated optional presence and does not infer a trigger. 13. minRate is a pooled-units-per-second decimal, but Section 4.3 does not unambiguously assign it the request, weight, or derived-pooled upper domain. The schema enforces only the canonical decimal representation pending regeneration. 14. The 256 KB request limit does not say whether KB is decimal or binary, so CDDL cannot express an exact byte .size bound without choosing a meaning. 15. Section 7.7 requires a Report that flushes the final meter after STOP, but does not state the value of that Report's required final field. The request schema accepts either Boolean, and the outage diagram does not choose one. 16. Section 6.4 requires limitOwner for a party-scoped bound but does not enumerate every refusal reason that can be party- scoped. Section 12.1 makes it unconditionally present for Petkov & Panitch Expires 21 March 2027 [Page 79] Internet-Draft The Pulse Protocol September 2026 EXPOSURE_CAP. The schema requires it there, permits it on the bound-like refusal shapes, and excludes it from conditions that the prose does not describe as bounds. Appendix C. Worked arithmetic (illustrative) This appendix follows one quantised lifecycle from Authorise through CLOSE. It illustrates the cumulative pipeline of Section 7.2; that section remains authoritative. All quantities are work units or pooled units — never money. C.1. Authorise and opening grant The Action Type declares two unit types with distinct weights and a pooled quantum of 10: +============+==========+ | Unit type | Weight | +============+==========+ | tokens_in | 0.333333 | +------------+----------+ | tokens_out | 2.5 | +------------+----------+ Table 21 The gateway opens the Action with both types in its estimate: POST /ocs/ai/v1/authorizations { "schemaVersion": 1, "actionId": "arith-action-1", "tenantRef": "4102", "accountRef": "subscriber-arith", "actionType": "chat.quantised", "estimate": { "tokens_in": "18", "tokens_out": "10.1" } } The full opening GRANT is: Petkov & Panitch Expires 21 March 2027 [Page 80] Internet-Draft The Pulse Protocol September 2026 200 { "schemaVersion": 1, "maxSchemaVersion": 1, "decision": "GRANT", "grantId": "g-arith-1", "grantSeq": 1, "grantIssuedAt": "2026-09-01T10:00:00.000Z", "grantedUnits": "20", "reportAt": "15", "expiresAt": "2026-09-01T10:05:00.000Z", "unitWeights": { "tokens_in": "0.333333", "tokens_out": "2.5" }, "quantumUnits": "10", "serverTime": "2026-09-01T10:00:00.000Z", "actionLive": true, "currentGrantSeq": 1, "currentGrantId": "g-arith-1", "unattributable": false, "lawVersion": 1, "limits": { "maxCallsPerAction": 10, "maxRuntimeSec": 600, "maxConcurrentActions": 4 } } The first cycle baseline is the zero meter (Section 7.3): M0 = {tokens_in: 0, tokens_out: 0}. Applying Section 7.2 gives pooled total P0 = 0 and block count B0 = ceil(P0 / 10) = ceil(0 / 10) = 0. For each accepted meter Mk, the calculations below use the Section 7.2 formulas: P_u(k) = HALF_UP_6(weight_u * cumulativeTotal_u(k)) P(k) = exact sum of P_u(k) over unit types D(k) = P(k) - P(cycle baseline) ; raw cycle pool consumption B(k) = ceil(P(k) / quantumUnits) DB(k) = B(k) - B(previous settlement) ; newly charged block delta D(k), not DB(k) * quantumUnits, is compared with reportAt and the grant pool (Section 7.2). cyclePooledUnits carries that raw pre- quantisation delta (Section 7.3). C.2. Cycle 1 — threshold crossing, settlement, and re-grant At 2026-09-01T10:01:00.000Z, the gateway reports one Call cumulatively against G1: Petkov & Panitch Expires 21 March 2027 [Page 81] Internet-Draft The Pulse Protocol September 2026 { "schemaVersion": 1, "actionId": "arith-action-1", "tenantRef": "4102", "accountRef": "subscriber-arith", "calls": [ { "callId": "c1", "units": { "tokens_in": "10", "tokens_out": "4.666669" } } ], "final": false } The cumulative products and exact sum are: P_in(1) = HALF_UP_6(0.333333 * 10) = HALF_UP_6(3.333330) = 3.33333 (section 7.2) P_out(1) = HALF_UP_6(2.5 * 4.666669) = HALF_UP_6(11.6666725) = 11.666673 (section 7.2) P(1) = 3.33333 + 11.666673 = 15.000003 (section 7.2) D(1) = P(1) - P0 = 15.000003 - 0 = 15.000003 (section 7.2) Thus D(1) = 15.000003 >= reportAt 15 and D(1) <= grantedUnits 20. The crossing runs a settling cycle and produces CONTINUE with G2. Quantised rating is cumulative: B(1) = ceil(15.000003 / 10) = 2 (section 7.2) DB(1) = B(1) - B0 = 2 - 0 = 2 newly charged blocks (section 7.2) Those two blocks represent 20 pooled units in the quantised rating. No such derived block field is added to the wire. The receipt members in the top-level response are exactly: { "settlementId": "s-arith-1", "acceptedCumulative": { "tokens_in": "10", "tokens_out": "4.666669" }, "acceptedCalls": [ { "callId": "c1", "units": { "tokens_in": "10", "tokens_out": "4.666669" } } ], "cyclePooledUnits": "15.000003" } Neither clamped nor adjustment is present. CONTINUE mints G2 with grantId: "g-arith-2", grantSeq: 2, grantIssuedAt: "2026-09-01T10:01:00.000Z", grantedUnits: "12", reportAt: "9", and expiresAt: "2026-09-01T10:06:00.000Z"; its pinned weights, quantum, law, and limits are unchanged. Its fresh envelope has actionLive: Petkov & Panitch Expires 21 March 2027 [Page 82] Internet-Draft The Pulse Protocol September 2026 true, currentGrantSeq: 2, currentGrantId: "g-arith-2", and unattributable: false. The cycle baseline moves to M1 = {tokens_in: 10, tokens_out: 4.666669}, whose pooled value is P(1) (Section 7.3). C.3. Stale, out-of-order report — zero merge and CURRENT At 2026-09-01T10:01:10.000Z, a stale worker sends a different non- final request against superseded G1 with c1 = {tokens_in: 9, tokens_out: 4.5}. It is not the retained Cycle 1 identity. Per Section 7.2, the max-merge is: max({10, 4.666669}, {9, 4.5}) = {10, 4.666669}; meter increase = 0 No ending condition holds. Under Section 3.5 LIVE row 6 and Section 7.3, the response is CURRENT. The full standing G2 grant is re-presented; the selected convergence fields are below. As in A.6, the per-unit map rendering is provisional under Appendix B, schema note 5. { "directive": "CURRENT", "grantId": "g-arith-2", "grantSeq": 2, "baselineCumulative": { "tokens_in": "10", "tokens_out": "4.666669" }, "meterCumulative": { "tokens_in": "10", "tokens_out": "4.666669" }, "actionLive": true, "currentGrantSeq": 2, "currentGrantId": "g-arith-2", "unattributable": false } There is no settlement, receipt, charge, or mint; the current generation remains 2. C.4. Cycle 2 — the baseline moves again At 2026-09-01T10:02:00.000Z, the gateway reports against G2 with the cumulative meter M2 = {tokens_in: 15, tokens_out: 7.8}: P_in(2) = HALF_UP_6(0.333333 * 15) = 4.999995 (section 7.2) P_out(2) = HALF_UP_6(2.5 * 7.8) = 19.5 (section 7.2) P(2) = 4.999995 + 19.5 = 24.499995 (section 7.2) D(2) = P(2) - P(1) = 24.499995 - 15.000003 = 9.499992 (section 7.2) Petkov & Panitch Expires 21 March 2027 [Page 83] Internet-Draft The Pulse Protocol September 2026 D(2) = 9.499992 >= reportAt 9 and D(2) <= grantedUnits 12, so this is another settlement and re-grant. The cumulative block calculation is: B(2) = ceil(24.499995 / 10) = 3 (section 7.2) DB(2) = B(2) - B(1) = 3 - 2 = 1 newly charged block (section 7.2) The exact top-level receipt members are: { "settlementId": "s-arith-2", "acceptedCumulative": { "tokens_in": "15", "tokens_out": "7.8" }, "acceptedCalls": [ { "callId": "c1", "units": { "tokens_in": "15", "tokens_out": "7.8" } } ], "cyclePooledUnits": "9.499992" } Again, clamped and adjustment are absent. CONTINUE mints G3 with grantId: "g-arith-3", grantSeq: 3, grantIssuedAt: "2026-09-01T10:02:00.000Z", grantedUnits: "10", reportAt: "8", and expiresAt: "2026-09-01T10:07:00.000Z". Its envelope carries current generation 3 and current id g-arith-3. The new baseline is M2 = {tokens_in: 15, tokens_out: 7.8} with pooled value P(2) (Section 7.3). C.5. Final report — CLOSE and terminal settlement At 2026-09-01T10:02:30.000Z, the gateway sends final: true against G3 with cumulative meter M3 = {tokens_in: 18, tokens_out: 10.1}. The close is allowed before reportAt; it settles the full remaining meter delta (Section 3.5, Section 7.3): P_in(3) = HALF_UP_6(0.333333 * 18) = 5.999994 (section 7.2) P_out(3) = HALF_UP_6(2.5 * 10.1) = 25.25 (section 7.2) P(3) = 5.999994 + 25.25 = 31.249994 (section 7.2) D(3) = P(3) - P(2) = 31.249994 - 24.499995 = 6.749999 (section 7.2) B(3) = ceil(31.249994 / 10) = 4 (section 7.2) DB(3) = B(3) - B(2) = 4 - 3 = 1 newly charged block (section 7.2) D(3) = 6.749999 <= grantedUnits 10. The exact terminal receipt members are: Petkov & Panitch Expires 21 March 2027 [Page 84] Internet-Draft The Pulse Protocol September 2026 { "settlementId": "s-arith-3", "acceptedCumulative": { "tokens_in": "18", "tokens_out": "10.1" }, "acceptedCalls": [ { "callId": "c1", "units": { "tokens_in": "18", "tokens_out": "10.1" } } ], "cyclePooledUnits": "6.749999" } Neither optional flag is present. The response directive is SETTLED; the Action's ending is CLOSE and endedAt is 2026-09-01T10:02:30.000Z. The closing outcome mints generation 4 without minting a serving grant. The terminal envelope is therefore actionLive: false, currentGrantSeq: 4, currentGrantId: "g-arith-3", and unattributable: false (Section 3.5). As a final check, the raw receipt deltas sum without cadence loss: 15.000003 + 9.499992 + 6.749999 = 31.249994 = P(3) - P0 The charged block deltas also telescope: 2 + 1 + 1 = 4 = ceil(31.249994 / 10) The stale report contributes zero to both checks. This is the cadence-independence property of the cumulative pipeline in Section 7.2. Appendix D. Vocabulary mapping (informative) The implementing charging stack and its surrounding documents use an established telecom vocabulary; Pulse deliberately uses short, wire- suited, vendor-neutral names. The table translates between the two. The wire vocabulary is the protocol's; the left column is one implementer's, recorded here so that a reader of either vocabulary can read the other. Each D.1 definition is the opening of the framework's own definition box in its current edition, pasted rather than paraphrased; the objects are identical in v3.1, the edition this document cites, *except row 17* (Relation: v3.1's box reads "EXPOSED or EXECUTES"; HOLDS and RELIES arrived at v4.0). "_No wire concept_" is a deliberate entry, not a gap: the concept lives entirely server-side, and the gateway sees only its consequences. Petkov & Panitch Expires 21 March 2027 [Page 85] Internet-Draft The Pulse Protocol September 2026 *Framework terms not listed here* — every Event the engine writes, the Class, the Draw-down Order, the Night, the Bill, the Commercial Group, the Governance Floor, the Platform, the record kinds — are server-side objects with no wire counterpart, and their absence is deliberate. Their one-line dispositions, together with every term this document and its implementation use, are carried in the *Pulse vocabulary register*: a companion document owned with this specification, seeded from the framework's register of 6 September 2026 and checked against every draft by script, so that silence here is declared once rather than mistaken for support (Section 1.4). D.1. The framework's terms +====+===========================+==================================+ | # | Framework term — the | This specification | | | opening of its | | | | definition box | | +====+===========================+==================================+ | 1 | *Action* — _One | *Action* (shared term) | | | customer-visible unit | | | | of work with one price | | | | question: a question | | | | answered, a picture | | | | generated, a research | | | | task completed._ | | +----+---------------------------+----------------------------------+ | 2 | *Balance* — _Money | _No wire concept_ — the server | | | with rules: an amount, | holds it; the gateway sees | | | a priority in the | refusals and stops. scope names | | | draw-down order, | a Balance's Scope (Section 6.1) | | | optionally a Scope and | | | | an Expiry, and a | | | | declared behaviour at | | | | its limit._ | | +----+---------------------------+----------------------------------+ | 3 | *Ceiling* — _The most | *Ceiling / headroom* (shared | | | one Action may charge | term, Section 2); it reaches the | | | — "up to" as an | wire only as CEILING_REACHED | | | enforced number._ | (stop) and CEILING_PREEMPT | | | | (refusal). limits is a different | | | | object (the disclosed protective | | | | bounds, Section 6.3), and | | | | limitOwner belongs to the | | | | sponsor cap (Section 12.1) | +----+---------------------------+----------------------------------+ | 4 | *Countable* — _A | _No wire concept_ — the meter's | | | number computed from | unit is the framework's _counted | | | records that answers | unit_: units and the unit types | Petkov & Panitch Expires 21 March 2027 [Page 86] Internet-Draft The Pulse Protocol September 2026 | | one operational | (Section 7.2) | | | question, on a stated | | | | cadence and at a | | | | stated grain._ | | +----+---------------------------+----------------------------------+ | 5 | *Cycle* — _One Quota's | *Cycle* (shared term); the cycle | | | span: grant, consume, | Report rides it (Section 7.3) | | | settle. A reserved | | | | Action is a chain of | | | | Cycles._ | | +----+---------------------------+----------------------------------+ | 6 | *Expiry* — _A date | _No wire concept._ Through | | | after which a | draft-03 this document's ending | | | Balance's unreserved | for a lapsed grant was named | | | credits are written | EXPIRY and the two were false | | | off. Reserved credits | friends; from draft-04 that | | | are spared until their | ending is LAPSE (D.2) | | | Actions settle._ | | +----+---------------------------+----------------------------------+ | 7 | *Facility* — _One | _No wire concept_ — tenantRef | | | regulatory regime, as | identifies the Tenant, which a | | | fine-grained as | Facility contains (D.2) | | | reality requires — | | | | Belgium; Kenya; a | | | | single US state where | | | | its rules differ._ | | +----+---------------------------+----------------------------------+ | 8 | *Grant* — no | decision: GRANT (Section 6.3) — | | | definition box: the | the act. The object the | | | second step of the | framework calls a Quota is this | | | loop _Authorise · | document's *Grant* (row 16) | | | Grant · Supervise · | | | | Settle_, the act of | | | | issuing a Quota | | +----+---------------------------+----------------------------------+ | 9 | *Metering Point* — | The *Gateway* (Section 2) — | | | _The component that | Section 15.7's attesting party | | | executes Inference | | | | Calls, counts | | | | consumption and obeys | | | | the Quota. In the AI | | | | ecosystem the role is | | | | played by the | | | | gateway._ | | +----+---------------------------+----------------------------------+ | 10 | *Node* — no definition | tenantRef, and accountRef for | | | box; the reference | the position below — *not scope* | | | engine's class Node: | | Petkov & Panitch Expires 21 March 2027 [Page 87] Internet-Draft The Pulse Protocol September 2026 | | _one path from the | | | | Platform through the | | | | Facility chain to a | | | | Tenant_ | | +----+---------------------------+----------------------------------+ | 11 | *Packet* | Partial: the per-settlement | | | (Substantiation | receipt (Section 7.3) is the | | | Packet) — _Everything | wire-visible record; the Packet | | | needed to defend one | itself is off-wire | | | Charge to a regulator | | | | or a court, in five | | | | limbs._ | | +----+---------------------------+----------------------------------+ | 12 | *Party* — _A legal or | The roles a party takes on this | | | natural person with | wire: sponsor (limitOwner), | | | standing in at least | subscriber (accountRef), gateway | | | one relation. The | (the credential), tenant | | | Framework records that | (Section 2); limitOwner names | | | a party holds a | the exposed one (Section 12.1) | | | relation, or changed; | | | | it never verifies who | | | | they are._ | | +----+---------------------------+----------------------------------+ | 13 | *Period* — _The Bill's | The sponsor's declared | | | integration window, | accounting window (Section 12.1) | | | opened and closed on | — narrower; the Bill's Period | | | the Facility's | has no wire term | | | calendar._ | | +----+---------------------------+----------------------------------+ | 14 | *Price Book* — _A | No wire object — lawVersion is | | | Tenant's complete set | "the pinned plan/price-book | | | of Action Types with | version" (Section 6.3); the book | | | their price forms and | itself is the tenant's published | | | elements, held as a | catalogue, server-side | | | versioned object with | | | | an identity._ | | +----+---------------------------+----------------------------------+ | 15 | *Profile* (Metering | *Integration profile* | | | Point Profile) — _The | (Section 2, Section 12.4) — a | | | record of what an | related object under a different | | | integration actually | name; *not this document's | | | does, per route … | _deployment profile_* — the | | | Established by running | false friend below | | | the conformance suite | | | | against it, never by | | | | reading its | | | | documentation._ | | +----+---------------------------+----------------------------------+ Petkov & Panitch Expires 21 March 2027 [Page 88] Internet-Draft The Pulse Protocol September 2026 | 16 | *Quota* — _The | *Grant* (grantedUnits, reportAt, | | | Reservation's outside | expiresAt) — the RFC 4006 | | | face: the same amount | granted-quota concept | | | expressed to the | (Section 1.2) | | | Metering Point in work | | | | units, carrying a | | | | report threshold and a | | | | Validity._ | | +----+---------------------------+----------------------------------+ | 17 | *Relation* — _A dated, | _No wire concept_ — the wire | | | typed, scoped link | sees the sponsor relation only | | | between a Party and | through limitOwner; the exposure | | | work: EXPOSED or | relation's aggregate is | | | EXECUTES._ (v3.1's | EXPOSURE_CAP's basis | | | box; the current | (Section 12.1) | | | edition adds _the | | | | record relation, HOLDS | | | | and RELIES_) | | +----+---------------------------+----------------------------------+ | 18 | *Reservation* — | An open grant (pool exposure) — | | | _Credits set aside on | "spend the sponsor is already | | | a Balance for one | standing behind" (Section 12.1) | | | Action, under the | | | | Solvency Rule. The | | | | promise held against | | | | money._ | | +----+---------------------------+----------------------------------+ | 19 | *Action Type* — _The | *Action Type* (actionType; | | | Price Book row an | Section 2, Section 6.1) | | | Action instantiates: | | | | its price form, its | | | | elements, its counted | | | | unit, its thresholds._ | | +----+---------------------------+----------------------------------+ | 20 | *Effective Law* — _The | The law pinned at open | | | single package of | (Section 2, Section 6.2); | | | properties an Action | lawVersion identifies its | | | receives at open: the | version | | | path from Platform | | | | through its Facility | | | | chain to its Tenant, | | | | resolved top-down._ | | +----+---------------------------+----------------------------------+ | 21 | *Tenant* — _Whose | *Tenant* (tenantRef; Section 2, | | | money and commercial | Section 6.1) | | | law. A Tenant owns | | | | its Price Book, its | | | | Balances and one | | Petkov & Panitch Expires 21 March 2027 [Page 89] Internet-Draft The Pulse Protocol September 2026 | | settlement | | | | relationship, and | | | | attaches to exactly | | | | one Facility._ | | +----+---------------------------+----------------------------------+ | 22 | *Weights* — _Per-type | unitWeights (Section 6.3) — the | | | conversion from | complete map, pinned with the | | | counted units to | law | | | billing units … Part | | | | of the price, pinned | | | | at open._ | | +----+---------------------------+----------------------------------+ | 23 | *stop_reason* (the | directive: STOP with stopReason | | | Section 9 field) — | (Section 7.3, Section 7.4) — the | | | _Why the Action must | stop obligation | | | end now: the Trip- | | | | wire, the cascade …_ | | +----+---------------------------+----------------------------------+ Table 22 *The sponsor-cap concept, by its framework name:* what EXPOSURE_CAP enforces is the framework's *Solvency Rule at a second scope* — _"a bound on exposure ranges over a relation, not over any one Balance."_ Where the engine enforces open grants ≤ the Balance, it also enforces commitments across the relation ≤ the exposure bound; Section 12.1 states the same basis (committed + settled, per relation) explicitly. *Two cross-vocabulary notes:* the framework's fourth velocity level, RELATION_VELOCITY (_Actions started and credits charged per minute, counted across a declared relation_), is this document's *GROUP_VELOCITY* — the same object, counted as the framework defines it, in both Actions started and credits charged. And the framework closes its refusal vocabulary (fourteen reasons) but enumerates no closed set of stop reasons — its reference engine's stop codes are the nearest list; this document closes both registries per version (Section 5, Section 7.4, Section 13.2) — a protocol decision, not a divergence. D.2. Additional implementation and telecom terms +============================+======================================+ | Implementation / telecom | This specification | | term | | +============================+======================================+ | Facility / region (tenancy | _No wire concept_ — subscriber- | | detail below the operator) | level detail below the tenant | | | boundary | Petkov & Panitch Expires 21 March 2027 [Page 90] Internet-Draft The Pulse Protocol September 2026 +----------------------------+--------------------------------------+ | Subscriber (MSISDN) | Account (accountRef — | | | pseudonymous on the wire, | | | Section 15.7) | +----------------------------+--------------------------------------+ | Charging session | Action | +----------------------------+--------------------------------------+ | Decision-tree | Action Type (Actions arrive | | classification | _typed_; classification collapses | | | into a catalogue lookup) | +----------------------------+--------------------------------------+ | Interim usage report | Cycle Report (final: false) | | (opIntermediate) | | +----------------------------+--------------------------------------+ | Final usage report | Closing Report (final: true, the | | (opFinal) | CLOSE ending) | +----------------------------+--------------------------------------+ | CDR (the concluded-session | The concluded Action record | | record) | (emitted at ending; not a wire | | | object) | +----------------------------+--------------------------------------+ | CODR (per-settlement | The cycle settlement and its | | record) | receipt | +----------------------------+--------------------------------------+ | Time band | Time band (shared term — | | | Section 10 adopted it from the | | | implementation) | +----------------------------+--------------------------------------+ Table 23 *False friends — same word, different meaning:* * *Profile.* Three meanings collide. In the charging stack, the _subscription profile_ is the subscriber's stored charging state. In the framework, a _Profile_ is the Metering Point's tested integration record — a test result, not stored state. In this specification, _profile_ appears only as _deployment profile_ — a named set of deployment choices (Section 12, Section 13.5). None of the three is any of the others. * *Expiry (resolved at draft-04).* In the framework, _Expiry_ is _a date after which a Balance's unreserved credits are written off_ — money is destroyed, and reserved credits are spared until their Actions settle; the implementation's counter expiry is the same event. Through draft-03 this document named the ending of a lapsed grant EXPIRY, and the two were false friends. From draft-04 that ending is *LAPSE* and its stop reason *LAPSED* (the Petkov & Panitch Expires 21 March 2027 [Page 91] Internet-Draft The Pulse Protocol September 2026 expiresAt field keeps its name): a lapse settles *at the reported meter* (Section 7.5) and writes nothing off; an Expiry is a balance event with no wire term. The word _expiry_ is no longer used for the lapse anywhere in this document. * *Envelope.* Here, the Envelope is the always-fresh part of a response (Section 2). In the framework, an _envelope_ is a money partition — the authorisation floor. Neither is the other. * *Minimum.* The framework's _Minimum_ is a price form: a per-Action floor on the Charge. Its counterpart here is the _contractual minimum_ reserved at open (Section 6.2) and never applied a second time (Section 7.6) — no wire field. limits (Section 6.3) are the disclosed protective bounds, and limits.minRate (Section 12.2) is a capacity rate: three objects, one word apart. * *Facility.* In the framework, a Facility is a regulatory regime that _contains_ Tenants. In the implementation, a _facility_ is a node of the operator's tenancy tree _below_ the tenant (the root facility is the tenant). Neither reaches the wire; tenantRef is the Tenant. *One field, two uses — signature.* On this wire, signature is a reserved, opaque per-call attestation (Section 7.2), enforced only where a bilateral profile is agreed (SIGNATURES_REQUIRED, Section 7.4). The framework's reference engine uses a per-call signature as a fingerprint for repeat detection (its Inflation wire), and the implementation does the same server-side with the value it receives. Same field, two purposes — recorded here so the wire contract and the server-side use are never conflated. Appendix E. Change history (informative) This is the first Internet-Draft of this specification. The protocol was developed privately through several revisions during 2026, under an earlier working name, and was reviewed against an independent implementer's assessment before this submission. Later versions record their changes in this appendix. Authors' Addresses Martin Petkov Effortel Technologies SA Email: martin.petkov@effortel.com Arkadi Panitch Effortel Technologies SA Petkov & Panitch Expires 21 March 2027 [Page 92] Internet-Draft The Pulse Protocol September 2026 Email: arkadi.panitch@effortel.com Petkov & Panitch Expires 21 March 2027 [Page 93]