Service Workers

W3C Editor's Draft

This version
Latest editor's draft
Previous version
Revision history
Discuss on (Web Applications Working Group)
File bugs ('s Bugzilla)
Alex Russell, Google, <>
Jungkee Song, Samsung Electronics, <>


This specification describes a method that enables applications to take advantage of persistent background processing, including hooks to enable bootstrapping of web applications while offline.

The core of this system is an event-driven Web Worker, which responds to events dispatched from documents and other sources. A system for managing installation, versions, and upgrades is provided.

The Service Worker is a generic entry point for event-driven background processing in the Web Platform that is extensible by other specifications.

Status of This Document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at

This document was published by the Web Applications Working Group as an Editor's Draft. If you wish to make comments regarding this document, please send them to (subscribe, archives). All feedback is welcome.

Publication as an Editor's Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

Table of Contents

  1. Introduction
    1. About this Document
    2. Dependencies
    3. Motivations
    4. Concepts
    5. Example: Offline Web Applications
  2. Service Worker Lifecycle
    1. Registration
    2. Event Handling
    3. Installation
      1. Worker Script Caching
    4. Activation
    5. Navigation Matching
    6. Fetch Handling
    7. Lifetime Extension
    8. Upgrade
  3. Document Context
    1. ServiceWorker
    2. navigator.serviceWorker
      1. active
      2. getAll()
      3. register()
      4. unregister()
      5. oninstall
      6. oninstallend
      7. onactivate
      8. onactivateend
      9. onreloadpage
      10. onerror
  4. Execution Context
    1. ServiceWorkerGlobalScope
      1. caches
      2. clients
      3. scope
      4. fetch(request)
      5. update()
      6. unregister()
      7. onmessage
    2. Request Objects
    3. Response Objects
      1. AbstractResponse
      2. Response
      3. OpaqueResponse
      4. CORSResponse
    4. fetch()
    5. Caches
      1. Cache Lifetime Semantics
      2. Cache
      3. AsyncMap
      4. CacheList
    6. Client
      1. ServiceWorkerClients
    7. Events
      1. InstallPhaseEvent
      2. oninstall
        1. InstallEvent
      3. onactivate
        1. ActivateEvent
      4. onfetch
        1. FetchEvent
  5. Security Considerations
    1. Origin Relativity
    2. Cross-Origin Resources & CORS
  6. Storage Considerations
  7. Extensibility
  8. Appendix A: Algorithms
  9. Acknowledgements


About this Document

All diagrams, examples, notes, are non-normative, as well as sections explicitly marked as non-normative. Everything else in this specification is normative.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this document are to be interpreted as described in RFC2119. For readability, these words do not appear in all uppercase letters in this specification.

Any point, at which a conforming UA must make decisions about the state or reaction to the state of the conceptual model, is captured as algorithm. The algorithms are defined in terms of processing equivalence. The processing equivalence is a constraint imposed on the algorithm implementors, requiring the output of the both UA-implemented and the specified algorithm to be exactly the same for all inputs.


This document relies on the following specifications:


Web Applications traditionally assume that the network is reachable. This assumption pervades the platform. HTML documents are loaded over HTTP and traditionally fetch all of their sub-resources via subsequent HTTP requests. This places web content at a disadvantage versus other technology stacks.

The Service Worker is designed first to redress this balance by providing a Web Worker context, which can be started by a runtime when navigations are about to occur. This event-driven worker is registered against an origin and a path (or pattern), meaning it can be consulted when navigations occur to that location. Events that correspond to network requests are dispatched to the worker and the responses generated by the worker may over-ride default network stack behavior. This puts the Service Worker, conceptually, between the network and a document renderer, allowing the Service Worker to provide content for documents, even while offline.

Web developers familiar with previous attempts to solve the offline problem have reported a deficit of flexibility in those solutions. As a result, the Service Worker is highly procedural, providing a maximum of flexibility at the price of additional complexity for developers. Part of this complexity arises from the need to keep Service Workers responsive in the face of a single-threaded execution model. As a result, APIs exposed by Service Workers are almost entirely asynchronous, a pattern familiar in other JavaScript contexts but accentuated here by the need to avoid blocking document and resource loading.

Developers using the HTML5 Application Cache have also reported that several attributes of the design contribute to unrecoverable errors. A key design principle of the Service Worker is that errors should always be recoverable. Many details of the update process of Service Workers are designed to avoid these hazards.

Service Workers are started and kept alive by their relationship to events, not documents. This design borrows heavily from developer and vendor experience with Shared Workers and Chrome Background Pages. A key lesson from these systems is the necessity to time-limit the execution of background processing contexts, both to conserve resources and to ensure that background context loss and restart is top-of-mind for developers. As a result, Service Workers bear more than a passing resemblance to Chrome Event Pages, the successor to Background Pages. Service Workers may be started by user agents without an attached document and may be killed by the user agent at nearly any time. Conceptually, Service Workers can be thought of as Shared Workers that can start, process events, and die without ever handling messages from documents. Developers are advised to keep in mind that Service Workers may be started and killed many times a second.

Service Workers are generic, event-driven, time-limited script contexts that run at an origin. These properties make them natural endpoints for a range of runtime services that may outlive the context of a particular document, e.g. handling push notifications, background data synchronization, responding to resource requests from other origins, or receiving centralized updates to expensive-to-calculate data (e.g., geolocation or gyroscope).


A Service Worker is a type of Web Worker. Unlike other types of Web Worker, the lifetime of a Service Worker is tied to the execution lifetime of events, not references held by documents to the worker object. In practice, this means that Service Workers may begin, end, and restart execution many times over the life of documents which they logically control.

Service Workers are installed by user agents after being registered by authors from the context of a document. Service Workers execute in the registering document's origin.

Registration maps a Service Worker script to url space, a tuple of origin and path expression. User agents may enable many registrations at a single origin so long as the path expression of the registration differs. Registration of an identical origin/path expression when one already exists in the user agent causes the existing registration to be replaced.

A path expression consists of a relative url which may, optionally, terminate with the charachter "*". Ending a path expression with "*" enables longest-prefix wildcard matching.

A document is "controlled" if an active Service Worker matches the doucment's URL upon navigation. Multiple documents may be concurrently controlled by a single Service Worker instance. That is, Service Workers have a one-to-many relationship with controlled documents.

The Lifecycle events of Service Workers are oninstall and onactivate. Functional events are DOM Events that are dispatched in the Service Worker global context which are not lifecycle events of the Service Worker.

Registered Service Workers do not immediately begin to receive functional events for documents. Registration is the first step in installation, which proceeds through several phases:

  1. Fetch:
    The script URL provided by the author (via a call to navigator.serviceWorker.register([script URL], [registration option]) from a document) is fetched without heuristic caching. If the return status code of the fetch is not 2xx, installation aborts.
  2. Startup:
    If fetching the worker script is successful, it is executed in a ServiceWorkerGlobalScope. These scripts may call importScripts resulting in further fetches. Imported scripts are fetched, parsed and executed in turn, per the ECMA-262 and Web Worker specifications. All resources downloaded as part of the very first startup of a Service Worker are cached along with the worker script as described in "Worker Script Caching".
  3. oninstall:
    Once a Service Worker has been fetched and started, it is ready to process events. The first event sent to every Service Worker is oninstall. Workers that handle this event are encouraged to use it as a way to prime the available storage mechanisms for supporting offline application use; perhaps by populating IndexedDB databases or Cache objects.

    Service Workers are not considered "installed" until the oninstall event completes. Given that many tasks, such as populating caches, may take a long time and are asynchronous, mechanisms are provided to let applications signal to the user agent when they consider themselves prepared to handle further events.

    If no oninstall event handler is registered, the Service Worker is considered to be successfully installed.

    If any oninstall handler throws an exception, or if any lifetime extension via event.waitUntil() fails (via Promise rejection), installation fails and activation is not carried out.

    Assuming a worker is successfully installed, it is now considered the worker in waiting. There may be only one active worker and one worker in waiting for a given url space.
  4. onactivate:
    After successful installation and just prior to receiving functional events (e.g., onfetch), the onactivate event is dispatched. The primary use of onactivate is for cleanup of resources used in previous versions of a Service Worker script.

    Like oninstall, this event may extend its lifetime using event.waitUntil(), however developers should note that activation is particularly performance sensitive. Performance sensitive events may be queued and will be delayed until successful completion of onctivate.

User Agents may request updated Service Worker scripts "in the background" while controlled documents for an existing Service Worker and url scope are active. Successful fetch, startup, and oninstall do not gaurantee that the worker-in-waiting will begin to immediately handle functional events. An existing Service Worker script will continue to service documents it controls (and will continue to control new documents in the url space) so long as any documents it controlled remain. API exists on the Service Worker to enable immediate activation but this is not the default behavior.

Once a service worker becomes active, the user agent may dispatch functional events. These events model various user-agent generated operations; for example the onfetch event handling HTTP requests.

Service Worker Lifecycle

The core concepts of the Service Worker related to the lifecycle. Service Workers are registered, fetched, installed, and activated before events are dispatched to them.


Event Handling


Worker Script Caching

Upon successful installation of a Service Worker, the provided worker script SHOULD be cached by the UA. The UA SHOULD honor the HTTP cache headers to avoid requests per navigation to the registered scope but with the upper limit of 24 hours. If no HTTP cache headers are provided, it MUST be treated as cache-control: must-revalidate.

These headers are for updating the worker only, not using it. A non-fresh worker will still be used to service requests.

Under this recommended caching rule, UAs SHOULD check for worker updates on each navigation to the matching scope, although they MAY do more regular checks (even when the site is not open) for sites it considers important to the user. UAs MAY NOT update the worker script for the documents which are not visited once in the last 24 hours.

Additionally, update() method is provided in the ServiceWorkerGlobalScope interface to allow a Service Worker to trigger a forced update.


Fetch Handling

Lifetime Extension


Document Context


[Constructor()] // no-op constructor
interface ServiceWorker : Worker {
  readonly attribute DOMString scope;
  readonly attribute DOMString url;
  Promise<ServiceWorker> ready();

  // event
  attribute EventHandler ondeactivate;

The ServiceWorker interface represents the document-side view of a Service Worker. This object provides a no-op constructor. Callers should note that only ServiceWorker objects created by the user agent (see will provide meaningful functionality.

The scope of a ServiceWorker reflects the scope of the ServiceWorker's registration. For example, consider a document created by a navigation to which matches via the following registration call which has been previously executed:

// Script on the page
navigator.serviceWorker.register("/service_worker.js", { scope: "*" });

The value of will be "*".

Similarly, in this example, the value of will be "". The url property is always an absolute URL corresponding to the script file which the Service Worker evaluates.

The ondeactivate event is fired in a turn following replacement of the ServiceWorker.

navigator.serviceWorker is an object which provides access to registration, removal, upgrade, and communication with service workers that are (or will become) active for the current document.

The active property of this object provides a reference to the service worker which was consulted during the construction of the current document (if any).

Communication with these workers is provided via standard HTML5 messaging APIs, and messaging occurs as per usual with Web Workers.

partial interface Navigator {
  readonly attribute ServiceWorkerContainer serviceWorker;

interface ServiceWorkerContainer {
  [Unforgeable] readonly attribute ServiceWorker? active;

  Promise<ServiceWorker> ready();
  Promise<ServiceWorker[]?> getAll();
  Promise<ServiceWorker> register(DOMString url, optional RegistrationOptionList options);
  Promise<ServiceWorker> unregister(DOMString? scope);

  // events
  attribute EventHandler oninstall;
  attribute EventHandler oninstallend;
  attribute EventHandler onactivate;
  attribute EventHandler onactivateend;
  attribute EventHandler onreloadpage;
  attribute EventHandler onerror;

dictionary RegistrationOptionList {
  DOMString scope = "/*";

interface ReloadPageEvent : Event {
  void waitUntil(Promise<any> f);

interface DocumentInstallPhaseEvent : Event {
  readonly attribute ServiceWorker worker;

interface DocumentInstallEvent : DocumentInstallPhaseEvent {
  readonly attribute ServiceWorker previous;
}; provides a reference to the ServiceWorker is null if the current document was not created under a service worker.

Execution Context


interface ServiceWorkerGlobalScope : WorkerGlobalScope {
  readonly attribute CacheList caches;
  // A container for a list of window objects, identifiable by ID, that
  // correspond to windows (or workers) that are "controlled" by this SW
  readonly attribute ServiceWorkerClients clients;
  [Unforgeable] readonly attribute DOMString scope;

  ResponsePromise<any> fetch((Request or [EnsureUTF16] URL or DOMString) request);

  void update();
  void unregister();

  attribute EventHandler oninstall;
  attribute EventHandler onactivate;
  attribute EventHandler onfetch;
  attribute EventHandler onbeforeevicted;
  attribute EventHandler onevicted;

  // The event.source of these MessageEvents are instances of Client
  attribute EventHandler onmessage;

  // close() method inherited from WorkerGlobalScope is not exposed.

The ServiceWorkerGlobalScope interface represents the global execution context of a Service Worker. ServiceWorkerGlobalScope object provides generic, event-driven, time-limited script execution contexts that run at an origin. Once successfully registered, a Service Worker is started, kept alive and killed by their relationship to events, not documents. Any type of synchronous requests MUST NOT be initiated inside of a Service Worker.






Ping the server for an updated version of this script without consulting caches. (See the Service Woker Update Algorithm.)



Request Objects

interface RequestPromise : Promise {

[Constructor(optional RequestInit init)]
interface Request {
  attribute unsigned long timeout;
  attribute DOMString url;
  attribute ByteString method;
  readonly attribute DOMString origin;
  readonly attribute Mode mode;
  attribute boolean synchronous;
  readonly attribute unsigned long redirectCount;
  attribute boolean forcePreflight;
  attribute boolean omitCredentials;
  readonly attribute URL referrer;
  readonly attribute HeaderMap headers; // alternative: sequence<Header> headers;
  attribute any body;

dictionary RequestInit {
  unsigned long timeout = 0;
  DOMString url;
  boolean synchronous = false;
  boolean forcePreflight = false;
  boolean omitCredentials = false;
  ByteString method = "GET";
  HeaderMap headers;
  any body;

enum Mode {
  "same origin",
  "tainted x-origin",

[MapClass(DOMString, DOMString)]
interface HeaderMap {

Response Objects

Resposne objects model HTTP responses.

interface ResponsePromise : Promise {
  Promise<Blob> toBlob();

interface AbstractResponse {

interface OpaqueResponse : AbstractResponse {
  readonly attribute unsigned short statusCode;
  readonly attribute ByteString statusText;
  readonly attribute ByteString method;
  // Returns a filtered list of headers. See prose for details.
  getter HeaderMap headers();
  // No setter for headers
  readonly DOMString url;

interface CORSResponse : Response {
  getter HeaderMap headers();

[Constructor(optional ResponseInit responseInitDict)]
interface Response : AbstractResponse {
  attribute unsigned short statusCode;
  attribute ByteString statusText;
  attribute ByteString method;
  getter HeaderMap headers();
  setter void headers(HeaderMap items);
  DOMString url;
  Promise<Blob> toBlob();

dictionary ResponseInit {
  unsigned short statusCode = 200;
  ByteString statusText = "OK";
  ByteString method;
  HeaderMap headers;


AbstractResponse is a superclass for all Resposne types. It should not be directly constructed (although, for compatibility with JavaScript, a constructor is provided).


Response objects are mutable and constructable. They model HTTP responses. The fetch() API returns this type for same-origin responses.

It may be possible to set the Location header of a Response object to someplace not in the current origin but this is not a security issue. Cross-origin response bodies are opaque to script, and since only same-origin documents will encounter these responses, the only systems the Service Worker can "lie to" are same-origin (and therefore safe from the perspective of other origins).



OpaqueResponse objects are immutable but constructable. The fetch() API returns this type for cross-origin responses.

Their role is to encapsulate the security properties of the web platform. As such, their body attribute will always be undefined and the list of readable headers is heavily filtered.

OpaqueResponse objects may be forwarded on to rendering documents in exactly the same way as mutable Response objects.







Understanding Cache Lifetimes



[Constructor((URL or [EnsureUTF16] DOMString)... urls)]
interface Cache {
  readonly attribute AsyncMap items;

  Promise<Response> match((URL or [EnsureUTF16] DOMString) name);
  Promise<any> add((URL or [EnsureUTF16] DOMString)... responses);
  Promise<any> addResponse((URL or [EnsureUTF16] DOMString) url, Response response);
  Promise<any> remove((URL or [EnsureUTF16] DOMString)... responses);
  Promise<any> update((URL or [EnsureUTF16] DOMString)... urls);
  Promise<any> ready();


[MapClass(DOMString, Response)]
interface AsyncMap {
  Promise<Response> get(DOMString key);
  Promise<boolean> has(DOMString key);
  Promise<any> set(DOMString key, Response val);
  Promise<any> clear();
  Promise<any> delete(DOMString key);
  Promise<any?> items();
  Promise<DOMString[]?> keys();
  Promise<Response[]?> values();

Issue: the following issues should be addressed in the WebIDL specification: the methods keys() and values() are not allowed to be declared as interface members of an interface declared with the [MapClass] extended attribute; the return type of get(key), has(key), set(key, val), clear(), delete(key) is meant to be declared as pre-defined IDL fragment. Namely, the interface declared with [MapClass] extended attribute cannot fully support creating asynchronous map objects.


[MapClass(DOMString, Cache)]
interface CacheList {
  ResponsePromise<any> match(DOMString name, (URL or [EnsureUTF16] DOMString) url);
  Promise<Cache> get(DOMString key);
  Promise<boolean> has(DOMString key);
  Promise<any> set(DOMString key, Cache val);
  Promise<any> clear();
  CacheList items();
  DOMString[] keys();
  Cache[] values();
  getter unsinged long size();


[Constructor()] // no-op constructor
interface Client {
  readonly attribute unsigned long id;
  void postMessage(any message, DOMString targetOrigin,
                   optional sequence<Transferable> transfer);

The Client interface represents the window or the worker (defined as client) that is controlled by the Service Worker. This object provides a no-op constructor. Callers should note that only Client objects created by the user agent (see this.clients.getServiced()) will provide meaningful functionality.

The id of a Client identifies the specific client object from the list of client objects serviced by the Service Worker. The postMessage(message, targetOrigin, transfer) method of a Client, when called, causes a MessageEvent to be dispatched at the client object.


interface ServiceWorkerClients {
  // A list of client objects, identifiable by ID, that correspond to windows
  // (or workers) that are "controlled" by this SW
  Promise<Client[]?> getServiced();

The ServiceWorkerClients interface represents a container for a list of Client objects. The getServiced() method of a ServiceWorkerClients, when called, returns a Promise that will resolve with a list of Client objects that are controlled by this Service Worker.




interface InstallPhaseEvent : Event {
  Promise<any> waitUntil(Promise<any> f);



interface InstallEvent : InstallPhaseEvent {
  readonly attribute ServiceWorker activeWorker;
  void replace();
  Promise<any> reloadAll();

This interacts with waitUntil method in the following way:





interface FetchEvent : Event {
  readonly attribute Request request;
  readonly attribute Client client; // The window issuing the request.
  readonly attribute Purpose purpose;
  readonly attribute boolean isReload;

  void respondWith(Promise<Response> r);
  Promise<any> forwardTo((URL or [EnsureUTF16] DOMString) url);

enum Purpose {

The r argument must resolve with a Response, else a NetworkError is thrown. If the request is a top-level navigation and the return value is a OpaqueResponse (an opaque response body), a NetworkError is thrown. The final URL of all successful (non network-error) responses is the requested URL. Renderer-side security checks about tainting for x-origin content are tied to the transparency (or opacity) of the Response body, not URLs.


Returns true if event was dispatched with the user's intention for the page reload, and false otherwise. Pressing the refresh button should be considered a reload while clicking a link and pressing the back button should not. The behavior of the Ctrl+l enter is left to the implementations of the user agents.

Security Considerations


Origin Relativity


Cross-Origin Resources & CORS


Storage Considerations




Appendix A: Algorithms


The Service Worker Registration Algorithm creates or updates a registration for some amount of url space. If successful, a registration ties the provided script to an origin + scope pair which is subsequently consulted by the user for navigation matching.

SCRIPT, the URL of the script to register; a string
ORIGIN, the origin of the registering document
SCOPE, a string representing a relative URL pattern
PROMISE, a promise whose resolution indicates the success or failure of the algorithm
  1. Let PROMISE be a newly-created promise.
  2. Return PROMISE.
  3. Run the following steps asynchronously:
    1. If the origin of SCRIPT is not the same as the origin of the current document:
      1. Reject PROMISE rejected with TypeError.
    2. Set the force same origin flag.
    3. Set the cache-busting headers if the last update from the server occured more than 24 hours ago.
    4. Fetch the script from SCRIPT.
    5. If the response is not success:
      1. Reject PROMISE with a DOMException whose name is NetworkError.
    6. Run a worker (per steps 5 and 6) for the fetched script with SCRIPT.
    7. If any error is encountered:
      1. Reject PROMISE with a DOMException whose name is InvalidStateError.
    8. Fire an event named install on the global object that is a ServiceWorkerGlobalScope object.
    9. Fire an event named install on navigator.serviceWorker for all documents which most specifically match ORIGIN + SCOPE.
    10. If an exception is thrown by any handler:
      1. Fire an event named error on the navigator.serviceWorker in all documents which most specifically match ORIGIN + SCOPE.
      2. Reject PROMISE with the exception.
    11. If any handler calls waitUntil:
      1. Extend the install process until they all resolve.
      2. If any extension handlers are rejected:
        1. Fire an event named error on the navigator.serviceWorker in all documents which most specifically match ORIGIN + SCOPE.
        2. Reject PROMISE with the exception.
      3. If all extension handlers resolve successfully:
        1. Resolve PROMISE with a newly-created associated Service Worker object.
    12. If no extension handlers are registered:
      1. Resolve PROMISE with a newly-created associated Service Worker object.
    13. Fire an event named installend on the navigator.serviceWorker in all documents which most specifically match ORIGIN + SCOPE.
    14. Set the worker in waiting flag on the installed worker and run the check-worker-in-waiting algorithm.






Jake Archibald is a ghost-author of this document. The best instincts in the design are his. He similarly shaped many of the details through discussion and experimentation. The bits which are not his (but which are good) owe everything to his experience, persistence, and focus on enabling web developers. He embodies a hopeful example for developers in shaping browser efforts to more directly address real-world pain points. If Service Workers solve "offline for the web", the credit is due him.

Deep thanks go to Andrew Betts for organizing and hosting a small workshop of like-minded individuals including: Jake Archibald, Jackson Gabbard, Tobie Langel, Robin Berjon, Patrick Lauke, Christian Heilmann. From the clarity of the day's discussions and the use-cases outlined there, much has become possible. Further thanks to Andrew for raising consciousness about the offline problem. His organization of EdgeConf and inclusion of Offline as a persistent topic there has created many opportunities and connections that have enabled this work to progress.

Anne van Kesteren has generously lent his encyclopedic knowledge of Web Platform arcana and standards development experience throught the development of the Service Worker. This specification would be incomplete without his previous work in describing the real-world behavior of URLs, HTTP Fetch, Promises, and DOM. Similarly, this specification would not be possible without Ian Hickson's rigorous Web Worker spec. Much thanks to him.

In no particular order, deep gratitude for design guidance and discussion goes to: Junkee Song, Alec Flett, David Barrett-Kahn, Aaron Boodman, Michael Nordman, Tom Ashworth, Kinuko Yasuda, Darin Fisher, Jonas Sicking, Jesús Leganés Combarro, Mark Christian, Dave Hermann, Yehuda Katz, François Remy, Ilya Grigorik, Will Chan, Domenic Denicola, Nikhil Marathe, Yves Lafon, Adam Barth, Greg Simon, and Devdatta Akhawe.

Jason Weber, Chris Wilson, Paul Kinlan, Ehsan Akhgari, and Daniel Austin have provided valuable, well-timed feedback on requirements and the standardization process.

The authors would also like to thank Dimitri Glazkov for his scripts and formatting tools which have been essential in the production of this specification. The authors are also grateful for his considerable guidance.

Thanks also to Vivian Cromwell, Greg Simon, and Alex Komoroske for their considerable professional support.