Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save moreaki/33d4072d65c5933b495941c707e723d8 to your computer and use it in GitHub Desktop.

Select an option

Save moreaki/33d4072d65c5933b495941c707e723d8 to your computer and use it in GitHub Desktop.
Sigma Backend: embedded HTML for Async Operations And SSE Runtime chapter (temporary)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="author" content="Project Authors" />
<meta name="dcterms.date" content="2026-04-24" />
<title>Async Operations And SSE Runtime - Sigma Developer Guide</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list[class]{list-style: none;}
ul.task-list li input[type="checkbox"] {
font-size: inherit;
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
html { -webkit-text-size-adjust: 100%; }
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; }
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.at { color: #7d9029; }
code span.bn { color: #40a070; }
code span.bu { color: #008000; }
code span.cf { color: #007020; font-weight: bold; }
code span.ch { color: #4070a0; }
code span.cn { color: #880000; }
code span.co { color: #60a0b0; font-style: italic; }
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.do { color: #ba2121; font-style: italic; }
code span.dt { color: #902000; }
code span.dv { color: #40a070; }
code span.er { color: #ff0000; font-weight: bold; }
code span.ex { }
code span.fl { color: #40a070; }
code span.fu { color: #06287e; }
code span.im { color: #008000; font-weight: bold; }
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.kw { color: #007020; font-weight: bold; }
code span.op { color: #666666; }
code span.ot { color: #007020; }
code span.pp { color: #bc7a00; }
code span.sc { color: #4070a0; }
code span.ss { color: #bb6688; }
code span.st { color: #4070a0; }
code span.va { color: #19177c; }
code span.vs { color: #4070a0; }
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; }
</style>
<style type="text/css">:root {
--paper: #fbfaf6;
--paper-strong: #f2eee6;
--surface: #ffffff;
--surface-soft: #f7f4ee;
--ink: #1f2a33;
--ink-muted: #5c6770;
--ink-soft: #7d8790;
--accent: #1b5f78;
--accent-strong: #123d4f;
--accent-soft: #e8f0f4;
--accent-tint: rgba(27, 95, 120, 0.12);
--line: #d5d9d3;
--line-strong: #adb7b2;
--code-bg: #f4f6f7;
--table-head: #eef2f2;
--font-body: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia, serif;
--font-display: "Avenir Next", "Helvetica Neue", "IBM Plex Sans", Arial, sans-serif;
--font-mono: "JetBrains Mono", "SFMono-Regular", monospace;
--font-size-body: 9.3pt;
--font-size-code-block: 6.2pt;
--font-size-caption: 6.2pt;
--font-size-table-head: 6.9pt;
--font-size-table-body: 6.5pt;
--font-size-h1: 22pt;
--font-size-h2: 15pt;
--font-size-h3: 11.5pt;
--font-size-h4: 10pt;
--font-size-h5: 9pt;
--line-height-body: 1.56;
--line-height-tight: 1.2;
--list-indent: 2.35em;
--space-xs: 0.24em;
--space-sm: 0.48em;
--space-md: 0.78em;
--space-lg: 1.2em;
--space-xl: 1.8em;
--space-2xl: 2.8em;
--cover-kicker: "Technical Book";
}
@page {
size: A4;
margin: 25mm 19mm;
@top-left {
content: string(book-title);
font-family: var(--font-display);
font-size: 7pt;
letter-spacing: 0.08em;
color: var(--ink-soft);
}
@top-right {
content: string(chapter);
font-family: var(--font-display);
font-size: 7pt;
color: var(--ink-soft);
}
@bottom-center {
content: counter(page);
font-family: var(--font-display);
font-size: 7pt;
color: var(--ink-soft);
}
}
@page :first {
@top-left {
content: none;
}
@top-right {
content: none;
}
@bottom-center {
content: none;
}
}
html,
body {
background: var(--surface);
color: var(--ink);
font-family: var(--font-body);
font-size: var(--font-size-body);
line-height: var(--line-height-body);
}
body {
margin: 0;
background: var(--surface);
hyphens: auto;
}
.book-sidebar {
display: none;
}
@media screen {
:root {
--sidebar-offset: 1rem;
--sidebar-width: 22rem;
--sidebar-gap: 2.6rem;
--screen-edge-gap: 1rem;
}
html {
background:
radial-gradient(circle at top right, rgba(27, 95, 120, 0.12), transparent 32%),
linear-gradient(180deg, #edf2f2, #e7ece8);
}
body {
box-sizing: border-box;
width: min(210mm, calc(100vw - 2rem));
min-height: calc(297mm - 2rem);
margin: 1rem auto;
padding: 25mm 19mm;
box-shadow: 0 22px 60px rgba(20, 36, 46, 0.14);
}
header#title-block-header {
margin-bottom: 0;
}
header#title-block-header .cover-copy {
transform: translateY(-12mm);
}
.book-sidebar {
position: fixed;
top: 1rem;
left: max(var(--sidebar-offset), calc(50vw - 105mm - var(--sidebar-width) - var(--sidebar-gap)));
z-index: 20;
width: min(var(--sidebar-width), calc(100vw - (2 * var(--sidebar-offset))));
max-height: calc(100vh - 2rem);
padding: 1rem 1rem 1.1rem;
overflow: auto;
border: 1px solid #d9e0e7;
border-radius: 0;
background: #ffffff;
box-shadow: 0 10px 28px rgba(20, 36, 46, 0.08);
transition: transform 180ms ease, box-shadow 180ms ease;
}
body:not(.book-sidebar-ready) .book-sidebar {
display: none;
}
body.book-sidebar-ready .book-sidebar {
display: block;
}
.book-sidebar[data-sidebar-state="collapsed"] {
transform: translateX(calc(-100% + 3.7rem));
box-shadow: 0 8px 20px rgba(20, 36, 46, 0.06);
}
.book-sidebar-header {
display: flex;
align-items: center;
gap: 0.65rem;
margin: 0 0 0.75rem;
}
.book-sidebar-toggle {
display: flex;
align-items: center;
gap: 0.55rem;
flex: 1 1 auto;
margin: 0;
padding: 0;
border: 0;
background: transparent;
color: var(--accent-strong);
font-family: var(--font-display);
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
cursor: pointer;
}
.book-sidebar-toggle::before {
content: "";
flex: 0 0 auto;
width: 0.9rem;
height: 0.9rem;
border-radius: 999px;
background: linear-gradient(135deg, var(--accent), var(--accent-strong));
box-shadow: inset 0 0 0 0.16rem rgba(255, 255, 255, 0.72);
}
.book-sidebar-toggle-label {
flex: 1 1 auto;
text-align: left;
}
.book-sidebar-top-link {
flex: 0 0 auto;
color: var(--ink-soft);
text-decoration: none;
font-family: var(--font-display);
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: none;
}
.book-sidebar-top-link:hover {
color: var(--accent-strong);
text-decoration: underline;
}
.book-sidebar nav#TOC-sidebar {
margin: 0;
font-size: 0.92rem;
}
.book-sidebar nav#TOC-sidebar::before {
margin-bottom: 0.5rem;
font-size: 0.88rem;
}
.book-sidebar nav#TOC-sidebar > ul {
display: block;
margin-top: 0;
column-count: 1;
}
.book-sidebar nav#TOC-sidebar > ul > li {
position: relative;
margin-bottom: 0.2rem;
padding: 0.18rem 0 0.22rem;
}
.book-sidebar nav#TOC-sidebar ul ul {
margin: 0.3rem 0 0.15rem 0.55rem;
padding-left: 0.85rem;
border-left: 0;
}
.book-sidebar nav#TOC-sidebar a {
padding: 0.24rem 0.45rem 0.24rem 0.35rem;
border-radius: 0;
border-bottom: 0;
transition: background-color 140ms ease, color 140ms ease;
}
.book-sidebar nav#TOC-sidebar a:hover {
background: rgba(27, 95, 120, 0.08);
}
.book-sidebar nav#TOC-sidebar a.is-active {
background: var(--accent-soft);
color: var(--accent-strong);
}
.book-sidebar .book-toc-fold {
position: absolute;
top: 0.2rem;
right: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.7rem;
height: 1.7rem;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
color: var(--ink-soft);
cursor: pointer;
}
.book-sidebar .book-toc-fold:hover {
background: rgba(27, 95, 120, 0.08);
color: var(--accent-strong);
}
.book-sidebar .book-toc-fold span {
width: 0.58rem;
height: 0.58rem;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(45deg);
transition: transform 140ms ease;
}
.book-sidebar .book-toc-chapter > ul {
display: none;
}
.book-sidebar .book-toc-chapter.is-open > ul {
display: block;
}
.book-sidebar .book-toc-chapter.is-open > .book-toc-fold span {
transform: rotate(225deg);
}
.book-sidebar .book-toc-chapter > a {
padding-right: 2rem;
}
body.book-sidebar-ready {
width: min(210mm, calc(100vw - 2rem));
margin: 1rem auto;
}
@media (max-width: 1200px) {
.book-sidebar {
left: 1rem;
width: min(20rem, calc(100vw - (2 * var(--sidebar-offset))));
}
body.book-sidebar-ready {
width: min(210mm, calc(100vw - 2rem));
margin: 1rem auto;
}
}
@media (max-width: 860px) {
.book-sidebar {
width: min(19rem, calc(100vw - 1rem));
left: 0.5rem;
top: 0.5rem;
max-height: calc(100vh - 1rem);
}
body.book-sidebar-ready {
width: min(210mm, calc(100vw - 1rem));
margin: 1rem auto;
}
}
header#title-block-header + nav#TOC {
margin-top: 8rem;
}
nav#TOC + section.level1 {
margin-top: 7rem;
}
.chapter-break {
break-before: auto;
page-break-before: auto;
display: block;
height: 4.2rem;
}
}
.chapter-break {
break-before: page;
page-break-before: always;
}
h1,
h2,
h3,
h4,
h5 {
font-family: var(--font-display);
line-height: var(--line-height-tight);
color: var(--ink);
margin-top: var(--space-xl);
margin-bottom: var(--space-sm);
page-break-after: avoid;
break-after: avoid;
}
header#title-block-header {
position: relative;
min-height: 247mm;
margin: 0 0 var(--space-xl);
padding: 0;
break-after: page;
page-break-after: always;
display: flex;
flex-direction: column;
justify-content: center;
overflow: visible;
background: none;
}
header#title-block-header .cover-copy {
width: auto;
max-width: none;
padding: 0;
position: relative;
}
header#title-block-header::before {
content: var(--cover-kicker);
position: absolute;
top: 0;
left: 0;
font-family: var(--font-display);
font-size: 7.6pt;
font-weight: 700;
letter-spacing: 0.24em;
text-transform: uppercase;
color: var(--accent);
}
header#title-block-header::after {
content: none;
}
header#title-block-header .title {
string-set: book-title content(text);
max-width: 100%;
margin: 0 0 0.3em;
border: 0;
padding: 0;
font-size: 34pt;
line-height: 1.02;
letter-spacing: -0.03em;
color: var(--accent-strong);
}
header#title-block-header .subtitle {
max-width: 100%;
margin: 0 0 1.35rem;
font-size: 12pt;
line-height: 1.44;
color: var(--ink-muted);
}
header#title-block-header .author,
header#title-block-header .date {
display: inline-block;
margin: 0;
font-family: var(--font-display);
font-size: 8pt;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink-soft);
}
header#title-block-header .author + .date {
margin-left: 0.95rem;
padding-left: 0.95rem;
border-left: 1px solid var(--line);
}
header#title-block-header .cover-copy::after {
content: "";
display: block;
width: 74mm;
max-width: 100%;
height: 1.6mm;
margin-top: 1.5rem;
border-radius: 999px;
background: var(--accent);
}
nav#TOC {
string-set: chapter "Contents";
margin: 0.2em 0 var(--space-xl);
padding: 0;
border: 0;
background: none;
page-break-inside: avoid;
font-size: 7.1pt;
}
nav#TOC::before {
content: "Contents";
display: block;
margin-bottom: 0.65em;
font-family: var(--font-display);
font-size: 10pt;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--accent-strong);
}
nav#TOC > ul {
margin: 0.15em 0 0;
padding-left: 0;
column-count: 2;
column-gap: 2.3em;
column-fill: balance;
list-style: none;
}
nav#TOC ul {
list-style: none;
}
nav#TOC > ul > li {
margin-bottom: 0.4em;
}
nav#TOC ul ul {
margin-top: 0.18em;
padding-left: 1.15em;
column-count: 1;
border-left: 1px solid var(--accent-tint);
}
nav#TOC li {
margin: 0;
padding: 0.08em 0 0.1em;
break-inside: avoid;
}
nav#TOC a {
display: block;
color: var(--ink);
text-decoration: none;
border-bottom: 0;
}
nav#TOC > ul > li > a {
font-family: var(--font-display);
font-weight: 600;
}
nav#TOC .toc-section-number {
display: inline-block;
min-width: 2.9em;
margin-right: 0.2em;
color: var(--accent);
font-family: var(--font-display);
font-variant-numeric: tabular-nums;
font-weight: 700;
}
nav#TOC + section.level1 {
break-before: page;
page-break-before: always;
}
.book-meta {
position: absolute;
left: 0;
bottom: 0;
width: auto;
max-width: 100%;
margin: 0;
padding: 0;
border: 0;
background: none;
break-after: auto;
page-break-after: auto;
}
.book-meta::before {
content: "Edition Details";
display: block;
margin-bottom: 0.55em;
font-family: var(--font-display);
font-size: 7pt;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent);
}
.book-meta table {
width: auto;
max-width: 100%;
margin: 0;
border-collapse: collapse;
table-layout: auto;
font-size: 6.9pt;
}
.book-meta tr,
.book-meta th,
.book-meta td {
border: 0;
background: transparent;
padding: 0;
}
.book-meta th,
.book-meta td {
padding: 0.14em 0;
vertical-align: top;
}
.book-meta th {
width: auto;
padding-right: 0.75em;
text-align: left;
white-space: nowrap;
font-family: var(--font-display);
font-weight: 600;
color: var(--ink);
}
.book-meta td {
min-width: 44mm;
text-align: left;
color: var(--ink-muted);
}
.book-meta tbody th,
.book-meta tbody td,
.book-meta tbody tr:last-child td {
border-bottom: 0;
}
.book-meta tbody tr:nth-child(even) {
background: rgba(27, 95, 120, 0.05);
}
section.level1 > h1 {
string-set: chapter content(text);
margin-top: 0;
margin-bottom: 1.35rem;
padding: 0;
border-bottom: 0;
font-size: var(--font-size-h1);
font-weight: 700;
letter-spacing: -0.03em;
color: var(--accent-strong);
}
section.level1 > h1::after {
content: "";
display: block;
width: 5.8rem;
height: 0.18rem;
margin-top: 0.6rem;
border-radius: 999px;
background: var(--accent);
}
section.level1 > h1 .header-section-number {
display: inline-block;
margin: 0 0.32em 0 0;
font-size: 1em;
font-weight: inherit;
line-height: 1;
letter-spacing: -0.03em;
vertical-align: baseline;
color: var(--accent-strong);
}
h2 {
padding-bottom: 0.18em;
border-bottom: 1px solid var(--accent-tint);
font-size: var(--font-size-h2);
letter-spacing: -0.02em;
color: var(--accent-strong);
}
h3 {
font-size: var(--font-size-h3);
color: var(--ink);
}
h4 {
font-size: var(--font-size-h4);
font-weight: 700;
color: var(--ink);
}
h5 {
font-size: var(--font-size-h5);
font-weight: 700;
color: var(--ink-muted);
}
p,
ul,
ol,
table,
blockquote,
pre {
margin-top: 0;
margin-bottom: var(--space-md);
}
p {
orphans: 2;
widows: 2;
}
ul,
ol {
padding-left: var(--list-indent);
}
ul ul,
ul ol,
ol ul,
ol ol {
padding-left: 1.6em;
}
ul {
list-style-type: disc;
}
ul ul {
list-style-type: circle;
}
ul ul ul {
list-style-type: square;
}
ol {
list-style-type: decimal;
}
ol ol {
list-style-type: lower-alpha;
}
ol ol ol {
list-style-type: lower-roman;
}
ol ol ol ol {
list-style-type: decimal;
}
li {
margin: 0.16em 0;
}
li::marker {
color: var(--accent);
}
li > p {
margin: 0.12em 0;
}
a {
color: var(--accent-strong);
text-decoration: none;
border-bottom: 0.5pt solid rgba(18, 61, 79, 0.22);
}
a code {
color: inherit;
}
strong {
font-weight: 700;
color: var(--ink);
}
em {
color: var(--ink-muted);
}
code {
font-family: var(--font-mono);
font-size: 0.88em;
background: var(--code-bg);
border-radius: 2px;
padding: 0.12em 0.3em;
}
pre:not(.sourceCode) {
font-family: var(--font-mono);
font-size: var(--font-size-code-block);
line-height: 1.35;
background: linear-gradient(rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.72)), var(--code-bg);
border: 1px solid var(--line);
border-radius: 5px;
padding: 0.9em 1em;
overflow-wrap: anywhere;
}
pre code {
font-family: var(--font-mono);
font-size: var(--font-size-code-block);
background: transparent;
padding: 0;
}
div.sourceCode {
font-family: var(--font-mono);
font-size: var(--font-size-code-block);
line-height: 1.35;
background: transparent;
border: 0;
border-radius: 0;
padding: 0;
}
pre.sourceCode {
font-family: var(--font-mono);
font-size: var(--font-size-code-block);
line-height: 1.35;
margin: 0;
padding: 0.9em 1em;
background: linear-gradient(rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.72)), var(--code-bg);
border: 1px solid var(--line);
border-radius: 5px;
box-decoration-break: clone;
}
code.sourceCode {
font-family: var(--font-mono);
font-size: var(--font-size-code-block);
}
blockquote {
margin-left: 0;
padding: 0.65em 0.95em 0.7em 1.05em;
border-left: 3px solid var(--accent);
border-radius: 0 10px 10px 0;
background: var(--accent-soft);
color: var(--ink-muted);
}
blockquote > :last-child {
margin-bottom: 0;
}
.local-footnote-ref {
font-size: 0.7em;
vertical-align: super;
line-height: 1;
margin-left: 0.18em;
display: inline-block;
}
.local-footnote-ref a {
display: inline-block;
padding: 0.02em 0.12em;
line-height: 1;
color: var(--accent);
text-decoration: none;
border-bottom: 0;
}
.local-footnotes {
margin: 0.35em 0 1.75em;
padding: 0.35em 0 0;
border-top: 0.6pt solid var(--line);
font-size: 5.8pt;
line-height: 1.25;
color: var(--ink-muted);
column-count: 1;
}
.local-footnote-target {
display: block;
width: 0;
height: 0;
overflow: hidden;
opacity: 0;
}
.local-footnote {
margin: 0.15em 0 0;
font-size: inherit;
line-height: inherit;
color: inherit;
font-family: var(--font-body);
font-weight: 400;
break-inside: avoid;
}
.local-footnote-number {
display: inline-block;
min-width: 1.2em;
margin-right: 0.2em;
font-weight: 700;
color: var(--ink);
}
.local-footnote-backref {
color: var(--accent);
text-decoration: none;
border-bottom: 0;
margin-left: 0.25em;
}
.local-footnote-body code {
font-size: 0.92em;
}
@media print {
@page {
@footnote {
border-top: 0.6pt solid var(--line);
padding-top: 0.35em;
}
}
.local-footnotes {
display: block;
width: 0;
height: 0;
overflow: visible;
margin: 0;
padding: 0;
border: 0;
font-size: 0;
line-height: 0;
column-count: auto;
}
.local-footnote-target {
display: block;
width: 0;
height: 0;
overflow: hidden;
opacity: 0;
}
.local-footnote {
float: footnote;
footnote-display: block;
margin: 0.15em 0 0.15em 0;
font-size: 6.3pt;
line-height: 1.25;
break-inside: auto;
}
.local-footnote-number {
display: none;
}
.local-footnote::footnote-call {
content: none;
}
.local-footnote::footnote-marker {
content: counter(footnote) ". ";
font-weight: 700;
color: var(--ink);
}
.local-footnote-backref {
display: inline;
}
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: var(--font-size-table-body);
margin-top: 0.2em;
margin-bottom: 0.55em;
border: 0;
background: none;
break-inside: auto;
}
table p,
table ul,
table ol {
margin: 0;
}
thead th {
font-size: var(--font-size-table-head);
font-family: var(--font-display);
font-weight: 700;
letter-spacing: 0.02em;
background: none;
border-bottom: 1.4px solid var(--accent);
text-align: left;
padding: 0.4em 0.35em 0.5em;
overflow-wrap: anywhere;
}
thead th:last-child {
border-right: 0;
}
tbody td {
font-size: var(--font-size-table-body);
border-bottom: 1px solid rgba(31, 42, 51, 0.14);
padding: 0.42em 0.35em;
vertical-align: top;
overflow-wrap: anywhere;
}
tbody td:last-child {
border-right: 0;
}
tbody tr:first-child td {
border-top: 0;
}
tbody tr:last-child td {
border-bottom: 1px solid var(--line-strong);
}
tbody tr:nth-child(even) {
background: rgba(27, 95, 120, 0.035);
}
hr {
border: 0;
border-top: 1px solid var(--line);
margin: var(--space-lg) 0;
}
img {
max-width: 100%;
height: auto;
display: block;
margin: 0;
page-break-inside: avoid;
}
.figure-caption {
display: block;
max-width: 100%;
width: auto;
margin: 0.8em 0 0;
font-family: var(--font-display);
font-size: var(--font-size-caption);
font-weight: 700;
letter-spacing: 0.04em;
color: var(--accent-strong);
page-break-after: avoid;
break-after: avoid;
}
.object-xref {
font-weight: 600;
color: var(--accent-strong);
text-decoration: none;
border-bottom: 0;
}
.object-xref:visited {
color: var(--accent-strong);
}
.captioned-block {
display: block;
width: 100%;
max-width: 100%;
margin: 1.9em 0 2.15em;
}
.captioned-block.captioned-figure,
.captioned-block.captioned-table {
page-break-inside: avoid;
break-inside: avoid;
}
.captioned-block.captioned-figure > .figure-caption,
.captioned-block.captioned-figure > p {
width: auto;
max-width: 100%;
margin-left: 0;
margin-right: 0;
}
.captioned-block.captioned-figure > p {
padding: 0;
border: 0;
background: none;
}
.captioned-block.captioned-figure > p img {
display: block;
max-width: 100%;
width: auto;
height: auto;
margin-left: auto;
margin-right: auto;
}
.bpmn-diagram,
.mermaid-diagram,
.xmind-diagram,
.mock-diagram {
max-width: 100% !important;
height: auto !important;
}
@media print {
.xmind-diagram {
max-height: 190mm !important;
width: auto !important;
margin-left: auto !important;
margin-right: auto !important;
}
}
.figure-caption + p,
.figure-caption + div.sourceCode,
.figure-caption + pre,
.figure-caption + table {
display: block;
max-width: 100%;
width: auto;
margin-left: 0;
margin-right: 0;
margin-bottom: 0;
page-break-before: avoid;
break-before: avoid;
}
.figure-caption + p img {
display: block;
max-width: 100%;
width: auto;
height: auto;
margin: 0 auto;
}
.captioned-block.captioned-code > pre,
.captioned-block.captioned-code > div.sourceCode,
.captioned-block.captioned-table > table {
page-break-inside: auto;
break-inside: auto;
}
section#list-of-figures,
section#list-of-tables,
section#list-of-code-listings {
background: var(--surface);
}
section#list-of-code-listings {
break-before: page;
page-break-before: always;
}
section#list-of-figures ul,
section#list-of-tables ul,
section#list-of-code-listings ul {
list-style: none;
padding-left: 0;
margin: 0.65em 0 0;
background: transparent;
}
section#list-of-figures li,
section#list-of-tables li,
section#list-of-code-listings li {
position: static;
margin: 0;
padding: 0.18em 0;
border-bottom: 0;
background: transparent;
}
section#list-of-figures li::before,
section#list-of-tables li::before,
section#list-of-code-listings li::before {
content: none;
}
section#list-of-figures li:last-child,
section#list-of-tables li:last-child,
section#list-of-code-listings li:last-child {
border-bottom: 0;
}
section#list-of-figures a,
section#list-of-tables a,
section#list-of-code-listings a {
display: inline;
color: var(--ink);
border-bottom: 0;
background: transparent;
}
.figure-caption.bpmn-metadata-table + table th:first-child,
.figure-caption.bpmn-metadata-table + table td:first-child {
width: 12em;
white-space: nowrap;
overflow-wrap: normal;
}
@media screen {
pre:not(.sourceCode),
pre.sourceCode {
box-shadow: 0 10px 24px rgba(20, 36, 46, 0.08);
}
}
:root {
--paper: #f8fafd;
--paper-strong: #e9eef6;
--surface-soft: #eef3fa;
--accent: #285a8d;
--accent-strong: #183a5a;
--accent-soft: #e2ebf6;
--accent-tint: rgba(40, 90, 141, 0.12);
--line: #d1dced;
--line-strong: #aabbd5;
--code-bg: #f2f6fb;
--table-head: #e8eff8;
--cover-kicker: "Developer Guide";
}
img.mermaid-diagram[style*="width: 314px"] {
width: 648px !important;
max-width: 100%;
}
</style>
</head>
<body>
<main class="book-body book-chapter-only">
<section id="async-operations-and-sse-runtime" class="level1" data-number="11">
<h1 data-number="11"><span class="header-section-number">11</span> Async
Operations And SSE Runtime</h1>
<p>Sigma now has a dedicated async-operation substrate for backend
actions whose logical lifetime is longer than one request/response cycle
and whose UI state should move away from polling toward live event
delivery.</p>
<p>This chapter explains the implementation mechanics, the current
runtime model, and the recommended adoption pattern for new backend
adopters.</p>
<section id="what-problem-this-runtime-solves" class="level2" data-number="11.1">
<h2 data-number="11.1"><span class="header-section-number">11.1</span>
What Problem This Runtime Solves</h2>
<p>The async-operation runtime is the small shared layer between:</p>
<ul>
<li>backend work that should survive a short-lived HTTP request</li>
<li>UI state that should receive live updates</li>
<li>gateway environments such as Apigee where long-lived synchronous
HTTPS calls are unsafe</li>
<li>long-running administrative, process-support, import, and export
actions that do not justify a second orchestration framework</li>
</ul>
<p>The implementation goal is deliberately narrow:</p>
<ul>
<li>durable current-state snapshots</li>
<li>SSE delivery of those snapshots</li>
<li>optional high-frequency volatile progress events between durable
state changes</li>
<li>simple reconnect and retention semantics</li>
</ul>
<p>This is not a replacement for Camunda or for Sigma&#39;s existing
long-running-job infrastructure. It is the live-status and
async-operation layer that those flows can publish into.</p>
</section>
<section id="transitional-note-for-lightweight-domain-notifications" class="level2" data-number="11.2">
<h2 data-number="11.2"><span class="header-section-number">11.2</span>
Transitional Note For Lightweight Domain Notifications</h2>
<p>The current SSE implementation is intentionally optimized for
stateful async work. New backend adopters should still prefer this lane
for long-running or lifecycle-oriented operations.</p>
<p>Some newer subsystems only need lightweight live invalidation or
change notifications rather than durable operation state. Runtime
diagnostics is the first example: its current SSE notifications are
published through the async-operation topic lane as small completed
snapshots so the frontend can already integrate with the existing
transport.</p>
<p>Treat that runtime-diagnostics usage as transitional:</p>
<ul>
<li>it reuses the heavy stateful lane on purpose so current UI work can
move forward without a second SSE stack</li>
<li>it is not the long-term model for lightweight domain
notifications</li>
<li>once Sigma adds a lightweight stateless in-memory event lane,
runtime-diagnostics notifications should be moved from the
async-operation lane to that lighter transport</li>
</ul>
<p>The intended future split is:</p>
<ul>
<li>keep async operations for durable, revisioned, reconnect-safe
workflows</li>
<li>add a sibling lightweight topic-event lane for best-effort,
live-only domain notifications</li>
</ul>
</section>
<section id="topology" class="level2" data-number="11.3">
<h2 data-number="11.3"><span class="header-section-number">11.3</span>
Topology</h2>
<div class="captioned-block captioned-figure">
<style>
#figure-11-1 + p, #figure-11-1 + div.sourceCode, #figure-11-1 + pre, #figure-11-1 + table { display: block; max-width: 100%; }
#figure-11-1 + p img { display: block; max-width: 100%; height: auto; }
</style>
<p><img role="img" aria-label="diagram" src="data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTIwOS43OHB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB3aGl0ZTsiIHZpZXdCb3g9IjAgMCAxMjA5Ljc4MTI1IDIyNi42MDAwMDYxMDM1MTU2MiIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJmbG93Y2hhcnQtdjIiPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5Ok9wZW4gU2FucyxzYW5zLXNlcmlmO2ZvbnQtc2l6ZTo4cHg7ZmlsbDojMzMzO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzJkNmVhMztzdHJva2U6IzJkNmVhMzt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojMmQ2ZWEzO30jbXktc3ZnIHN2Z3tmb250LWZhbWlseTpPcGVuIFNhbnMsc2Fucy1zZXJpZjtmb250LXNpemU6OHB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5Ok9wZW4gU2FucyxzYW5zLXNlcmlmO2NvbG9yOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW4gcHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O30jbXktc3ZnIC5sYWJlbCB0ZXh0LCNteS1zdmcgc3BhbntmaWxsOiMzMzM7Y29sb3I6IzMzMzt9I215LXN2ZyAubm9kZSByZWN0LCNteS1zdmcgLm5vZGUgY2lyY2xlLCNteS1zdmcgLm5vZGUgZWxsaXBzZSwjbXktc3ZnIC5ub2RlIHBvbHlnb24sI215LXN2ZyAubm9kZSBwYXRoe2ZpbGw6I2ZmZmZmZjtzdHJva2U6IzkzNzBEQjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLm5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYW5jaG9yOm1pZGRsZTt9I215LXN2ZyAubm9kZSAua2F0ZXggcGF0aHtmaWxsOiMwMDA7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwsI215LXN2ZyAubm9kZSAubGFiZWwsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5ub2RlLmNsaWNrYWJsZXtjdXJzb3I6cG9pbnRlcjt9I215LXN2ZyAucm9vdCAuYW5jaG9yIHBhdGh7ZmlsbDojMmQ2ZWEzIWltcG9ydGFudDtzdHJva2Utd2lkdGg6MDtzdHJva2U6IzJkNmVhMzt9I215LXN2ZyAuYXJyb3doZWFkUGF0aHtmaWxsOiMzMzMzMzM7fSNteS1zdmcgLmVkZ2VQYXRoIC5wYXRoe3N0cm9rZTojMmQ2ZWEzO3N0cm9rZS13aWR0aDoyLjBweDt9I215LXN2ZyAuZmxvd2NoYXJ0LWxpbmt7c3Ryb2tlOiMyZDZlYTM7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlTGFiZWx7YmFja2dyb3VuZC1jb2xvcjojZjdmYmZmO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOiNmN2ZiZmY7fSNteS1zdmcgLmVkZ2VMYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6I2Y3ZmJmZjtmaWxsOiNmN2ZiZmY7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNDcsIDI1MSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDojZjhmYmZkO3N0cm9rZTojYjdjYWQ2O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmNsdXN0ZXIgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXIgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIGRpdi5tZXJtYWlkVG9vbHRpcHtwb3NpdGlvbjphYnNvbHV0ZTt0ZXh0LWFsaWduOmNlbnRlcjttYXgtd2lkdGg6MjAwcHg7cGFkZGluZzoycHg7Zm9udC1mYW1pbHk6T3BlbiBTYW5zLHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7YmFja2dyb3VuZDojZTZmMGZiO2JvcmRlcjoxcHggc29saWQgI2FhYWEzMztib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzMzMzt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6I2Y3ZmJmZjt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuaWNvbi1zaGFwZSBwLCNteS1zdmcgLmltYWdlLXNoYXBlIHB7YmFja2dyb3VuZC1jb2xvcjojZjdmYmZmO3BhZGRpbmc6MnB4O30jbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbCByZWN0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6I2Y3ZmJmZjtmaWxsOiNmN2ZiZmY7fSNteS1zdmcgLmxhYmVsLWljb257ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjFlbTtvdmVyZmxvdzp2aXNpYmxlO3ZlcnRpY2FsLWFsaWduOi0wLjEyNWVtO30jbXktc3ZnIC5ub2RlIC5sYWJlbC1pY29uIHBhdGh7ZmlsbDpjdXJyZW50Q29sb3I7c3Ryb2tlOnJldmVydDtzdHJva2Utd2lkdGg6cmV2ZXJ0O30jbXktc3ZnIHN2ZywjbXktc3ZnIHN2ZyAqe2ZvbnQtZmFtaWx5OidPcGVuIFNhbnMnLHNhbnMtc2VyaWYhaW1wb3J0YW50O2ZvbnQtd2VpZ2h0OjQwMCFpbXBvcnRhbnQ7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5Ok9wZW4gU2FucyxzYW5zLXNlcmlmO308L3N0eWxlPjxnPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI0LjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgNSBMIDEwIDEwIEwgMTAgMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSIxMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxnIGNsYXNzPSJyb290Ij48ZyBjbGFzcz0iY2x1c3RlcnMiLz48ZyBjbGFzcz0iZWRnZVBhdGhzIj48cGF0aCBkPSJNMTkzLjUyMywxNzQuOUwyMDAuMjMsMTc0LjlDMjA2LjkzOCwxNzQuOSwyMjAuMzUyLDE3NC45LDIzNC40OTIsMTcyLjk4M0MyNDguNjMyLDE3MS4wNjYsMjYzLjQ5OSwxNjcuMjMzLDI3MC45MzIsMTY1LjMxNkwyNzguMzY1LDE2My4zOTkiIGlkPSJMX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVGt6TGpVeU16UXpOelVzSW5raU9qRTNOQzQ0T1RrNU9UazJNVGcxTXpBeU4zMHNleUo0SWpveU16TXVOelkxTmpJMUxDSjVJam94TnpRdU9EazVPVGs1TmpFNE5UTXdNamQ5TEhzaWVDSTZNamd5TGpJek9EWXpOak0yTXpZek5qUXNJbmtpT2pFMk1pNHpPVGs1T1RrMk1UZzFNekF5TjMxZCIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0zOTguOTk4LDEyMS40TDQxNC4xMTYsMTEzLjA4M0M0MjkuMjMzLDEwNC43NjcsNDU5LjQ2OCw4OC4xMzMsNDgxLjUxNSw3Ny45MUM1MDMuNTYyLDY3LjY4Nyw1MTcuNDIxLDYzLjg3NCw1MjQuMzUxLDYxLjk2OEw1MzEuMjgsNjAuMDYxIiBpZD0iTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TXprNExqazVPREF3TWpZNE56Y3hNak1zSW5raU9qRXlNUzR6T1RrNU9UazJNVGcxTXpBeU4zMHNleUo0SWpvME9Ea3VOekF6TVRJMUxDSjVJam8zTVM0MWZTeDdJbmdpT2pVek5TNHhNelk1TlRVME9USTBNalF5TENKNUlqbzFPWDFkIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQ3Mi4yMDMsMTUwLjUzMkw0NzUuMTIsMTUwLjc2QzQ3OC4wMzYsMTUwLjk4OCw0ODMuODcsMTUxLjQ0NCw0OTMuNzc2LDE1MS42NzJDNTAzLjY4MiwxNTEuOSw1MTcuNjYxLDE1MS45LDUyNC42NTEsMTUxLjlMNTMxLjY0MSwxNTEuOSIgaWQ9IkxfQl9EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5EY3lMakl3TXpFeU5Td2llU0k2TVRVd0xqVXpNalEzT0RJMU1UQXdPRGw5TEhzaWVDSTZORGc1TGpjd016RXlOU3dpZVNJNk1UVXhMamc1T1RrNU9UWXhPRFV6TURJM2ZTeDdJbmdpT2pVek5TNDJOREEyTWpVc0lua2lPakUxTVM0NE9UazVPVGsyTVRnMU16QXlOMzFkIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTcxMi4wOTQsMzguNUw3MTUuMDEsMzguNUM3MTcuOTI3LDM4LjUsNzIzLjc2LDM4LjUsNzM1LjgzNywzOC41Qzc0Ny45MTQsMzguNSw3NjYuMjM0LDM4LjUsNzc1LjM5NSwzOC41TDc4NC41NTUsMzguNSIgaWQ9IkxfQ19FXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19FXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk56RXlMakE1TXpjMUxDSjVJam96T0M0MWZTeDdJbmdpT2pjeU9TNDFPVE0zTlN3aWVTSTZNemd1Tlgwc2V5SjRJam8zT0RndU5UVTBOamczTlN3aWVTSTZNemd1TlgxZCIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik02NzUuMzk0LDEzMS40TDY4NC40MjcsMTI4LjU4M0M2OTMuNDYsMTI1Ljc2Nyw3MTEuNTI3LDEyMC4xMzMsNzI0LjA4NiwxMTcuMzE3QzczNi42NDYsMTE0LjUsNzQzLjY5OCwxMTQuNSw3NDcuMjI0LDExNC41TDc1MC43NSwxMTQuNSIgaWQ9IkxfRF9GXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRF9GXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5qYzFMak01TXpnMk16UTNNVE00TnpVc0lua2lPakV6TVM0ek9UazVPVGsyTVRnMU16QXlOMzBzZXlKNElqbzNNamt1TlRrek56VXNJbmtpT2pFeE5DNDFmU3g3SW5naU9qYzFOQzQzTlN3aWVTSTZNVEUwTGpWOVhRPT0iIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNjc1LjM5NCwxNzIuNEw2ODQuNDI3LDE3NS4yMTdDNjkzLjQ2LDE3OC4wMzMsNzExLjUyNywxODMuNjY3LDcyMi44MSwxODYuNDgzQzczNC4wOTQsMTg5LjMsNzM4LjU5NCwxODkuMyw3NDAuODQ0LDE4OS4zTDc0My4wOTQsMTg5LjMiIGlkPSJMX0RfR18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfR18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOamMxTGpNNU16ZzJNelEzTVRNNE56VXNJbmtpT2pFM01pNHpPVGs1T1RrMk1UZzFNekF5TjMwc2V5SjRJam8zTWprdU5Ua3pOelVzSW5raU9qRTRPUzR5T1RrNU9Ua3lNemN3TmpBMU5YMHNleUo0SWpvM05EY3VNRGt6TnpVc0lua2lPakU0T1M0eU9UazVPVGt5TXpjd05qQTFOWDFkIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQxNy44NzUsMjguNUw0MjkuODQ2LDI4LjVDNDQxLjgxOCwyOC41LDQ2NS43NiwyOC41LDQ3OS45ODQsMjguNjg4QzQ5NC4yMDgsMjguODc2LDQ5OC43MTIsMjkuMjUxLDUwMC45NjUsMjkuNDM5TDUwMy4yMTcsMjkuNjI3IiBpZD0iTF9IX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9IX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRFM0xqZzNOU3dpZVNJNk1qZ3VOWDBzZXlKNElqbzBPRGt1TnpBek1USTFMQ0o1SWpveU9DNDFmU3g3SW5naU9qVXdOeTR5TURNeE1qVXNJbmtpT2pJNUxqazFPRGs1T0RJME1UTTROakExZlYwPSIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yMTYuMjY2LDEwOC45TDIxOS4xODIsMTA4LjlDMjIyLjA5OSwxMDguOSwyMjcuOTMyLDEwOC45LDIzOC4yODIsMTEwLjgxN0MyNDguNjMyLDExMi43MzQsMjYzLjQ5OSwxMTYuNTY3LDI3MC45MzIsMTE4LjQ4NEwyNzguMzY1LDEyMC40MDEiIGlkPSJMX0lfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0lfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNakUyTGpJMk5UWXlOU3dpZVNJNk1UQTRMamc1T1RrNU9UWXhPRFV6TURJM2ZTeDdJbmdpT2pJek15NDNOalUyTWpVc0lua2lPakV3T0M0NE9UazVPVGsyTVRnMU16QXlOMzBzZXlKNElqb3lPREl1TWpNNE5qTTJNell6TmpNMk5Dd2llU0k2TVRJeExqTTVPVGs1T1RZeE9EVXpNREkzZlYwPSIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik05OTguOTUzLDExNC41TDEwMDMuMTQ2LDExNC41QzEwMDcuMzM5LDExNC41LDEwMTUuNzI0LDExNC41LDEwMjIuMTY3LDExNC41QzEwMjguNjA5LDExNC41LDEwMzMuMTA5LDExNC41LDEwMzUuMzU5LDExNC41TDEwMzcuNjA5LDExNC41IiBpZD0iTF9GX0pfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9GX0pfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2T1RrNExqazFNekV5TlN3aWVTSTZNVEUwTGpWOUxIc2llQ0k2TVRBeU5DNHhNRGt6TnpVc0lua2lPakV4TkM0MWZTeDdJbmdpT2pFd05ERXVOakE1TXpjMUxDSjVJam94TVRRdU5YMWQiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTAwNi42MDksMTg5LjNMMTAwOS41MjYsMTg5LjNDMTAxMi40NDMsMTg5LjMsMTAxOC4yNzYsMTg5LjMsMTAyNi44MTUsMTg5LjNDMTAzNS4zNTQsMTg5LjMsMTA0Ni41OTksMTg5LjMsMTA1Mi4yMjEsMTg5LjNMMTA1Ny44NDQsMTg5LjMiIGlkPSJMX0dfS18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0dfS18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEF3Tmk0Mk1Ea3pOelVzSW5raU9qRTRPUzR5T1RrNU9Ua3lNemN3TmpBMU5YMHNleUo0SWpveE1ESTBMakV3T1RNM05Td2llU0k2TVRnNUxqSTVPVGs1T1RJek56QTJNRFUxZlN4N0luZ2lPakV3TmpFdU9EUXpOelVzSW5raU9qRTRPUzR5T1RrNU9Ua3lNemN3TmpBMU5YMWQiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9DX0VfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9EX0ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9EX0dfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9IX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9JX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9GX0pfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9HX0tfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjx0ZXh0IHk9Ii0xMC4xIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSIgdGV4dC1hbmNob3I9Im1pZGRsZSIvPjwvdGV4dD48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJmbG93Y2hhcnQtQS0wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMTIuMTMyODEyNSwgMTc0Ljg5OTk5OTYxODUzMDI3KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTgxLjM5MDYyNSIgeT0iLTIwLjUiIHdpZHRoPSIxNjIuNzgxMjUiIGhlaWdodD0iNDEiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIC01LjUpIj48cmVjdC8+PGc+PHJlY3QgY2xhc3M9ImJhY2tncm91bmQiIHN0eWxlPSJzdHJva2U6IG5vbmUiLz48dGV4dCB5PSItMTAuMSIgc3R5bGU9IiI+PHRzcGFuIGNsYXNzPSJ0ZXh0LW91dGVyLXRzcGFuIHJvdyIgeD0iMCIgeT0iLTAuMWVtIiBkeT0iMS4xZW0iPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj5kb21haW48L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gb3I8L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gYWRtaW48L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gY29udHJvbGxlcjwvdHNwYW4+PC90c3Bhbj48L3RleHQ+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9ImZsb3djaGFydC1CLTEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDM2MS43MzQzNzUsIDE0MS44OTk5OTk2MTg1MzAyNykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMTAuNDY4NzUiIHk9Ii0yMC41IiB3aWR0aD0iMjIwLjkzNzUiIGhlaWdodD0iNDEiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIC01LjUpIj48cmVjdC8+PGc+PHJlY3QgY2xhc3M9ImJhY2tncm91bmQiIHN0eWxlPSJzdHJva2U6IG5vbmUiLz48dGV4dCB5PSItMTAuMSIgc3R5bGU9IiI+PHRzcGFuIGNsYXNzPSJ0ZXh0LW91dGVyLXRzcGFuIHJvdyIgeD0iMCIgeT0iLTAuMWVtIiBkeT0iMS4xZW0iPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj5Bc3luY09wZXJhdGlvblNlcnZpY2VJbXBsPC90c3Bhbj48dHNwYW4gZm9udC1zdHlsZT0ibm9ybWFsIiBjbGFzcz0idGV4dC1pbm5lci10c3BhbiIgZm9udC13ZWlnaHQ9Im5vcm1hbCI+IHN1Ym1pdC9wdWJsaXNoPC90c3Bhbj48L3RzcGFuPjwvdGV4dD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0iZmxvd2NoYXJ0LUMtMyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjA5LjY0ODQzNzUsIDM4LjUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTAyLjQ0NTMxMjUiIHk9Ii0yMC41IiB3aWR0aD0iMjA0Ljg5MDYyNSIgaGVpZ2h0PSI0MSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgLTUuNSkiPjxyZWN0Lz48Zz48cmVjdCBjbGFzcz0iYmFja2dyb3VuZCIgc3R5bGU9InN0cm9rZTogbm9uZSIvPjx0ZXh0IHk9Ii0xMC4xIiBzdHlsZT0iIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSI+PHRzcGFuIGZvbnQtc3R5bGU9Im5vcm1hbCIgY2xhc3M9InRleHQtaW5uZXItdHNwYW4iIGZvbnQtd2VpZ2h0PSJub3JtYWwiPkFTWU5DX09QRVJBVElPTl9SVU5TPC90c3Bhbj48dHNwYW4gZm9udC1zdHlsZT0ibm9ybWFsIiBjbGFzcz0idGV4dC1pbm5lci10c3BhbiIgZm9udC13ZWlnaHQ9Im5vcm1hbCI+IGR1cmFibGU8L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gcm93PC90c3Bhbj48L3RzcGFuPjwvdGV4dD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0iZmxvd2NoYXJ0LUQtNSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjA5LjY0ODQzNzUsIDE1MS44OTk5OTk2MTg1MzAyNykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03NC4wMDc4MTI1IiB5PSItMjAuNSIgd2lkdGg9IjE0OC4wMTU2MjUiIGhlaWdodD0iNDEiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIC01LjUpIj48cmVjdC8+PGc+PHJlY3QgY2xhc3M9ImJhY2tncm91bmQiIHN0eWxlPSJzdHJva2U6IG5vbmUiLz48dGV4dCB5PSItMTAuMSIgc3R5bGU9IiI+PHRzcGFuIGNsYXNzPSJ0ZXh0LW91dGVyLXRzcGFuIHJvdyIgeD0iMCIgeT0iLTAuMWVtIiBkeT0iMS4xZW0iPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj5pbi1tZW1vcnk8L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gYnJvYWRjYXN0ZXI8L3RzcGFuPjwvdHNwYW4+PC90ZXh0PjwvZz48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJmbG93Y2hhcnQtRS03IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4NzYuODUxNTYyNSwgMzguNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii04OC4yOTY4NzUiIHk9Ii0yMC41IiB3aWR0aD0iMTc2LjU5Mzc1IiBoZWlnaHQ9IjQxIi8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAtNS41KSI+PHJlY3QvPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PHRleHQgeT0iLTEwLjEiIHN0eWxlPSIiPjx0c3BhbiBjbGFzcz0idGV4dC1vdXRlci10c3BhbiByb3ciIHg9IjAiIHk9Ii0wLjFlbSIgZHk9IjEuMWVtIj48dHNwYW4gZm9udC1zdHlsZT0ibm9ybWFsIiBjbGFzcz0idGV4dC1pbm5lci10c3BhbiIgZm9udC13ZWlnaHQ9Im5vcm1hbCI+R0VUPC90c3Bhbj48dHNwYW4gZm9udC1zdHlsZT0ibm9ybWFsIiBjbGFzcz0idGV4dC1pbm5lci10c3BhbiIgZm9udC13ZWlnaHQ9Im5vcm1hbCI+IC9hcHAvYXN5bmMtb3BlcmF0aW9ucy97aWR9PC90c3Bhbj48L3RzcGFuPjwvdGV4dD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0iZmxvd2NoYXJ0LUYtOSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoODc2Ljg1MTU2MjUsIDExNC41KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTEyMi4xMDE1NjI1IiB5PSItMjAuNSIgd2lkdGg9IjI0NC4yMDMxMjUiIGhlaWdodD0iNDEiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIC01LjUpIj48cmVjdC8+PGc+PHJlY3QgY2xhc3M9ImJhY2tncm91bmQiIHN0eWxlPSJzdHJva2U6IG5vbmUiLz48dGV4dCB5PSItMTAuMSIgc3R5bGU9IiI+PHRzcGFuIGNsYXNzPSJ0ZXh0LW91dGVyLXRzcGFuIHJvdyIgeD0iMCIgeT0iLTAuMWVtIiBkeT0iMS4xZW0iPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj5HRVQ8L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gL2FwcC1zdHJlYW1pbmcvYXN5bmMtb3BlcmF0aW9ucy97aWR9L2V2ZW50czwvdHNwYW4+PC90c3Bhbj48L3RleHQ+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9ImZsb3djaGFydC1HLTExIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4NzYuODUxNTYyNSwgMTg5LjI5OTk5OTIzNzA2MDU1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTEyOS43NTc4MTI1IiB5PSItMjkuMjk5OTk5MjM3MDYwNTQ3IiB3aWR0aD0iMjU5LjUxNTYyNSIgaGVpZ2h0PSI1OC41OTk5OTg0NzQxMjEwOTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIC0xNC4yOTk5OTkyMzcwNjA1NDcpIj48cmVjdC8+PGc+PHJlY3QgY2xhc3M9ImJhY2tncm91bmQiIHN0eWxlPSJzdHJva2U6IG5vbmUiLz48dGV4dCB5PSItMTAuMSIgc3R5bGU9IiI+PHRzcGFuIGNsYXNzPSJ0ZXh0LW91dGVyLXRzcGFuIHJvdyIgeD0iMCIgeT0iLTAuMWVtIiBkeT0iMS4xZW0iPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj5HRVQ8L3RzcGFuPjwvdHNwYW4+PHRzcGFuIGNsYXNzPSJ0ZXh0LW91dGVyLXRzcGFuIHJvdyIgeD0iMCIgeT0iMWVtIiBkeT0iMS4xZW0iPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4vYXBwLXN0cmVhbWluZy9hc3luYy1vcGVyYXRpb25zL2V2ZW50cz90b3BpY1ByZWZpeD08L3RzcGFuPjwvdHNwYW4+PHRzcGFuIGNsYXNzPSJ0ZXh0LW91dGVyLXRzcGFuIHJvdyIgeD0iMCIgeT0iMi4xZW0iIGR5PSIxLjFlbSI+PHRzcGFuIGZvbnQtc3R5bGU9Im5vcm1hbCIgY2xhc3M9InRleHQtaW5uZXItdHNwYW4iIGZvbnQtd2VpZ2h0PSJub3JtYWwiPi4uLjwvdHNwYW4+PC90c3Bhbj48L3RleHQ+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9ImZsb3djaGFydC1ILTEyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNjEuNzM0Mzc1LCAyOC41KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTU2LjE0MDYyNSIgeT0iLTIwLjUiIHdpZHRoPSIxMTIuMjgxMjUiIGhlaWdodD0iNDEiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIC01LjUpIj48cmVjdC8+PGc+PHJlY3QgY2xhc3M9ImJhY2tncm91bmQiIHN0eWxlPSJzdHJva2U6IG5vbmUiLz48dGV4dCB5PSItMTAuMSIgc3R5bGU9IiI+PHRzcGFuIGNsYXNzPSJ0ZXh0LW91dGVyLXRzcGFuIHJvdyIgeD0iMCIgeT0iLTAuMWVtIiBkeT0iMS4xZW0iPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj5ob3VzZWtlZXBpbmc8L3RzcGFuPjwvdHNwYW4+PC90ZXh0PjwvZz48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJmbG93Y2hhcnQtSS0xNCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTEyLjEzMjgxMjUsIDEwOC44OTk5OTk2MTg1MzAyNykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMDQuMTMyODEyNSIgeT0iLTIwLjUiIHdpZHRoPSIyMDguMjY1NjI1IiBoZWlnaHQ9IjQxIi8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAtNS41KSI+PHJlY3QvPjxnPjxyZWN0IGNsYXNzPSJiYWNrZ3JvdW5kIiBzdHlsZT0ic3Ryb2tlOiBub25lIi8+PHRleHQgeT0iLTEwLjEiIHN0eWxlPSIiPjx0c3BhbiBjbGFzcz0idGV4dC1vdXRlci10c3BhbiByb3ciIHg9IjAiIHk9Ii0wLjFlbSIgZHk9IjEuMWVtIj48dHNwYW4gZm9udC1zdHlsZT0ibm9ybWFsIiBjbGFzcz0idGV4dC1pbm5lci10c3BhbiIgZm9udC13ZWlnaHQ9Im5vcm1hbCI+YmFja2VuZDwvdHNwYW4+PHRzcGFuIGZvbnQtc3R5bGU9Im5vcm1hbCIgY2xhc3M9InRleHQtaW5uZXItdHNwYW4iIGZvbnQtd2VpZ2h0PSJub3JtYWwiPiB3b3JrZXI8L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gb3I8L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gQlBNTi1mYWNpbmc8L3RzcGFuPjx0c3BhbiBmb250LXN0eWxlPSJub3JtYWwiIGNsYXNzPSJ0ZXh0LWlubmVyLXRzcGFuIiBmb250LXdlaWdodD0ibm9ybWFsIj4gc2VydmljZTwvdHNwYW4+PC90c3Bhbj48L3RleHQ+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9ImZsb3djaGFydC1KLTE3IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMTIxLjY5NTMxMjUsIDExNC41KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTgwLjA4NTkzNzUiIHk9Ii0yMC41IiB3aWR0aD0iMTYwLjE3MTg3NSIgaGVpZ2h0PSI0MSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgLTUuNSkiPjxyZWN0Lz48Zz48cmVjdCBjbGFzcz0iYmFja2dyb3VuZCIgc3R5bGU9InN0cm9rZTogbm9uZSIvPjx0ZXh0IHk9Ii0xMC4xIiBzdHlsZT0iIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSI+PHRzcGFuIGZvbnQtc3R5bGU9Im5vcm1hbCIgY2xhc3M9InRleHQtaW5uZXItdHNwYW4iIGZvbnQtd2VpZ2h0PSJub3JtYWwiPmV4YWN0LW9wZXJhdGlvbjwvdHNwYW4+PHRzcGFuIGZvbnQtc3R5bGU9Im5vcm1hbCIgY2xhc3M9InRleHQtaW5uZXItdHNwYW4iIGZvbnQtd2VpZ2h0PSJub3JtYWwiPiBzdWJzY3JpYmVyPC90c3Bhbj48L3RzcGFuPjwvdGV4dD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0iZmxvd2NoYXJ0LUstMTkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExMjEuNjk1MzEyNSwgMTg5LjI5OTk5OTIzNzA2MDU1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTU5Ljg1MTU2MjUiIHk9Ii0yMC41IiB3aWR0aD0iMTE5LjcwMzEyNSIgaGVpZ2h0PSI0MSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgLTUuNSkiPjxyZWN0Lz48Zz48cmVjdCBjbGFzcz0iYmFja2dyb3VuZCIgc3R5bGU9InN0cm9rZTogbm9uZSIvPjx0ZXh0IHk9Ii0xMC4xIiBzdHlsZT0iIj48dHNwYW4gY2xhc3M9InRleHQtb3V0ZXItdHNwYW4gcm93IiB4PSIwIiB5PSItMC4xZW0iIGR5PSIxLjFlbSI+PHRzcGFuIGZvbnQtc3R5bGU9Im5vcm1hbCIgY2xhc3M9InRleHQtaW5uZXItdHNwYW4iIGZvbnQtd2VpZ2h0PSJub3JtYWwiPnRvcGljPC90c3Bhbj48dHNwYW4gZm9udC1zdHlsZT0ibm9ybWFsIiBjbGFzcz0idGV4dC1pbm5lci10c3BhbiIgZm9udC13ZWlnaHQ9Im5vcm1hbCI+IHN1YnNjcmliZXI8L3RzcGFuPjwvdHNwYW4+PC90ZXh0PjwvZz48L2c+PC9nPjwvZz48L2c+PC9nPjwvc3ZnPg==" alt="diagram" class="mermaid-diagram" style="width: 484px" /></p>
<div id="figure-11-1" class="figure-caption figure-kind-diagram">Figure 11.1: Async-operation runtime topology from start endpoint to durable state and SSE subscribers</div>
</div>
<p><a class="object-xref" href="#figure-11-1">Figure 11.1</a> captures
the runtime split that matters most in practice:</p>
<ul>
<li>the database stores authoritative operation state</li>
<li>the broadcaster only stores live listeners and the latest volatile
event per operation</li>
<li>the polling endpoint and the streaming endpoint read from the same
logical operation model</li>
<li>housekeeping owns expiry and stale-operation cleanup rather than
each caller inventing its own retention logic</li>
</ul>
</section>
<section id="responsibility-boundaries" class="level2" data-number="11.4">
<h2 data-number="11.4"><span class="header-section-number">11.4</span>
Responsibility Boundaries</h2>
<div class="captioned-block captioned-table">
<style>
#table-11-1 + p, #table-11-1 + div.sourceCode, #table-11-1 + pre, #table-11-1 + table { display: block; max-width: 100%; }
#table-11-1 + p img { display: block; max-width: 100%; height: auto; }
</style>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Main responsibility</th>
</tr>
</thead>
<tbody>
<tr>
<td>controller <code>app</code></td>
<td>return the current durable snapshot for one operation</td>
</tr>
<tr>
<td>controller <code>app-streaming</code></td>
<td>expose <code>text/event-stream</code> endpoints, heartbeats, SSE
ids, and reconnect headers</td>
</tr>
<tr>
<td><code>AsyncOperationStreamingService</code></td>
<td>keep controllers thin by resolving operation or topic
subscriptions</td>
</tr>
<tr>
<td><code>AsyncOperationServiceImpl</code></td>
<td>durable submit/update/fail/complete logic, deduplication,
revisioning, retention calculation, and after-commit publication</td>
</tr>
<tr>
<td><code>AsyncOperationBroadcaster</code></td>
<td>node-local in-memory fan-out to exact-operation and topic-prefix
listeners</td>
</tr>
<tr>
<td>persistence</td>
<td>store one current-state row per operation instead of a full event
log</td>
</tr>
<tr>
<td>housekeeping</td>
<td>mark stale active operations failed and purge expired terminal
operations</td>
</tr>
</tbody>
</table>
<div id="table-11-1" class="figure-caption figure-kind-table">Table 11.1: Async-operation runtime responsibilities by layer</div>
</div>
<p>This layering is intentional. Controllers stay transport-focused.
Policy such as deduplication, retention, topic construction, and event
classification stays below the HTTP layer.</p>
</section>
<section id="stream-model" class="level2" data-number="11.5">
<h2 data-number="11.5"><span class="header-section-number">11.5</span>
Stream Model</h2>
<p>The stream contract is explicit and strongly typed.</p>
<div class="captioned-block captioned-code">
<style>
#code-11-1 + p, #code-11-1 + div.sourceCode, #code-11-1 + pre, #code-11-1 + table { display: block; max-width: 100%; }
#code-11-1 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb50"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb50-1"><a href="#cb50-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="kw">class</span> AsyncOperationSnapshot<span class="op">(</span></span>
<span id="cb50-2"><a href="#cb50-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb50-3"><a href="#cb50-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">topic</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb50-4"><a href="#cb50-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">operationKey</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb50-5"><a href="#cb50-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">status</span><span class="op">:</span> <span class="dt">AsyncOperationStatus</span><span class="op">,</span></span>
<span id="cb50-6"><a href="#cb50-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">submittedBy</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb50-7"><a href="#cb50-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">submittedAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">,</span></span>
<span id="cb50-8"><a href="#cb50-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">updatedAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">,</span></span>
<span id="cb50-9"><a href="#cb50-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">startedAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">?,</span></span>
<span id="cb50-10"><a href="#cb50-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">endedAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">?,</span></span>
<span id="cb50-11"><a href="#cb50-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">timings</span><span class="op">:</span> <span class="dt">AsyncOperationTimings</span><span class="op">,</span></span>
<span id="cb50-12"><a href="#cb50-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">revision</span><span class="op">:</span> <span class="dt">Long</span><span class="op">,</span></span>
<span id="cb50-13"><a href="#cb50-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">requestHash</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb50-14"><a href="#cb50-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">retentionMode</span><span class="op">:</span> <span class="dt">AsyncOperationRetentionMode</span><span class="op">,</span></span>
<span id="cb50-15"><a href="#cb50-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">retentionUntil</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">?,</span></span>
<span id="cb50-16"><a href="#cb50-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb50-17"><a href="#cb50-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb50-18"><a href="#cb50-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">errorCode</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb50-19"><a href="#cb50-19" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">errorMessage</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb50-20"><a href="#cb50-20" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">processedCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">,</span></span>
<span id="cb50-21"><a href="#cb50-21" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">successCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">,</span></span>
<span id="cb50-22"><a href="#cb50-22" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">failureCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">,</span></span>
<span id="cb50-23"><a href="#cb50-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">attributes</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">String</span>&gt;<span class="op">,</span></span>
<span id="cb50-24"><a href="#cb50-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">referenceIds</span><span class="op">:</span> <span class="dt">List</span>&lt;<span class="va">String</span>&gt;<span class="op">,</span></span>
<span id="cb50-25"><a href="#cb50-25" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt;<span class="op">,</span></span>
<span id="cb50-26"><a href="#cb50-26" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt;</span>
<span id="cb50-27"><a href="#cb50-27" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span>
<span id="cb50-28"><a href="#cb50-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb50-29"><a href="#cb50-29" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="kw">class</span> AsyncOperationTimings<span class="op">(</span></span>
<span id="cb50-30"><a href="#cb50-30" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">queueWaitMillis</span><span class="op">:</span> <span class="dt">Long</span><span class="op">?,</span></span>
<span id="cb50-31"><a href="#cb50-31" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">executionMillis</span><span class="op">:</span> <span class="dt">Long</span><span class="op">?,</span></span>
<span id="cb50-32"><a href="#cb50-32" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">totalMillis</span><span class="op">:</span> <span class="dt">Long</span><span class="op">?</span></span>
<span id="cb50-33"><a href="#cb50-33" aria-hidden="true" tabindex="-1"></a><span class="op">)</span> <span class="op">{</span></span>
<span id="cb50-34"><a href="#cb50-34" aria-hidden="true" tabindex="-1"></a> <span class="kw">companion</span> <span class="kw">object</span> <span class="op">{</span></span>
<span id="cb50-35"><a href="#cb50-35" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">of</span><span class="op">(</span></span>
<span id="cb50-36"><a href="#cb50-36" aria-hidden="true" tabindex="-1"></a> <span class="va">submittedAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">,</span></span>
<span id="cb50-37"><a href="#cb50-37" aria-hidden="true" tabindex="-1"></a> <span class="va">startedAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">?,</span></span>
<span id="cb50-38"><a href="#cb50-38" aria-hidden="true" tabindex="-1"></a> <span class="va">endedAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">?</span></span>
<span id="cb50-39"><a href="#cb50-39" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationTimings</span> <span class="op">=</span></span>
<span id="cb50-40"><a href="#cb50-40" aria-hidden="true" tabindex="-1"></a> AsyncOperationTimings<span class="op">(</span></span>
<span id="cb50-41"><a href="#cb50-41" aria-hidden="true" tabindex="-1"></a> queueWaitMillis <span class="op">=</span> startedAt<span class="op">?.</span>let <span class="op">{</span> durationMillis<span class="op">(</span>submittedAt<span class="op">,</span> it<span class="op">)</span> <span class="op">},</span></span>
<span id="cb50-42"><a href="#cb50-42" aria-hidden="true" tabindex="-1"></a> executionMillis <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>startedAt <span class="op">!=</span> <span class="kw">null</span> <span class="op">&amp;&amp;</span> endedAt <span class="op">!=</span> <span class="kw">null</span><span class="op">)</span> durationMillis<span class="op">(</span>startedAt<span class="op">,</span> endedAt<span class="op">)</span> <span class="cf">else</span> <span class="kw">null</span><span class="op">,</span></span>
<span id="cb50-43"><a href="#cb50-43" aria-hidden="true" tabindex="-1"></a> totalMillis <span class="op">=</span> endedAt<span class="op">?.</span>let <span class="op">{</span> durationMillis<span class="op">(</span>submittedAt<span class="op">,</span> it<span class="op">)</span> <span class="op">}</span></span>
<span id="cb50-44"><a href="#cb50-44" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb50-45"><a href="#cb50-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb50-46"><a href="#cb50-46" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">durationMillis</span><span class="op">(</span><span class="va">from</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">,</span> <span class="va">to</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">):</span> <span class="dt">Long</span> <span class="op">=</span></span>
<span id="cb50-47"><a href="#cb50-47" aria-hidden="true" tabindex="-1"></a> Duration<span class="op">.</span>between<span class="op">(</span>from<span class="op">,</span> to<span class="op">).</span>toMillis<span class="op">().</span>coerceAtLeast<span class="op">(</span><span class="dv">0</span><span class="op">)</span></span>
<span id="cb50-48"><a href="#cb50-48" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb50-49"><a href="#cb50-49" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb50-50"><a href="#cb50-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb50-51"><a href="#cb50-51" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="kw">class</span> AsyncOperationVolatileUpdate<span class="op">(</span></span>
<span id="cb50-52"><a href="#cb50-52" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb50-53"><a href="#cb50-53" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">topic</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb50-54"><a href="#cb50-54" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">baseRevision</span><span class="op">:</span> <span class="dt">Long</span><span class="op">,</span></span>
<span id="cb50-55"><a href="#cb50-55" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">sequence</span><span class="op">:</span> <span class="dt">Long</span><span class="op">,</span></span>
<span id="cb50-56"><a href="#cb50-56" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">publishedAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">,</span></span>
<span id="cb50-57"><a href="#cb50-57" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb50-58"><a href="#cb50-58" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb50-59"><a href="#cb50-59" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">processedCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?,</span></span>
<span id="cb50-60"><a href="#cb50-60" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">successCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?,</span></span>
<span id="cb50-61"><a href="#cb50-61" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">failureCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?,</span></span>
<span id="cb50-62"><a href="#cb50-62" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt;<span class="op">,</span></span>
<span id="cb50-63"><a href="#cb50-63" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt;</span>
<span id="cb50-64"><a href="#cb50-64" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span>
<span id="cb50-65"><a href="#cb50-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb50-66"><a href="#cb50-66" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="kw">class</span> AsyncOperationStreamEvent<span class="op">(</span></span>
<span id="cb50-67"><a href="#cb50-67" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">eventType</span><span class="op">:</span> <span class="dt">AsyncOperationEventType</span><span class="op">,</span></span>
<span id="cb50-68"><a href="#cb50-68" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">topic</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb50-69"><a href="#cb50-69" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">snapshot</span><span class="op">:</span> <span class="dt">AsyncOperationSnapshot</span><span class="op">,</span></span>
<span id="cb50-70"><a href="#cb50-70" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">volatileUpdate</span><span class="op">:</span> <span class="dt">AsyncOperationVolatileUpdate</span><span class="op">?</span> <span class="op">=</span> null</span>
<span id="cb50-71"><a href="#cb50-71" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span>
<span id="cb50-72"><a href="#cb50-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb50-73"><a href="#cb50-73" aria-hidden="true" tabindex="-1"></a><span class="kw">fun</span> AsyncOperationSnapshot.toStreamEvent<span class="op">():</span> <span class="dt">AsyncOperationStreamEvent</span> =</span>
<span id="cb50-74"><a href="#cb50-74" aria-hidden="true" tabindex="-1"></a> <span class="dt">AsyncOperationStreamEvent</span><span class="op">(</span></span>
<span id="cb50-75"><a href="#cb50-75" aria-hidden="true" tabindex="-1"></a> <span class="va">eventType</span> <span class="op">=</span> AsyncOperationEventType<span class="op">.</span>fromSnapshot<span class="op">(</span>this<span class="op">),</span></span>
<span id="cb50-76"><a href="#cb50-76" aria-hidden="true" tabindex="-1"></a> <span class="va">topic</span> <span class="op">=</span> topic<span class="op">,</span></span>
<span id="cb50-77"><a href="#cb50-77" aria-hidden="true" tabindex="-1"></a> <span class="va">snapshot</span> <span class="op">=</span> this</span>
<span id="cb50-78"><a href="#cb50-78" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span></code></pre></div>
<div id="code-11-1" class="figure-caption figure-kind-insert">Code 11.1: AsyncOperationSnapshot is the durable contract, AsyncOperationVolatileUpdate is the in-memory high-frequency overlay, AsyncOperationTimings is the lightweight derived timing view, and AsyncOperationStreamEvent is the envelope sent to SSE subscribers.</div>
</div>
<div class="captioned-block captioned-code">
<style>
#code-11-2 + p, #code-11-2 + div.sourceCode, #code-11-2 + pre, #code-11-2 + table { display: block; max-width: 100%; }
#code-11-2 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb51"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb51-1"><a href="#cb51-1" aria-hidden="true" tabindex="-1"></a><span class="kw">enum</span> <span class="kw">class</span> AsyncOperationEventType<span class="op">(</span><span class="kw">val</span> <span class="va">sseEventName</span><span class="op">:</span> <span class="dt">String</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb51-2"><a href="#cb51-2" aria-hidden="true" tabindex="-1"></a> SNAPSHOT<span class="op">(</span><span class="st">&quot;snapshot&quot;</span><span class="op">),</span></span>
<span id="cb51-3"><a href="#cb51-3" aria-hidden="true" tabindex="-1"></a> PROGRESS<span class="op">(</span><span class="st">&quot;progress&quot;</span><span class="op">),</span></span>
<span id="cb51-4"><a href="#cb51-4" aria-hidden="true" tabindex="-1"></a> VOLATILE_PROGRESS<span class="op">(</span><span class="st">&quot;volatile-progress&quot;</span><span class="op">),</span></span>
<span id="cb51-5"><a href="#cb51-5" aria-hidden="true" tabindex="-1"></a> COMPLETED<span class="op">(</span><span class="st">&quot;completed&quot;</span><span class="op">),</span></span>
<span id="cb51-6"><a href="#cb51-6" aria-hidden="true" tabindex="-1"></a> FAILED<span class="op">(</span><span class="st">&quot;failed&quot;</span><span class="op">),</span></span>
<span id="cb51-7"><a href="#cb51-7" aria-hidden="true" tabindex="-1"></a> HEARTBEAT<span class="op">(</span><span class="st">&quot;heartbeat&quot;</span><span class="op">);</span></span>
<span id="cb51-8"><a href="#cb51-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb51-9"><a href="#cb51-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">companion</span> <span class="kw">object</span> <span class="op">{</span></span>
<span id="cb51-10"><a href="#cb51-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">fromSnapshot</span><span class="op">(</span><span class="va">snapshot</span><span class="op">:</span> <span class="dt">AsyncOperationSnapshot</span><span class="op">):</span> <span class="dt">AsyncOperationEventType</span> <span class="op">=</span></span>
<span id="cb51-11"><a href="#cb51-11" aria-hidden="true" tabindex="-1"></a> <span class="cf">when</span> <span class="op">{</span></span>
<span id="cb51-12"><a href="#cb51-12" aria-hidden="true" tabindex="-1"></a> snapshot<span class="op">.</span>status <span class="op">==</span> AsyncOperationStatus<span class="op">.</span>FAILED <span class="op">-&gt;</span> FAILED</span>
<span id="cb51-13"><a href="#cb51-13" aria-hidden="true" tabindex="-1"></a> snapshot<span class="op">.</span>endedAt <span class="op">!=</span> <span class="kw">null</span> <span class="op">-&gt;</span> COMPLETED</span>
<span id="cb51-14"><a href="#cb51-14" aria-hidden="true" tabindex="-1"></a> snapshot<span class="op">.</span>startedAt <span class="op">==</span> <span class="kw">null</span> <span class="op">-&gt;</span> SNAPSHOT</span>
<span id="cb51-15"><a href="#cb51-15" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span> <span class="op">-&gt;</span> PROGRESS</span>
<span id="cb51-16"><a href="#cb51-16" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb51-17"><a href="#cb51-17" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div>
<div id="code-11-2" class="figure-caption figure-kind-insert">Code 11.2: AsyncOperationEventType is a curated enum rather than a free-form string surface. It distinguishes durable snapshots from volatile progress and heartbeat transport events.</div>
</div>
<p>The important semantic split is:</p>
<ul>
<li><code>AsyncOperationSnapshot</code>: durable, revisioned,
reconnect-safe current truth</li>
<li><code>AsyncOperationVolatileUpdate</code>: best-effort in-memory
progress overlay that does not touch the database</li>
<li><code>AsyncOperationStreamEvent</code>: one envelope that works for
both exact-operation and topic subscribers</li>
</ul>
<p>That means the UI only needs one subscriber model:</p>
<ul>
<li>trust <code>snapshot</code> as the durable source of truth</li>
<li>optionally overlay <code>volatileUpdate</code> for smoother live
progress</li>
<li>treat <code>eventType</code> as rendering guidance, not as the only
state source</li>
</ul>
</section>
<section id="snapshot-ownership-and-extension-fields" class="level2" data-number="11.6">
<h2 data-number="11.6"><span class="header-section-number">11.6</span>
Snapshot Ownership And Extension Fields</h2>
<p><code>AsyncOperationSnapshot</code> is a read model, not an input
DTO. A caller does not fill every field manually. The framework
assembles the full snapshot from lifecycle bookkeeping plus the business
updates published by the caller.</p>
<p>Framework-owned fields are the generic runtime bookkeeping:</p>
<ul>
<li><code>operationId</code></li>
<li><code>topic</code></li>
<li><code>status</code></li>
<li><code>submittedAt</code></li>
<li><code>updatedAt</code></li>
<li><code>startedAt</code></li>
<li><code>endedAt</code></li>
<li><code>revision</code></li>
<li><code>requestHash</code> when the caller does not provide an
explicit one</li>
<li><code>retentionUntil</code> when the caller does not provide an
explicit one</li>
</ul>
<p>Caller- or domain-owned fields are the business-facing parts of the
operation:</p>
<ul>
<li><code>operationKey</code></li>
<li><code>submittedBy</code></li>
<li><code>retentionMode</code></li>
<li><code>phase</code></li>
<li><code>summary</code></li>
<li><code>errorCode</code></li>
<li><code>errorMessage</code></li>
<li><code>processedCount</code></li>
<li><code>successCount</code></li>
<li><code>failureCount</code></li>
<li><code>attributes</code></li>
<li><code>referenceIds</code></li>
<li><code>payload</code></li>
<li><code>runtimeContext</code></li>
</ul>
<p>The practical rule is:</p>
<ul>
<li>the caller describes the business operation and publishes relevant
business updates</li>
<li>the framework owns lifecycle state, revisioning, timestamps, topic
derivation, and retention resolution</li>
</ul>
<p>For per-process or per-job detail, use the fields with these
semantics:</p>
<ul>
<li><code>runtimeContext</code> Use for live or evolving runtime detail
such as current batch, nested step information, process-local
diagnostics, or extra context the UI should see while the operation is
still running.</li>
<li><code>payload</code> Use for result or output detail such as
generated file ids, export summary data, or final outcome metadata.</li>
<li>top-level fields such as <code>phase</code>, <code>summary</code>,
and the counters Use for the small cross-cutting signals the generic UI
should always understand without knowing the operation-specific
schema.</li>
</ul>
<p>In short:</p>
<ul>
<li><code>runtimeContext</code> means &quot;what is going on right now&quot;</li>
<li><code>payload</code> means &quot;what came out of this operation&quot;</li>
</ul>
<p>The lightweight timing guidance follows the same principle. The
snapshot now exposes derived <code>timings</code> built from the
timestamps the framework already owns:</p>
<ul>
<li><code>queueWaitMillis</code> Time from <code>submittedAt</code> to
<code>startedAt</code></li>
<li><code>executionMillis</code> Time from <code>startedAt</code> to
<code>endedAt</code></li>
<li><code>totalMillis</code> Time from <code>submittedAt</code> to
<code>endedAt</code></li>
</ul>
<p>Those timings are intentionally lightweight:</p>
<ul>
<li>no extra table</li>
<li>no persisted event history</li>
<li>no per-phase replay log</li>
</ul>
<p>They are useful for generic operation-level instrumentation and UI
inspection. If a caller later needs detailed per-phase timings, that
richer detail should go into <code>runtimeContext</code> or into a
dedicated domain timing service rather than expanding the shared
async-operation core into a history engine.</p>
</section>
<section id="ui-richness-levels" class="level2" data-number="11.7">
<h2 data-number="11.7"><span class="header-section-number">11.7</span>
UI Richness Levels</h2>
<p>One benefit of the current contract is that the UI can start simple
and become richer over time without asking the backend for a new async
model each time.</p>
<section id="level-1-minimal-job-card" class="level3" data-number="11.7.1">
<h3 data-number="11.7.1"><span class="header-section-number">11.7.1</span> Level 1: Minimal Job
Card</h3>
<p>Use only the generic cross-cutting fields:</p>
<ul>
<li><code>status</code></li>
<li><code>phase</code></li>
<li><code>summary</code></li>
</ul>
<p>Example:</p>
<ul>
<li>title: &quot;Customer export&quot;</li>
<li>status badge from <code>status</code></li>
<li>subtitle from <code>summary ?: phase</code></li>
</ul>
<p>That is enough for compact list views, notification trays, or admin
dashboards that only need to answer &quot;what is happening?&quot;</p>
</section>
<section id="level-2-progress-oriented-job-view" class="level3" data-number="11.7.2">
<h3 data-number="11.7.2"><span class="header-section-number">11.7.2</span> Level 2: Progress-Oriented
Job View</h3>
<p>Add the generic counters:</p>
<ul>
<li><code>processedCount</code></li>
<li><code>successCount</code></li>
<li><code>failureCount</code></li>
</ul>
<p>Example:</p>
<ul>
<li>progress label: <code>&quot;8,400 processed&quot;</code></li>
<li>success/error split: <code>&quot;8,350 succeeded, 50 failed&quot;</code></li>
<li>progress bar derived from <code>processedCount</code> plus a total
stored in <code>runtimeContext</code></li>
</ul>
<p>This level works well for import/export detail pages and operational
maintenance screens.</p>
</section>
<section id="level-3-timing-aware-operation-view" class="level3" data-number="11.7.3">
<h3 data-number="11.7.3"><span class="header-section-number">11.7.3</span> Level 3: Timing-Aware
Operation View</h3>
<p>Add the derived <code>timings</code> block:</p>
<ul>
<li><code>queueWaitMillis</code></li>
<li><code>executionMillis</code></li>
<li><code>totalMillis</code></li>
</ul>
<p>Example:</p>
<ul>
<li>queue wait: <code>&quot;Started after 2.1s in queue&quot;</code></li>
<li>execution time: <code>&quot;Running for 43s&quot;</code></li>
<li>total duration after completion:
<code>&quot;Completed in 45s total&quot;</code></li>
</ul>
<p>This level gives operators and users a useful sense of flow and
responsiveness without requiring a detailed phase history.</p>
</section>
<section id="level-4-context-rich-live-operation-view" class="level3" data-number="11.7.4">
<h3 data-number="11.7.4"><span class="header-section-number">11.7.4</span> Level 4: Context-Rich Live
Operation View</h3>
<p>Add <code>runtimeContext</code> for process-specific live detail.</p>
<p>Example runtime context for an export:</p>
<div class="sourceCode" id="cb52"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb52-1"><a href="#cb52-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb52-2"><a href="#cb52-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;export&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb52-3"><a href="#cb52-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;currentBatch&quot;</span><span class="fu">:</span> <span class="dv">12</span><span class="fu">,</span></span>
<span id="cb52-4"><a href="#cb52-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;totalBatches&quot;</span><span class="fu">:</span> <span class="dv">40</span><span class="fu">,</span></span>
<span id="cb52-5"><a href="#cb52-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;currentStep&quot;</span><span class="fu">:</span> <span class="st">&quot;collect-signatures&quot;</span><span class="fu">,</span></span>
<span id="cb52-6"><a href="#cb52-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;currentLegalEntity&quot;</span><span class="fu">:</span> <span class="st">&quot;LE-2048&quot;</span></span>
<span id="cb52-7"><a href="#cb52-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
<span id="cb52-8"><a href="#cb52-8" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>Possible UI rendering:</p>
<ul>
<li>current batch indicator</li>
<li>current sub-step label</li>
<li>&quot;currently processing legal entity LE-2048&quot;</li>
<li>expandable live diagnostics or operator context</li>
</ul>
<p>This is the right place for evolving job-local detail that should not
become a new top-level framework field.</p>
</section>
<section id="level-5-result-centric-terminal-view" class="level3" data-number="11.7.5">
<h3 data-number="11.7.5"><span class="header-section-number">11.7.5</span> Level 5: Result-Centric
Terminal View</h3>
<p>Add <code>payload</code> for output/result detail once the operation
is terminal.</p>
<p>Example payload for an export:</p>
<div class="sourceCode" id="cb53"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb53-1"><a href="#cb53-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb53-2"><a href="#cb53-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;fileId&quot;</span><span class="fu">:</span> <span class="st">&quot;7c2d...&quot;</span><span class="fu">,</span></span>
<span id="cb53-3"><a href="#cb53-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;fileName&quot;</span><span class="fu">:</span> <span class="st">&quot;customer-export-2026-04-11.zip&quot;</span><span class="fu">,</span></span>
<span id="cb53-4"><a href="#cb53-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;downloadSizeBytes&quot;</span><span class="fu">:</span> <span class="dv">18344219</span></span>
<span id="cb53-5"><a href="#cb53-5" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>Possible UI rendering:</p>
<ul>
<li>download button</li>
<li>file name and size</li>
<li>terminal result summary or follow-up actions</li>
</ul>
<p>This is the right place for &quot;what came out of the operation&quot; rather
than &quot;what is happening right now.&quot;</p>
</section>
<section id="level-6-smooth-live-motion" class="level3" data-number="11.7.6">
<h3 data-number="11.7.6"><span class="header-section-number">11.7.6</span> Level 6: Smooth Live
Motion</h3>
<p>Overlay <code>volatileUpdate</code> on top of the durable snapshot
for fast UI responsiveness.</p>
<p>Example:</p>
<ul>
<li>durable snapshot says
<code>phase = &quot;Collecting customers&quot;</code></li>
<li>volatile updates push <code>processedCount</code> every 250ms</li>
<li>UI animates progress smoothly without forcing every tick into the
database</li>
</ul>
<p>The important rule is:</p>
<ul>
<li>render from the durable <code>snapshot</code></li>
<li>use <code>volatileUpdate</code> only as a transient overlay</li>
<li>on reconnect, always trust the latest durable snapshot again</li>
</ul>
<p>That gives Sigma a progressive UI path:</p>
<ul>
<li>small generic job cards first</li>
<li>richer job detail pages later</li>
<li>live, low-latency operation views where they are actually worth the
complexity</li>
</ul>
</section>
</section>
<section id="publish-surface" class="level2" data-number="11.8">
<h2 data-number="11.8"><span class="header-section-number">11.8</span>
Publish Surface</h2>
<p>The publish contract is intentionally small.</p>
<div class="captioned-block captioned-code">
<style>
#code-11-3 + p, #code-11-3 + div.sourceCode, #code-11-3 + pre, #code-11-3 + table { display: block; max-width: 100%; }
#code-11-3 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb54"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb54-1"><a href="#cb54-1" aria-hidden="true" tabindex="-1"></a><span class="kw">interface</span> AsyncOperationPublisher <span class="op">{</span></span>
<span id="cb54-2"><a href="#cb54-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">publishProgress</span><span class="op">(</span></span>
<span id="cb54-3"><a href="#cb54-3" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb54-4"><a href="#cb54-4" aria-hidden="true" tabindex="-1"></a> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-5"><a href="#cb54-5" aria-hidden="true" tabindex="-1"></a> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-6"><a href="#cb54-6" aria-hidden="true" tabindex="-1"></a> <span class="va">processedCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-7"><a href="#cb54-7" aria-hidden="true" tabindex="-1"></a> <span class="va">successCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-8"><a href="#cb54-8" aria-hidden="true" tabindex="-1"></a> <span class="va">failureCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-9"><a href="#cb54-9" aria-hidden="true" tabindex="-1"></a> <span class="va">referenceIds</span><span class="op">:</span> <span class="dt">List</span>&lt;<span class="va">String</span>&gt;? <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-10"><a href="#cb54-10" aria-hidden="true" tabindex="-1"></a> <span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">(),</span></span>
<span id="cb54-11"><a href="#cb54-11" aria-hidden="true" tabindex="-1"></a> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">()</span></span>
<span id="cb54-12"><a href="#cb54-12" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span></span>
<span id="cb54-13"><a href="#cb54-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb54-14"><a href="#cb54-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">publishRuntimeContext</span><span class="op">(</span></span>
<span id="cb54-15"><a href="#cb54-15" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb54-16"><a href="#cb54-16" aria-hidden="true" tabindex="-1"></a> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt;<span class="op">,</span></span>
<span id="cb54-17"><a href="#cb54-17" aria-hidden="true" tabindex="-1"></a> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-18"><a href="#cb54-18" aria-hidden="true" tabindex="-1"></a> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null</span>
<span id="cb54-19"><a href="#cb54-19" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span></span>
<span id="cb54-20"><a href="#cb54-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb54-21"><a href="#cb54-21" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">publishVolatileProgress</span><span class="op">(</span></span>
<span id="cb54-22"><a href="#cb54-22" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb54-23"><a href="#cb54-23" aria-hidden="true" tabindex="-1"></a> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-24"><a href="#cb54-24" aria-hidden="true" tabindex="-1"></a> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-25"><a href="#cb54-25" aria-hidden="true" tabindex="-1"></a> <span class="va">processedCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-26"><a href="#cb54-26" aria-hidden="true" tabindex="-1"></a> <span class="va">successCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-27"><a href="#cb54-27" aria-hidden="true" tabindex="-1"></a> <span class="va">failureCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-28"><a href="#cb54-28" aria-hidden="true" tabindex="-1"></a> <span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">(),</span></span>
<span id="cb54-29"><a href="#cb54-29" aria-hidden="true" tabindex="-1"></a> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">()</span></span>
<span id="cb54-30"><a href="#cb54-30" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb54-31"><a href="#cb54-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb54-32"><a href="#cb54-32" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">complete</span><span class="op">(</span></span>
<span id="cb54-33"><a href="#cb54-33" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb54-34"><a href="#cb54-34" aria-hidden="true" tabindex="-1"></a> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb54-35"><a href="#cb54-35" aria-hidden="true" tabindex="-1"></a> <span class="va">processedCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">,</span></span>
<span id="cb54-36"><a href="#cb54-36" aria-hidden="true" tabindex="-1"></a> <span class="va">successCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">,</span></span>
<span id="cb54-37"><a href="#cb54-37" aria-hidden="true" tabindex="-1"></a> <span class="va">failureCount</span><span class="op">:</span> <span class="dt">Int</span> <span class="op">=</span> <span class="dv">0</span><span class="op">,</span></span>
<span id="cb54-38"><a href="#cb54-38" aria-hidden="true" tabindex="-1"></a> <span class="va">referenceIds</span><span class="op">:</span> <span class="dt">List</span>&lt;<span class="va">String</span>&gt; <span class="op">=</span> emptyList<span class="op">(),</span></span>
<span id="cb54-39"><a href="#cb54-39" aria-hidden="true" tabindex="-1"></a> <span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">(),</span></span>
<span id="cb54-40"><a href="#cb54-40" aria-hidden="true" tabindex="-1"></a> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">()</span></span>
<span id="cb54-41"><a href="#cb54-41" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span></span>
<span id="cb54-42"><a href="#cb54-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb54-43"><a href="#cb54-43" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">fail</span><span class="op">(</span></span>
<span id="cb54-44"><a href="#cb54-44" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb54-45"><a href="#cb54-45" aria-hidden="true" tabindex="-1"></a> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb54-46"><a href="#cb54-46" aria-hidden="true" tabindex="-1"></a> <span class="va">errorMessage</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb54-47"><a href="#cb54-47" aria-hidden="true" tabindex="-1"></a> <span class="va">errorCode</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb54-48"><a href="#cb54-48" aria-hidden="true" tabindex="-1"></a> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span> <span class="op">=</span> <span class="st">&quot;Failed&quot;</span><span class="op">,</span></span>
<span id="cb54-49"><a href="#cb54-49" aria-hidden="true" tabindex="-1"></a> <span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">(),</span></span>
<span id="cb54-50"><a href="#cb54-50" aria-hidden="true" tabindex="-1"></a> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">()</span></span>
<span id="cb54-51"><a href="#cb54-51" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span></span>
<span id="cb54-52"><a href="#cb54-52" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<div id="code-11-3" class="figure-caption figure-kind-insert">Code 11.3: AsyncOperationPublisher exposes the durable lifecycle methods plus publishVolatileProgress for fast in-memory UI updates between durable state transitions.</div>
</div>
<p>The intended use is:</p>
<ul>
<li><code>publishProgress(...)</code> Use for durable state transitions
or durable milestone updates.</li>
<li><code>publishRuntimeContext(...)</code> Use when runtime context
should be merged into the durable snapshot without forcing result
semantics.</li>
<li><code>publishVolatileProgress(...)</code> Use for fast in-memory UI
updates that should not create database churn.</li>
<li><code>complete(...)</code> and <code>fail(...)</code> Use for
terminal durable outcomes.</li>
</ul>
<p>The key design rule is that a publisher updates an operation, not a
transport channel. The framework derives both the exact operation stream
and the topic hierarchy from that one operation state.</p>
</section>
<section id="observability" class="level2" data-number="11.9">
<h2 data-number="11.9"><span class="header-section-number">11.9</span>
Observability</h2>
<p>Async and SSE systems are much easier to operate when the framework
defines a small observability contract up front.</p>
<p>The most important things to observe for this runtime are:</p>
<ul>
<li>lifecycle timings Queue wait, execution duration, and terminal total
duration through the derived <code>timings</code> block on the
snapshot</li>
<li>durable update pressure How often callers publish durable updates
and how large payload or runtime-context merges become</li>
<li>volatile update pressure How often callers use
<code>publishVolatileProgress(...)</code>, especially on hot paths</li>
<li>SSE health Active emitters, reconnect frequency, heartbeat failures,
and subscription pressure by topic or operation</li>
<li>executor pressure Queue depth, worker saturation, and rejected async
submissions</li>
<li>housekeeping Stale operations marked failed, rows purged by
retention policy, and lingering terminal rows</li>
</ul>
<p>The intended discipline is:</p>
<ul>
<li>measure operation-level timing and load first</li>
<li>add deeper domain timing only when the operation-level view shows a
real hotspot</li>
<li>avoid inventing a detailed event history just to answer basic
runtime questions</li>
</ul>
</section>
<section id="durable-lane-versus-volatile-lane" class="level2" data-number="11.10">
<h2 data-number="11.10"><span class="header-section-number">11.10</span>
Durable Lane Versus Volatile Lane</h2>
<p>The implementation in <code>AsyncOperationServiceImpl</code> is the
important reference for this distinction.</p>
<div class="captioned-block captioned-code">
<style>
#code-11-4 + p, #code-11-4 + div.sourceCode, #code-11-4 + pre, #code-11-4 + table { display: block; max-width: 100%; }
#code-11-4 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb55"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb55-1"><a href="#cb55-1" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">getOperation</span><span class="op">(</span><span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span><span class="op">?</span> <span class="op">=</span></span>
<span id="cb55-2"><a href="#cb55-2" aria-hidden="true" tabindex="-1"></a> latestSnapshots<span class="op">[</span>operationId<span class="op">]</span></span>
<span id="cb55-3"><a href="#cb55-3" aria-hidden="true" tabindex="-1"></a> <span class="op">?:</span> asyncOperationRunRepository<span class="op">.</span>findById<span class="op">(</span>operationId<span class="op">).</span>orElse<span class="op">(</span><span class="kw">null</span><span class="op">)?.</span>toSnapshot<span class="op">()?.</span>also <span class="op">{</span></span>
<span id="cb55-4"><a href="#cb55-4" aria-hidden="true" tabindex="-1"></a> latestSnapshots<span class="op">[</span>operationId<span class="op">]</span> <span class="op">=</span> it</span>
<span id="cb55-5"><a href="#cb55-5" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-6"><a href="#cb55-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb55-7"><a href="#cb55-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">subscribe</span><span class="op">(</span></span>
<span id="cb55-8"><a href="#cb55-8" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb55-9"><a href="#cb55-9" aria-hidden="true" tabindex="-1"></a> <span class="va">lastSeenRevision</span><span class="op">:</span> <span class="dt">Long</span><span class="op">?,</span></span>
<span id="cb55-10"><a href="#cb55-10" aria-hidden="true" tabindex="-1"></a> <span class="va">listener</span><span class="op">:</span> <span class="dt">AsyncOperationStreamListener</span></span>
<span id="cb55-11"><a href="#cb55-11" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSubscription</span> <span class="op">{</span></span>
<span id="cb55-12"><a href="#cb55-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">subscription</span> <span class="op">=</span> broadcaster<span class="op">.</span>subscribe<span class="op">(</span>operationId<span class="op">,</span> listener<span class="op">)</span></span>
<span id="cb55-13"><a href="#cb55-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">snapshot</span> <span class="op">=</span> getOperation<span class="op">(</span>operationId<span class="op">)</span></span>
<span id="cb55-14"><a href="#cb55-14" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>snapshot <span class="op">==</span> <span class="kw">null</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb55-15"><a href="#cb55-15" aria-hidden="true" tabindex="-1"></a> subscription<span class="op">.</span>close<span class="op">()</span></span>
<span id="cb55-16"><a href="#cb55-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">throw</span> IllegalArgumentException<span class="op">(</span><span class="st">&quot;Async operation </span><span class="ss">$operationId</span><span class="st"> not found&quot;</span><span class="op">)</span></span>
<span id="cb55-17"><a href="#cb55-17" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-18"><a href="#cb55-18" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>lastSeenRevision <span class="op">==</span> <span class="kw">null</span> <span class="op">||</span> snapshot<span class="op">.</span>revision <span class="op">&gt;</span> lastSeenRevision <span class="op">||</span> snapshot<span class="op">.</span>status<span class="op">.</span>isTerminal<span class="op">())</span> <span class="op">{</span></span>
<span id="cb55-19"><a href="#cb55-19" aria-hidden="true" tabindex="-1"></a> listener<span class="op">.</span>onEvent<span class="op">(</span>snapshot<span class="op">.</span>toStreamEvent<span class="op">())</span></span>
<span id="cb55-20"><a href="#cb55-20" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-21"><a href="#cb55-21" aria-hidden="true" tabindex="-1"></a> latestVolatileEvents<span class="op">[</span>operationId<span class="op">]</span></span>
<span id="cb55-22"><a href="#cb55-22" aria-hidden="true" tabindex="-1"></a> <span class="op">?.</span>takeIf <span class="op">{</span> latest <span class="op">-&gt;</span></span>
<span id="cb55-23"><a href="#cb55-23" aria-hidden="true" tabindex="-1"></a> <span class="op">!</span>snapshot<span class="op">.</span>status<span class="op">.</span>isTerminal<span class="op">()</span> <span class="op">&amp;&amp;</span></span>
<span id="cb55-24"><a href="#cb55-24" aria-hidden="true" tabindex="-1"></a> <span class="op">(</span>lastSeenRevision <span class="op">==</span> <span class="kw">null</span> <span class="op">||</span> latest<span class="op">.</span>volatileUpdate<span class="op">?.</span>baseRevision <span class="op">?:</span> <span class="op">-</span><span class="dv">1L</span> <span class="op">&gt;=</span> lastSeenRevision<span class="op">)</span></span>
<span id="cb55-25"><a href="#cb55-25" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-26"><a href="#cb55-26" aria-hidden="true" tabindex="-1"></a> <span class="op">?.</span>let <span class="op">{</span> listener<span class="op">.</span>onEvent<span class="op">(</span>it<span class="op">)</span> <span class="op">}</span></span>
<span id="cb55-27"><a href="#cb55-27" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> subscription</span>
<span id="cb55-28"><a href="#cb55-28" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-29"><a href="#cb55-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb55-30"><a href="#cb55-30" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">subscribe</span><span class="op">(</span><span class="va">topicPrefix</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span> <span class="va">listener</span><span class="op">:</span> <span class="dt">AsyncOperationStreamListener</span><span class="op">):</span> <span class="dt">AsyncOperationSubscription</span> <span class="op">=</span></span>
<span id="cb55-31"><a href="#cb55-31" aria-hidden="true" tabindex="-1"></a> broadcaster<span class="op">.</span>subscribe<span class="op">(</span>topicPrefix<span class="op">,</span> listener<span class="op">)</span></span>
<span id="cb55-32"><a href="#cb55-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb55-33"><a href="#cb55-33" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">publishVolatileProgress</span><span class="op">(</span></span>
<span id="cb55-34"><a href="#cb55-34" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb55-35"><a href="#cb55-35" aria-hidden="true" tabindex="-1"></a> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb55-36"><a href="#cb55-36" aria-hidden="true" tabindex="-1"></a> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb55-37"><a href="#cb55-37" aria-hidden="true" tabindex="-1"></a> <span class="va">processedCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?,</span></span>
<span id="cb55-38"><a href="#cb55-38" aria-hidden="true" tabindex="-1"></a> <span class="va">successCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?,</span></span>
<span id="cb55-39"><a href="#cb55-39" aria-hidden="true" tabindex="-1"></a> <span class="va">failureCount</span><span class="op">:</span> <span class="dt">Int</span><span class="op">?,</span></span>
<span id="cb55-40"><a href="#cb55-40" aria-hidden="true" tabindex="-1"></a> <span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt;<span class="op">,</span></span>
<span id="cb55-41"><a href="#cb55-41" aria-hidden="true" tabindex="-1"></a> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt;</span>
<span id="cb55-42"><a href="#cb55-42" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span> <span class="op">{</span></span>
<span id="cb55-43"><a href="#cb55-43" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">snapshot</span> <span class="op">=</span> getOperation<span class="op">(</span>operationId<span class="op">)</span></span>
<span id="cb55-44"><a href="#cb55-44" aria-hidden="true" tabindex="-1"></a> <span class="op">?:</span> <span class="kw">throw</span> IllegalArgumentException<span class="op">(</span><span class="st">&quot;Async operation </span><span class="ss">$operationId</span><span class="st"> not found&quot;</span><span class="op">)</span></span>
<span id="cb55-45"><a href="#cb55-45" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>snapshot<span class="op">.</span>status<span class="op">.</span>isTerminal<span class="op">())</span> <span class="op">{</span></span>
<span id="cb55-46"><a href="#cb55-46" aria-hidden="true" tabindex="-1"></a> logger<span class="op">.</span>debug<span class="op">(</span><span class="st">&quot;Ignoring volatile progress for terminal async operation. operationId={}&quot;</span><span class="op">,</span> operationId<span class="op">)</span></span>
<span id="cb55-47"><a href="#cb55-47" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span></span>
<span id="cb55-48"><a href="#cb55-48" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-49"><a href="#cb55-49" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">sequence</span> <span class="op">=</span> volatileSequences<span class="op">.</span>computeIfAbsent<span class="op">(</span>operationId<span class="op">)</span> <span class="op">{</span> AtomicLong<span class="op">(</span><span class="dv">0</span><span class="op">)</span> <span class="op">}.</span>incrementAndGet<span class="op">()</span></span>
<span id="cb55-50"><a href="#cb55-50" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">event</span> <span class="op">=</span> AsyncOperationStreamEvent<span class="op">(</span></span>
<span id="cb55-51"><a href="#cb55-51" aria-hidden="true" tabindex="-1"></a> eventType <span class="op">=</span> AsyncOperationEventType<span class="op">.</span>VOLATILE_PROGRESS<span class="op">,</span></span>
<span id="cb55-52"><a href="#cb55-52" aria-hidden="true" tabindex="-1"></a> topic <span class="op">=</span> snapshot<span class="op">.</span>topic<span class="op">,</span></span>
<span id="cb55-53"><a href="#cb55-53" aria-hidden="true" tabindex="-1"></a> snapshot <span class="op">=</span> snapshot<span class="op">,</span></span>
<span id="cb55-54"><a href="#cb55-54" aria-hidden="true" tabindex="-1"></a> volatileUpdate <span class="op">=</span> AsyncOperationVolatileUpdate<span class="op">(</span></span>
<span id="cb55-55"><a href="#cb55-55" aria-hidden="true" tabindex="-1"></a> operationId <span class="op">=</span> snapshot<span class="op">.</span>operationId<span class="op">,</span></span>
<span id="cb55-56"><a href="#cb55-56" aria-hidden="true" tabindex="-1"></a> topic <span class="op">=</span> snapshot<span class="op">.</span>topic<span class="op">,</span></span>
<span id="cb55-57"><a href="#cb55-57" aria-hidden="true" tabindex="-1"></a> baseRevision <span class="op">=</span> snapshot<span class="op">.</span>revision<span class="op">,</span></span>
<span id="cb55-58"><a href="#cb55-58" aria-hidden="true" tabindex="-1"></a> sequence <span class="op">=</span> sequence<span class="op">,</span></span>
<span id="cb55-59"><a href="#cb55-59" aria-hidden="true" tabindex="-1"></a> publishedAt <span class="op">=</span> LocalDateTime<span class="op">.</span>now<span class="op">(),</span></span>
<span id="cb55-60"><a href="#cb55-60" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> phase<span class="op">,</span></span>
<span id="cb55-61"><a href="#cb55-61" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> summary<span class="op">,</span></span>
<span id="cb55-62"><a href="#cb55-62" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> processedCount<span class="op">,</span></span>
<span id="cb55-63"><a href="#cb55-63" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> successCount<span class="op">,</span></span>
<span id="cb55-64"><a href="#cb55-64" aria-hidden="true" tabindex="-1"></a> failureCount <span class="op">=</span> failureCount<span class="op">,</span></span>
<span id="cb55-65"><a href="#cb55-65" aria-hidden="true" tabindex="-1"></a> payload <span class="op">=</span> payload<span class="op">,</span></span>
<span id="cb55-66"><a href="#cb55-66" aria-hidden="true" tabindex="-1"></a> runtimeContext <span class="op">=</span> runtimeContext</span>
<span id="cb55-67"><a href="#cb55-67" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb55-68"><a href="#cb55-68" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb55-69"><a href="#cb55-69" aria-hidden="true" tabindex="-1"></a> latestVolatileEvents<span class="op">[</span>operationId<span class="op">]</span> <span class="op">=</span> event</span>
<span id="cb55-70"><a href="#cb55-70" aria-hidden="true" tabindex="-1"></a> broadcaster<span class="op">.</span>broadcast<span class="op">(</span>event<span class="op">)</span></span>
<span id="cb55-71"><a href="#cb55-71" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-72"><a href="#cb55-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb55-73"><a href="#cb55-73" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">submit</span><span class="op">(</span></span>
<span id="cb55-74"><a href="#cb55-74" aria-hidden="true" tabindex="-1"></a> <span class="va">operationKey</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb55-75"><a href="#cb55-75" aria-hidden="true" tabindex="-1"></a> <span class="va">submittedBy</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb55-76"><a href="#cb55-76" aria-hidden="true" tabindex="-1"></a> <span class="va">attributes</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">String</span>&gt;<span class="op">,</span></span>
<span id="cb55-77"><a href="#cb55-77" aria-hidden="true" tabindex="-1"></a> <span class="va">initialPhase</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb55-78"><a href="#cb55-78" aria-hidden="true" tabindex="-1"></a> <span class="va">requestHash</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb55-79"><a href="#cb55-79" aria-hidden="true" tabindex="-1"></a> <span class="va">retentionMode</span><span class="op">:</span> <span class="dt">AsyncOperationRetentionMode</span> <span class="op">=</span> AsyncOperationRetentionMode<span class="op">.</span>DEFAULT<span class="op">,</span></span>
<span id="cb55-80"><a href="#cb55-80" aria-hidden="true" tabindex="-1"></a> <span class="va">retentionUntil</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb55-81"><a href="#cb55-81" aria-hidden="true" tabindex="-1"></a> <span class="va">work</span><span class="op">:</span> <span class="op">(</span><span class="dt">progress</span>: (<span class="dt">AsyncOperationProgress</span><span class="op">)</span> <span class="op">-&gt;</span> <span class="dt">Unit</span><span class="op">)</span> -&gt; <span class="fu">AsyncOperationResult</span></span>
<span id="cb55-82"><a href="#cb55-82" aria-hidden="true" tabindex="-1"></a> )<span class="op">:</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">{</span></span>
<span id="cb55-83"><a href="#cb55-83" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">submitOutcome</span> <span class="op">=</span> transactionTemplate<span class="op">.</span>execute <span class="op">{</span></span>
<span id="cb55-84"><a href="#cb55-84" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">effectiveRequestHash</span> <span class="op">=</span> requestHash <span class="op">?:</span> buildRequestHash<span class="op">(</span>operationKey<span class="op">,</span> attributes<span class="op">)</span></span>
<span id="cb55-85"><a href="#cb55-85" aria-hidden="true" tabindex="-1"></a> asyncOperationRunRepository<span class="op">.</span>findFirstByOperationKeyAndRequestHashAndStatusInOrderBySubmittedAtDesc<span class="op">(</span></span>
<span id="cb55-86"><a href="#cb55-86" aria-hidden="true" tabindex="-1"></a> operationKey <span class="op">=</span> operationKey<span class="op">,</span></span>
<span id="cb55-87"><a href="#cb55-87" aria-hidden="true" tabindex="-1"></a> requestHash <span class="op">=</span> effectiveRequestHash<span class="op">,</span></span>
<span id="cb55-88"><a href="#cb55-88" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> ACTIVE_STATUSES</span>
<span id="cb55-89"><a href="#cb55-89" aria-hidden="true" tabindex="-1"></a> <span class="op">)?.</span>let <span class="op">{</span></span>
<span id="cb55-90"><a href="#cb55-90" aria-hidden="true" tabindex="-1"></a> return<span class="at">@execute</span> SubmitOutcome<span class="op">(</span>it<span class="op">.</span>toSnapshot<span class="op">(),</span> created <span class="op">=</span> <span class="kw">false</span><span class="op">)</span></span>
<span id="cb55-91"><a href="#cb55-91" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-92"><a href="#cb55-92" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb55-93"><a href="#cb55-93" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">now</span> <span class="op">=</span> LocalDateTime<span class="op">.</span>now<span class="op">()</span></span>
<span id="cb55-94"><a href="#cb55-94" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">queued</span> <span class="op">=</span> asyncOperationRunRepository<span class="op">.</span>save<span class="op">(</span></span>
<span id="cb55-95"><a href="#cb55-95" aria-hidden="true" tabindex="-1"></a> AsyncOperationRun<span class="op">().</span>apply <span class="op">{</span></span>
<span id="cb55-96"><a href="#cb55-96" aria-hidden="true" tabindex="-1"></a> <span class="kw">this</span><span class="op">.</span>operationKey <span class="op">=</span> operationKey</span>
<span id="cb55-97"><a href="#cb55-97" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> AsyncOperationStatus<span class="op">.</span>QUEUED</span>
<span id="cb55-98"><a href="#cb55-98" aria-hidden="true" tabindex="-1"></a> <span class="kw">this</span><span class="op">.</span>submittedBy <span class="op">=</span> submittedBy</span>
<span id="cb55-99"><a href="#cb55-99" aria-hidden="true" tabindex="-1"></a> submittedAt <span class="op">=</span> now</span>
<span id="cb55-100"><a href="#cb55-100" aria-hidden="true" tabindex="-1"></a> updatedAt <span class="op">=</span> now</span>
<span id="cb55-101"><a href="#cb55-101" aria-hidden="true" tabindex="-1"></a> revision <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb55-102"><a href="#cb55-102" aria-hidden="true" tabindex="-1"></a> <span class="kw">this</span><span class="op">.</span>requestHash <span class="op">=</span> effectiveRequestHash</span>
<span id="cb55-103"><a href="#cb55-103" aria-hidden="true" tabindex="-1"></a> <span class="kw">this</span><span class="op">.</span>retentionMode <span class="op">=</span> retentionMode</span>
<span id="cb55-104"><a href="#cb55-104" aria-hidden="true" tabindex="-1"></a> <span class="kw">this</span><span class="op">.</span>retentionUntil <span class="op">=</span> retentionUntil</span>
<span id="cb55-105"><a href="#cb55-105" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> initialPhase</span>
<span id="cb55-106"><a href="#cb55-106" aria-hidden="true" tabindex="-1"></a> attributesJson <span class="op">=</span> writeAttributes<span class="op">(</span>attributes<span class="op">)</span></span>
<span id="cb55-107"><a href="#cb55-107" aria-hidden="true" tabindex="-1"></a> referenceIdsJson <span class="op">=</span> writeReferenceIds<span class="op">(</span>emptyList<span class="op">())</span></span>
<span id="cb55-108"><a href="#cb55-108" aria-hidden="true" tabindex="-1"></a> payloadJson <span class="op">=</span> writePayload<span class="op">(</span>emptyMap<span class="op">())</span></span>
<span id="cb55-109"><a href="#cb55-109" aria-hidden="true" tabindex="-1"></a> runtimeContextJson <span class="op">=</span> writePayload<span class="op">(</span>emptyMap<span class="op">())</span></span>
<span id="cb55-110"><a href="#cb55-110" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-111"><a href="#cb55-111" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb55-112"><a href="#cb55-112" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">queuedSnapshot</span> <span class="op">=</span> queued<span class="op">.</span>toSnapshot<span class="op">()</span></span>
<span id="cb55-113"><a href="#cb55-113" aria-hidden="true" tabindex="-1"></a> registerAfterCommit<span class="op">(</span>queuedSnapshot<span class="op">)</span></span>
<span id="cb55-114"><a href="#cb55-114" aria-hidden="true" tabindex="-1"></a> SubmitOutcome<span class="op">(</span>queuedSnapshot<span class="op">,</span> created <span class="op">=</span> <span class="kw">true</span><span class="op">)</span></span>
<span id="cb55-115"><a href="#cb55-115" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="op">?:</span> error<span class="op">(</span><span class="st">&quot;Submitting async operation unexpectedly returned null&quot;</span><span class="op">)</span></span>
<span id="cb55-116"><a href="#cb55-116" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb55-117"><a href="#cb55-117" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(!</span>submitOutcome<span class="op">.</span>created<span class="op">)</span> <span class="op">{</span></span>
<span id="cb55-118"><a href="#cb55-118" aria-hidden="true" tabindex="-1"></a> logger<span class="op">.</span>info<span class="op">(</span></span>
<span id="cb55-119"><a href="#cb55-119" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Async operation deduplicated. operationKey={}, requestHash={}, operationId={}&quot;</span><span class="op">,</span></span>
<span id="cb55-120"><a href="#cb55-120" aria-hidden="true" tabindex="-1"></a> operationKey<span class="op">,</span></span>
<span id="cb55-121"><a href="#cb55-121" aria-hidden="true" tabindex="-1"></a> submitOutcome<span class="op">.</span>snapshot<span class="op">.</span>requestHash<span class="op">,</span></span>
<span id="cb55-122"><a href="#cb55-122" aria-hidden="true" tabindex="-1"></a> submitOutcome<span class="op">.</span>snapshot<span class="op">.</span>operationId</span>
<span id="cb55-123"><a href="#cb55-123" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb55-124"><a href="#cb55-124" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> submitOutcome<span class="op">.</span>snapshot</span>
<span id="cb55-125"><a href="#cb55-125" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-126"><a href="#cb55-126" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb55-127"><a href="#cb55-127" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb55-128"><a href="#cb55-128" aria-hidden="true" tabindex="-1"></a> asyncOperationExecutor<span class="op">.</span>execute <span class="op">{</span></span>
<span id="cb55-129"><a href="#cb55-129" aria-hidden="true" tabindex="-1"></a> runOperation<span class="op">(</span>submitOutcome<span class="op">.</span>snapshot<span class="op">.</span>operationId<span class="op">,</span> work<span class="op">)</span></span>
<span id="cb55-130"><a href="#cb55-130" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb55-131"><a href="#cb55-131" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span>ex<span class="op">:</span> RejectedExecutionException<span class="op">)</span> <span class="op">{</span></span>
<span id="cb55-132"><a href="#cb55-132" aria-hidden="true" tabindex="-1"></a> logger<span class="op">.</span>warn<span class="op">(</span></span>
<span id="cb55-133"><a href="#cb55-133" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Async operation rejected. operationKey={}, operationId={}&quot;</span><span class="op">,</span></span>
<span id="cb55-134"><a href="#cb55-134" aria-hidden="true" tabindex="-1"></a> operationKey<span class="op">,</span></span>
<span id="cb55-135"><a href="#cb55-135" aria-hidden="true" tabindex="-1"></a> submitOutcome<span class="op">.</span>snapshot<span class="op">.</span>operationId<span class="op">,</span></span>
<span id="cb55-136"><a href="#cb55-136" aria-hidden="true" tabindex="-1"></a> ex</span>
<span id="cb55-137"><a href="#cb55-137" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb55-138"><a href="#cb55-138" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> markFailed<span class="op">(</span></span>
<span id="cb55-139"><a href="#cb55-139" aria-hidden="true" tabindex="-1"></a> submitOutcome<span class="op">.</span>snapshot<span class="op">.</span>operationId<span class="op">,</span></span>
<span id="cb55-140"><a href="#cb55-140" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Rejected&quot;</span><span class="op">,</span></span>
<span id="cb55-141"><a href="#cb55-141" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Operation could not be started&quot;</span><span class="op">,</span></span>
<span id="cb55-142"><a href="#cb55-142" aria-hidden="true" tabindex="-1"></a> errorCode <span class="op">=</span> <span class="st">&quot;ASYNC_OPERATION_REJECTED&quot;</span><span class="op">,</span></span>
<span id="cb55-143"><a href="#cb55-143" aria-hidden="true" tabindex="-1"></a> errorMessage <span class="op">=</span> ex<span class="op">.</span>message <span class="op">?:</span> <span class="st">&quot;Operation executor rejected the task&quot;</span></span>
<span id="cb55-144"><a href="#cb55-144" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb55-145"><a href="#cb55-145" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div>
<div id="code-11-4" class="figure-caption figure-kind-insert">Code 11.4: AsyncOperationServiceImpl keeps the latest durable snapshot per operation, replays it on subscription, publishes volatile progress only in memory, and creates the queued durable row on submit.</div>
</div>
<div class="captioned-block captioned-code">
<style>
#code-11-5 + p, #code-11-5 + div.sourceCode, #code-11-5 + pre, #code-11-5 + table { display: block; max-width: 100%; }
#code-11-5 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb56"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb56-1"><a href="#cb56-1" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-2"><a href="#cb56-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">markRunning</span><span class="op">(</span><span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb56-3"><a href="#cb56-3" aria-hidden="true" tabindex="-1"></a> mutateOperation<span class="op">(</span>operationId<span class="op">)</span> <span class="op">{</span> run<span class="op">,</span> now <span class="op">-&gt;</span></span>
<span id="cb56-4"><a href="#cb56-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>run<span class="op">.</span>status<span class="op">.</span>isTerminal<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-5"><a href="#cb56-5" aria-hidden="true" tabindex="-1"></a> return<span class="at">@mutateOperation</span> <span class="kw">false</span></span>
<span id="cb56-6"><a href="#cb56-6" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-7"><a href="#cb56-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>run<span class="op">.</span>startedAt <span class="op">==</span> <span class="kw">null</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb56-8"><a href="#cb56-8" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>startedAt <span class="op">=</span> now</span>
<span id="cb56-9"><a href="#cb56-9" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-10"><a href="#cb56-10" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>status <span class="op">=</span> AsyncOperationStatus<span class="op">.</span>RUNNING</span>
<span id="cb56-11"><a href="#cb56-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">true</span></span>
<span id="cb56-12"><a href="#cb56-12" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-13"><a href="#cb56-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-14"><a href="#cb56-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">applyProgress</span><span class="op">(</span><span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span> <span class="va">progress</span><span class="op">:</span> <span class="dt">AsyncOperationProgress</span><span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb56-15"><a href="#cb56-15" aria-hidden="true" tabindex="-1"></a> mutateOperation<span class="op">(</span>operationId<span class="op">)</span> <span class="op">{</span> run<span class="op">,</span> _ <span class="op">-&gt;</span></span>
<span id="cb56-16"><a href="#cb56-16" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>run<span class="op">.</span>status<span class="op">.</span>isTerminal<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-17"><a href="#cb56-17" aria-hidden="true" tabindex="-1"></a> return<span class="at">@mutateOperation</span> <span class="kw">false</span></span>
<span id="cb56-18"><a href="#cb56-18" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-19"><a href="#cb56-19" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>phase <span class="op">=</span> progress<span class="op">.</span>phase <span class="op">?:</span> run<span class="op">.</span>phase</span>
<span id="cb56-20"><a href="#cb56-20" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>summary <span class="op">=</span> progress<span class="op">.</span>summary <span class="op">?:</span> run<span class="op">.</span>summary</span>
<span id="cb56-21"><a href="#cb56-21" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>processedCount <span class="op">=</span> progress<span class="op">.</span>processedCount <span class="op">?:</span> run<span class="op">.</span>processedCount</span>
<span id="cb56-22"><a href="#cb56-22" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>successCount <span class="op">=</span> progress<span class="op">.</span>successCount <span class="op">?:</span> run<span class="op">.</span>successCount</span>
<span id="cb56-23"><a href="#cb56-23" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>failureCount <span class="op">=</span> progress<span class="op">.</span>failureCount <span class="op">?:</span> run<span class="op">.</span>failureCount</span>
<span id="cb56-24"><a href="#cb56-24" aria-hidden="true" tabindex="-1"></a> progress<span class="op">.</span>referenceIds<span class="op">?.</span>let <span class="op">{</span> run<span class="op">.</span>referenceIdsJson <span class="op">=</span> writeReferenceIds<span class="op">(</span>it<span class="op">)</span> <span class="op">}</span></span>
<span id="cb56-25"><a href="#cb56-25" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>progress<span class="op">.</span>payload<span class="op">.</span>isNotEmpty<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-26"><a href="#cb56-26" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>payloadJson <span class="op">=</span> writePayload<span class="op">(</span>mergeMaps<span class="op">(</span>readPayload<span class="op">(</span>run<span class="op">.</span>payloadJson<span class="op">),</span> progress<span class="op">.</span>payload<span class="op">))</span></span>
<span id="cb56-27"><a href="#cb56-27" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-28"><a href="#cb56-28" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>progress<span class="op">.</span>runtimeContext<span class="op">.</span>isNotEmpty<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-29"><a href="#cb56-29" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>runtimeContextJson <span class="op">=</span> writePayload<span class="op">(</span></span>
<span id="cb56-30"><a href="#cb56-30" aria-hidden="true" tabindex="-1"></a> mergeMaps<span class="op">(</span>readPayload<span class="op">(</span>run<span class="op">.</span>runtimeContextJson<span class="op">),</span> progress<span class="op">.</span>runtimeContext<span class="op">)</span></span>
<span id="cb56-31"><a href="#cb56-31" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb56-32"><a href="#cb56-32" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-33"><a href="#cb56-33" aria-hidden="true" tabindex="-1"></a> <span class="kw">true</span></span>
<span id="cb56-34"><a href="#cb56-34" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-35"><a href="#cb56-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-36"><a href="#cb56-36" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">markCompleted</span><span class="op">(</span><span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span> <span class="va">result</span><span class="op">:</span> <span class="dt">AsyncOperationResult</span><span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb56-37"><a href="#cb56-37" aria-hidden="true" tabindex="-1"></a> mutateOperation<span class="op">(</span>operationId<span class="op">,</span> clearListeners <span class="op">=</span> <span class="kw">true</span><span class="op">)</span> <span class="op">{</span> run<span class="op">,</span> now <span class="op">-&gt;</span></span>
<span id="cb56-38"><a href="#cb56-38" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>run<span class="op">.</span>status<span class="op">.</span>isTerminal<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-39"><a href="#cb56-39" aria-hidden="true" tabindex="-1"></a> return<span class="at">@mutateOperation</span> <span class="kw">false</span></span>
<span id="cb56-40"><a href="#cb56-40" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-41"><a href="#cb56-41" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>status <span class="op">=</span> AsyncOperationStatus<span class="op">.</span>COMPLETED</span>
<span id="cb56-42"><a href="#cb56-42" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>endedAt <span class="op">=</span> now</span>
<span id="cb56-43"><a href="#cb56-43" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>retentionUntil <span class="op">=</span> resolvedRetentionUntil<span class="op">(</span>run<span class="op">,</span> now<span class="op">)</span></span>
<span id="cb56-44"><a href="#cb56-44" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>summary <span class="op">=</span> result<span class="op">.</span>summary</span>
<span id="cb56-45"><a href="#cb56-45" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>processedCount <span class="op">=</span> result<span class="op">.</span>processedCount</span>
<span id="cb56-46"><a href="#cb56-46" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>successCount <span class="op">=</span> result<span class="op">.</span>successCount</span>
<span id="cb56-47"><a href="#cb56-47" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>failureCount <span class="op">=</span> result<span class="op">.</span>failureCount</span>
<span id="cb56-48"><a href="#cb56-48" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>referenceIdsJson <span class="op">=</span> writeReferenceIds<span class="op">(</span>result<span class="op">.</span>referenceIds<span class="op">)</span></span>
<span id="cb56-49"><a href="#cb56-49" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>result<span class="op">.</span>payload<span class="op">.</span>isNotEmpty<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-50"><a href="#cb56-50" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>payloadJson <span class="op">=</span> writePayload<span class="op">(</span>mergeMaps<span class="op">(</span>readPayload<span class="op">(</span>run<span class="op">.</span>payloadJson<span class="op">),</span> result<span class="op">.</span>payload<span class="op">))</span></span>
<span id="cb56-51"><a href="#cb56-51" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-52"><a href="#cb56-52" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>result<span class="op">.</span>runtimeContext<span class="op">.</span>isNotEmpty<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-53"><a href="#cb56-53" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>runtimeContextJson <span class="op">=</span> writePayload<span class="op">(</span></span>
<span id="cb56-54"><a href="#cb56-54" aria-hidden="true" tabindex="-1"></a> mergeMaps<span class="op">(</span>readPayload<span class="op">(</span>run<span class="op">.</span>runtimeContextJson<span class="op">),</span> result<span class="op">.</span>runtimeContext<span class="op">)</span></span>
<span id="cb56-55"><a href="#cb56-55" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb56-56"><a href="#cb56-56" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-57"><a href="#cb56-57" aria-hidden="true" tabindex="-1"></a> <span class="kw">true</span></span>
<span id="cb56-58"><a href="#cb56-58" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-59"><a href="#cb56-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-60"><a href="#cb56-60" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">markFailed</span><span class="op">(</span></span>
<span id="cb56-61"><a href="#cb56-61" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb56-62"><a href="#cb56-62" aria-hidden="true" tabindex="-1"></a> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb56-63"><a href="#cb56-63" aria-hidden="true" tabindex="-1"></a> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb56-64"><a href="#cb56-64" aria-hidden="true" tabindex="-1"></a> <span class="va">errorMessage</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb56-65"><a href="#cb56-65" aria-hidden="true" tabindex="-1"></a> <span class="va">errorCode</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span> null<span class="op">,</span></span>
<span id="cb56-66"><a href="#cb56-66" aria-hidden="true" tabindex="-1"></a> <span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">(),</span></span>
<span id="cb56-67"><a href="#cb56-67" aria-hidden="true" tabindex="-1"></a> <span class="va">runtimeContext</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt; <span class="op">=</span> emptyMap<span class="op">()</span></span>
<span id="cb56-68"><a href="#cb56-68" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb56-69"><a href="#cb56-69" aria-hidden="true" tabindex="-1"></a> mutateOperation<span class="op">(</span>operationId<span class="op">,</span> clearListeners <span class="op">=</span> <span class="kw">true</span><span class="op">)</span> <span class="op">{</span> run<span class="op">,</span> now <span class="op">-&gt;</span></span>
<span id="cb56-70"><a href="#cb56-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>run<span class="op">.</span>status<span class="op">.</span>isTerminal<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-71"><a href="#cb56-71" aria-hidden="true" tabindex="-1"></a> return<span class="at">@mutateOperation</span> <span class="kw">false</span></span>
<span id="cb56-72"><a href="#cb56-72" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-73"><a href="#cb56-73" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>status <span class="op">=</span> AsyncOperationStatus<span class="op">.</span>FAILED</span>
<span id="cb56-74"><a href="#cb56-74" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>phase <span class="op">=</span> phase</span>
<span id="cb56-75"><a href="#cb56-75" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>summary <span class="op">=</span> summary</span>
<span id="cb56-76"><a href="#cb56-76" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>errorCode <span class="op">=</span> errorCode</span>
<span id="cb56-77"><a href="#cb56-77" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>errorMessage <span class="op">=</span> errorMessage</span>
<span id="cb56-78"><a href="#cb56-78" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>endedAt <span class="op">=</span> now</span>
<span id="cb56-79"><a href="#cb56-79" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>retentionUntil <span class="op">=</span> resolvedRetentionUntil<span class="op">(</span>run<span class="op">,</span> now<span class="op">)</span></span>
<span id="cb56-80"><a href="#cb56-80" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>payload<span class="op">.</span>isNotEmpty<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-81"><a href="#cb56-81" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>payloadJson <span class="op">=</span> writePayload<span class="op">(</span>mergeMaps<span class="op">(</span>readPayload<span class="op">(</span>run<span class="op">.</span>payloadJson<span class="op">),</span> payload<span class="op">))</span></span>
<span id="cb56-82"><a href="#cb56-82" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-83"><a href="#cb56-83" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>runtimeContext<span class="op">.</span>isNotEmpty<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-84"><a href="#cb56-84" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>runtimeContextJson <span class="op">=</span> writePayload<span class="op">(</span></span>
<span id="cb56-85"><a href="#cb56-85" aria-hidden="true" tabindex="-1"></a> mergeMaps<span class="op">(</span>readPayload<span class="op">(</span>run<span class="op">.</span>runtimeContextJson<span class="op">),</span> runtimeContext<span class="op">)</span></span>
<span id="cb56-86"><a href="#cb56-86" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb56-87"><a href="#cb56-87" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-88"><a href="#cb56-88" aria-hidden="true" tabindex="-1"></a> <span class="kw">true</span></span>
<span id="cb56-89"><a href="#cb56-89" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-90"><a href="#cb56-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-91"><a href="#cb56-91" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">requireRun</span><span class="op">(</span><span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">):</span> <span class="dt">AsyncOperationRun</span> <span class="op">=</span></span>
<span id="cb56-92"><a href="#cb56-92" aria-hidden="true" tabindex="-1"></a> asyncOperationRunRepository<span class="op">.</span>findById<span class="op">(</span>operationId<span class="op">).</span>orElseThrow <span class="op">{</span></span>
<span id="cb56-93"><a href="#cb56-93" aria-hidden="true" tabindex="-1"></a> IllegalArgumentException<span class="op">(</span><span class="st">&quot;Async operation </span><span class="ss">$operationId</span><span class="st"> not found&quot;</span><span class="op">)</span></span>
<span id="cb56-94"><a href="#cb56-94" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-95"><a href="#cb56-95" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-96"><a href="#cb56-96" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">mutateOperation</span><span class="op">(</span></span>
<span id="cb56-97"><a href="#cb56-97" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb56-98"><a href="#cb56-98" aria-hidden="true" tabindex="-1"></a> <span class="va">clearListeners</span><span class="op">:</span> <span class="dt">Boolean</span> <span class="op">=</span> false<span class="op">,</span></span>
<span id="cb56-99"><a href="#cb56-99" aria-hidden="true" tabindex="-1"></a> <span class="va">mutation</span><span class="op">:</span> <span class="op">(</span><span class="dt">AsyncOperationRun</span><span class="op">,</span> <span class="dt">LocalDateTime</span><span class="op">)</span> <span class="op">-&gt;</span> <span class="dt">Boolean</span></span>
<span id="cb56-100"><a href="#cb56-100" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb56-101"><a href="#cb56-101" aria-hidden="true" tabindex="-1"></a> transactionTemplate<span class="op">.</span>execute <span class="op">{</span></span>
<span id="cb56-102"><a href="#cb56-102" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">run</span> <span class="op">=</span> requireRun<span class="op">(</span>operationId<span class="op">)</span></span>
<span id="cb56-103"><a href="#cb56-103" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">now</span> <span class="op">=</span> LocalDateTime<span class="op">.</span>now<span class="op">()</span></span>
<span id="cb56-104"><a href="#cb56-104" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(!</span>mutation<span class="op">(</span>run<span class="op">,</span> now<span class="op">))</span> <span class="op">{</span></span>
<span id="cb56-105"><a href="#cb56-105" aria-hidden="true" tabindex="-1"></a> return<span class="at">@execute</span> run<span class="op">.</span>toSnapshot<span class="op">()</span></span>
<span id="cb56-106"><a href="#cb56-106" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-107"><a href="#cb56-107" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>updatedAt <span class="op">=</span> now</span>
<span id="cb56-108"><a href="#cb56-108" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>revision <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb56-109"><a href="#cb56-109" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">snapshot</span> <span class="op">=</span> asyncOperationRunRepository<span class="op">.</span>save<span class="op">(</span>run<span class="op">).</span>toSnapshot<span class="op">()</span></span>
<span id="cb56-110"><a href="#cb56-110" aria-hidden="true" tabindex="-1"></a> registerAfterCommit<span class="op">(</span>snapshot<span class="op">,</span> clearListeners<span class="op">)</span></span>
<span id="cb56-111"><a href="#cb56-111" aria-hidden="true" tabindex="-1"></a> snapshot</span>
<span id="cb56-112"><a href="#cb56-112" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="op">?:</span> error<span class="op">(</span><span class="st">&quot;Mutating async operation unexpectedly returned null&quot;</span><span class="op">)</span></span>
<span id="cb56-113"><a href="#cb56-113" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-114"><a href="#cb56-114" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">registerAfterCommit</span><span class="op">(</span><span class="va">snapshot</span><span class="op">:</span> <span class="dt">AsyncOperationSnapshot</span><span class="op">,</span> <span class="va">clearListeners</span><span class="op">:</span> <span class="dt">Boolean</span> <span class="op">=</span> false<span class="op">)</span> <span class="op">{</span></span>
<span id="cb56-115"><a href="#cb56-115" aria-hidden="true" tabindex="-1"></a> latestSnapshots<span class="op">[</span>snapshot<span class="op">.</span>operationId<span class="op">]</span> <span class="op">=</span> snapshot</span>
<span id="cb56-116"><a href="#cb56-116" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>clearListeners<span class="op">)</span> <span class="op">{</span></span>
<span id="cb56-117"><a href="#cb56-117" aria-hidden="true" tabindex="-1"></a> latestVolatileEvents<span class="op">.</span>remove<span class="op">(</span>snapshot<span class="op">.</span>operationId<span class="op">)</span></span>
<span id="cb56-118"><a href="#cb56-118" aria-hidden="true" tabindex="-1"></a> volatileSequences<span class="op">.</span>remove<span class="op">(</span>snapshot<span class="op">.</span>operationId<span class="op">)</span></span>
<span id="cb56-119"><a href="#cb56-119" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-120"><a href="#cb56-120" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">event</span> <span class="op">=</span> snapshot<span class="op">.</span>toStreamEvent<span class="op">()</span></span>
<span id="cb56-121"><a href="#cb56-121" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>TransactionSynchronizationManager<span class="op">.</span>isSynchronizationActive<span class="op">())</span> <span class="op">{</span></span>
<span id="cb56-122"><a href="#cb56-122" aria-hidden="true" tabindex="-1"></a> TransactionSynchronizationManager<span class="op">.</span>registerSynchronization<span class="op">(</span><span class="kw">object</span> <span class="op">:</span> <span class="dt">TransactionSynchronization</span> <span class="op">{</span></span>
<span id="cb56-123"><a href="#cb56-123" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">afterCommit</span><span class="op">()</span> <span class="op">{</span></span>
<span id="cb56-124"><a href="#cb56-124" aria-hidden="true" tabindex="-1"></a> broadcaster<span class="op">.</span>broadcast<span class="op">(</span>event<span class="op">)</span></span>
<span id="cb56-125"><a href="#cb56-125" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>clearListeners<span class="op">)</span> <span class="op">{</span></span>
<span id="cb56-126"><a href="#cb56-126" aria-hidden="true" tabindex="-1"></a> broadcaster<span class="op">.</span>clear<span class="op">(</span>snapshot<span class="op">.</span>operationId<span class="op">)</span></span>
<span id="cb56-127"><a href="#cb56-127" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-128"><a href="#cb56-128" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-129"><a href="#cb56-129" aria-hidden="true" tabindex="-1"></a> <span class="op">})</span></span>
<span id="cb56-130"><a href="#cb56-130" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span></span>
<span id="cb56-131"><a href="#cb56-131" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-132"><a href="#cb56-132" aria-hidden="true" tabindex="-1"></a> broadcaster<span class="op">.</span>broadcast<span class="op">(</span>event<span class="op">)</span></span>
<span id="cb56-133"><a href="#cb56-133" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>clearListeners<span class="op">)</span> <span class="op">{</span></span>
<span id="cb56-134"><a href="#cb56-134" aria-hidden="true" tabindex="-1"></a> broadcaster<span class="op">.</span>clear<span class="op">(</span>snapshot<span class="op">.</span>operationId<span class="op">)</span></span>
<span id="cb56-135"><a href="#cb56-135" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-136"><a href="#cb56-136" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-137"><a href="#cb56-137" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-138"><a href="#cb56-138" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">AsyncOperationRun</span><span class="op">.</span><span class="fu">toSnapshot</span><span class="op">():</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb56-139"><a href="#cb56-139" aria-hidden="true" tabindex="-1"></a> AsyncOperationSnapshot<span class="op">(</span></span>
<span id="cb56-140"><a href="#cb56-140" aria-hidden="true" tabindex="-1"></a> operationId <span class="op">=</span> requireId<span class="op">(),</span></span>
<span id="cb56-141"><a href="#cb56-141" aria-hidden="true" tabindex="-1"></a> topic <span class="op">=</span> AsyncOperationTopic<span class="op">.</span>forOperation<span class="op">(</span></span>
<span id="cb56-142"><a href="#cb56-142" aria-hidden="true" tabindex="-1"></a> operationKey <span class="op">=</span> operationKey<span class="op">,</span></span>
<span id="cb56-143"><a href="#cb56-143" aria-hidden="true" tabindex="-1"></a> operationId <span class="op">=</span> requireId<span class="op">()</span></span>
<span id="cb56-144"><a href="#cb56-144" aria-hidden="true" tabindex="-1"></a> <span class="op">).</span>value<span class="op">,</span></span>
<span id="cb56-145"><a href="#cb56-145" aria-hidden="true" tabindex="-1"></a> operationKey <span class="op">=</span> operationKey<span class="op">,</span></span>
<span id="cb56-146"><a href="#cb56-146" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> status<span class="op">,</span></span>
<span id="cb56-147"><a href="#cb56-147" aria-hidden="true" tabindex="-1"></a> submittedBy <span class="op">=</span> submittedBy<span class="op">,</span></span>
<span id="cb56-148"><a href="#cb56-148" aria-hidden="true" tabindex="-1"></a> submittedAt <span class="op">=</span> submittedAt<span class="op">,</span></span>
<span id="cb56-149"><a href="#cb56-149" aria-hidden="true" tabindex="-1"></a> updatedAt <span class="op">=</span> updatedAt<span class="op">,</span></span>
<span id="cb56-150"><a href="#cb56-150" aria-hidden="true" tabindex="-1"></a> startedAt <span class="op">=</span> startedAt<span class="op">,</span></span>
<span id="cb56-151"><a href="#cb56-151" aria-hidden="true" tabindex="-1"></a> endedAt <span class="op">=</span> endedAt<span class="op">,</span></span>
<span id="cb56-152"><a href="#cb56-152" aria-hidden="true" tabindex="-1"></a> timings <span class="op">=</span> AsyncOperationTimings<span class="op">.</span>of<span class="op">(</span></span>
<span id="cb56-153"><a href="#cb56-153" aria-hidden="true" tabindex="-1"></a> submittedAt <span class="op">=</span> submittedAt<span class="op">,</span></span>
<span id="cb56-154"><a href="#cb56-154" aria-hidden="true" tabindex="-1"></a> startedAt <span class="op">=</span> startedAt<span class="op">,</span></span>
<span id="cb56-155"><a href="#cb56-155" aria-hidden="true" tabindex="-1"></a> endedAt <span class="op">=</span> endedAt</span>
<span id="cb56-156"><a href="#cb56-156" aria-hidden="true" tabindex="-1"></a> <span class="op">),</span></span>
<span id="cb56-157"><a href="#cb56-157" aria-hidden="true" tabindex="-1"></a> revision <span class="op">=</span> revision<span class="op">,</span></span>
<span id="cb56-158"><a href="#cb56-158" aria-hidden="true" tabindex="-1"></a> requestHash <span class="op">=</span> requestHash<span class="op">,</span></span>
<span id="cb56-159"><a href="#cb56-159" aria-hidden="true" tabindex="-1"></a> retentionMode <span class="op">=</span> retentionMode<span class="op">,</span></span>
<span id="cb56-160"><a href="#cb56-160" aria-hidden="true" tabindex="-1"></a> retentionUntil <span class="op">=</span> retentionUntil<span class="op">,</span></span>
<span id="cb56-161"><a href="#cb56-161" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> phase<span class="op">,</span></span>
<span id="cb56-162"><a href="#cb56-162" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> summary<span class="op">,</span></span>
<span id="cb56-163"><a href="#cb56-163" aria-hidden="true" tabindex="-1"></a> errorCode <span class="op">=</span> errorCode<span class="op">,</span></span>
<span id="cb56-164"><a href="#cb56-164" aria-hidden="true" tabindex="-1"></a> errorMessage <span class="op">=</span> errorMessage<span class="op">,</span></span>
<span id="cb56-165"><a href="#cb56-165" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> processedCount<span class="op">,</span></span>
<span id="cb56-166"><a href="#cb56-166" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> successCount<span class="op">,</span></span>
<span id="cb56-167"><a href="#cb56-167" aria-hidden="true" tabindex="-1"></a> failureCount <span class="op">=</span> failureCount<span class="op">,</span></span>
<span id="cb56-168"><a href="#cb56-168" aria-hidden="true" tabindex="-1"></a> attributes <span class="op">=</span> readAttributes<span class="op">(</span>attributesJson<span class="op">),</span></span>
<span id="cb56-169"><a href="#cb56-169" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> readReferenceIds<span class="op">(</span>referenceIdsJson<span class="op">),</span></span>
<span id="cb56-170"><a href="#cb56-170" aria-hidden="true" tabindex="-1"></a> payload <span class="op">=</span> readPayload<span class="op">(</span>payloadJson<span class="op">),</span></span>
<span id="cb56-171"><a href="#cb56-171" aria-hidden="true" tabindex="-1"></a> runtimeContext <span class="op">=</span> readPayload<span class="op">(</span>runtimeContextJson<span class="op">)</span></span>
<span id="cb56-172"><a href="#cb56-172" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb56-173"><a href="#cb56-173" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-174"><a href="#cb56-174" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">writeAttributes</span><span class="op">(</span><span class="va">attributes</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">String</span>&gt;<span class="op">):</span> <span class="dt">String</span> <span class="op">=</span></span>
<span id="cb56-175"><a href="#cb56-175" aria-hidden="true" tabindex="-1"></a> objectMapper<span class="op">.</span>writeValueAsString<span class="op">(</span>attributes<span class="op">)</span></span>
<span id="cb56-176"><a href="#cb56-176" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-177"><a href="#cb56-177" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">readAttributes</span><span class="op">(</span><span class="va">raw</span><span class="op">:</span> <span class="dt">String</span><span class="op">?):</span> <span class="dt">Map</span><span class="op">&lt;</span><span class="dt">String</span>, <span class="dt">String</span><span class="op">&gt;</span> <span class="op">=</span></span>
<span id="cb56-178"><a href="#cb56-178" aria-hidden="true" tabindex="-1"></a> raw<span class="op">?.</span>takeIf <span class="op">{</span> it<span class="op">.</span>isNotBlank<span class="op">()</span> <span class="op">}?.</span>let <span class="op">{</span></span>
<span id="cb56-179"><a href="#cb56-179" aria-hidden="true" tabindex="-1"></a> objectMapper<span class="op">.</span>readValue<span class="op">(</span>it<span class="op">,</span> <span class="kw">object</span> <span class="op">:</span> <span class="dt">TypeReference</span><span class="op">&lt;</span><span class="dt">Map</span><span class="op">&lt;</span><span class="dt">String</span>, <span class="dt">String</span><span class="op">&gt;&gt;()</span> <span class="op">{})</span></span>
<span id="cb56-180"><a href="#cb56-180" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="op">?:</span> emptyMap<span class="op">()</span></span>
<span id="cb56-181"><a href="#cb56-181" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-182"><a href="#cb56-182" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">writeReferenceIds</span><span class="op">(</span><span class="va">referenceIds</span><span class="op">:</span> <span class="dt">List</span>&lt;<span class="va">String</span>&gt;<span class="op">):</span> <span class="dt">String</span> <span class="op">=</span></span>
<span id="cb56-183"><a href="#cb56-183" aria-hidden="true" tabindex="-1"></a> objectMapper<span class="op">.</span>writeValueAsString<span class="op">(</span>referenceIds<span class="op">)</span></span>
<span id="cb56-184"><a href="#cb56-184" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-185"><a href="#cb56-185" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">readReferenceIds</span><span class="op">(</span><span class="va">raw</span><span class="op">:</span> <span class="dt">String</span><span class="op">?):</span> <span class="dt">List</span><span class="op">&lt;</span><span class="dt">String</span><span class="op">&gt;</span> <span class="op">=</span></span>
<span id="cb56-186"><a href="#cb56-186" aria-hidden="true" tabindex="-1"></a> raw<span class="op">?.</span>takeIf <span class="op">{</span> it<span class="op">.</span>isNotBlank<span class="op">()</span> <span class="op">}?.</span>let <span class="op">{</span></span>
<span id="cb56-187"><a href="#cb56-187" aria-hidden="true" tabindex="-1"></a> objectMapper<span class="op">.</span>readValue<span class="op">(</span>it<span class="op">,</span> <span class="kw">object</span> <span class="op">:</span> <span class="dt">TypeReference</span><span class="op">&lt;</span><span class="dt">List</span><span class="op">&lt;</span><span class="dt">String</span><span class="op">&gt;&gt;()</span> <span class="op">{})</span></span>
<span id="cb56-188"><a href="#cb56-188" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="op">?:</span> emptyList<span class="op">()</span></span>
<span id="cb56-189"><a href="#cb56-189" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-190"><a href="#cb56-190" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">writePayload</span><span class="op">(</span><span class="va">payload</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?&gt;<span class="op">):</span> <span class="dt">String</span> <span class="op">=</span></span>
<span id="cb56-191"><a href="#cb56-191" aria-hidden="true" tabindex="-1"></a> objectMapper<span class="op">.</span>writeValueAsString<span class="op">(</span>payload<span class="op">)</span></span>
<span id="cb56-192"><a href="#cb56-192" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-193"><a href="#cb56-193" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">readPayload</span><span class="op">(</span><span class="va">raw</span><span class="op">:</span> <span class="dt">String</span><span class="op">?):</span> <span class="dt">Map</span><span class="op">&lt;</span><span class="dt">String</span>, <span class="dt">Any</span><span class="op">?&gt;</span> <span class="op">=</span></span>
<span id="cb56-194"><a href="#cb56-194" aria-hidden="true" tabindex="-1"></a> raw<span class="op">?.</span>takeIf <span class="op">{</span> it<span class="op">.</span>isNotBlank<span class="op">()</span> <span class="op">}?.</span>let <span class="op">{</span></span>
<span id="cb56-195"><a href="#cb56-195" aria-hidden="true" tabindex="-1"></a> objectMapper<span class="op">.</span>readValue<span class="op">(</span>it<span class="op">,</span> <span class="kw">object</span> <span class="op">:</span> <span class="dt">TypeReference</span><span class="op">&lt;</span><span class="dt">Map</span><span class="op">&lt;</span><span class="dt">String</span>, <span class="dt">Any</span><span class="op">?&gt;&gt;()</span> <span class="op">{})</span></span>
<span id="cb56-196"><a href="#cb56-196" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="op">?:</span> emptyMap<span class="op">()</span></span>
<span id="cb56-197"><a href="#cb56-197" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-198"><a href="#cb56-198" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">buildRequestHash</span><span class="op">(</span></span>
<span id="cb56-199"><a href="#cb56-199" aria-hidden="true" tabindex="-1"></a> <span class="va">operationKey</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb56-200"><a href="#cb56-200" aria-hidden="true" tabindex="-1"></a> <span class="va">attributes</span><span class="op">:</span> <span class="dt">Map</span>&lt;<span class="va">String</span><span class="op">,</span> <span class="va">String</span>&gt;</span>
<span id="cb56-201"><a href="#cb56-201" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">String</span> <span class="op">{</span></span>
<span id="cb56-202"><a href="#cb56-202" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">canonicalInput</span> <span class="op">=</span> linkedMapOf<span class="op">(</span></span>
<span id="cb56-203"><a href="#cb56-203" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;operationKey&quot;</span> to operationKey<span class="op">,</span></span>
<span id="cb56-204"><a href="#cb56-204" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;attributes&quot;</span> to attributes<span class="op">.</span>toSortedMap<span class="op">()</span></span>
<span id="cb56-205"><a href="#cb56-205" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb56-206"><a href="#cb56-206" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">raw</span> <span class="op">=</span> objectMapper<span class="op">.</span>writeValueAsString<span class="op">(</span>canonicalInput<span class="op">)</span></span>
<span id="cb56-207"><a href="#cb56-207" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> MessageDigest<span class="op">.</span>getInstance<span class="op">(</span><span class="st">&quot;SHA-256&quot;</span><span class="op">)</span></span>
<span id="cb56-208"><a href="#cb56-208" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>digest<span class="op">(</span>raw<span class="op">.</span>toByteArray<span class="op">(</span>StandardCharsets<span class="op">.</span>UTF_8<span class="op">))</span></span>
<span id="cb56-209"><a href="#cb56-209" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>joinToString<span class="op">(</span><span class="st">&quot;&quot;</span><span class="op">)</span> <span class="op">{</span> <span class="st">&quot;%02x&quot;</span><span class="op">.</span>format<span class="op">(</span>it<span class="op">)</span> <span class="op">}</span></span>
<span id="cb56-210"><a href="#cb56-210" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb56-211"><a href="#cb56-211" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb56-212"><a href="#cb56-212" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">resolvedRetentionUntil</span><span class="op">(</span><span class="va">run</span><span class="op">:</span> <span class="dt">AsyncOperationRun</span><span class="op">,</span> <span class="va">terminalAt</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">):</span> <span class="dt">LocalDateTime</span><span class="op">?</span> <span class="op">=</span></span>
<span id="cb56-213"><a href="#cb56-213" aria-hidden="true" tabindex="-1"></a> run<span class="op">.</span>retentionUntil <span class="op">?:</span> <span class="cf">when</span> <span class="op">(</span>run<span class="op">.</span>retentionMode<span class="op">)</span> <span class="op">{</span></span>
<span id="cb56-214"><a href="#cb56-214" aria-hidden="true" tabindex="-1"></a> AsyncOperationRetentionMode<span class="op">.</span>DEFAULT <span class="op">-&gt;</span> terminalAt<span class="op">.</span>plusDays<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>housekeeping<span class="op">.</span>retentionDays<span class="op">)</span></span>
<span id="cb56-215"><a href="#cb56-215" aria-hidden="true" tabindex="-1"></a> AsyncOperationRetentionMode<span class="op">.</span>SHORT_LIVED <span class="op">-&gt;</span> terminalAt<span class="op">.</span>plusMinutes<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>housekeeping<span class="op">.</span>shortLivedMinutes<span class="op">)</span></span>
<span id="cb56-216"><a href="#cb56-216" aria-hidden="true" tabindex="-1"></a> AsyncOperationRetentionMode<span class="op">.</span>EPHEMERAL <span class="op">-&gt;</span> terminalAt<span class="op">.</span>plusSeconds<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>housekeeping<span class="op">.</span>ephemeralGraceSeconds<span class="op">)</span></span>
<span id="cb56-217"><a href="#cb56-217" aria-hidden="true" tabindex="-1"></a> AsyncOperationRetentionMode<span class="op">.</span>AUDIT <span class="op">-&gt;</span> terminalAt<span class="op">.</span>plusDays<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>housekeeping<span class="op">.</span>auditDays<span class="op">)</span></span>
<span id="cb56-218"><a href="#cb56-218" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div>
<div id="code-11-5" class="figure-caption figure-kind-insert">Code 11.5: Durable progress, completion, failure, snapshot conversion, after-commit broadcast, and retention calculation are all centralized in AsyncOperationServiceImpl.</div>
</div>
<p>Read the implementation with this mental model:</p>
<ol type="1">
<li><code>submit(...)</code> creates one durable queued row.</li>
<li><code>markRunning(...)</code>, <code>publishProgress(...)</code>,
<code>complete(...)</code>, and <code>fail(...)</code> mutate that row,
increment <code>revision</code>, and broadcast after commit.</li>
<li><code>publishVolatileProgress(...)</code> creates only an in-memory
<code>VOLATILE_PROGRESS</code> event with a <code>baseRevision</code>
and per-operation volatile <code>sequence</code>.</li>
<li>exact subscribers see the latest durable snapshot first and may then
see the latest volatile overlay if the operation is still active.</li>
<li>terminal durable transitions clear live listeners and volatile
caches for that operation.</li>
</ol>
<p>This keeps the database authoritative without forcing every tiny
progress tick to become durable state.</p>
</section>
<section id="topic-hierarchy" class="level2" data-number="11.11">
<h2 data-number="11.11"><span class="header-section-number">11.11</span>
Topic Hierarchy</h2>
<p>Topics are derived, dot-delimited, and prefix-matchable.</p>
<div class="captioned-block captioned-code">
<style>
#code-11-6 + p, #code-11-6 + div.sourceCode, #code-11-6 + pre, #code-11-6 + table { display: block; max-width: 100%; }
#code-11-6 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb57"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb57-1"><a href="#cb57-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="kw">class</span> AsyncOperationTopic<span class="op">(</span></span>
<span id="cb57-2"><a href="#cb57-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">value</span><span class="op">:</span> <span class="dt">String</span></span>
<span id="cb57-3"><a href="#cb57-3" aria-hidden="true" tabindex="-1"></a><span class="op">)</span> <span class="op">{</span></span>
<span id="cb57-4"><a href="#cb57-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">prefixes</span><span class="op">():</span> <span class="dt">List</span><span class="op">&lt;</span><span class="dt">String</span><span class="op">&gt;</span> <span class="op">{</span></span>
<span id="cb57-5"><a href="#cb57-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">segments</span> <span class="op">=</span> value<span class="op">.</span>split<span class="op">(</span><span class="ch">&#39;.&#39;</span><span class="op">)</span></span>
<span id="cb57-6"><a href="#cb57-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> segments<span class="op">.</span>indices<span class="op">.</span>map <span class="op">{</span> index <span class="op">-&gt;</span></span>
<span id="cb57-7"><a href="#cb57-7" aria-hidden="true" tabindex="-1"></a> segments<span class="op">.</span>subList<span class="op">(</span><span class="dv">0</span><span class="op">,</span> index <span class="op">+</span> <span class="dv">1</span><span class="op">).</span>joinToString<span class="op">(</span><span class="st">&quot;.&quot;</span><span class="op">)</span></span>
<span id="cb57-8"><a href="#cb57-8" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb57-9"><a href="#cb57-9" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb57-10"><a href="#cb57-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb57-11"><a href="#cb57-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">companion</span> <span class="kw">object</span> <span class="op">{</span></span>
<span id="cb57-12"><a href="#cb57-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">const</span> <span class="kw">val</span> <span class="va">ROOT</span> <span class="op">=</span> <span class="st">&quot;async-operations&quot;</span></span>
<span id="cb57-13"><a href="#cb57-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb57-14"><a href="#cb57-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">forOperation</span><span class="op">(</span></span>
<span id="cb57-15"><a href="#cb57-15" aria-hidden="true" tabindex="-1"></a> <span class="va">operationKey</span><span class="op">:</span> <span class="dt">String</span><span class="op">?,</span></span>
<span id="cb57-16"><a href="#cb57-16" aria-hidden="true" tabindex="-1"></a> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span></span>
<span id="cb57-17"><a href="#cb57-17" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationTopic</span> <span class="op">{</span></span>
<span id="cb57-18"><a href="#cb57-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">segments</span> <span class="op">=</span> buildList <span class="op">{</span></span>
<span id="cb57-19"><a href="#cb57-19" aria-hidden="true" tabindex="-1"></a> add<span class="op">(</span>ROOT<span class="op">)</span></span>
<span id="cb57-20"><a href="#cb57-20" aria-hidden="true" tabindex="-1"></a> addAll<span class="op">(</span>normalizeSegments<span class="op">(</span>operationKey<span class="op">).</span>ifEmpty <span class="op">{</span> listOf<span class="op">(</span><span class="st">&quot;general&quot;</span><span class="op">)</span> <span class="op">})</span></span>
<span id="cb57-21"><a href="#cb57-21" aria-hidden="true" tabindex="-1"></a> add<span class="op">(</span><span class="st">&quot;operations&quot;</span><span class="op">)</span></span>
<span id="cb57-22"><a href="#cb57-22" aria-hidden="true" tabindex="-1"></a> add<span class="op">(</span>operationId<span class="op">.</span>toString<span class="op">())</span></span>
<span id="cb57-23"><a href="#cb57-23" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb57-24"><a href="#cb57-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> AsyncOperationTopic<span class="op">(</span>segments<span class="op">.</span>joinToString<span class="op">(</span><span class="st">&quot;.&quot;</span><span class="op">))</span></span>
<span id="cb57-25"><a href="#cb57-25" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb57-26"><a href="#cb57-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb57-27"><a href="#cb57-27" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">forPrefix</span><span class="op">(</span><span class="va">operationKeyPrefix</span><span class="op">:</span> <span class="dt">String</span><span class="op">?):</span> <span class="dt">AsyncOperationTopic</span> <span class="op">{</span></span>
<span id="cb57-28"><a href="#cb57-28" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">segments</span> <span class="op">=</span> buildList <span class="op">{</span></span>
<span id="cb57-29"><a href="#cb57-29" aria-hidden="true" tabindex="-1"></a> add<span class="op">(</span>ROOT<span class="op">)</span></span>
<span id="cb57-30"><a href="#cb57-30" aria-hidden="true" tabindex="-1"></a> addAll<span class="op">(</span>normalizeSegments<span class="op">(</span>operationKeyPrefix<span class="op">))</span></span>
<span id="cb57-31"><a href="#cb57-31" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb57-32"><a href="#cb57-32" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> AsyncOperationTopic<span class="op">(</span>segments<span class="op">.</span>joinToString<span class="op">(</span><span class="st">&quot;.&quot;</span><span class="op">))</span></span>
<span id="cb57-33"><a href="#cb57-33" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb57-34"><a href="#cb57-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb57-35"><a href="#cb57-35" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">normalizeSegments</span><span class="op">(</span><span class="va">raw</span><span class="op">:</span> <span class="dt">String</span><span class="op">?):</span> <span class="dt">List</span><span class="op">&lt;</span><span class="dt">String</span><span class="op">&gt;</span> <span class="op">=</span></span>
<span id="cb57-36"><a href="#cb57-36" aria-hidden="true" tabindex="-1"></a> raw</span>
<span id="cb57-37"><a href="#cb57-37" aria-hidden="true" tabindex="-1"></a> <span class="op">?.</span>split<span class="op">(</span><span class="ch">&#39;.&#39;</span><span class="op">)</span></span>
<span id="cb57-38"><a href="#cb57-38" aria-hidden="true" tabindex="-1"></a> <span class="op">?.</span>mapNotNull <span class="op">{</span> normalizeSegment<span class="op">(</span>it<span class="op">)</span> <span class="op">}</span></span>
<span id="cb57-39"><a href="#cb57-39" aria-hidden="true" tabindex="-1"></a> <span class="op">?:</span> emptyList<span class="op">()</span></span>
<span id="cb57-40"><a href="#cb57-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb57-41"><a href="#cb57-41" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">normalizeSegment</span><span class="op">(</span><span class="va">raw</span><span class="op">:</span> <span class="dt">String</span><span class="op">?):</span> <span class="dt">String</span><span class="op">?</span> <span class="op">=</span></span>
<span id="cb57-42"><a href="#cb57-42" aria-hidden="true" tabindex="-1"></a> raw</span>
<span id="cb57-43"><a href="#cb57-43" aria-hidden="true" tabindex="-1"></a> <span class="op">?.</span>trim<span class="op">()</span></span>
<span id="cb57-44"><a href="#cb57-44" aria-hidden="true" tabindex="-1"></a> <span class="op">?.</span>takeIf <span class="op">{</span> it<span class="op">.</span>isNotEmpty<span class="op">()</span> <span class="op">}</span></span>
<span id="cb57-45"><a href="#cb57-45" aria-hidden="true" tabindex="-1"></a> <span class="op">?.</span>replace<span class="op">(</span>Regex<span class="op">(</span><span class="st">&quot;[^A-Za-z0-9]+&quot;</span><span class="op">),</span> <span class="st">&quot;-&quot;</span><span class="op">)</span></span>
<span id="cb57-46"><a href="#cb57-46" aria-hidden="true" tabindex="-1"></a> <span class="op">?.</span>trim<span class="op">(</span><span class="ch">&#39;-&#39;</span><span class="op">)</span></span></code></pre></div>
<div id="code-11-6" class="figure-caption figure-kind-insert">Code 11.6: AsyncOperationTopic builds the dot-delimited hierarchy used for domain-level subscriptions and prefix fan-out.</div>
</div>
<p>Example shapes:</p>
<ul>
<li><code>async-operations</code></li>
<li><code>async-operations.process-support</code></li>
<li><code>async-operations.process-support.cleanup-orphan-process-instances</code></li>
<li><code>async-operations.process-support.cleanup-orphan-process-instances.operations.&lt;id&gt;</code></li>
</ul>
<p>The practical consequence is:</p>
<ul>
<li>the publisher only updates one operation</li>
<li>exact subscribers filter by <code>operationId</code></li>
<li>topic subscribers filter by prefix over the derived
<code>topic</code></li>
</ul>
<p>The broadcaster itself is still node-local and in-memory, which is
acceptable for the first phase because exact-operation recovery does not
depend on live in-memory fan-out.</p>
</section>
<section id="http-surface-and-reconnect-contract" class="level2" data-number="11.12">
<h2 data-number="11.12"><span class="header-section-number">11.12</span>
HTTP Surface And Reconnect Contract</h2>
<p>The generic async-operation HTTP surface is intentionally separate
from process-specific controllers.</p>
<div class="captioned-block captioned-code">
<style>
#code-11-7 + p, #code-11-7 + div.sourceCode, #code-11-7 + pre, #code-11-7 + table { display: block; max-width: 100%; }
#code-11-7 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb58"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb58-1"><a href="#cb58-1" aria-hidden="true" tabindex="-1"></a><span class="at">@Tag</span><span class="op">(</span>name <span class="op">=</span> <span class="st">&quot;sigma-app&quot;</span><span class="op">,</span> description <span class="op">=</span> <span class="st">&quot;Sigma System/App APIs&quot;</span><span class="op">)</span></span>
<span id="cb58-2"><a href="#cb58-2" aria-hidden="true" tabindex="-1"></a><span class="at">@RestController</span></span>
<span id="cb58-3"><a href="#cb58-3" aria-hidden="true" tabindex="-1"></a><span class="at">@RequestMapping</span><span class="op">(</span>path <span class="op">=</span> <span class="op">[</span><span class="st">&quot;</span><span class="sc">\$</span><span class="st">{app.openapi.controller-prefixes.app}/async-operations&quot;</span><span class="op">],</span> produces <span class="op">=</span> <span class="op">[</span>MediaType<span class="op">.</span>APPLICATION_JSON_VALUE<span class="op">])</span></span>
<span id="cb58-4"><a href="#cb58-4" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> AsyncOperationController<span class="op">(</span></span>
<span id="cb58-5"><a href="#cb58-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">asyncOperationStreamingService</span><span class="op">:</span> <span class="dt">AsyncOperationStreamingService</span></span>
<span id="cb58-6"><a href="#cb58-6" aria-hidden="true" tabindex="-1"></a><span class="op">)</span> <span class="op">{</span></span>
<span id="cb58-7"><a href="#cb58-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-8"><a href="#cb58-8" aria-hidden="true" tabindex="-1"></a> <span class="at">@PreAuthorize</span><span class="op">(</span><span class="st">&quot;hasRole(&#39;ADMIN&#39;)&quot;</span><span class="op">)</span></span>
<span id="cb58-9"><a href="#cb58-9" aria-hidden="true" tabindex="-1"></a> <span class="at">@GetMapping</span><span class="op">(</span><span class="st">&quot;/{operationId}&quot;</span><span class="op">)</span></span>
<span id="cb58-10"><a href="#cb58-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">getAsyncOperation</span><span class="op">(</span>@<span class="va">PathVariable</span> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">):</span> <span class="dt">AsyncOperationDto</span> <span class="op">=</span></span>
<span id="cb58-11"><a href="#cb58-11" aria-hidden="true" tabindex="-1"></a> asyncOperationStreamingService<span class="op">.</span>getOperation<span class="op">(</span>operationId<span class="op">)?.</span>toDto<span class="op">()</span></span>
<span id="cb58-12"><a href="#cb58-12" aria-hidden="true" tabindex="-1"></a> <span class="op">?:</span> <span class="kw">throw</span> ResponseStatusException<span class="op">(</span>HttpStatus<span class="op">.</span>NOT_FOUND<span class="op">,</span> <span class="st">&quot;Async operation </span><span class="ss">$operationId</span><span class="st"> not found&quot;</span><span class="op">)</span></span>
<span id="cb58-13"><a href="#cb58-13" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<div id="code-11-7" class="figure-caption figure-kind-insert">Code 11.7: AsyncOperationController exposes the durable current snapshot for one operation under the app API namespace.</div>
</div>
<div class="captioned-block captioned-code">
<style>
#code-11-8 + p, #code-11-8 + div.sourceCode, #code-11-8 + pre, #code-11-8 + table { display: block; max-width: 100%; }
#code-11-8 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb59"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb59-1"><a href="#cb59-1" aria-hidden="true" tabindex="-1"></a><span class="at">@RestController</span></span>
<span id="cb59-2"><a href="#cb59-2" aria-hidden="true" tabindex="-1"></a><span class="at">@RequestMapping</span><span class="op">(</span>path <span class="op">=</span> <span class="op">[</span><span class="st">&quot;</span><span class="sc">\$</span><span class="st">{app.openapi.controller-prefixes.app-streaming}/async-operations&quot;</span><span class="op">])</span></span>
<span id="cb59-3"><a href="#cb59-3" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> AsyncOperationControllerStreaming<span class="op">(</span></span>
<span id="cb59-4"><a href="#cb59-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">asyncOperationStreamingService</span><span class="op">:</span> <span class="dt">AsyncOperationStreamingService</span><span class="op">,</span></span>
<span id="cb59-5"><a href="#cb59-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">asyncOperationsProperties</span><span class="op">:</span> <span class="dt">AsyncOperationsProperties</span></span>
<span id="cb59-6"><a href="#cb59-6" aria-hidden="true" tabindex="-1"></a><span class="op">)</span> <span class="op">{</span></span>
<span id="cb59-7"><a href="#cb59-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-8"><a href="#cb59-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">heartbeatExecutor</span><span class="op">:</span> ScheduledExecutorService <span class="op">=</span> MdcExecutors<span class="op">.</span>newSingleThreadScheduledExecutor<span class="op">()</span></span>
<span id="cb59-9"><a href="#cb59-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-10"><a href="#cb59-10" aria-hidden="true" tabindex="-1"></a> <span class="at">@PreDestroy</span></span>
<span id="cb59-11"><a href="#cb59-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">shutdownHeartbeatExecutor</span><span class="op">()</span> <span class="op">{</span></span>
<span id="cb59-12"><a href="#cb59-12" aria-hidden="true" tabindex="-1"></a> heartbeatExecutor<span class="op">.</span>shutdownNow<span class="op">()</span></span>
<span id="cb59-13"><a href="#cb59-13" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-14"><a href="#cb59-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-15"><a href="#cb59-15" aria-hidden="true" tabindex="-1"></a> <span class="at">@PreAuthorize</span><span class="op">(</span><span class="st">&quot;hasRole(&#39;ADMIN&#39;)&quot;</span><span class="op">)</span></span>
<span id="cb59-16"><a href="#cb59-16" aria-hidden="true" tabindex="-1"></a> <span class="at">@GetMapping</span><span class="op">(</span><span class="st">&quot;/{operationId}/events&quot;</span><span class="op">,</span> produces <span class="op">=</span> <span class="op">[</span>MediaType<span class="op">.</span>TEXT_EVENT_STREAM_VALUE<span class="op">])</span></span>
<span id="cb59-17"><a href="#cb59-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">streamOperationEvents</span><span class="op">(</span></span>
<span id="cb59-18"><a href="#cb59-18" aria-hidden="true" tabindex="-1"></a> @<span class="va">PathVariable</span> <span class="va">operationId</span><span class="op">:</span> <span class="dt">UUID</span><span class="op">,</span></span>
<span id="cb59-19"><a href="#cb59-19" aria-hidden="true" tabindex="-1"></a> @<span class="va">RequestHeader</span>(<span class="va">name</span> <span class="op">=</span> <span class="st">&quot;Last-Event-ID&quot;</span><span class="op">,</span> <span class="va">required</span> <span class="op">=</span> false<span class="op">)</span> <span class="fu">lastEventId</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span>,</span>
<span id="cb59-20"><a href="#cb59-20" aria-hidden="true" tabindex="-1"></a> <span class="at">@RequestParam</span><span class="op">(</span><span class="va">required</span> <span class="op">=</span> false<span class="op">)</span> <span class="fu">sinceRevision</span><span class="op">:</span> <span class="dt">Long</span><span class="op">?</span></span>
<span id="cb59-21"><a href="#cb59-21" aria-hidden="true" tabindex="-1"></a> )<span class="op">:</span> <span class="dt">ResponseEntity</span><span class="op">&lt;</span><span class="dt">SseEmitter</span><span class="op">&gt;</span> <span class="op">{</span></span>
<span id="cb59-22"><a href="#cb59-22" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">currentSnapshot</span> <span class="op">=</span> asyncOperationStreamingService<span class="op">.</span>getOperation<span class="op">(</span>operationId<span class="op">)</span></span>
<span id="cb59-23"><a href="#cb59-23" aria-hidden="true" tabindex="-1"></a> <span class="op">?:</span> <span class="kw">throw</span> ResponseStatusException<span class="op">(</span>HttpStatus<span class="op">.</span>NOT_FOUND<span class="op">,</span> <span class="st">&quot;Async operation </span><span class="ss">$operationId</span><span class="st"> not found&quot;</span><span class="op">)</span></span>
<span id="cb59-24"><a href="#cb59-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">lastSeenRevision</span> <span class="op">=</span> listOfNotNull<span class="op">(</span>sinceRevision<span class="op">,</span> lastEventId<span class="op">?.</span>toLongOrNull<span class="op">()).</span>maxOrNull<span class="op">()</span></span>
<span id="cb59-25"><a href="#cb59-25" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">emitter</span> <span class="op">=</span> SseEmitter<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>emitterTimeoutMillis<span class="op">)</span></span>
<span id="cb59-26"><a href="#cb59-26" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">latestSnapshot</span> <span class="op">=</span> AtomicReference<span class="op">&lt;</span>AsyncOperationSnapshot<span class="op">?&gt;(</span>currentSnapshot<span class="op">)</span></span>
<span id="cb59-27"><a href="#cb59-27" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">subscription</span> <span class="op">=</span> asyncOperationStreamingService<span class="op">.</span>subscribeToOperation<span class="op">(</span>operationId<span class="op">,</span> lastSeenRevision<span class="op">)</span> <span class="op">{</span> event <span class="op">-&gt;</span></span>
<span id="cb59-28"><a href="#cb59-28" aria-hidden="true" tabindex="-1"></a> latestSnapshot<span class="op">.</span><span class="kw">set</span><span class="op">(</span>event<span class="op">.</span>snapshot<span class="op">)</span></span>
<span id="cb59-29"><a href="#cb59-29" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>send<span class="op">(</span></span>
<span id="cb59-30"><a href="#cb59-30" aria-hidden="true" tabindex="-1"></a> SseEmitter<span class="op">.</span>event<span class="op">()</span></span>
<span id="cb59-31"><a href="#cb59-31" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>id<span class="op">(</span>eventId<span class="op">(</span>event<span class="op">))</span></span>
<span id="cb59-32"><a href="#cb59-32" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>name<span class="op">(</span>event<span class="op">.</span>eventType<span class="op">.</span>sseEventName<span class="op">)</span></span>
<span id="cb59-33"><a href="#cb59-33" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>reconnectTime<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>reconnectDelayMillis<span class="op">)</span></span>
<span id="cb59-34"><a href="#cb59-34" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="kw">data</span><span class="op">(</span>event<span class="op">.</span>toDto<span class="op">())</span></span>
<span id="cb59-35"><a href="#cb59-35" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-36"><a href="#cb59-36" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>event<span class="op">.</span>snapshot<span class="op">.</span>status<span class="op">.</span>isTerminal<span class="op">()</span> <span class="op">&amp;&amp;</span> event<span class="op">.</span>eventType <span class="op">!=</span> AsyncOperationEventType<span class="op">.</span>VOLATILE_PROGRESS<span class="op">)</span> <span class="op">{</span></span>
<span id="cb59-37"><a href="#cb59-37" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>complete<span class="op">()</span></span>
<span id="cb59-38"><a href="#cb59-38" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-39"><a href="#cb59-39" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-40"><a href="#cb59-40" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">heartbeat</span> <span class="op">=</span> heartbeatExecutor<span class="op">.</span>scheduleAtFixedRate<span class="op">(</span></span>
<span id="cb59-41"><a href="#cb59-41" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span></span>
<span id="cb59-42"><a href="#cb59-42" aria-hidden="true" tabindex="-1"></a> latestSnapshot<span class="op">.</span><span class="kw">get</span><span class="op">()?.</span>let <span class="op">{</span> snapshot <span class="op">-&gt;</span></span>
<span id="cb59-43"><a href="#cb59-43" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>send<span class="op">(</span></span>
<span id="cb59-44"><a href="#cb59-44" aria-hidden="true" tabindex="-1"></a> SseEmitter<span class="op">.</span>event<span class="op">()</span></span>
<span id="cb59-45"><a href="#cb59-45" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>name<span class="op">(</span>AsyncOperationEventType<span class="op">.</span>HEARTBEAT<span class="op">.</span>sseEventName<span class="op">)</span></span>
<span id="cb59-46"><a href="#cb59-46" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>reconnectTime<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>reconnectDelayMillis<span class="op">)</span></span>
<span id="cb59-47"><a href="#cb59-47" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="kw">data</span><span class="op">(</span></span>
<span id="cb59-48"><a href="#cb59-48" aria-hidden="true" tabindex="-1"></a> AsyncOperationHeartbeatDto<span class="op">(</span></span>
<span id="cb59-49"><a href="#cb59-49" aria-hidden="true" tabindex="-1"></a> operationId <span class="op">=</span> snapshot<span class="op">.</span>operationId<span class="op">.</span>toString<span class="op">(),</span></span>
<span id="cb59-50"><a href="#cb59-50" aria-hidden="true" tabindex="-1"></a> revision <span class="op">=</span> snapshot<span class="op">.</span>revision<span class="op">,</span></span>
<span id="cb59-51"><a href="#cb59-51" aria-hidden="true" tabindex="-1"></a> updatedAt <span class="op">=</span> snapshot<span class="op">.</span>updatedAt<span class="op">,</span></span>
<span id="cb59-52"><a href="#cb59-52" aria-hidden="true" tabindex="-1"></a> serverTime <span class="op">=</span> LocalDateTime<span class="op">.</span>now<span class="op">()</span></span>
<span id="cb59-53"><a href="#cb59-53" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-54"><a href="#cb59-54" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-55"><a href="#cb59-55" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-56"><a href="#cb59-56" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-57"><a href="#cb59-57" aria-hidden="true" tabindex="-1"></a> <span class="op">},</span></span>
<span id="cb59-58"><a href="#cb59-58" aria-hidden="true" tabindex="-1"></a> asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>heartbeatIntervalSeconds<span class="op">,</span></span>
<span id="cb59-59"><a href="#cb59-59" aria-hidden="true" tabindex="-1"></a> asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>heartbeatIntervalSeconds<span class="op">,</span></span>
<span id="cb59-60"><a href="#cb59-60" aria-hidden="true" tabindex="-1"></a> TimeUnit<span class="op">.</span>SECONDS</span>
<span id="cb59-61"><a href="#cb59-61" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-62"><a href="#cb59-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-63"><a href="#cb59-63" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">cleanup</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb59-64"><a href="#cb59-64" aria-hidden="true" tabindex="-1"></a> heartbeat<span class="op">.</span>cancel<span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb59-65"><a href="#cb59-65" aria-hidden="true" tabindex="-1"></a> subscription<span class="op">.</span>close<span class="op">()</span></span>
<span id="cb59-66"><a href="#cb59-66" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-67"><a href="#cb59-67" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>onCompletion<span class="op">(</span>cleanup<span class="op">)</span></span>
<span id="cb59-68"><a href="#cb59-68" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>onTimeout<span class="op">(</span>cleanup<span class="op">)</span></span>
<span id="cb59-69"><a href="#cb59-69" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>onError <span class="op">{</span> cleanup<span class="op">()</span> <span class="op">}</span></span>
<span id="cb59-70"><a href="#cb59-70" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> streamingResponse<span class="op">(</span>emitter<span class="op">)</span></span>
<span id="cb59-71"><a href="#cb59-71" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-72"><a href="#cb59-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-73"><a href="#cb59-73" aria-hidden="true" tabindex="-1"></a> <span class="at">@PreAuthorize</span><span class="op">(</span><span class="st">&quot;hasRole(&#39;ADMIN&#39;)&quot;</span><span class="op">)</span></span>
<span id="cb59-74"><a href="#cb59-74" aria-hidden="true" tabindex="-1"></a> <span class="at">@GetMapping</span><span class="op">(</span><span class="st">&quot;/events&quot;</span><span class="op">,</span> produces <span class="op">=</span> <span class="op">[</span>MediaType<span class="op">.</span>TEXT_EVENT_STREAM_VALUE<span class="op">])</span></span>
<span id="cb59-75"><a href="#cb59-75" aria-hidden="true" tabindex="-1"></a> <span class="kw">fun</span> <span class="fu">streamTopicEvents</span><span class="op">(</span></span>
<span id="cb59-76"><a href="#cb59-76" aria-hidden="true" tabindex="-1"></a> @<span class="va">RequestParam</span>(<span class="va">required</span> <span class="op">=</span> false<span class="op">)</span> <span class="fu">topicPrefix</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span></span>
<span id="cb59-77"><a href="#cb59-77" aria-hidden="true" tabindex="-1"></a> )<span class="op">:</span> <span class="dt">ResponseEntity</span><span class="op">&lt;</span><span class="dt">SseEmitter</span><span class="op">&gt;</span> <span class="op">{</span></span>
<span id="cb59-78"><a href="#cb59-78" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">emitter</span> <span class="op">=</span> SseEmitter<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>emitterTimeoutMillis<span class="op">)</span></span>
<span id="cb59-79"><a href="#cb59-79" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">latestSnapshot</span> <span class="op">=</span> AtomicReference<span class="op">&lt;</span>AsyncOperationSnapshot<span class="op">?&gt;(</span><span class="kw">null</span><span class="op">)</span></span>
<span id="cb59-80"><a href="#cb59-80" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">topicSubscription</span> <span class="op">=</span> asyncOperationStreamingService<span class="op">.</span>subscribeToTopic<span class="op">(</span>topicPrefix<span class="op">)</span> <span class="op">{</span> event <span class="op">-&gt;</span></span>
<span id="cb59-81"><a href="#cb59-81" aria-hidden="true" tabindex="-1"></a> latestSnapshot<span class="op">.</span><span class="kw">set</span><span class="op">(</span>event<span class="op">.</span>snapshot<span class="op">)</span></span>
<span id="cb59-82"><a href="#cb59-82" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>send<span class="op">(</span></span>
<span id="cb59-83"><a href="#cb59-83" aria-hidden="true" tabindex="-1"></a> SseEmitter<span class="op">.</span>event<span class="op">()</span></span>
<span id="cb59-84"><a href="#cb59-84" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>id<span class="op">(</span>eventId<span class="op">(</span>event<span class="op">))</span></span>
<span id="cb59-85"><a href="#cb59-85" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>name<span class="op">(</span>event<span class="op">.</span>eventType<span class="op">.</span>sseEventName<span class="op">)</span></span>
<span id="cb59-86"><a href="#cb59-86" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>reconnectTime<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>reconnectDelayMillis<span class="op">)</span></span>
<span id="cb59-87"><a href="#cb59-87" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="kw">data</span><span class="op">(</span>event<span class="op">.</span>toDto<span class="op">())</span></span>
<span id="cb59-88"><a href="#cb59-88" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-89"><a href="#cb59-89" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-90"><a href="#cb59-90" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">resolvedTopicPrefix</span> <span class="op">=</span> topicSubscription<span class="op">.</span>topicPrefix</span>
<span id="cb59-91"><a href="#cb59-91" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">heartbeat</span> <span class="op">=</span> heartbeatExecutor<span class="op">.</span>scheduleAtFixedRate<span class="op">(</span></span>
<span id="cb59-92"><a href="#cb59-92" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span></span>
<span id="cb59-93"><a href="#cb59-93" aria-hidden="true" tabindex="-1"></a> latestSnapshot<span class="op">.</span><span class="kw">get</span><span class="op">()?.</span>let <span class="op">{</span> snapshot <span class="op">-&gt;</span></span>
<span id="cb59-94"><a href="#cb59-94" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>send<span class="op">(</span></span>
<span id="cb59-95"><a href="#cb59-95" aria-hidden="true" tabindex="-1"></a> SseEmitter<span class="op">.</span>event<span class="op">()</span></span>
<span id="cb59-96"><a href="#cb59-96" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>name<span class="op">(</span>AsyncOperationEventType<span class="op">.</span>HEARTBEAT<span class="op">.</span>sseEventName<span class="op">)</span></span>
<span id="cb59-97"><a href="#cb59-97" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>reconnectTime<span class="op">(</span>asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>reconnectDelayMillis<span class="op">)</span></span>
<span id="cb59-98"><a href="#cb59-98" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="kw">data</span><span class="op">(</span></span>
<span id="cb59-99"><a href="#cb59-99" aria-hidden="true" tabindex="-1"></a> mapOf<span class="op">(</span></span>
<span id="cb59-100"><a href="#cb59-100" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;topicPrefix&quot;</span> to resolvedTopicPrefix<span class="op">,</span></span>
<span id="cb59-101"><a href="#cb59-101" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;topic&quot;</span> to snapshot<span class="op">.</span>topic<span class="op">,</span></span>
<span id="cb59-102"><a href="#cb59-102" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;serverTime&quot;</span> to LocalDateTime<span class="op">.</span>now<span class="op">(),</span></span>
<span id="cb59-103"><a href="#cb59-103" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;operationId&quot;</span> to snapshot<span class="op">.</span>operationId<span class="op">,</span></span>
<span id="cb59-104"><a href="#cb59-104" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;revision&quot;</span> to snapshot<span class="op">.</span>revision</span>
<span id="cb59-105"><a href="#cb59-105" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-106"><a href="#cb59-106" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-107"><a href="#cb59-107" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-108"><a href="#cb59-108" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-109"><a href="#cb59-109" aria-hidden="true" tabindex="-1"></a> <span class="op">},</span></span>
<span id="cb59-110"><a href="#cb59-110" aria-hidden="true" tabindex="-1"></a> asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>heartbeatIntervalSeconds<span class="op">,</span></span>
<span id="cb59-111"><a href="#cb59-111" aria-hidden="true" tabindex="-1"></a> asyncOperationsProperties<span class="op">.</span>streaming<span class="op">.</span>heartbeatIntervalSeconds<span class="op">,</span></span>
<span id="cb59-112"><a href="#cb59-112" aria-hidden="true" tabindex="-1"></a> TimeUnit<span class="op">.</span>SECONDS</span>
<span id="cb59-113"><a href="#cb59-113" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb59-114"><a href="#cb59-114" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-115"><a href="#cb59-115" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">cleanup</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb59-116"><a href="#cb59-116" aria-hidden="true" tabindex="-1"></a> heartbeat<span class="op">.</span>cancel<span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb59-117"><a href="#cb59-117" aria-hidden="true" tabindex="-1"></a> topicSubscription<span class="op">.</span>subscription<span class="op">.</span>close<span class="op">()</span></span>
<span id="cb59-118"><a href="#cb59-118" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-119"><a href="#cb59-119" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>onCompletion<span class="op">(</span>cleanup<span class="op">)</span></span>
<span id="cb59-120"><a href="#cb59-120" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>onTimeout<span class="op">(</span>cleanup<span class="op">)</span></span>
<span id="cb59-121"><a href="#cb59-121" aria-hidden="true" tabindex="-1"></a> emitter<span class="op">.</span>onError <span class="op">{</span> cleanup<span class="op">()</span> <span class="op">}</span></span>
<span id="cb59-122"><a href="#cb59-122" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> streamingResponse<span class="op">(</span>emitter<span class="op">)</span></span>
<span id="cb59-123"><a href="#cb59-123" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-124"><a href="#cb59-124" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-125"><a href="#cb59-125" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">streamingResponse</span><span class="op">(</span><span class="va">emitter</span><span class="op">:</span> <span class="dt">SseEmitter</span><span class="op">):</span> <span class="dt">ResponseEntity</span><span class="op">&lt;</span><span class="dt">SseEmitter</span><span class="op">&gt;</span> <span class="op">=</span></span>
<span id="cb59-126"><a href="#cb59-126" aria-hidden="true" tabindex="-1"></a> ResponseEntity<span class="op">.</span>ok<span class="op">()</span></span>
<span id="cb59-127"><a href="#cb59-127" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>cacheControl<span class="op">(</span>CacheControl<span class="op">.</span>noStore<span class="op">())</span></span>
<span id="cb59-128"><a href="#cb59-128" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>header<span class="op">(</span><span class="st">&quot;X-Accel-Buffering&quot;</span><span class="op">,</span> <span class="st">&quot;no&quot;</span><span class="op">)</span></span>
<span id="cb59-129"><a href="#cb59-129" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>header<span class="op">(</span>HttpHeaders<span class="op">.</span>CONNECTION<span class="op">,</span> <span class="st">&quot;keep-alive&quot;</span><span class="op">)</span></span>
<span id="cb59-130"><a href="#cb59-130" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>contentType<span class="op">(</span>MediaType<span class="op">.</span>TEXT_EVENT_STREAM<span class="op">)</span></span>
<span id="cb59-131"><a href="#cb59-131" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>body<span class="op">(</span>emitter<span class="op">)</span></span>
<span id="cb59-132"><a href="#cb59-132" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-133"><a href="#cb59-133" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">AsyncOperationEventType</span><span class="op">.</span><span class="fu">isVolatile</span><span class="op">():</span> <span class="dt">Boolean</span> <span class="op">=</span></span>
<span id="cb59-134"><a href="#cb59-134" aria-hidden="true" tabindex="-1"></a> <span class="kw">this</span> <span class="op">==</span> AsyncOperationEventType<span class="op">.</span>VOLATILE_PROGRESS</span>
<span id="cb59-135"><a href="#cb59-135" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-136"><a href="#cb59-136" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">eventId</span><span class="op">(</span><span class="va">event</span><span class="op">:</span> <span class="dt">AsyncOperationStreamEvent</span><span class="op">):</span> <span class="dt">String</span> <span class="op">=</span></span>
<span id="cb59-137"><a href="#cb59-137" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>event<span class="op">.</span>eventType<span class="op">.</span>isVolatile<span class="op">())</span> <span class="op">{</span></span>
<span id="cb59-138"><a href="#cb59-138" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;</span><span class="ss">${</span>event<span class="op">.</span>snapshot<span class="op">.</span>operationId<span class="ss">}</span><span class="st">:</span><span class="ss">${</span>event<span class="op">.</span>snapshot<span class="op">.</span>revision<span class="ss">}</span><span class="st">:v</span><span class="ss">${</span>event<span class="op">.</span>volatileUpdate<span class="op">?.</span>sequence <span class="op">?:</span> <span class="dv">0</span><span class="ss">}</span><span class="st">&quot;</span></span>
<span id="cb59-139"><a href="#cb59-139" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb59-140"><a href="#cb59-140" aria-hidden="true" tabindex="-1"></a> event<span class="op">.</span>snapshot<span class="op">.</span>revision<span class="op">.</span>toString<span class="op">()</span></span>
<span id="cb59-141"><a href="#cb59-141" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb59-142"><a href="#cb59-142" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb59-143"><a href="#cb59-143" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">AsyncOperationStatus</span><span class="op">.</span><span class="fu">isTerminal</span><span class="op">():</span> <span class="dt">Boolean</span> <span class="op">=</span></span>
<span id="cb59-144"><a href="#cb59-144" aria-hidden="true" tabindex="-1"></a> <span class="kw">this</span> <span class="op">==</span> AsyncOperationStatus<span class="op">.</span>COMPLETED <span class="op">||</span> <span class="kw">this</span> <span class="op">==</span> AsyncOperationStatus<span class="op">.</span>FAILED</span>
<span id="cb59-145"><a href="#cb59-145" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<div id="code-11-8" class="figure-caption figure-kind-insert">Code 11.8: AsyncOperationControllerStreaming exposes exact-operation and topic-prefix SSE streams under the app-streaming namespace with heartbeats, Last-Event-ID support for exact streams, and proxy-friendly headers.</div>
</div>
<p>The most important operational details are:</p>
<ul>
<li>exact streams are under the <code>app-streaming</code> namespace,
which is the intended Apigee registration point</li>
<li>exact streams accept <code>Last-Event-ID</code> and
<code>sinceRevision</code></li>
<li>heartbeats are emitted on a configurable interval below the expected
gateway timeout boundary</li>
<li>volatile SSE ids are distinct from durable <code>revision</code>
ids, so the client can tell they are overlays rather than authoritative
durable ordering markers</li>
</ul>
</section>
<section id="retention-and-housekeeping" class="level2" data-number="11.13">
<h2 data-number="11.13"><span class="header-section-number">11.13</span>
Retention And Housekeeping</h2>
<p>Retention is policy-based, not callback-based.</p>
<div class="captioned-block captioned-code">
<style>
#code-11-9 + p, #code-11-9 + div.sourceCode, #code-11-9 + pre, #code-11-9 + table { display: block; max-width: 100%; }
#code-11-9 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb60"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb60-1"><a href="#cb60-1" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-2"><a href="#cb60-2" aria-hidden="true" tabindex="-1"></a><span class="at">@Service</span></span>
<span id="cb60-3"><a href="#cb60-3" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> AsyncOperationHousekeepingServiceImpl<span class="op">(</span></span>
<span id="cb60-4"><a href="#cb60-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">asyncOperationRunRepository</span><span class="op">:</span> <span class="dt">AsyncOperationRunRepository</span><span class="op">,</span></span>
<span id="cb60-5"><a href="#cb60-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">asyncOperationPublisher</span><span class="op">:</span> <span class="dt">AsyncOperationPublisher</span><span class="op">,</span></span>
<span id="cb60-6"><a href="#cb60-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">asyncOperationsProperties</span><span class="op">:</span> <span class="dt">AsyncOperationsProperties</span><span class="op">,</span></span>
<span id="cb60-7"><a href="#cb60-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">runtimeDiagnostics</span><span class="op">:</span> <span class="dt">RuntimeInstrumentationSettings</span></span>
<span id="cb60-8"><a href="#cb60-8" aria-hidden="true" tabindex="-1"></a><span class="op">)</span> <span class="op">:</span> <span class="dt">AsyncOperationHousekeepingService</span> <span class="op">{</span></span>
<span id="cb60-9"><a href="#cb60-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-10"><a href="#cb60-10" aria-hidden="true" tabindex="-1"></a> <span class="at">@Transactional</span></span>
<span id="cb60-11"><a href="#cb60-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">runHousekeeping</span><span class="op">(</span><span class="va">now</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">):</span> <span class="dt">AsyncOperationHousekeepingResult</span> <span class="op">{</span></span>
<span id="cb60-12"><a href="#cb60-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">properties</span> <span class="op">=</span> asyncOperationsProperties<span class="op">.</span>housekeeping</span>
<span id="cb60-13"><a href="#cb60-13" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(!</span>runtimeDiagnostics<span class="op">.</span>asyncOperations<span class="op">.</span>housekeepingEnabled<span class="op">())</span> <span class="op">{</span></span>
<span id="cb60-14"><a href="#cb60-14" aria-hidden="true" tabindex="-1"></a> logger<span class="op">.</span>trace<span class="op">(</span><span class="st">&quot;Async operation housekeeping skipped; disabled by configuration&quot;</span><span class="op">)</span></span>
<span id="cb60-15"><a href="#cb60-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> AsyncOperationHousekeepingResult<span class="op">()</span></span>
<span id="cb60-16"><a href="#cb60-16" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-17"><a href="#cb60-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-18"><a href="#cb60-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">staleQueued</span> <span class="op">=</span> failStaleOperations<span class="op">(</span></span>
<span id="cb60-19"><a href="#cb60-19" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> AsyncOperationStatus<span class="op">.</span>QUEUED<span class="op">,</span></span>
<span id="cb60-20"><a href="#cb60-20" aria-hidden="true" tabindex="-1"></a> cutoff <span class="op">=</span> now<span class="op">.</span>minusMinutes<span class="op">(</span>properties<span class="op">.</span>staleQueuedMinutes<span class="op">),</span></span>
<span id="cb60-21"><a href="#cb60-21" aria-hidden="true" tabindex="-1"></a> batchSize <span class="op">=</span> properties<span class="op">.</span>staleBatchSize<span class="op">,</span></span>
<span id="cb60-22"><a href="#cb60-22" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Timed out&quot;</span><span class="op">,</span></span>
<span id="cb60-23"><a href="#cb60-23" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Operation expired before starting&quot;</span><span class="op">,</span></span>
<span id="cb60-24"><a href="#cb60-24" aria-hidden="true" tabindex="-1"></a> errorMessage <span class="op">=</span> <span class="st">&quot;Housekeeping marked the queued async operation as stale before execution started.&quot;</span><span class="op">,</span></span>
<span id="cb60-25"><a href="#cb60-25" aria-hidden="true" tabindex="-1"></a> now <span class="op">=</span> now</span>
<span id="cb60-26"><a href="#cb60-26" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-27"><a href="#cb60-27" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">staleRunning</span> <span class="op">=</span> failStaleOperations<span class="op">(</span></span>
<span id="cb60-28"><a href="#cb60-28" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> AsyncOperationStatus<span class="op">.</span>RUNNING<span class="op">,</span></span>
<span id="cb60-29"><a href="#cb60-29" aria-hidden="true" tabindex="-1"></a> cutoff <span class="op">=</span> now<span class="op">.</span>minusMinutes<span class="op">(</span>properties<span class="op">.</span>staleRunningMinutes<span class="op">),</span></span>
<span id="cb60-30"><a href="#cb60-30" aria-hidden="true" tabindex="-1"></a> batchSize <span class="op">=</span> properties<span class="op">.</span>staleBatchSize<span class="op">,</span></span>
<span id="cb60-31"><a href="#cb60-31" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Timed out&quot;</span><span class="op">,</span></span>
<span id="cb60-32"><a href="#cb60-32" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Operation timed out while running&quot;</span><span class="op">,</span></span>
<span id="cb60-33"><a href="#cb60-33" aria-hidden="true" tabindex="-1"></a> errorMessage <span class="op">=</span> <span class="st">&quot;Housekeeping marked the running async operation as stale because it stopped reporting progress.&quot;</span><span class="op">,</span></span>
<span id="cb60-34"><a href="#cb60-34" aria-hidden="true" tabindex="-1"></a> now <span class="op">=</span> now</span>
<span id="cb60-35"><a href="#cb60-35" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-36"><a href="#cb60-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-37"><a href="#cb60-37" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">policyExpiredDeleted</span> <span class="op">=</span> asyncOperationRunRepository<span class="op">.</span>deleteAllByStatusInAndRetentionUntilBefore<span class="op">(</span></span>
<span id="cb60-38"><a href="#cb60-38" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> TERMINAL_STATUSES<span class="op">,</span></span>
<span id="cb60-39"><a href="#cb60-39" aria-hidden="true" tabindex="-1"></a> retentionUntil <span class="op">=</span> now</span>
<span id="cb60-40"><a href="#cb60-40" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-41"><a href="#cb60-41" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">fallbackDeleted</span> <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>properties<span class="op">.</span>retentionDays <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb60-42"><a href="#cb60-42" aria-hidden="true" tabindex="-1"></a> asyncOperationRunRepository<span class="op">.</span>deleteAllByStatusInAndRetentionUntilIsNullAndEndedAtBefore<span class="op">(</span></span>
<span id="cb60-43"><a href="#cb60-43" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> TERMINAL_STATUSES<span class="op">,</span></span>
<span id="cb60-44"><a href="#cb60-44" aria-hidden="true" tabindex="-1"></a> endedAt <span class="op">=</span> now<span class="op">.</span>minusDays<span class="op">(</span>properties<span class="op">.</span>retentionDays<span class="op">)</span></span>
<span id="cb60-45"><a href="#cb60-45" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-46"><a href="#cb60-46" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb60-47"><a href="#cb60-47" aria-hidden="true" tabindex="-1"></a> <span class="dv">0</span></span>
<span id="cb60-48"><a href="#cb60-48" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-49"><a href="#cb60-49" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">deleted</span> <span class="op">=</span> policyExpiredDeleted <span class="op">+</span> fallbackDeleted</span>
<span id="cb60-50"><a href="#cb60-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-51"><a href="#cb60-51" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> AsyncOperationHousekeepingResult<span class="op">(</span></span>
<span id="cb60-52"><a href="#cb60-52" aria-hidden="true" tabindex="-1"></a> staleQueuedMarkedFailed <span class="op">=</span> staleQueued<span class="op">,</span></span>
<span id="cb60-53"><a href="#cb60-53" aria-hidden="true" tabindex="-1"></a> staleRunningMarkedFailed <span class="op">=</span> staleRunning<span class="op">,</span></span>
<span id="cb60-54"><a href="#cb60-54" aria-hidden="true" tabindex="-1"></a> retainedTerminalOperationsDeleted <span class="op">=</span> deleted</span>
<span id="cb60-55"><a href="#cb60-55" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-56"><a href="#cb60-56" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-57"><a href="#cb60-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-58"><a href="#cb60-58" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">fun</span> <span class="fu">failStaleOperations</span><span class="op">(</span></span>
<span id="cb60-59"><a href="#cb60-59" aria-hidden="true" tabindex="-1"></a> <span class="va">status</span><span class="op">:</span> <span class="dt">AsyncOperationStatus</span><span class="op">,</span></span>
<span id="cb60-60"><a href="#cb60-60" aria-hidden="true" tabindex="-1"></a> <span class="va">cutoff</span><span class="op">:</span> <span class="dt">LocalDateTime</span><span class="op">,</span></span>
<span id="cb60-61"><a href="#cb60-61" aria-hidden="true" tabindex="-1"></a> <span class="va">batchSize</span><span class="op">:</span> <span class="dt">Int</span><span class="op">,</span></span>
<span id="cb60-62"><a href="#cb60-62" aria-hidden="true" tabindex="-1"></a> <span class="va">phase</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb60-63"><a href="#cb60-63" aria-hidden="true" tabindex="-1"></a> <span class="va">summary</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb60-64"><a href="#cb60-64" aria-hidden="true" tabindex="-1"></a> <span class="va">errorMessage</span><span class="op">:</span> <span class="dt">String</span><span class="op">,</span></span>
<span id="cb60-65"><a href="#cb60-65" aria-hidden="true" tabindex="-1"></a> <span class="va">now</span><span class="op">:</span> <span class="dt">LocalDateTime</span></span>
<span id="cb60-66"><a href="#cb60-66" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">Int</span> <span class="op">{</span></span>
<span id="cb60-67"><a href="#cb60-67" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>batchSize <span class="op">&lt;=</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb60-68"><a href="#cb60-68" aria-hidden="true" tabindex="-1"></a> logger<span class="op">.</span>debug<span class="op">(</span><span class="st">&quot;Async operation housekeeping skipped stale {} scan; batchSize={}&quot;</span><span class="op">,</span> status<span class="op">,</span> batchSize<span class="op">)</span></span>
<span id="cb60-69"><a href="#cb60-69" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> <span class="dv">0</span></span>
<span id="cb60-70"><a href="#cb60-70" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-71"><a href="#cb60-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-72"><a href="#cb60-72" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> <span class="va">affected</span> <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb60-73"><a href="#cb60-73" aria-hidden="true" tabindex="-1"></a> <span class="cf">while</span> <span class="op">(</span><span class="kw">true</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb60-74"><a href="#cb60-74" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">staleRuns</span> <span class="op">=</span> asyncOperationRunRepository<span class="op">.</span>findAllByStatusAndUpdatedAtBeforeOrderByUpdatedAtAsc<span class="op">(</span></span>
<span id="cb60-75"><a href="#cb60-75" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> status<span class="op">,</span></span>
<span id="cb60-76"><a href="#cb60-76" aria-hidden="true" tabindex="-1"></a> updatedAt <span class="op">=</span> cutoff<span class="op">,</span></span>
<span id="cb60-77"><a href="#cb60-77" aria-hidden="true" tabindex="-1"></a> pageable <span class="op">=</span> PageRequest<span class="op">.</span>of<span class="op">(</span><span class="dv">0</span><span class="op">,</span> batchSize<span class="op">)</span></span>
<span id="cb60-78"><a href="#cb60-78" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-79"><a href="#cb60-79" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>staleRuns<span class="op">.</span>isEmpty<span class="op">())</span> <span class="op">{</span></span>
<span id="cb60-80"><a href="#cb60-80" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> affected</span>
<span id="cb60-81"><a href="#cb60-81" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-82"><a href="#cb60-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-83"><a href="#cb60-83" aria-hidden="true" tabindex="-1"></a> staleRuns<span class="op">.</span>forEach <span class="op">{</span> run <span class="op">-&gt;</span></span>
<span id="cb60-84"><a href="#cb60-84" aria-hidden="true" tabindex="-1"></a> asyncOperationPublisher<span class="op">.</span>fail<span class="op">(</span></span>
<span id="cb60-85"><a href="#cb60-85" aria-hidden="true" tabindex="-1"></a> operationId <span class="op">=</span> run<span class="op">.</span>requireId<span class="op">(),</span></span>
<span id="cb60-86"><a href="#cb60-86" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> summary<span class="op">,</span></span>
<span id="cb60-87"><a href="#cb60-87" aria-hidden="true" tabindex="-1"></a> errorMessage <span class="op">=</span> errorMessage<span class="op">,</span></span>
<span id="cb60-88"><a href="#cb60-88" aria-hidden="true" tabindex="-1"></a> errorCode <span class="op">=</span> STALE_ERROR_CODE<span class="op">,</span></span>
<span id="cb60-89"><a href="#cb60-89" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> phase<span class="op">,</span></span>
<span id="cb60-90"><a href="#cb60-90" aria-hidden="true" tabindex="-1"></a> runtimeContext <span class="op">=</span> mapOf<span class="op">(</span></span>
<span id="cb60-91"><a href="#cb60-91" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;housekeeping&quot;</span> to mapOf<span class="op">(</span></span>
<span id="cb60-92"><a href="#cb60-92" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;reason&quot;</span> to <span class="st">&quot;stale-operation&quot;</span><span class="op">,</span></span>
<span id="cb60-93"><a href="#cb60-93" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;previousStatus&quot;</span> to status<span class="op">.</span>name<span class="op">,</span></span>
<span id="cb60-94"><a href="#cb60-94" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;cutoff&quot;</span> to cutoff<span class="op">,</span></span>
<span id="cb60-95"><a href="#cb60-95" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;markedAt&quot;</span> to now</span>
<span id="cb60-96"><a href="#cb60-96" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-97"><a href="#cb60-97" aria-hidden="true" tabindex="-1"></a> <span class="op">),</span></span>
<span id="cb60-98"><a href="#cb60-98" aria-hidden="true" tabindex="-1"></a> payload <span class="op">=</span> mapOf<span class="op">(</span></span>
<span id="cb60-99"><a href="#cb60-99" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;housekeeping&quot;</span> to mapOf<span class="op">(</span></span>
<span id="cb60-100"><a href="#cb60-100" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;reason&quot;</span> to <span class="st">&quot;stale-operation&quot;</span><span class="op">,</span></span>
<span id="cb60-101"><a href="#cb60-101" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;previousStatus&quot;</span> to status<span class="op">.</span>name</span>
<span id="cb60-102"><a href="#cb60-102" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-103"><a href="#cb60-103" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-104"><a href="#cb60-104" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb60-105"><a href="#cb60-105" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-106"><a href="#cb60-106" aria-hidden="true" tabindex="-1"></a> affected <span class="op">+=</span> staleRuns<span class="op">.</span>size</span>
<span id="cb60-107"><a href="#cb60-107" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>staleRuns<span class="op">.</span>size <span class="op">&lt;</span> batchSize<span class="op">)</span> <span class="op">{</span></span>
<span id="cb60-108"><a href="#cb60-108" aria-hidden="true" tabindex="-1"></a> <span class="kw">return</span> affected</span>
<span id="cb60-109"><a href="#cb60-109" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-110"><a href="#cb60-110" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-111"><a href="#cb60-111" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb60-112"><a href="#cb60-112" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-113"><a href="#cb60-113" aria-hidden="true" tabindex="-1"></a> <span class="kw">companion</span> <span class="kw">object</span> <span class="op">{</span></span>
<span id="cb60-114"><a href="#cb60-114" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">val</span> <span class="va">logger</span> <span class="op">=</span> LoggerFactory<span class="op">.</span>getLogger<span class="op">(</span>AsyncOperationHousekeepingServiceImpl<span class="op">::</span><span class="kw">class</span>.java)</span>
<span id="cb60-115"><a href="#cb60-115" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> <span class="kw">const</span> <span class="kw">val</span> STALE_ERROR_CODE = &quot;ASYNC_OPERATION_STALE&quot;</span></code></pre></div>
<div id="code-11-9" class="figure-caption figure-kind-insert">Code 11.9: AsyncOperationHousekeepingServiceImpl marks stale active operations as failed and purges terminal rows either by explicit retentionUntil or by fallback retention windows.</div>
</div>
<div class="captioned-block captioned-code">
<style>
#code-11-10 + p, #code-11-10 + div.sourceCode, #code-11-10 + pre, #code-11-10 + table { display: block; max-width: 100%; }
#code-11-10 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb61"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb61-1"><a href="#cb61-1" aria-hidden="true" tabindex="-1"></a><span class="at">@ConfigurationProperties</span><span class="op">(</span>prefix <span class="op">=</span> <span class="st">&quot;async-operations&quot;</span><span class="op">)</span></span>
<span id="cb61-2"><a href="#cb61-2" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="kw">class</span> AsyncOperationsProperties<span class="op">(</span></span>
<span id="cb61-3"><a href="#cb61-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">threadPoolSize</span><span class="op">:</span> <span class="dt">Int</span> <span class="op">=</span> <span class="dv">2</span><span class="op">,</span></span>
<span id="cb61-4"><a href="#cb61-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">maxPoolSize</span><span class="op">:</span> <span class="dt">Int</span> <span class="op">=</span> <span class="dv">4</span><span class="op">,</span></span>
<span id="cb61-5"><a href="#cb61-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">queueCapacity</span><span class="op">:</span> <span class="dt">Int</span> <span class="op">=</span> <span class="dv">32</span><span class="op">,</span></span>
<span id="cb61-6"><a href="#cb61-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">streaming</span><span class="op">:</span> <span class="dt">Streaming</span> <span class="op">=</span> Streaming<span class="op">(),</span></span>
<span id="cb61-7"><a href="#cb61-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">housekeeping</span><span class="op">:</span> <span class="dt">Housekeeping</span> <span class="op">=</span> Housekeeping<span class="op">()</span></span>
<span id="cb61-8"><a href="#cb61-8" aria-hidden="true" tabindex="-1"></a><span class="op">)</span> <span class="op">{</span></span>
<span id="cb61-9"><a href="#cb61-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">data</span> <span class="kw">class</span> Streaming<span class="op">(</span></span>
<span id="cb61-10"><a href="#cb61-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">heartbeatIntervalSeconds</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">15</span><span class="op">,</span></span>
<span id="cb61-11"><a href="#cb61-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">reconnectDelayMillis</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">2_000</span><span class="op">,</span></span>
<span id="cb61-12"><a href="#cb61-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">emitterTimeoutMillis</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb61-13"><a href="#cb61-13" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb61-14"><a href="#cb61-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb61-15"><a href="#cb61-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">data</span> <span class="kw">class</span> Housekeeping<span class="op">(</span></span>
<span id="cb61-16"><a href="#cb61-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">enabled</span><span class="op">:</span> <span class="dt">Boolean</span> <span class="op">=</span> true<span class="op">,</span></span>
<span id="cb61-17"><a href="#cb61-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">cron</span><span class="op">:</span> <span class="dt">String</span> <span class="op">=</span> <span class="st">&quot;0 0/15 * * * ?&quot;</span><span class="op">,</span></span>
<span id="cb61-18"><a href="#cb61-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">retentionDays</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">14</span><span class="op">,</span></span>
<span id="cb61-19"><a href="#cb61-19" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">shortLivedMinutes</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">60</span><span class="op">,</span></span>
<span id="cb61-20"><a href="#cb61-20" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">auditDays</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">90</span><span class="op">,</span></span>
<span id="cb61-21"><a href="#cb61-21" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">ephemeralGraceSeconds</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">0</span><span class="op">,</span></span>
<span id="cb61-22"><a href="#cb61-22" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">staleQueuedMinutes</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">30</span><span class="op">,</span></span>
<span id="cb61-23"><a href="#cb61-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">staleRunningMinutes</span><span class="op">:</span> <span class="dt">Long</span> <span class="op">=</span> <span class="dv">240</span><span class="op">,</span></span>
<span id="cb61-24"><a href="#cb61-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">staleBatchSize</span><span class="op">:</span> <span class="dt">Int</span> <span class="op">=</span> <span class="dv">100</span></span>
<span id="cb61-25"><a href="#cb61-25" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span></code></pre></div>
<div id="code-11-10" class="figure-caption figure-kind-insert">Code 11.10: AsyncOperationsProperties keeps thread-pool, streaming, and housekeeping tuning in one focused configuration model.</div>
</div>
<div class="captioned-block captioned-code">
<style>
#code-11-11 + p, #code-11-11 + div.sourceCode, #code-11-11 + pre, #code-11-11 + table { display: block; max-width: 100%; }
#code-11-11 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb62"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb62-1"><a href="#cb62-1" aria-hidden="true" tabindex="-1"></a><span class="fu">async-operations</span><span class="kw">:</span></span>
<span id="cb62-2"><a href="#cb62-2" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">thread-pool-size</span><span class="kw">:</span><span class="at"> </span><span class="dv">2</span></span>
<span id="cb62-3"><a href="#cb62-3" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">max-pool-size</span><span class="kw">:</span><span class="at"> </span><span class="dv">4</span></span>
<span id="cb62-4"><a href="#cb62-4" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">queue-capacity</span><span class="kw">:</span><span class="at"> </span><span class="dv">32</span></span>
<span id="cb62-5"><a href="#cb62-5" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">streaming</span><span class="kw">:</span></span>
<span id="cb62-6"><a href="#cb62-6" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">heartbeat-interval-seconds</span><span class="kw">:</span><span class="at"> </span><span class="dv">15</span></span>
<span id="cb62-7"><a href="#cb62-7" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">reconnect-delay-millis</span><span class="kw">:</span><span class="at"> </span><span class="dv">2000</span></span>
<span id="cb62-8"><a href="#cb62-8" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">emitter-timeout-millis</span><span class="kw">:</span><span class="at"> </span><span class="dv">0</span></span>
<span id="cb62-9"><a href="#cb62-9" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">housekeeping</span><span class="kw">:</span></span>
<span id="cb62-10"><a href="#cb62-10" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">enabled</span><span class="kw">:</span><span class="at"> </span><span class="ch">true</span></span>
<span id="cb62-11"><a href="#cb62-11" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">cron</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;0 0/15 * * * ?&quot;</span></span>
<span id="cb62-12"><a href="#cb62-12" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">retention-days</span><span class="kw">:</span><span class="at"> </span><span class="dv">14</span></span>
<span id="cb62-13"><a href="#cb62-13" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">short-lived-minutes</span><span class="kw">:</span><span class="at"> </span><span class="dv">60</span></span>
<span id="cb62-14"><a href="#cb62-14" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">audit-days</span><span class="kw">:</span><span class="at"> </span><span class="dv">90</span></span>
<span id="cb62-15"><a href="#cb62-15" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">ephemeral-grace-seconds</span><span class="kw">:</span><span class="at"> </span><span class="dv">0</span></span>
<span id="cb62-16"><a href="#cb62-16" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">stale-queued-minutes</span><span class="kw">:</span><span class="at"> </span><span class="dv">30</span></span>
<span id="cb62-17"><a href="#cb62-17" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">stale-running-minutes</span><span class="kw">:</span><span class="at"> </span><span class="dv">240</span></span>
<span id="cb62-18"><a href="#cb62-18" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">stale-batch-size</span><span class="kw">:</span><span class="at"> </span><span class="dv">100</span></span></code></pre></div>
<div id="code-11-11" class="figure-caption figure-kind-insert">Code 11.11: The default runtime tuning for async operations lives in application.yaml, including the retention windows used by the housekeeping policy resolver.</div>
</div>
<p>The retention modes are:</p>
<ul>
<li><code>DEFAULT</code> Use the normal retention window.</li>
<li><code>SHORT_LIVED</code> Use a shorter terminal retention window for
operational or cleanup actions.</li>
<li><code>EPHEMERAL</code> Purge almost immediately after terminal
state.</li>
<li><code>AUDIT</code> Keep longer than the default when operational
history matters.</li>
</ul>
<p>That model is cleaner than ad hoc &quot;flush now&quot; callbacks because it
keeps retention decisions declarative and centralized.</p>
<p>The mapping from <code>AsyncOperationRetentionMode</code> to an
actual expiry timestamp is resolved centrally in
<code>AsyncOperationServiceImpl.resolvedRetentionUntil(...)</code>. With
the current application defaults, the behavior is:</p>
<ul>
<li><code>DEFAULT</code> Terminal operation is kept for <code>14</code>
days via <code>async-operations.housekeeping.retention-days</code>.</li>
<li><code>SHORT_LIVED</code> Terminal operation is kept for
<code>60</code> minutes via
<code>async-operations.housekeeping.short-lived-minutes</code>.</li>
<li><code>EPHEMERAL</code> Terminal operation is eligible for purge
immediately because
<code>async-operations.housekeeping.ephemeral-grace-seconds</code> is
currently <code>0</code>.</li>
<li><code>AUDIT</code> Terminal operation is kept for <code>90</code>
days via <code>async-operations.housekeeping.audit-days</code>.</li>
</ul>
<p>Examples of how to choose a mode in practice:</p>
<ul>
<li><code>DEFAULT</code> Use for normal user-visible async operations
where recent history is useful but not audit-grade, for example a
standard export that a user may want to revisit for a few days.</li>
<li><code>SHORT_LIVED</code> Use for operator cleanup or maintenance
actions such as the current process-support cleanup jobs, where the
result should stay visible briefly and then disappear.</li>
<li><code>EPHEMERAL</code> Use for hot-path or UI-supporting operations
where the durable row only exists to bridge request/response and short
reconnect windows, not to preserve operator history.</li>
<li><code>AUDIT</code> Use for operations whose terminal outcome may
need to be inspected much later, for example a compliance-relevant
import, supervisory recalculation, or signed-output generation job.</li>
</ul>
<p>The framework also supports an explicit <code>retentionUntil</code>
override when a caller knows the exact expiry it wants. That override
takes precedence over the enum-derived policy and is still enforced by
the same housekeeping job.</p>
</section>
<section id="current-adopters" class="level2" data-number="11.14">
<h2 data-number="11.14"><span class="header-section-number">11.14</span>
Current Adopters</h2>
<p>The first concrete adopters are the process-support cleanup
flows.</p>
<div class="captioned-block captioned-code">
<style>
#code-11-12 + p, #code-11-12 + div.sourceCode, #code-11-12 + pre, #code-11-12 + table { display: block; max-width: 100%; }
#code-11-12 + p img { display: block; max-width: 100%; height: auto; }
</style>
<div class="sourceCode" id="cb63"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb63-1"><a href="#cb63-1" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">startCleanupOrphanProcessInstances</span><span class="op">(</span></span>
<span id="cb63-2"><a href="#cb63-2" aria-hidden="true" tabindex="-1"></a> <span class="va">dryRun</span><span class="op">:</span> <span class="dt">Boolean</span><span class="op">,</span></span>
<span id="cb63-3"><a href="#cb63-3" aria-hidden="true" tabindex="-1"></a> <span class="va">includeMissingCases</span><span class="op">:</span> <span class="dt">Boolean</span><span class="op">,</span></span>
<span id="cb63-4"><a href="#cb63-4" aria-hidden="true" tabindex="-1"></a> <span class="va">submittedBy</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span></span>
<span id="cb63-5"><a href="#cb63-5" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb63-6"><a href="#cb63-6" aria-hidden="true" tabindex="-1"></a> asyncOperationService<span class="op">.</span>submit<span class="op">(</span></span>
<span id="cb63-7"><a href="#cb63-7" aria-hidden="true" tabindex="-1"></a> operationKey <span class="op">=</span> OPERATION_KEY_CLEANUP_ORPHAN_PROCESS_INSTANCES<span class="op">,</span></span>
<span id="cb63-8"><a href="#cb63-8" aria-hidden="true" tabindex="-1"></a> submittedBy <span class="op">=</span> submittedBy<span class="op">,</span></span>
<span id="cb63-9"><a href="#cb63-9" aria-hidden="true" tabindex="-1"></a> attributes <span class="op">=</span> mapOf<span class="op">(</span></span>
<span id="cb63-10"><a href="#cb63-10" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;dryRun&quot;</span> to dryRun<span class="op">.</span>toString<span class="op">(),</span></span>
<span id="cb63-11"><a href="#cb63-11" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;includeMissingCases&quot;</span> to includeMissingCases<span class="op">.</span>toString<span class="op">()</span></span>
<span id="cb63-12"><a href="#cb63-12" aria-hidden="true" tabindex="-1"></a> <span class="op">),</span></span>
<span id="cb63-13"><a href="#cb63-13" aria-hidden="true" tabindex="-1"></a> retentionMode <span class="op">=</span> AsyncOperationRetentionMode<span class="op">.</span>SHORT_LIVED<span class="op">,</span></span>
<span id="cb63-14"><a href="#cb63-14" aria-hidden="true" tabindex="-1"></a> initialPhase <span class="op">=</span> <span class="st">&quot;Queued cleanup for orphaned process instances&quot;</span></span>
<span id="cb63-15"><a href="#cb63-15" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span> <span class="op">{</span> progress <span class="op">-&gt;</span></span>
<span id="cb63-16"><a href="#cb63-16" aria-hidden="true" tabindex="-1"></a> progress<span class="op">(</span></span>
<span id="cb63-17"><a href="#cb63-17" aria-hidden="true" tabindex="-1"></a> AsyncOperationProgress<span class="op">(</span></span>
<span id="cb63-18"><a href="#cb63-18" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Scanning orphaned process instances&quot;</span><span class="op">,</span></span>
<span id="cb63-19"><a href="#cb63-19" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Querying active runtime instances&quot;</span></span>
<span id="cb63-20"><a href="#cb63-20" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-21"><a href="#cb63-21" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-22"><a href="#cb63-22" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">processInstanceIds</span> <span class="op">=</span> processEngineService<span class="op">.</span>cleanupOrphanProcessInstances<span class="op">(</span>dryRun<span class="op">,</span> includeMissingCases<span class="op">)</span></span>
<span id="cb63-23"><a href="#cb63-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">cleaned</span> <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="dv">0</span> <span class="cf">else</span> processInstanceIds<span class="op">.</span>size</span>
<span id="cb63-24"><a href="#cb63-24" aria-hidden="true" tabindex="-1"></a> progress<span class="op">(</span></span>
<span id="cb63-25"><a href="#cb63-25" aria-hidden="true" tabindex="-1"></a> AsyncOperationProgress<span class="op">(</span></span>
<span id="cb63-26"><a href="#cb63-26" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="st">&quot;Dry run complete&quot;</span> <span class="cf">else</span> <span class="st">&quot;Cleanup complete&quot;</span><span class="op">,</span></span>
<span id="cb63-27"><a href="#cb63-27" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> processInstanceIds<span class="op">.</span>size<span class="op">,</span></span>
<span id="cb63-28"><a href="#cb63-28" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> cleaned<span class="op">,</span></span>
<span id="cb63-29"><a href="#cb63-29" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> processInstanceIds</span>
<span id="cb63-30"><a href="#cb63-30" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-31"><a href="#cb63-31" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-32"><a href="#cb63-32" aria-hidden="true" tabindex="-1"></a> AsyncOperationResult<span class="op">(</span></span>
<span id="cb63-33"><a href="#cb63-33" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="op">{</span></span>
<span id="cb63-34"><a href="#cb63-34" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Matched </span><span class="ss">${</span>processInstanceIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> orphaned process instances (dry run)&quot;</span></span>
<span id="cb63-35"><a href="#cb63-35" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb63-36"><a href="#cb63-36" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Cleaned </span><span class="ss">${</span>processInstanceIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> orphaned process instances&quot;</span></span>
<span id="cb63-37"><a href="#cb63-37" aria-hidden="true" tabindex="-1"></a> <span class="op">},</span></span>
<span id="cb63-38"><a href="#cb63-38" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> processInstanceIds<span class="op">.</span>size<span class="op">,</span></span>
<span id="cb63-39"><a href="#cb63-39" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> cleaned<span class="op">,</span></span>
<span id="cb63-40"><a href="#cb63-40" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> processInstanceIds<span class="op">,</span></span>
<span id="cb63-41"><a href="#cb63-41" aria-hidden="true" tabindex="-1"></a> payload <span class="op">=</span> mapOf<span class="op">(</span><span class="st">&quot;referenceIds&quot;</span> to processInstanceIds<span class="op">)</span></span>
<span id="cb63-42"><a href="#cb63-42" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-43"><a href="#cb63-43" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb63-44"><a href="#cb63-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb63-45"><a href="#cb63-45" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">startCleanupProcessInstancesForFinishedCases</span><span class="op">(</span></span>
<span id="cb63-46"><a href="#cb63-46" aria-hidden="true" tabindex="-1"></a> <span class="va">dryRun</span><span class="op">:</span> <span class="dt">Boolean</span><span class="op">,</span></span>
<span id="cb63-47"><a href="#cb63-47" aria-hidden="true" tabindex="-1"></a> <span class="va">submittedBy</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span></span>
<span id="cb63-48"><a href="#cb63-48" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb63-49"><a href="#cb63-49" aria-hidden="true" tabindex="-1"></a> asyncOperationService<span class="op">.</span>submit<span class="op">(</span></span>
<span id="cb63-50"><a href="#cb63-50" aria-hidden="true" tabindex="-1"></a> operationKey <span class="op">=</span> OPERATION_KEY_CLEANUP_PROCESS_INSTANCES_FOR_FINISHED_CASES<span class="op">,</span></span>
<span id="cb63-51"><a href="#cb63-51" aria-hidden="true" tabindex="-1"></a> submittedBy <span class="op">=</span> submittedBy<span class="op">,</span></span>
<span id="cb63-52"><a href="#cb63-52" aria-hidden="true" tabindex="-1"></a> attributes <span class="op">=</span> mapOf<span class="op">(</span><span class="st">&quot;dryRun&quot;</span> to dryRun<span class="op">.</span>toString<span class="op">()),</span></span>
<span id="cb63-53"><a href="#cb63-53" aria-hidden="true" tabindex="-1"></a> retentionMode <span class="op">=</span> AsyncOperationRetentionMode<span class="op">.</span>SHORT_LIVED<span class="op">,</span></span>
<span id="cb63-54"><a href="#cb63-54" aria-hidden="true" tabindex="-1"></a> initialPhase <span class="op">=</span> <span class="st">&quot;Queued cleanup for finished-case runtime mismatches&quot;</span></span>
<span id="cb63-55"><a href="#cb63-55" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span> <span class="op">{</span> progress <span class="op">-&gt;</span></span>
<span id="cb63-56"><a href="#cb63-56" aria-hidden="true" tabindex="-1"></a> progress<span class="op">(</span></span>
<span id="cb63-57"><a href="#cb63-57" aria-hidden="true" tabindex="-1"></a> AsyncOperationProgress<span class="op">(</span></span>
<span id="cb63-58"><a href="#cb63-58" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Scanning runtime instances linked to finished cases&quot;</span><span class="op">,</span></span>
<span id="cb63-59"><a href="#cb63-59" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Querying runtime state and linked cases&quot;</span></span>
<span id="cb63-60"><a href="#cb63-60" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-61"><a href="#cb63-61" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-62"><a href="#cb63-62" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">processInstanceIds</span> <span class="op">=</span> processEngineService<span class="op">.</span>cleanupProcessInstancesForFinishedCases<span class="op">(</span>dryRun<span class="op">)</span></span>
<span id="cb63-63"><a href="#cb63-63" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">cleaned</span> <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="dv">0</span> <span class="cf">else</span> processInstanceIds<span class="op">.</span>size</span>
<span id="cb63-64"><a href="#cb63-64" aria-hidden="true" tabindex="-1"></a> progress<span class="op">(</span></span>
<span id="cb63-65"><a href="#cb63-65" aria-hidden="true" tabindex="-1"></a> AsyncOperationProgress<span class="op">(</span></span>
<span id="cb63-66"><a href="#cb63-66" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="st">&quot;Dry run complete&quot;</span> <span class="cf">else</span> <span class="st">&quot;Cleanup complete&quot;</span><span class="op">,</span></span>
<span id="cb63-67"><a href="#cb63-67" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> processInstanceIds<span class="op">.</span>size<span class="op">,</span></span>
<span id="cb63-68"><a href="#cb63-68" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> cleaned<span class="op">,</span></span>
<span id="cb63-69"><a href="#cb63-69" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> processInstanceIds</span>
<span id="cb63-70"><a href="#cb63-70" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-71"><a href="#cb63-71" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-72"><a href="#cb63-72" aria-hidden="true" tabindex="-1"></a> AsyncOperationResult<span class="op">(</span></span>
<span id="cb63-73"><a href="#cb63-73" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="op">{</span></span>
<span id="cb63-74"><a href="#cb63-74" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Matched </span><span class="ss">${</span>processInstanceIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> active process instances linked to finished cases (dry run)&quot;</span></span>
<span id="cb63-75"><a href="#cb63-75" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb63-76"><a href="#cb63-76" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Cleaned </span><span class="ss">${</span>processInstanceIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> active process instances linked to finished cases&quot;</span></span>
<span id="cb63-77"><a href="#cb63-77" aria-hidden="true" tabindex="-1"></a> <span class="op">},</span></span>
<span id="cb63-78"><a href="#cb63-78" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> processInstanceIds<span class="op">.</span>size<span class="op">,</span></span>
<span id="cb63-79"><a href="#cb63-79" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> cleaned<span class="op">,</span></span>
<span id="cb63-80"><a href="#cb63-80" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> processInstanceIds<span class="op">,</span></span>
<span id="cb63-81"><a href="#cb63-81" aria-hidden="true" tabindex="-1"></a> payload <span class="op">=</span> mapOf<span class="op">(</span><span class="st">&quot;referenceIds&quot;</span> to processInstanceIds<span class="op">)</span></span>
<span id="cb63-82"><a href="#cb63-82" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-83"><a href="#cb63-83" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb63-84"><a href="#cb63-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb63-85"><a href="#cb63-85" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">startCleanupCasesMissingProcessKey</span><span class="op">(</span></span>
<span id="cb63-86"><a href="#cb63-86" aria-hidden="true" tabindex="-1"></a> <span class="va">dryRun</span><span class="op">:</span> <span class="dt">Boolean</span><span class="op">,</span></span>
<span id="cb63-87"><a href="#cb63-87" aria-hidden="true" tabindex="-1"></a> <span class="va">close</span><span class="op">:</span> <span class="dt">Boolean</span><span class="op">,</span></span>
<span id="cb63-88"><a href="#cb63-88" aria-hidden="true" tabindex="-1"></a> <span class="va">submittedBy</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span></span>
<span id="cb63-89"><a href="#cb63-89" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb63-90"><a href="#cb63-90" aria-hidden="true" tabindex="-1"></a> asyncOperationService<span class="op">.</span>submit<span class="op">(</span></span>
<span id="cb63-91"><a href="#cb63-91" aria-hidden="true" tabindex="-1"></a> operationKey <span class="op">=</span> OPERATION_KEY_CLEANUP_CASES_MISSING_PROCESS_KEY<span class="op">,</span></span>
<span id="cb63-92"><a href="#cb63-92" aria-hidden="true" tabindex="-1"></a> submittedBy <span class="op">=</span> submittedBy<span class="op">,</span></span>
<span id="cb63-93"><a href="#cb63-93" aria-hidden="true" tabindex="-1"></a> attributes <span class="op">=</span> mapOf<span class="op">(</span></span>
<span id="cb63-94"><a href="#cb63-94" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;dryRun&quot;</span> to dryRun<span class="op">.</span>toString<span class="op">(),</span></span>
<span id="cb63-95"><a href="#cb63-95" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;close&quot;</span> to close<span class="op">.</span>toString<span class="op">()</span></span>
<span id="cb63-96"><a href="#cb63-96" aria-hidden="true" tabindex="-1"></a> <span class="op">),</span></span>
<span id="cb63-97"><a href="#cb63-97" aria-hidden="true" tabindex="-1"></a> retentionMode <span class="op">=</span> AsyncOperationRetentionMode<span class="op">.</span>SHORT_LIVED<span class="op">,</span></span>
<span id="cb63-98"><a href="#cb63-98" aria-hidden="true" tabindex="-1"></a> initialPhase <span class="op">=</span> <span class="st">&quot;Queued cleanup for cases missing process key&quot;</span></span>
<span id="cb63-99"><a href="#cb63-99" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span> <span class="op">{</span> progress <span class="op">-&gt;</span></span>
<span id="cb63-100"><a href="#cb63-100" aria-hidden="true" tabindex="-1"></a> progress<span class="op">(</span></span>
<span id="cb63-101"><a href="#cb63-101" aria-hidden="true" tabindex="-1"></a> AsyncOperationProgress<span class="op">(</span></span>
<span id="cb63-102"><a href="#cb63-102" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Scanning running cases missing process key&quot;</span><span class="op">,</span></span>
<span id="cb63-103"><a href="#cb63-103" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Querying running cases with NULL process key&quot;</span></span>
<span id="cb63-104"><a href="#cb63-104" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-105"><a href="#cb63-105" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-106"><a href="#cb63-106" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">caseIds</span> <span class="op">=</span> caseService<span class="op">.</span>cleanupCasesMissingProcessKey<span class="op">(</span>dryRun<span class="op">,</span> close<span class="op">).</span>map <span class="op">{</span> it<span class="op">.</span>toString<span class="op">()</span> <span class="op">}</span></span>
<span id="cb63-107"><a href="#cb63-107" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">cleaned</span> <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="dv">0</span> <span class="cf">else</span> caseIds<span class="op">.</span>size</span>
<span id="cb63-108"><a href="#cb63-108" aria-hidden="true" tabindex="-1"></a> progress<span class="op">(</span></span>
<span id="cb63-109"><a href="#cb63-109" aria-hidden="true" tabindex="-1"></a> AsyncOperationProgress<span class="op">(</span></span>
<span id="cb63-110"><a href="#cb63-110" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="st">&quot;Dry run complete&quot;</span> <span class="cf">else</span> <span class="st">&quot;Cleanup complete&quot;</span><span class="op">,</span></span>
<span id="cb63-111"><a href="#cb63-111" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> caseIds<span class="op">.</span>size<span class="op">,</span></span>
<span id="cb63-112"><a href="#cb63-112" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> cleaned<span class="op">,</span></span>
<span id="cb63-113"><a href="#cb63-113" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> caseIds</span>
<span id="cb63-114"><a href="#cb63-114" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-115"><a href="#cb63-115" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-116"><a href="#cb63-116" aria-hidden="true" tabindex="-1"></a> AsyncOperationResult<span class="op">(</span></span>
<span id="cb63-117"><a href="#cb63-117" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="op">{</span></span>
<span id="cb63-118"><a href="#cb63-118" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Matched </span><span class="ss">${</span>caseIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> cases missing process key (dry run)&quot;</span></span>
<span id="cb63-119"><a href="#cb63-119" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb63-120"><a href="#cb63-120" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Updated </span><span class="ss">${</span>caseIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> cases missing process key&quot;</span></span>
<span id="cb63-121"><a href="#cb63-121" aria-hidden="true" tabindex="-1"></a> <span class="op">},</span></span>
<span id="cb63-122"><a href="#cb63-122" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> caseIds<span class="op">.</span>size<span class="op">,</span></span>
<span id="cb63-123"><a href="#cb63-123" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> cleaned<span class="op">,</span></span>
<span id="cb63-124"><a href="#cb63-124" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> caseIds<span class="op">,</span></span>
<span id="cb63-125"><a href="#cb63-125" aria-hidden="true" tabindex="-1"></a> payload <span class="op">=</span> mapOf<span class="op">(</span><span class="st">&quot;referenceIds&quot;</span> to caseIds<span class="op">)</span></span>
<span id="cb63-126"><a href="#cb63-126" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-127"><a href="#cb63-127" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb63-128"><a href="#cb63-128" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb63-129"><a href="#cb63-129" aria-hidden="true" tabindex="-1"></a> <span class="kw">override</span> <span class="kw">fun</span> <span class="fu">startCleanupRunningCasesMissingActiveProcess</span><span class="op">(</span></span>
<span id="cb63-130"><a href="#cb63-130" aria-hidden="true" tabindex="-1"></a> <span class="va">dryRun</span><span class="op">:</span> <span class="dt">Boolean</span><span class="op">,</span></span>
<span id="cb63-131"><a href="#cb63-131" aria-hidden="true" tabindex="-1"></a> <span class="va">close</span><span class="op">:</span> <span class="dt">Boolean</span><span class="op">,</span></span>
<span id="cb63-132"><a href="#cb63-132" aria-hidden="true" tabindex="-1"></a> <span class="va">submittedBy</span><span class="op">:</span> <span class="dt">String</span><span class="op">?</span></span>
<span id="cb63-133"><a href="#cb63-133" aria-hidden="true" tabindex="-1"></a> <span class="op">):</span> <span class="dt">AsyncOperationSnapshot</span> <span class="op">=</span></span>
<span id="cb63-134"><a href="#cb63-134" aria-hidden="true" tabindex="-1"></a> asyncOperationService<span class="op">.</span>submit<span class="op">(</span></span>
<span id="cb63-135"><a href="#cb63-135" aria-hidden="true" tabindex="-1"></a> operationKey <span class="op">=</span> OPERATION_KEY_CLEANUP_RUNNING_CASES_MISSING_ACTIVE_PROCESS<span class="op">,</span></span>
<span id="cb63-136"><a href="#cb63-136" aria-hidden="true" tabindex="-1"></a> submittedBy <span class="op">=</span> submittedBy<span class="op">,</span></span>
<span id="cb63-137"><a href="#cb63-137" aria-hidden="true" tabindex="-1"></a> attributes <span class="op">=</span> mapOf<span class="op">(</span></span>
<span id="cb63-138"><a href="#cb63-138" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;dryRun&quot;</span> to dryRun<span class="op">.</span>toString<span class="op">(),</span></span>
<span id="cb63-139"><a href="#cb63-139" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;close&quot;</span> to close<span class="op">.</span>toString<span class="op">()</span></span>
<span id="cb63-140"><a href="#cb63-140" aria-hidden="true" tabindex="-1"></a> <span class="op">),</span></span>
<span id="cb63-141"><a href="#cb63-141" aria-hidden="true" tabindex="-1"></a> retentionMode <span class="op">=</span> AsyncOperationRetentionMode<span class="op">.</span>SHORT_LIVED<span class="op">,</span></span>
<span id="cb63-142"><a href="#cb63-142" aria-hidden="true" tabindex="-1"></a> initialPhase <span class="op">=</span> <span class="st">&quot;Queued cleanup for running cases missing active process&quot;</span></span>
<span id="cb63-143"><a href="#cb63-143" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span> <span class="op">{</span> progress <span class="op">-&gt;</span></span>
<span id="cb63-144"><a href="#cb63-144" aria-hidden="true" tabindex="-1"></a> progress<span class="op">(</span></span>
<span id="cb63-145"><a href="#cb63-145" aria-hidden="true" tabindex="-1"></a> AsyncOperationProgress<span class="op">(</span></span>
<span id="cb63-146"><a href="#cb63-146" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Scanning running cases missing active process&quot;</span><span class="op">,</span></span>
<span id="cb63-147"><a href="#cb63-147" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Checking runtime and business-key liveness&quot;</span></span>
<span id="cb63-148"><a href="#cb63-148" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-149"><a href="#cb63-149" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-150"><a href="#cb63-150" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">caseIds</span> <span class="op">=</span> caseService<span class="op">.</span>cleanupRunningCasesMissingActiveProcess<span class="op">(</span>dryRun<span class="op">,</span> close<span class="op">).</span>map <span class="op">{</span> it<span class="op">.</span>toString<span class="op">()</span> <span class="op">}</span></span>
<span id="cb63-151"><a href="#cb63-151" aria-hidden="true" tabindex="-1"></a> <span class="kw">val</span> <span class="va">cleaned</span> <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="dv">0</span> <span class="cf">else</span> caseIds<span class="op">.</span>size</span>
<span id="cb63-152"><a href="#cb63-152" aria-hidden="true" tabindex="-1"></a> progress<span class="op">(</span></span>
<span id="cb63-153"><a href="#cb63-153" aria-hidden="true" tabindex="-1"></a> AsyncOperationProgress<span class="op">(</span></span>
<span id="cb63-154"><a href="#cb63-154" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="st">&quot;Dry run complete&quot;</span> <span class="cf">else</span> <span class="st">&quot;Cleanup complete&quot;</span><span class="op">,</span></span>
<span id="cb63-155"><a href="#cb63-155" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> caseIds<span class="op">.</span>size<span class="op">,</span></span>
<span id="cb63-156"><a href="#cb63-156" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> cleaned<span class="op">,</span></span>
<span id="cb63-157"><a href="#cb63-157" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> caseIds</span>
<span id="cb63-158"><a href="#cb63-158" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-159"><a href="#cb63-159" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-160"><a href="#cb63-160" aria-hidden="true" tabindex="-1"></a> AsyncOperationResult<span class="op">(</span></span>
<span id="cb63-161"><a href="#cb63-161" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="cf">if</span> <span class="op">(</span>dryRun<span class="op">)</span> <span class="op">{</span></span>
<span id="cb63-162"><a href="#cb63-162" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Matched </span><span class="ss">${</span>caseIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> running cases missing active process (dry run)&quot;</span></span>
<span id="cb63-163"><a href="#cb63-163" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb63-164"><a href="#cb63-164" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Updated </span><span class="ss">${</span>caseIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> running cases missing active process&quot;</span></span>
<span id="cb63-165"><a href="#cb63-165" aria-hidden="true" tabindex="-1"></a> <span class="op">},</span></span>
<span id="cb63-166"><a href="#cb63-166" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> caseIds<span class="op">.</span>size<span class="op">,</span></span>
<span id="cb63-167"><a href="#cb63-167" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> cleaned<span class="op">,</span></span>
<span id="cb63-168"><a href="#cb63-168" aria-hidden="true" tabindex="-1"></a> referenceIds <span class="op">=</span> caseIds<span class="op">,</span></span>
<span id="cb63-169"><a href="#cb63-169" aria-hidden="true" tabindex="-1"></a> payload <span class="op">=</span> mapOf<span class="op">(</span><span class="st">&quot;referenceIds&quot;</span> to caseIds<span class="op">)</span></span>
<span id="cb63-170"><a href="#cb63-170" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb63-171"><a href="#cb63-171" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb63-172"><a href="#cb63-172" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb63-173"><a href="#cb63-173" aria-hidden="true" tabindex="-1"></a> <span class="kw">companion</span> <span class="kw">object</span> <span class="op">{</span></span>
<span id="cb63-174"><a href="#cb63-174" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> <span class="kw">val</span> <span class="va">OPERATION_KEY_CLEANUP_ORPHAN_PROCESS_INSTANCES</span> <span class="op">=</span> <span class="st">&quot;process-support.cleanup-orphan-process-instances&quot;</span></span>
<span id="cb63-175"><a href="#cb63-175" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> <span class="kw">val</span> <span class="va">OPERATION_KEY_CLEANUP_PROCESS_INSTANCES_FOR_FINISHED_CASES</span> <span class="op">=</span></span>
<span id="cb63-176"><a href="#cb63-176" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;process-support.cleanup-process-instances-for-finished-cases&quot;</span></span>
<span id="cb63-177"><a href="#cb63-177" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> <span class="kw">val</span> <span class="va">OPERATION_KEY_CLEANUP_CASES_MISSING_PROCESS_KEY</span> <span class="op">=</span></span>
<span id="cb63-178"><a href="#cb63-178" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;process-support.cleanup-cases-missing-process-key&quot;</span></span>
<span id="cb63-179"><a href="#cb63-179" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> <span class="kw">val</span> <span class="va">OPERATION_KEY_CLEANUP_RUNNING_CASES_MISSING_ACTIVE_PROCESS</span> <span class="op">=</span></span>
<span id="cb63-180"><a href="#cb63-180" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;process-support.cleanup-running-cases-missing-active-process&quot;</span></span>
<span id="cb63-181"><a href="#cb63-181" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb63-182"><a href="#cb63-182" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<div id="code-11-12" class="figure-caption figure-kind-insert">Code 11.12: ProcessSupportAsyncOperationServiceImpl is the first concrete adopter. It starts cleanup operations, assigns stable operation keys, uses SHORT_LIVED retention, and publishes durable progress/result updates.</div>
</div>
<p>Those adopters currently prove:</p>
<ul>
<li>async submit for process-support actions</li>
<li>durable progress and terminal updates</li>
<li>process-support topic hierarchy derived from stable operation
keys</li>
<li>short-lived retention for admin-style maintenance work</li>
</ul>
<p>What they do not yet prove is the full export/import pattern with
frequent volatile progress updates.</p>
<p>The current process-support integration sequence looks like this:</p>
<div class="captioned-block captioned-figure">
<style>
#figure-11-2 + p, #figure-11-2 + div.sourceCode, #figure-11-2 + pre, #figure-11-2 + table { display: block; max-width: 100%; }
#figure-11-2 + p img { display: block; max-width: 100%; height: auto; }
</style>
<p><img role="img" aria-label="diagram" src="data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgc3R5bGU9Im1heC13aWR0aDogMjcyOC41cHg7IGJhY2tncm91bmQtY29sb3I6IHdoaXRlOyIgdmlld0JveD0iLTUwIC0xMCAyNzI4LjUgMTMwNSIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJzZXF1ZW5jZSI+PGc+PHJlY3QgeD0iMjMyNS41IiB5PSIxMjE5IiBmaWxsPSIjZWFlYWVhIiBzdHJva2U9IiM2NjYiIHdpZHRoPSIzMDMiIGhlaWdodD0iNjUiIG5hbWU9IlN0cmVhbSIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm90dG9tIi8+PHRleHQgeD0iMjQ3NyIgeT0iMTI1MS41IiBkb21pbmFudC1iYXNlbGluZT0iY2VudHJhbCIgYWxpZ25tZW50LWJhc2VsaW5lPSJjZW50cmFsIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm94IiBzdHlsZT0idGV4dC1hbmNob3I6IG1pZGRsZTsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyBmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyI+PHRzcGFuIHg9IjI0NzciIGR5PSIwIj4iQXN5bmNPcGVyYXRpb25Db250cm9sbGVyU3RyZWFtaW5nIjwvdHNwYW4+PC90ZXh0PjwvZz48Zz48cmVjdCB4PSIxOTY4LjUiIHk9IjEyMTkiIGZpbGw9IiNlYWVhZWEiIHN0cm9rZT0iIzY2NiIgd2lkdGg9IjMwNyIgaGVpZ2h0PSI2NSIgbmFtZT0iRG9tYWluIiByeD0iMyIgcnk9IjMiIGNsYXNzPSJhY3RvciBhY3Rvci1ib3R0b20iLz48dGV4dCB4PSIyMTIyIiB5PSIxMjUxLjUiIGRvbWluYW50LWJhc2VsaW5lPSJjZW50cmFsIiBhbGlnbm1lbnQtYmFzZWxpbmU9ImNlbnRyYWwiIGNsYXNzPSJhY3RvciBhY3Rvci1ib3giIHN0eWxlPSJ0ZXh0LWFuY2hvcjogbWlkZGxlOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7IGZvbnQtZmFtaWx5OiAmcXVvdDtPcGVuIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Ij48dHNwYW4geD0iMjEyMiIgZHk9IjAiPiJQcm9jZXNzRW5naW5lU2VydmljZSBvciBDYXNlU2VydmljZSI8L3RzcGFuPjwvdGV4dD48L2c+PGc+PHJlY3QgeD0iMTc2OC41IiB5PSIxMjE5IiBmaWxsPSIjZWFlYWVhIiBzdHJva2U9IiM2NjYiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjUiIG5hbWU9IkV4ZWN1dG9yIiByeD0iMyIgcnk9IjMiIGNsYXNzPSJhY3RvciBhY3Rvci1ib3R0b20iLz48dGV4dCB4PSIxODQzLjUiIHk9IjEyNTEuNSIgZG9taW5hbnQtYmFzZWxpbmU9ImNlbnRyYWwiIGFsaWdubWVudC1iYXNlbGluZT0iY2VudHJhbCIgY2xhc3M9ImFjdG9yIGFjdG9yLWJveCIgc3R5bGU9InRleHQtYW5jaG9yOiBtaWRkbGU7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsgZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsiPjx0c3BhbiB4PSIxODQzLjUiIGR5PSIwIj4iQXN5bmMgRXhlY3V0b3IiPC90c3Bhbj48L3RleHQ+PC9nPjxnPjxyZWN0IHg9IjE0OTAuNSIgeT0iMTIxOSIgZmlsbD0iI2VhZWFlYSIgc3Ryb2tlPSIjNjY2IiB3aWR0aD0iMjI4IiBoZWlnaHQ9IjY1IiBuYW1lPSJEQiIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm90dG9tIi8+PHRleHQgeD0iMTYwNC41IiB5PSIxMjUxLjUiIGRvbWluYW50LWJhc2VsaW5lPSJjZW50cmFsIiBhbGlnbm1lbnQtYmFzZWxpbmU9ImNlbnRyYWwiIGNsYXNzPSJhY3RvciBhY3Rvci1ib3giIHN0eWxlPSJ0ZXh0LWFuY2hvcjogbWlkZGxlOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7IGZvbnQtZmFtaWx5OiAmcXVvdDtPcGVuIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Ij48dHNwYW4geD0iMTYwNC41IiBkeT0iMCI+IkFTWU5DX09QRVJBVElPTl9SVU5TIjwvdHNwYW4+PC90ZXh0PjwvZz48Zz48cmVjdCB4PSIxMDE3LjUiIHk9IjEyMTkiIGZpbGw9IiNlYWVhZWEiIHN0cm9rZT0iIzY2NiIgd2lkdGg9IjIzOCIgaGVpZ2h0PSI2NSIgbmFtZT0iQXN5bmNPcHMiIHJ4PSIzIiByeT0iMyIgY2xhc3M9ImFjdG9yIGFjdG9yLWJvdHRvbSIvPjx0ZXh0IHg9IjExMzYuNSIgeT0iMTI1MS41IiBkb21pbmFudC1iYXNlbGluZT0iY2VudHJhbCIgYWxpZ25tZW50LWJhc2VsaW5lPSJjZW50cmFsIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm94IiBzdHlsZT0idGV4dC1hbmNob3I6IG1pZGRsZTsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyBmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyI+PHRzcGFuIHg9IjExMzYuNSIgZHk9IjAiPiJBc3luY09wZXJhdGlvblNlcnZpY2VJbXBsIjwvdHNwYW4+PC90ZXh0PjwvZz48Zz48cmVjdCB4PSI0ODUiIHk9IjEyMTkiIGZpbGw9IiNlYWVhZWEiIHN0cm9rZT0iIzY2NiIgd2lkdGg9IjMyMyIgaGVpZ2h0PSI2NSIgbmFtZT0iU3VwcG9ydCIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm90dG9tIi8+PHRleHQgeD0iNjQ2LjUiIHk9IjEyNTEuNSIgZG9taW5hbnQtYmFzZWxpbmU9ImNlbnRyYWwiIGFsaWdubWVudC1iYXNlbGluZT0iY2VudHJhbCIgY2xhc3M9ImFjdG9yIGFjdG9yLWJveCIgc3R5bGU9InRleHQtYW5jaG9yOiBtaWRkbGU7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsgZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsiPjx0c3BhbiB4PSI2NDYuNSIgZHk9IjAiPiJQcm9jZXNzU3VwcG9ydEFzeW5jT3BlcmF0aW9uU2VydmljZSI8L3RzcGFuPjwvdGV4dD48L2c+PGc+PHJlY3QgeD0iMjA1IiB5PSIxMjE5IiBmaWxsPSIjZWFlYWVhIiBzdHJva2U9IiM2NjYiIHdpZHRoPSIyMzAiIGhlaWdodD0iNjUiIG5hbWU9IkNvbnRyb2xsZXIiIHJ4PSIzIiByeT0iMyIgY2xhc3M9ImFjdG9yIGFjdG9yLWJvdHRvbSIvPjx0ZXh0IHg9IjMyMCIgeT0iMTI1MS41IiBkb21pbmFudC1iYXNlbGluZT0iY2VudHJhbCIgYWxpZ25tZW50LWJhc2VsaW5lPSJjZW50cmFsIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm94IiBzdHlsZT0idGV4dC1hbmNob3I6IG1pZGRsZTsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyBmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyI+PHRzcGFuIHg9IjMyMCIgZHk9IjAiPiJQcm9jZXNzIG9yIENhc2UgQ29udHJvbGxlciI8L3RzcGFuPjwvdGV4dD48L2c+PGc+PHJlY3QgeD0iMCIgeT0iMTIxOSIgZmlsbD0iI2VhZWFlYSIgc3Ryb2tlPSIjNjY2IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY1IiBuYW1lPSJVSSIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm90dG9tIi8+PHRleHQgeD0iNzUiIHk9IjEyNTEuNSIgZG9taW5hbnQtYmFzZWxpbmU9ImNlbnRyYWwiIGFsaWdubWVudC1iYXNlbGluZT0iY2VudHJhbCIgY2xhc3M9ImFjdG9yIGFjdG9yLWJveCIgc3R5bGU9InRleHQtYW5jaG9yOiBtaWRkbGU7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsgZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsiPjx0c3BhbiB4PSI3NSIgZHk9IjAiPiJBZG1pbiBVSSI8L3RzcGFuPjwvdGV4dD48L2c+PGc+PGxpbmUgaWQ9ImFjdG9yNyIgeDE9IjI0NzciIHkxPSI2NSIgeDI9IjI0NzciIHkyPSIxMjE5IiBjbGFzcz0iYWN0b3ItbGluZSAyMDAiIHN0cm9rZS13aWR0aD0iMC41cHgiIHN0cm9rZT0iIzk5OSIgbmFtZT0iU3RyZWFtIi8+PGcgaWQ9InJvb3QtNyI+PHJlY3QgeD0iMjMyNS41IiB5PSIwIiBmaWxsPSIjZWFlYWVhIiBzdHJva2U9IiM2NjYiIHdpZHRoPSIzMDMiIGhlaWdodD0iNjUiIG5hbWU9IlN0cmVhbSIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItdG9wIi8+PHRleHQgeD0iMjQ3NyIgeT0iMzIuNSIgZG9taW5hbnQtYmFzZWxpbmU9ImNlbnRyYWwiIGFsaWdubWVudC1iYXNlbGluZT0iY2VudHJhbCIgY2xhc3M9ImFjdG9yIGFjdG9yLWJveCIgc3R5bGU9InRleHQtYW5jaG9yOiBtaWRkbGU7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsgZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsiPjx0c3BhbiB4PSIyNDc3IiBkeT0iMCI+IkFzeW5jT3BlcmF0aW9uQ29udHJvbGxlclN0cmVhbWluZyI8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjxnPjxsaW5lIGlkPSJhY3RvcjYiIHgxPSIyMTIyIiB5MT0iNjUiIHgyPSIyMTIyIiB5Mj0iMTIxOSIgY2xhc3M9ImFjdG9yLWxpbmUgMjAwIiBzdHJva2Utd2lkdGg9IjAuNXB4IiBzdHJva2U9IiM5OTkiIG5hbWU9IkRvbWFpbiIvPjxnIGlkPSJyb290LTYiPjxyZWN0IHg9IjE5NjguNSIgeT0iMCIgZmlsbD0iI2VhZWFlYSIgc3Ryb2tlPSIjNjY2IiB3aWR0aD0iMzA3IiBoZWlnaHQ9IjY1IiBuYW1lPSJEb21haW4iIHJ4PSIzIiByeT0iMyIgY2xhc3M9ImFjdG9yIGFjdG9yLXRvcCIvPjx0ZXh0IHg9IjIxMjIiIHk9IjMyLjUiIGRvbWluYW50LWJhc2VsaW5lPSJjZW50cmFsIiBhbGlnbm1lbnQtYmFzZWxpbmU9ImNlbnRyYWwiIGNsYXNzPSJhY3RvciBhY3Rvci1ib3giIHN0eWxlPSJ0ZXh0LWFuY2hvcjogbWlkZGxlOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7IGZvbnQtZmFtaWx5OiAmcXVvdDtPcGVuIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Ij48dHNwYW4geD0iMjEyMiIgZHk9IjAiPiJQcm9jZXNzRW5naW5lU2VydmljZSBvciBDYXNlU2VydmljZSI8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjxnPjxsaW5lIGlkPSJhY3RvcjUiIHgxPSIxODQzLjUiIHkxPSI2NSIgeDI9IjE4NDMuNSIgeTI9IjEyMTkiIGNsYXNzPSJhY3Rvci1saW5lIDIwMCIgc3Ryb2tlLXdpZHRoPSIwLjVweCIgc3Ryb2tlPSIjOTk5IiBuYW1lPSJFeGVjdXRvciIvPjxnIGlkPSJyb290LTUiPjxyZWN0IHg9IjE3NjguNSIgeT0iMCIgZmlsbD0iI2VhZWFlYSIgc3Ryb2tlPSIjNjY2IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY1IiBuYW1lPSJFeGVjdXRvciIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItdG9wIi8+PHRleHQgeD0iMTg0My41IiB5PSIzMi41IiBkb21pbmFudC1iYXNlbGluZT0iY2VudHJhbCIgYWxpZ25tZW50LWJhc2VsaW5lPSJjZW50cmFsIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm94IiBzdHlsZT0idGV4dC1hbmNob3I6IG1pZGRsZTsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyBmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyI+PHRzcGFuIHg9IjE4NDMuNSIgZHk9IjAiPiJBc3luYyBFeGVjdXRvciI8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjxnPjxsaW5lIGlkPSJhY3RvcjQiIHgxPSIxNjA0LjUiIHkxPSI2NSIgeDI9IjE2MDQuNSIgeTI9IjEyMTkiIGNsYXNzPSJhY3Rvci1saW5lIDIwMCIgc3Ryb2tlLXdpZHRoPSIwLjVweCIgc3Ryb2tlPSIjOTk5IiBuYW1lPSJEQiIvPjxnIGlkPSJyb290LTQiPjxyZWN0IHg9IjE0OTAuNSIgeT0iMCIgZmlsbD0iI2VhZWFlYSIgc3Ryb2tlPSIjNjY2IiB3aWR0aD0iMjI4IiBoZWlnaHQ9IjY1IiBuYW1lPSJEQiIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItdG9wIi8+PHRleHQgeD0iMTYwNC41IiB5PSIzMi41IiBkb21pbmFudC1iYXNlbGluZT0iY2VudHJhbCIgYWxpZ25tZW50LWJhc2VsaW5lPSJjZW50cmFsIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm94IiBzdHlsZT0idGV4dC1hbmNob3I6IG1pZGRsZTsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyBmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyI+PHRzcGFuIHg9IjE2MDQuNSIgZHk9IjAiPiJBU1lOQ19PUEVSQVRJT05fUlVOUyI8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjxnPjxsaW5lIGlkPSJhY3RvcjMiIHgxPSIxMTM2LjUiIHkxPSI2NSIgeDI9IjExMzYuNSIgeTI9IjEyMTkiIGNsYXNzPSJhY3Rvci1saW5lIDIwMCIgc3Ryb2tlLXdpZHRoPSIwLjVweCIgc3Ryb2tlPSIjOTk5IiBuYW1lPSJBc3luY09wcyIvPjxnIGlkPSJyb290LTMiPjxyZWN0IHg9IjEwMTcuNSIgeT0iMCIgZmlsbD0iI2VhZWFlYSIgc3Ryb2tlPSIjNjY2IiB3aWR0aD0iMjM4IiBoZWlnaHQ9IjY1IiBuYW1lPSJBc3luY09wcyIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItdG9wIi8+PHRleHQgeD0iMTEzNi41IiB5PSIzMi41IiBkb21pbmFudC1iYXNlbGluZT0iY2VudHJhbCIgYWxpZ25tZW50LWJhc2VsaW5lPSJjZW50cmFsIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm94IiBzdHlsZT0idGV4dC1hbmNob3I6IG1pZGRsZTsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyBmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyI+PHRzcGFuIHg9IjExMzYuNSIgZHk9IjAiPiJBc3luY09wZXJhdGlvblNlcnZpY2VJbXBsIjwvdHNwYW4+PC90ZXh0PjwvZz48L2c+PGc+PGxpbmUgaWQ9ImFjdG9yMiIgeDE9IjY0Ni41IiB5MT0iNjUiIHgyPSI2NDYuNSIgeTI9IjEyMTkiIGNsYXNzPSJhY3Rvci1saW5lIDIwMCIgc3Ryb2tlLXdpZHRoPSIwLjVweCIgc3Ryb2tlPSIjOTk5IiBuYW1lPSJTdXBwb3J0Ii8+PGcgaWQ9InJvb3QtMiI+PHJlY3QgeD0iNDg1IiB5PSIwIiBmaWxsPSIjZWFlYWVhIiBzdHJva2U9IiM2NjYiIHdpZHRoPSIzMjMiIGhlaWdodD0iNjUiIG5hbWU9IlN1cHBvcnQiIHJ4PSIzIiByeT0iMyIgY2xhc3M9ImFjdG9yIGFjdG9yLXRvcCIvPjx0ZXh0IHg9IjY0Ni41IiB5PSIzMi41IiBkb21pbmFudC1iYXNlbGluZT0iY2VudHJhbCIgYWxpZ25tZW50LWJhc2VsaW5lPSJjZW50cmFsIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm94IiBzdHlsZT0idGV4dC1hbmNob3I6IG1pZGRsZTsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyBmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyI+PHRzcGFuIHg9IjY0Ni41IiBkeT0iMCI+IlByb2Nlc3NTdXBwb3J0QXN5bmNPcGVyYXRpb25TZXJ2aWNlIjwvdHNwYW4+PC90ZXh0PjwvZz48L2c+PGc+PGxpbmUgaWQ9ImFjdG9yMSIgeDE9IjMyMCIgeTE9IjY1IiB4Mj0iMzIwIiB5Mj0iMTIxOSIgY2xhc3M9ImFjdG9yLWxpbmUgMjAwIiBzdHJva2Utd2lkdGg9IjAuNXB4IiBzdHJva2U9IiM5OTkiIG5hbWU9IkNvbnRyb2xsZXIiLz48ZyBpZD0icm9vdC0xIj48cmVjdCB4PSIyMDUiIHk9IjAiIGZpbGw9IiNlYWVhZWEiIHN0cm9rZT0iIzY2NiIgd2lkdGg9IjIzMCIgaGVpZ2h0PSI2NSIgbmFtZT0iQ29udHJvbGxlciIgcng9IjMiIHJ5PSIzIiBjbGFzcz0iYWN0b3IgYWN0b3ItdG9wIi8+PHRleHQgeD0iMzIwIiB5PSIzMi41IiBkb21pbmFudC1iYXNlbGluZT0iY2VudHJhbCIgYWxpZ25tZW50LWJhc2VsaW5lPSJjZW50cmFsIiBjbGFzcz0iYWN0b3IgYWN0b3ItYm94IiBzdHlsZT0idGV4dC1hbmNob3I6IG1pZGRsZTsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyBmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyI+PHRzcGFuIHg9IjMyMCIgZHk9IjAiPiJQcm9jZXNzIG9yIENhc2UgQ29udHJvbGxlciI8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjxnPjxsaW5lIGlkPSJhY3RvcjAiIHgxPSI3NSIgeTE9IjY1IiB4Mj0iNzUiIHkyPSIxMjE5IiBjbGFzcz0iYWN0b3ItbGluZSAyMDAiIHN0cm9rZS13aWR0aD0iMC41cHgiIHN0cm9rZT0iIzk5OSIgbmFtZT0iVUkiLz48ZyBpZD0icm9vdC0wIj48cmVjdCB4PSIwIiB5PSIwIiBmaWxsPSIjZWFlYWVhIiBzdHJva2U9IiM2NjYiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjUiIG5hbWU9IlVJIiByeD0iMyIgcnk9IjMiIGNsYXNzPSJhY3RvciBhY3Rvci10b3AiLz48dGV4dCB4PSI3NSIgeT0iMzIuNSIgZG9taW5hbnQtYmFzZWxpbmU9ImNlbnRyYWwiIGFsaWdubWVudC1iYXNlbGluZT0iY2VudHJhbCIgY2xhc3M9ImFjdG9yIGFjdG9yLWJveCIgc3R5bGU9InRleHQtYW5jaG9yOiBtaWRkbGU7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsgZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsiPjx0c3BhbiB4PSI3NSIgZHk9IjAiPiJBZG1pbiBVSSI8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5Ok9wZW4gU2FucyxzYW5zLXNlcmlmO2ZvbnQtc2l6ZTo4cHg7ZmlsbDojMzMzO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzJkNmVhMztzdHJva2U6IzJkNmVhMzt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojMmQ2ZWEzO30jbXktc3ZnIHN2Z3tmb250LWZhbWlseTpPcGVuIFNhbnMsc2Fucy1zZXJpZjtmb250LXNpemU6OHB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmFjdG9ye3N0cm9rZTpoc2woMjU5LjYyNjE2ODIyNDMsIDU5Ljc3NjUzNjMxMjglLCA4Ny45MDE5NjA3ODQzJSk7ZmlsbDojZmZmZmZmO30jbXktc3ZnIHRleHQuYWN0b3ImZ3Q7dHNwYW57ZmlsbDpibGFjaztzdHJva2U6bm9uZTt9I215LXN2ZyAuYWN0b3ItbGluZXtzdHJva2U6aHNsKDI1OS42MjYxNjgyMjQzLCA1OS43NzY1MzYzMTI4JSwgODcuOTAxOTYwNzg0MyUpO30jbXktc3ZnIC5pbm5lckFyY3tzdHJva2Utd2lkdGg6MS41O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTt9I215LXN2ZyAubWVzc2FnZUxpbmUwe3N0cm9rZS13aWR0aDoxLjU7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZTojMzMzO30jbXktc3ZnIC5tZXNzYWdlTGluZTF7c3Ryb2tlLXdpZHRoOjEuNTtzdHJva2UtZGFzaGFycmF5OjIsMjtzdHJva2U6IzMzMzt9I215LXN2ZyAjYXJyb3doZWFkIHBhdGh7ZmlsbDojMzMzO3N0cm9rZTojMzMzO30jbXktc3ZnIC5zZXF1ZW5jZU51bWJlcntmaWxsOndoaXRlO30jbXktc3ZnICNzZXF1ZW5jZW51bWJlcntmaWxsOiMzMzM7fSNteS1zdmcgI2Nyb3NzaGVhZCBwYXRoe2ZpbGw6IzMzMztzdHJva2U6IzMzMzt9I215LXN2ZyAubWVzc2FnZVRleHR7ZmlsbDojMzMzO3N0cm9rZTpub25lO30jbXktc3ZnIC5sYWJlbEJveHtzdHJva2U6aHNsKDI1OS42MjYxNjgyMjQzLCA1OS43NzY1MzYzMTI4JSwgODcuOTAxOTYwNzg0MyUpO2ZpbGw6I2ZmZmZmZjt9I215LXN2ZyAubGFiZWxUZXh0LCNteS1zdmcgLmxhYmVsVGV4dCZndDt0c3BhbntmaWxsOmJsYWNrO3N0cm9rZTpub25lO30jbXktc3ZnIC5sb29wVGV4dCwjbXktc3ZnIC5sb29wVGV4dCZndDt0c3BhbntmaWxsOmJsYWNrO3N0cm9rZTpub25lO30jbXktc3ZnIC5sb29wTGluZXtzdHJva2Utd2lkdGg6MnB4O3N0cm9rZS1kYXNoYXJyYXk6MiwyO3N0cm9rZTpoc2woMjU5LjYyNjE2ODIyNDMsIDU5Ljc3NjUzNjMxMjglLCA4Ny45MDE5NjA3ODQzJSk7ZmlsbDpoc2woMjU5LjYyNjE2ODIyNDMsIDU5Ljc3NjUzNjMxMjglLCA4Ny45MDE5NjA3ODQzJSk7fSNteS1zdmcgLm5vdGV7c3Ryb2tlOiNhYWFhMzM7ZmlsbDojZmZmNWFkO30jbXktc3ZnIC5ub3RlVGV4dCwjbXktc3ZnIC5ub3RlVGV4dCZndDt0c3BhbntmaWxsOmJsYWNrO3N0cm9rZTpub25lO30jbXktc3ZnIC5hY3RpdmF0aW9uMHtmaWxsOiNmNGY0ZjQ7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFjdGl2YXRpb24xe2ZpbGw6I2Y0ZjRmNDtzdHJva2U6IzY2Njt9I215LXN2ZyAuYWN0aXZhdGlvbjJ7ZmlsbDojZjRmNGY0O3N0cm9rZTojNjY2O30jbXktc3ZnIC5hY3RvclBvcHVwTWVudXtwb3NpdGlvbjphYnNvbHV0ZTt9I215LXN2ZyAuYWN0b3JQb3B1cE1lbnVQYW5lbHtwb3NpdGlvbjphYnNvbHV0ZTtmaWxsOiNmZmZmZmY7Ym94LXNoYWRvdzowcHggOHB4IDE2cHggMHB4IHJnYmEoMCwwLDAsMC4yKTtmaWx0ZXI6ZHJvcC1zaGFkb3coM3B4IDVweCAycHggcmdiKDAgMCAwIC8gMC40KSk7fSNteS1zdmcgLmFjdG9yLW1hbiBsaW5le3N0cm9rZTpoc2woMjU5LjYyNjE2ODIyNDMsIDU5Ljc3NjUzNjMxMjglLCA4Ny45MDE5NjA3ODQzJSk7ZmlsbDojZmZmZmZmO30jbXktc3ZnIC5hY3Rvci1tYW4gY2lyY2xlLCNteS1zdmcgbGluZXtzdHJva2U6aHNsKDI1OS42MjYxNjgyMjQzLCA1OS43NzY1MzYzMTI4JSwgODcuOTAxOTYwNzg0MyUpO2ZpbGw6I2ZmZmZmZjtzdHJva2Utd2lkdGg6MnB4O30jbXktc3ZnIHN2ZywjbXktc3ZnIHN2ZyAqe2ZvbnQtZmFtaWx5OidPcGVuIFNhbnMnLHNhbnMtc2VyaWYhaW1wb3J0YW50O2ZvbnQtd2VpZ2h0OjQwMCFpbXBvcnRhbnQ7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5Ok9wZW4gU2FucyxzYW5zLXNlcmlmO308L3N0eWxlPjxnLz48ZGVmcz48c3ltYm9sIGlkPSJjb21wdXRlciIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCB0cmFuc2Zvcm09InNjYWxlKC41KSIgZD0iTTIgMnYxM2gyMHYtMTNoLTIwem0xOCAxMWgtMTZ2LTloMTZ2OXptLTEwLjIyOCA2bC40NjYtMWgzLjUyNGwuNDY3IDFoLTQuNDU3em0xNC4yMjggM2gtMjRsMi02aDIuMTA0bC0xLjMzIDRoMTguNDVsLTEuMjk3LTRoMi4wNzNsMiA2em0tNS0xMGgtMTR2LTdoMTR2N3oiLz48L3N5bWJvbD48L2RlZnM+PGRlZnM+PHN5bWJvbCBpZD0iZGF0YWJhc2UiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIj48cGF0aCB0cmFuc2Zvcm09InNjYWxlKC41KSIgZD0iTTEyLjI1OC4wMDFsLjI1Ni4wMDQuMjU1LjAwNS4yNTMuMDA4LjI1MS4wMS4yNDkuMDEyLjI0Ny4wMTUuMjQ2LjAxNi4yNDIuMDE5LjI0MS4wMi4yMzkuMDIzLjIzNi4wMjQuMjMzLjAyNy4yMzEuMDI4LjIyOS4wMzEuMjI1LjAzMi4yMjMuMDM0LjIyLjAzNi4yMTcuMDM4LjIxNC4wNC4yMTEuMDQxLjIwOC4wNDMuMjA1LjA0NS4yMDEuMDQ2LjE5OC4wNDguMTk0LjA1LjE5MS4wNTEuMTg3LjA1My4xODMuMDU0LjE4LjA1Ni4xNzUuMDU3LjE3Mi4wNTkuMTY4LjA2LjE2My4wNjEuMTYuMDYzLjE1NS4wNjQuMTUuMDY2LjA3NC4wMzMuMDczLjAzMy4wNzEuMDM0LjA3LjAzNC4wNjkuMDM1LjA2OC4wMzUuMDY3LjAzNS4wNjYuMDM1LjA2NC4wMzYuMDY0LjAzNi4wNjIuMDM2LjA2LjAzNi4wNi4wMzcuMDU4LjAzNy4wNTguMDM3LjA1NS4wMzguMDU1LjAzOC4wNTMuMDM4LjA1Mi4wMzguMDUxLjAzOS4wNS4wMzkuMDQ4LjAzOS4wNDcuMDM5LjA0NS4wNC4wNDQuMDQuMDQzLjA0LjA0MS4wNC4wNC4wNDEuMDM5LjA0MS4wMzcuMDQxLjAzNi4wNDEuMDM0LjA0MS4wMzMuMDQyLjAzMi4wNDIuMDMuMDQyLjAyOS4wNDIuMDI3LjA0Mi4wMjYuMDQzLjAyNC4wNDMuMDIzLjA0My4wMjEuMDQzLjAyLjA0My4wMTguMDQ0LjAxNy4wNDMuMDE1LjA0NC4wMTMuMDQ0LjAxMi4wNDQuMDExLjA0NS4wMDkuMDQ0LjAwNy4wNDUuMDA2LjA0NS4wMDQuMDQ1LjAwMi4wNDUuMDAxLjA0NXYxN2wtLjAwMS4wNDUtLjAwMi4wNDUtLjAwNC4wNDUtLjAwNi4wNDUtLjAwNy4wNDUtLjAwOS4wNDQtLjAxMS4wNDUtLjAxMi4wNDQtLjAxMy4wNDQtLjAxNS4wNDQtLjAxNy4wNDMtLjAxOC4wNDQtLjAyLjA0My0uMDIxLjA0My0uMDIzLjA0My0uMDI0LjA0My0uMDI2LjA0My0uMDI3LjA0Mi0uMDI5LjA0Mi0uMDMuMDQyLS4wMzIuMDQyLS4wMzMuMDQyLS4wMzQuMDQxLS4wMzYuMDQxLS4wMzcuMDQxLS4wMzkuMDQxLS4wNC4wNDEtLjA0MS4wNC0uMDQzLjA0LS4wNDQuMDQtLjA0NS4wNC0uMDQ3LjAzOS0uMDQ4LjAzOS0uMDUuMDM5LS4wNTEuMDM5LS4wNTIuMDM4LS4wNTMuMDM4LS4wNTUuMDM4LS4wNTUuMDM4LS4wNTguMDM3LS4wNTguMDM3LS4wNi4wMzctLjA2LjAzNi0uMDYyLjAzNi0uMDY0LjAzNi0uMDY0LjAzNi0uMDY2LjAzNS0uMDY3LjAzNS0uMDY4LjAzNS0uMDY5LjAzNS0uMDcuMDM0LS4wNzEuMDM0LS4wNzMuMDMzLS4wNzQuMDMzLS4xNS4wNjYtLjE1NS4wNjQtLjE2LjA2My0uMTYzLjA2MS0uMTY4LjA2LS4xNzIuMDU5LS4xNzUuMDU3LS4xOC4wNTYtLjE4My4wNTQtLjE4Ny4wNTMtLjE5MS4wNTEtLjE5NC4wNS0uMTk4LjA0OC0uMjAxLjA0Ni0uMjA1LjA0NS0uMjA4LjA0My0uMjExLjA0MS0uMjE0LjA0LS4yMTcuMDM4LS4yMi4wMzYtLjIyMy4wMzQtLjIyNS4wMzItLjIyOS4wMzEtLjIzMS4wMjgtLjIzMy4wMjctLjIzNi4wMjQtLjIzOS4wMjMtLjI0MS4wMi0uMjQyLjAxOS0uMjQ2LjAxNi0uMjQ3LjAxNS0uMjQ5LjAxMi0uMjUxLjAxLS4yNTMuMDA4LS4yNTUuMDA1LS4yNTYuMDA0LS4yNTguMDAxLS4yNTgtLjAwMS0uMjU2LS4wMDQtLjI1NS0uMDA1LS4yNTMtLjAwOC0uMjUxLS4wMS0uMjQ5LS4wMTItLjI0Ny0uMDE1LS4yNDUtLjAxNi0uMjQzLS4wMTktLjI0MS0uMDItLjIzOC0uMDIzLS4yMzYtLjAyNC0uMjM0LS4wMjctLjIzMS0uMDI4LS4yMjgtLjAzMS0uMjI2LS4wMzItLjIyMy0uMDM0LS4yMi0uMDM2LS4yMTctLjAzOC0uMjE0LS4wNC0uMjExLS4wNDEtLjIwOC0uMDQzLS4yMDQtLjA0NS0uMjAxLS4wNDYtLjE5OC0uMDQ4LS4xOTUtLjA1LS4xOS0uMDUxLS4xODctLjA1My0uMTg0LS4wNTQtLjE3OS0uMDU2LS4xNzYtLjA1Ny0uMTcyLS4wNTktLjE2Ny0uMDYtLjE2NC0uMDYxLS4xNTktLjA2My0uMTU1LS4wNjQtLjE1MS0uMDY2LS4wNzQtLjAzMy0uMDcyLS4wMzMtLjA3Mi0uMDM0LS4wNy0uMDM0LS4wNjktLjAzNS0uMDY4LS4wMzUtLjA2Ny0uMDM1LS4wNjYtLjAzNS0uMDY0LS4wMzYtLjA2My0uMDM2LS4wNjItLjAzNi0uMDYxLS4wMzYtLjA2LS4wMzctLjA1OC0uMDM3LS4wNTctLjAzNy0uMDU2LS4wMzgtLjA1NS0uMDM4LS4wNTMtLjAzOC0uMDUyLS4wMzgtLjA1MS0uMDM5LS4wNDktLjAzOS0uMDQ5LS4wMzktLjA0Ni0uMDM5LS4wNDYtLjA0LS4wNDQtLjA0LS4wNDMtLjA0LS4wNDEtLjA0LS4wNC0uMDQxLS4wMzktLjA0MS0uMDM3LS4wNDEtLjAzNi0uMDQxLS4wMzQtLjA0MS0uMDMzLS4wNDItLjAzMi0uMDQyLS4wMy0uMDQyLS4wMjktLjA0Mi0uMDI3LS4wNDItLjAyNi0uMDQzLS4wMjQtLjA0My0uMDIzLS4wNDMtLjAyMS0uMDQzLS4wMi0uMDQzLS4wMTgtLjA0NC0uMDE3LS4wNDMtLjAxNS0uMDQ0LS4wMTMtLjA0NC0uMDEyLS4wNDQtLjAxMS0uMDQ1LS4wMDktLjA0NC0uMDA3LS4wNDUtLjAwNi0uMDQ1LS4wMDQtLjA0NS0uMDAyLS4wNDUtLjAwMS0uMDQ1di0xN2wuMDAxLS4wNDUuMDAyLS4wNDUuMDA0LS4wNDUuMDA2LS4wNDUuMDA3LS4wNDUuMDA5LS4wNDQuMDExLS4wNDUuMDEyLS4wNDQuMDEzLS4wNDQuMDE1LS4wNDQuMDE3LS4wNDMuMDE4LS4wNDQuMDItLjA0My4wMjEtLjA0My4wMjMtLjA0My4wMjQtLjA0My4wMjYtLjA0My4wMjctLjA0Mi4wMjktLjA0Mi4wMy0uMDQyLjAzMi0uMDQyLjAzMy0uMDQyLjAzNC0uMDQxLjAzNi0uMDQxLjAzNy0uMDQxLjAzOS0uMDQxLjA0LS4wNDEuMDQxLS4wNC4wNDMtLjA0LjA0NC0uMDQuMDQ2LS4wNC4wNDYtLjAzOS4wNDktLjAzOS4wNDktLjAzOS4wNTEtLjAzOS4wNTItLjAzOC4wNTMtLjAzOC4wNTUtLjAzOC4wNTYtLjAzOC4wNTctLjAzNy4wNTgtLjAzNy4wNi0uMDM3LjA2MS0uMDM2LjA2Mi0uMDM2LjA2My0uMDM2LjA2NC0uMDM2LjA2Ni0uMDM1LjA2Ny0uMDM1LjA2OC0uMDM1LjA2OS0uMDM1LjA3LS4wMzQuMDcyLS4wMzQuMDcyLS4wMzMuMDc0LS4wMzMuMTUxLS4wNjYuMTU1LS4wNjQuMTU5LS4wNjMuMTY0LS4wNjEuMTY3LS4wNi4xNzItLjA1OS4xNzYtLjA1Ny4xNzktLjA1Ni4xODQtLjA1NC4xODctLjA1My4xOS0uMDUxLjE5NS0uMDUuMTk4LS4wNDguMjAxLS4wNDYuMjA0LS4wNDUuMjA4LS4wNDMuMjExLS4wNDEuMjE0LS4wNC4yMTctLjAzOC4yMi0uMDM2LjIyMy0uMDM0LjIyNi0uMDMyLjIyOC0uMDMxLjIzMS0uMDI4LjIzNC0uMDI3LjIzNi0uMDI0LjIzOC0uMDIzLjI0MS0uMDIuMjQzLS4wMTkuMjQ1LS4wMTYuMjQ3LS4wMTUuMjQ5LS4wMTIuMjUxLS4wMS4yNTMtLjAwOC4yNTUtLjAwNS4yNTYtLjAwNC4yNTgtLjAwMS4yNTguMDAxem0tOS4yNTggMjAuNDk5di4wMWwuMDAxLjAyMS4wMDMuMDIxLjAwNC4wMjIuMDA1LjAyMS4wMDYuMDIyLjAwNy4wMjIuMDA5LjAyMy4wMS4wMjIuMDExLjAyMy4wMTIuMDIzLjAxMy4wMjMuMDE1LjAyMy4wMTYuMDI0LjAxNy4wMjMuMDE4LjAyNC4wMTkuMDI0LjAyMS4wMjQuMDIyLjAyNS4wMjMuMDI0LjAyNC4wMjUuMDUyLjA0OS4wNTYuMDUuMDYxLjA1MS4wNjYuMDUxLjA3LjA1MS4wNzUuMDUxLjA3OS4wNTIuMDg0LjA1Mi4wODguMDUyLjA5Mi4wNTIuMDk3LjA1Mi4xMDIuMDUxLjEwNS4wNTIuMTEuMDUyLjExNC4wNTEuMTE5LjA1MS4xMjMuMDUxLjEyNy4wNS4xMzEuMDUuMTM1LjA1LjEzOS4wNDguMTQ0LjA0OS4xNDcuMDQ3LjE1Mi4wNDcuMTU1LjA0Ny4xNi4wNDUuMTYzLjA0NS4xNjcuMDQzLjE3MS4wNDMuMTc2LjA0MS4xNzguMDQxLjE4My4wMzkuMTg3LjAzOS4xOS4wMzcuMTk0LjAzNS4xOTcuMDM1LjIwMi4wMzMuMjA0LjAzMS4yMDkuMDMuMjEyLjAyOS4yMTYuMDI3LjIxOS4wMjUuMjIyLjAyNC4yMjYuMDIxLjIzLjAyLjIzMy4wMTguMjM2LjAxNi4yNC4wMTUuMjQzLjAxMi4yNDYuMDEuMjQ5LjAwOC4yNTMuMDA1LjI1Ni4wMDQuMjU5LjAwMS4yNi0uMDAxLjI1Ny0uMDA0LjI1NC0uMDA1LjI1LS4wMDguMjQ3LS4wMTEuMjQ0LS4wMTIuMjQxLS4wMTQuMjM3LS4wMTYuMjMzLS4wMTguMjMxLS4wMjEuMjI2LS4wMjEuMjI0LS4wMjQuMjItLjAyNi4yMTYtLjAyNy4yMTItLjAyOC4yMS0uMDMxLjIwNS0uMDMxLjIwMi0uMDM0LjE5OC0uMDM0LjE5NC0uMDM2LjE5MS0uMDM3LjE4Ny0uMDM5LjE4My0uMDQuMTc5LS4wNC4xNzUtLjA0Mi4xNzItLjA0My4xNjgtLjA0NC4xNjMtLjA0NS4xNi0uMDQ2LjE1NS0uMDQ2LjE1Mi0uMDQ3LjE0OC0uMDQ4LjE0My0uMDQ5LjEzOS0uMDQ5LjEzNi0uMDUuMTMxLS4wNS4xMjYtLjA1LjEyMy0uMDUxLjExOC0uMDUyLjExNC0uMDUxLjExLS4wNTIuMTA2LS4wNTIuMTAxLS4wNTIuMDk2LS4wNTIuMDkyLS4wNTIuMDg4LS4wNTMuMDgzLS4wNTEuMDc5LS4wNTIuMDc0LS4wNTIuMDctLjA1MS4wNjUtLjA1MS4wNi0uMDUxLjA1Ni0uMDUuMDUxLS4wNS4wMjMtLjAyNC4wMjMtLjAyNS4wMjEtLjAyNC4wMi0uMDI0LjAxOS0uMDI0LjAxOC0uMDI0LjAxNy0uMDI0LjAxNS0uMDIzLjAxNC0uMDI0LjAxMy0uMDIzLjAxMi0uMDIzLjAxLS4wMjMuMDEtLjAyMi4wMDgtLjAyMi4wMDYtLjAyMi4wMDYtLjAyMi4wMDQtLjAyMi4wMDQtLjAyMS4wMDEtLjAyMS4wMDEtLjAyMXYtNC4xMjdsLS4wNzcuMDU1LS4wOC4wNTMtLjA4My4wNTQtLjA4NS4wNTMtLjA4Ny4wNTItLjA5LjA1Mi0uMDkzLjA1MS0uMDk1LjA1LS4wOTcuMDUtLjEuMDQ5LS4xMDIuMDQ5LS4xMDUuMDQ4LS4xMDYuMDQ3LS4xMDkuMDQ3LS4xMTEuMDQ2LS4xMTQuMDQ1LS4xMTUuMDQ1LS4xMTguMDQ0LS4xMi4wNDMtLjEyMi4wNDItLjEyNC4wNDItLjEyNi4wNDEtLjEyOC4wNC0uMTMuMDQtLjEzMi4wMzgtLjEzNC4wMzgtLjEzNS4wMzctLjEzOC4wMzctLjEzOS4wMzUtLjE0Mi4wMzUtLjE0My4wMzQtLjE0NC4wMzMtLjE0Ny4wMzItLjE0OC4wMzEtLjE1LjAzLS4xNTEuMDMtLjE1My4wMjktLjE1NC4wMjctLjE1Ni4wMjctLjE1OC4wMjYtLjE1OS4wMjUtLjE2MS4wMjQtLjE2Mi4wMjMtLjE2My4wMjItLjE2NS4wMjEtLjE2Ni4wMi0uMTY3LjAxOS0uMTY5LjAxOC0uMTY5LjAxNy0uMTcxLjAxNi0uMTczLjAxNS0uMTczLjAxNC0uMTc1LjAxMy0uMTc1LjAxMi0uMTc3LjAxMS0uMTc4LjAxLS4xNzkuMDA4LS4xNzkuMDA4LS4xODEuMDA2LS4xODIuMDA1LS4xODIuMDA0LS4xODQuMDAzLS4xODQuMDAyaC0uMzdsLS4xODQtLjAwMi0uMTg0LS4wMDMtLjE4Mi0uMDA0LS4xODItLjAwNS0uMTgxLS4wMDYtLjE3OS0uMDA4LS4xNzktLjAwOC0uMTc4LS4wMS0uMTc2LS4wMTEtLjE3Ni0uMDEyLS4xNzUtLjAxMy0uMTczLS4wMTQtLjE3Mi0uMDE1LS4xNzEtLjAxNi0uMTctLjAxNy0uMTY5LS4wMTgtLjE2Ny0uMDE5LS4xNjYtLjAyLS4xNjUtLjAyMS0uMTYzLS4wMjItLjE2Mi0uMDIzLS4xNjEtLjAyNC0uMTU5LS4wMjUtLjE1Ny0uMDI2LS4xNTYtLjAyNy0uMTU1LS4wMjctLjE1My0uMDI5LS4xNTEtLjAzLS4xNS0uMDMtLjE0OC0uMDMxLS4xNDYtLjAzMi0uMTQ1LS4wMzMtLjE0My0uMDM0LS4xNDEtLjAzNS0uMTQtLjAzNS0uMTM3LS4wMzctLjEzNi0uMDM3LS4xMzQtLjAzOC0uMTMyLS4wMzgtLjEzLS4wNC0uMTI4LS4wNC0uMTI2LS4wNDEtLjEyNC0uMDQyLS4xMjItLjA0Mi0uMTItLjA0NC0uMTE3LS4wNDMtLjExNi0uMDQ1LS4xMTMtLjA0NS0uMTEyLS4wNDYtLjEwOS0uMDQ3LS4xMDYtLjA0Ny0uMTA1LS4wNDgtLjEwMi0uMDQ5LS4xLS4wNDktLjA5Ny0uMDUtLjA5NS0uMDUtLjA5My0uMDUyLS4wOS0uMDUxLS4wODctLjA1Mi0uMDg1LS4wNTMtLjA4My0uMDU0LS4wOC0uMDU0LS4wNzctLjA1NHY0LjEyN3ptMC01LjY1NHYuMDExbC4wMDEuMDIxLjAwMy4wMjEuMDA0LjAyMS4wMDUuMDIyLjAwNi4wMjIuMDA3LjAyMi4wMDkuMDIyLjAxLjAyMi4wMTEuMDIzLjAxMi4wMjMuMDEzLjAyMy4wMTUuMDI0LjAxNi4wMjMuMDE3LjAyNC4wMTguMDI0LjAxOS4wMjQuMDIxLjAyNC4wMjIuMDI0LjAyMy4wMjUuMDI0LjAyNC4wNTIuMDUuMDU2LjA1LjA2MS4wNS4wNjYuMDUxLjA3LjA1MS4wNzUuMDUyLjA3OS4wNTEuMDg0LjA1Mi4wODguMDUyLjA5Mi4wNTIuMDk3LjA1Mi4xMDIuMDUyLjEwNS4wNTIuMTEuMDUxLjExNC4wNTEuMTE5LjA1Mi4xMjMuMDUuMTI3LjA1MS4xMzEuMDUuMTM1LjA0OS4xMzkuMDQ5LjE0NC4wNDguMTQ3LjA0OC4xNTIuMDQ3LjE1NS4wNDYuMTYuMDQ1LjE2My4wNDUuMTY3LjA0NC4xNzEuMDQyLjE3Ni4wNDIuMTc4LjA0LjE4My4wNC4xODcuMDM4LjE5LjAzNy4xOTQuMDM2LjE5Ny4wMzQuMjAyLjAzMy4yMDQuMDMyLjIwOS4wMy4yMTIuMDI4LjIxNi4wMjcuMjE5LjAyNS4yMjIuMDI0LjIyNi4wMjIuMjMuMDIuMjMzLjAxOC4yMzYuMDE2LjI0LjAxNC4yNDMuMDEyLjI0Ni4wMS4yNDkuMDA4LjI1My4wMDYuMjU2LjAwMy4yNTkuMDAxLjI2LS4wMDEuMjU3LS4wMDMuMjU0LS4wMDYuMjUtLjAwOC4yNDctLjAxLjI0NC0uMDEyLjI0MS0uMDE1LjIzNy0uMDE2LjIzMy0uMDE4LjIzMS0uMDIuMjI2LS4wMjIuMjI0LS4wMjQuMjItLjAyNS4yMTYtLjAyNy4yMTItLjAyOS4yMS0uMDMuMjA1LS4wMzIuMjAyLS4wMzMuMTk4LS4wMzUuMTk0LS4wMzYuMTkxLS4wMzcuMTg3LS4wMzkuMTgzLS4wMzkuMTc5LS4wNDEuMTc1LS4wNDIuMTcyLS4wNDMuMTY4LS4wNDQuMTYzLS4wNDUuMTYtLjA0NS4xNTUtLjA0Ny4xNTItLjA0Ny4xNDgtLjA0OC4xNDMtLjA0OC4xMzktLjA1LjEzNi0uMDQ5LjEzMS0uMDUuMTI2LS4wNTEuMTIzLS4wNTEuMTE4LS4wNTEuMTE0LS4wNTIuMTEtLjA1Mi4xMDYtLjA1Mi4xMDEtLjA1Mi4wOTYtLjA1Mi4wOTItLjA1Mi4wODgtLjA1Mi4wODMtLjA1Mi4wNzktLjA1Mi4wNzQtLjA1MS4wNy0uMDUyLjA2NS0uMDUxLjA2LS4wNS4wNTYtLjA1MS4wNTEtLjA0OS4wMjMtLjAyNS4wMjMtLjAyNC4wMjEtLjAyNS4wMi0uMDI0LjAxOS0uMDI0LjAxOC0uMDI0LjAxNy0uMDI0LjAxNS0uMDIzLjAxNC0uMDIzLjAxMy0uMDI0LjAxMi0uMDIyLjAxLS4wMjMuMDEtLjAyMy4wMDgtLjAyMi4wMDYtLjAyMi4wMDYtLjAyMi4wMDQtLjAyMS4wMDQtLjAyMi4wMDEtLjAyMS4wMDEtLjAyMXYtNC4xMzlsLS4wNzcuMDU0LS4wOC4wNTQtLjA4My4wNTQtLjA4NS4wNTItLjA4Ny4wNTMtLjA5LjA1MS0uMDkzLjA1MS0uMDk1LjA1MS0uMDk3LjA1LS4xLjA0OS0uMTAyLjA0OS0uMTA1LjA0OC0uMTA2LjA0Ny0uMTA5LjA0Ny0uMTExLjA0Ni0uMTE0LjA0NS0uMTE1LjA0NC0uMTE4LjA0NC0uMTIuMDQ0LS4xMjIuMDQyLS4xMjQuMDQyLS4xMjYuMDQxLS4xMjguMDQtLjEzLjAzOS0uMTMyLjAzOS0uMTM0LjAzOC0uMTM1LjAzNy0uMTM4LjAzNi0uMTM5LjAzNi0uMTQyLjAzNS0uMTQzLjAzMy0uMTQ0LjAzMy0uMTQ3LjAzMy0uMTQ4LjAzMS0uMTUuMDMtLjE1MS4wMy0uMTUzLjAyOC0uMTU0LjAyOC0uMTU2LjAyNy0uMTU4LjAyNi0uMTU5LjAyNS0uMTYxLjAyNC0uMTYyLjAyMy0uMTYzLjAyMi0uMTY1LjAyMS0uMTY2LjAyLS4xNjcuMDE5LS4xNjkuMDE4LS4xNjkuMDE3LS4xNzEuMDE2LS4xNzMuMDE1LS4xNzMuMDE0LS4xNzUuMDEzLS4xNzUuMDEyLS4xNzcuMDExLS4xNzguMDA5LS4xNzkuMDA5LS4xNzkuMDA3LS4xODEuMDA3LS4xODIuMDA1LS4xODIuMDA0LS4xODQuMDAzLS4xODQuMDAyaC0uMzdsLS4xODQtLjAwMi0uMTg0LS4wMDMtLjE4Mi0uMDA0LS4xODItLjAwNS0uMTgxLS4wMDctLjE3OS0uMDA3LS4xNzktLjAwOS0uMTc4LS4wMDktLjE3Ni0uMDExLS4xNzYtLjAxMi0uMTc1LS4wMTMtLjE3My0uMDE0LS4xNzItLjAxNS0uMTcxLS4wMTYtLjE3LS4wMTctLjE2OS0uMDE4LS4xNjctLjAxOS0uMTY2LS4wMi0uMTY1LS4wMjEtLjE2My0uMDIyLS4xNjItLjAyMy0uMTYxLS4wMjQtLjE1OS0uMDI1LS4xNTctLjAyNi0uMTU2LS4wMjctLjE1NS0uMDI4LS4xNTMtLjAyOC0uMTUxLS4wMy0uMTUtLjAzLS4xNDgtLjAzMS0uMTQ2LS4wMzMtLjE0NS0uMDMzLS4xNDMtLjAzMy0uMTQxLS4wMzUtLjE0LS4wMzYtLjEzNy0uMDM2LS4xMzYtLjAzNy0uMTM0LS4wMzgtLjEzMi0uMDM5LS4xMy0uMDM5LS4xMjgtLjA0LS4xMjYtLjA0MS0uMTI0LS4wNDItLjEyMi0uMDQzLS4xMi0uMDQzLS4xMTctLjA0NC0uMTE2LS4wNDQtLjExMy0uMDQ2LS4xMTItLjA0Ni0uMTA5LS4wNDYtLjEwNi0uMDQ3LS4xMDUtLjA0OC0uMTAyLS4wNDktLjEtLjA0OS0uMDk3LS4wNS0uMDk1LS4wNTEtLjA5My0uMDUxLS4wOS0uMDUxLS4wODctLjA1My0uMDg1LS4wNTItLjA4My0uMDU0LS4wOC0uMDU0LS4wNzctLjA1NHY0LjEzOXptMC01LjY2NnYuMDExbC4wMDEuMDIuMDAzLjAyMi4wMDQuMDIxLjAwNS4wMjIuMDA2LjAyMS4wMDcuMDIyLjAwOS4wMjMuMDEuMDIyLjAxMS4wMjMuMDEyLjAyMy4wMTMuMDIzLjAxNS4wMjMuMDE2LjAyNC4wMTcuMDI0LjAxOC4wMjMuMDE5LjAyNC4wMjEuMDI1LjAyMi4wMjQuMDIzLjAyNC4wMjQuMDI1LjA1Mi4wNS4wNTYuMDUuMDYxLjA1LjA2Ni4wNTEuMDcuMDUxLjA3NS4wNTIuMDc5LjA1MS4wODQuMDUyLjA4OC4wNTIuMDkyLjA1Mi4wOTcuMDUyLjEwMi4wNTIuMTA1LjA1MS4xMS4wNTIuMTE0LjA1MS4xMTkuMDUxLjEyMy4wNTEuMTI3LjA1LjEzMS4wNS4xMzUuMDUuMTM5LjA0OS4xNDQuMDQ4LjE0Ny4wNDguMTUyLjA0Ny4xNTUuMDQ2LjE2LjA0NS4xNjMuMDQ1LjE2Ny4wNDMuMTcxLjA0My4xNzYuMDQyLjE3OC4wNC4xODMuMDQuMTg3LjAzOC4xOS4wMzcuMTk0LjAzNi4xOTcuMDM0LjIwMi4wMzMuMjA0LjAzMi4yMDkuMDMuMjEyLjAyOC4yMTYuMDI3LjIxOS4wMjUuMjIyLjAyNC4yMjYuMDIxLjIzLjAyLjIzMy4wMTguMjM2LjAxNy4yNC4wMTQuMjQzLjAxMi4yNDYuMDEuMjQ5LjAwOC4yNTMuMDA2LjI1Ni4wMDMuMjU5LjAwMS4yNi0uMDAxLjI1Ny0uMDAzLjI1NC0uMDA2LjI1LS4wMDguMjQ3LS4wMS4yNDQtLjAxMy4yNDEtLjAxNC4yMzctLjAxNi4yMzMtLjAxOC4yMzEtLjAyLjIyNi0uMDIyLjIyNC0uMDI0LjIyLS4wMjUuMjE2LS4wMjcuMjEyLS4wMjkuMjEtLjAzLjIwNS0uMDMyLjIwMi0uMDMzLjE5OC0uMDM1LjE5NC0uMDM2LjE5MS0uMDM3LjE4Ny0uMDM5LjE4My0uMDM5LjE3OS0uMDQxLjE3NS0uMDQyLjE3Mi0uMDQzLjE2OC0uMDQ0LjE2My0uMDQ1LjE2LS4wNDUuMTU1LS4wNDcuMTUyLS4wNDcuMTQ4LS4wNDguMTQzLS4wNDkuMTM5LS4wNDkuMTM2LS4wNDkuMTMxLS4wNTEuMTI2LS4wNS4xMjMtLjA1MS4xMTgtLjA1Mi4xMTQtLjA1MS4xMS0uMDUyLjEwNi0uMDUyLjEwMS0uMDUyLjA5Ni0uMDUyLjA5Mi0uMDUyLjA4OC0uMDUyLjA4My0uMDUyLjA3OS0uMDUyLjA3NC0uMDUyLjA3LS4wNTEuMDY1LS4wNTEuMDYtLjA1MS4wNTYtLjA1LjA1MS0uMDQ5LjAyMy0uMDI1LjAyMy0uMDI1LjAyMS0uMDI0LjAyLS4wMjQuMDE5LS4wMjQuMDE4LS4wMjQuMDE3LS4wMjQuMDE1LS4wMjMuMDE0LS4wMjQuMDEzLS4wMjMuMDEyLS4wMjMuMDEtLjAyMi4wMS0uMDIzLjAwOC0uMDIyLjAwNi0uMDIyLjAwNi0uMDIyLjAwNC0uMDIyLjAwNC0uMDIxLjAwMS0uMDIxLjAwMS0uMDIxdi00LjE1M2wtLjA3Ny4wNTQtLjA4LjA1NC0uMDgzLjA1My0uMDg1LjA1My0uMDg3LjA1My0uMDkuMDUxLS4wOTMuMDUxLS4wOTUuMDUxLS4wOTcuMDUtLjEuMDQ5LS4xMDIuMDQ4LS4xMDUuMDQ4LS4xMDYuMDQ4LS4xMDkuMDQ2LS4xMTEuMDQ2LS4xMTQuMDQ2LS4xMTUuMDQ0LS4xMTguMDQ0LS4xMi4wNDMtLjEyMi4wNDMtLjEyNC4wNDItLjEyNi4wNDEtLjEyOC4wNC0uMTMuMDM5LS4xMzIuMDM5LS4xMzQuMDM4LS4xMzUuMDM3LS4xMzguMDM2LS4xMzkuMDM2LS4xNDIuMDM0LS4xNDMuMDM0LS4xNDQuMDMzLS4xNDcuMDMyLS4xNDguMDMyLS4xNS4wMy0uMTUxLjAzLS4xNTMuMDI4LS4xNTQuMDI4LS4xNTYuMDI3LS4xNTguMDI2LS4xNTkuMDI0LS4xNjEuMDI0LS4xNjIuMDIzLS4xNjMuMDIzLS4xNjUuMDIxLS4xNjYuMDItLjE2Ny4wMTktLjE2OS4wMTgtLjE2OS4wMTctLjE3MS4wMTYtLjE3My4wMTUtLjE3My4wMTQtLjE3NS4wMTMtLjE3NS4wMTItLjE3Ny4wMS0uMTc4LjAxLS4xNzkuMDA5LS4xNzkuMDA3LS4xODEuMDA2LS4xODIuMDA2LS4xODIuMDA0LS4xODQuMDAzLS4xODQuMDAxLS4xODUuMDAxLS4xODUtLjAwMS0uMTg0LS4wMDEtLjE4NC0uMDAzLS4xODItLjAwNC0uMTgyLS4wMDYtLjE4MS0uMDA2LS4xNzktLjAwNy0uMTc5LS4wMDktLjE3OC0uMDEtLjE3Ni0uMDEtLjE3Ni0uMDEyLS4xNzUtLjAxMy0uMTczLS4wMTQtLjE3Mi0uMDE1LS4xNzEtLjAxNi0uMTctLjAxNy0uMTY5LS4wMTgtLjE2Ny0uMDE5LS4xNjYtLjAyLS4xNjUtLjAyMS0uMTYzLS4wMjMtLjE2Mi0uMDIzLS4xNjEtLjAyNC0uMTU5LS4wMjQtLjE1Ny0uMDI2LS4xNTYtLjAyNy0uMTU1LS4wMjgtLjE1My0uMDI4LS4xNTEtLjAzLS4xNS0uMDMtLjE0OC0uMDMyLS4xNDYtLjAzMi0uMTQ1LS4wMzMtLjE0My0uMDM0LS4xNDEtLjAzNC0uMTQtLjAzNi0uMTM3LS4wMzYtLjEzNi0uMDM3LS4xMzQtLjAzOC0uMTMyLS4wMzktLjEzLS4wMzktLjEyOC0uMDQxLS4xMjYtLjA0MS0uMTI0LS4wNDEtLjEyMi0uMDQzLS4xMi0uMDQzLS4xMTctLjA0NC0uMTE2LS4wNDQtLjExMy0uMDQ2LS4xMTItLjA0Ni0uMTA5LS4wNDYtLjEwNi0uMDQ4LS4xMDUtLjA0OC0uMTAyLS4wNDgtLjEtLjA1LS4wOTctLjA0OS0uMDk1LS4wNTEtLjA5My0uMDUxLS4wOS0uMDUyLS4wODctLjA1Mi0uMDg1LS4wNTMtLjA4My0uMDUzLS4wOC0uMDU0LS4wNzctLjA1NHY0LjE1M3ptOC43NC04LjE3OWwtLjI1Ny4wMDQtLjI1NC4wMDUtLjI1LjAwOC0uMjQ3LjAxMS0uMjQ0LjAxMi0uMjQxLjAxNC0uMjM3LjAxNi0uMjMzLjAxOC0uMjMxLjAyMS0uMjI2LjAyMi0uMjI0LjAyMy0uMjIuMDI2LS4yMTYuMDI3LS4yMTIuMDI4LS4yMS4wMzEtLjIwNS4wMzItLjIwMi4wMzMtLjE5OC4wMzQtLjE5NC4wMzYtLjE5MS4wMzgtLjE4Ny4wMzgtLjE4My4wNC0uMTc5LjA0MS0uMTc1LjA0Mi0uMTcyLjA0My0uMTY4LjA0My0uMTYzLjA0NS0uMTYuMDQ2LS4xNTUuMDQ2LS4xNTIuMDQ4LS4xNDguMDQ4LS4xNDMuMDQ4LS4xMzkuMDQ5LS4xMzYuMDUtLjEzMS4wNS0uMTI2LjA1MS0uMTIzLjA1MS0uMTE4LjA1MS0uMTE0LjA1Mi0uMTEuMDUyLS4xMDYuMDUyLS4xMDEuMDUyLS4wOTYuMDUyLS4wOTIuMDUyLS4wODguMDUyLS4wODMuMDUyLS4wNzkuMDUyLS4wNzQuMDUxLS4wNy4wNTItLjA2NS4wNTEtLjA2LjA1LS4wNTYuMDUtLjA1MS4wNS0uMDIzLjAyNS0uMDIzLjAyNC0uMDIxLjAyNC0uMDIuMDI1LS4wMTkuMDI0LS4wMTguMDI0LS4wMTcuMDIzLS4wMTUuMDI0LS4wMTQuMDIzLS4wMTMuMDIzLS4wMTIuMDIzLS4wMS4wMjMtLjAxLjAyMi0uMDA4LjAyMi0uMDA2LjAyMy0uMDA2LjAyMS0uMDA0LjAyMi0uMDA0LjAyMS0uMDAxLjAyMS0uMDAxLjAyMS4wMDEuMDIxLjAwMS4wMjEuMDA0LjAyMS4wMDQuMDIyLjAwNi4wMjEuMDA2LjAyMy4wMDguMDIyLjAxLjAyMi4wMS4wMjMuMDEyLjAyMy4wMTMuMDIzLjAxNC4wMjMuMDE1LjAyNC4wMTcuMDIzLjAxOC4wMjQuMDE5LjAyNC4wMi4wMjUuMDIxLjAyNC4wMjMuMDI0LjAyMy4wMjUuMDUxLjA1LjA1Ni4wNS4wNi4wNS4wNjUuMDUxLjA3LjA1Mi4wNzQuMDUxLjA3OS4wNTIuMDgzLjA1Mi4wODguMDUyLjA5Mi4wNTIuMDk2LjA1Mi4xMDEuMDUyLjEwNi4wNTIuMTEuMDUyLjExNC4wNTIuMTE4LjA1MS4xMjMuMDUxLjEyNi4wNTEuMTMxLjA1LjEzNi4wNS4xMzkuMDQ5LjE0My4wNDguMTQ4LjA0OC4xNTIuMDQ4LjE1NS4wNDYuMTYuMDQ2LjE2My4wNDUuMTY4LjA0My4xNzIuMDQzLjE3NS4wNDIuMTc5LjA0MS4xODMuMDQuMTg3LjAzOC4xOTEuMDM4LjE5NC4wMzYuMTk4LjAzNC4yMDIuMDMzLjIwNS4wMzIuMjEuMDMxLjIxMi4wMjguMjE2LjAyNy4yMi4wMjYuMjI0LjAyMy4yMjYuMDIyLjIzMS4wMjEuMjMzLjAxOC4yMzcuMDE2LjI0MS4wMTQuMjQ0LjAxMi4yNDcuMDExLjI1LjAwOC4yNTQuMDA1LjI1Ny4wMDQuMjYuMDAxLjI2LS4wMDEuMjU3LS4wMDQuMjU0LS4wMDUuMjUtLjAwOC4yNDctLjAxMS4yNDQtLjAxMi4yNDEtLjAxNC4yMzctLjAxNi4yMzMtLjAxOC4yMzEtLjAyMS4yMjYtLjAyMi4yMjQtLjAyMy4yMi0uMDI2LjIxNi0uMDI3LjIxMi0uMDI4LjIxLS4wMzEuMjA1LS4wMzIuMjAyLS4wMzMuMTk4LS4wMzQuMTk0LS4wMzYuMTkxLS4wMzguMTg3LS4wMzguMTgzLS4wNC4xNzktLjA0MS4xNzUtLjA0Mi4xNzItLjA0My4xNjgtLjA0My4xNjMtLjA0NS4xNi0uMDQ2LjE1NS0uMDQ2LjE1Mi0uMDQ4LjE0OC0uMDQ4LjE0My0uMDQ4LjEzOS0uMDQ5LjEzNi0uMDUuMTMxLS4wNS4xMjYtLjA1MS4xMjMtLjA1MS4xMTgtLjA1MS4xMTQtLjA1Mi4xMS0uMDUyLjEwNi0uMDUyLjEwMS0uMDUyLjA5Ni0uMDUyLjA5Mi0uMDUyLjA4OC0uMDUyLjA4My0uMDUyLjA3OS0uMDUyLjA3NC0uMDUxLjA3LS4wNTIuMDY1LS4wNTEuMDYtLjA1LjA1Ni0uMDUuMDUxLS4wNS4wMjMtLjAyNS4wMjMtLjAyNC4wMjEtLjAyNC4wMi0uMDI1LjAxOS0uMDI0LjAxOC0uMDI0LjAxNy0uMDIzLjAxNS0uMDI0LjAxNC0uMDIzLjAxMy0uMDIzLjAxMi0uMDIzLjAxLS4wMjMuMDEtLjAyMi4wMDgtLjAyMi4wMDYtLjAyMy4wMDYtLjAyMS4wMDQtLjAyMi4wMDQtLjAyMS4wMDEtLjAyMS4wMDEtLjAyMS0uMDAxLS4wMjEtLjAwMS0uMDIxLS4wMDQtLjAyMS0uMDA0LS4wMjItLjAwNi0uMDIxLS4wMDYtLjAyMy0uMDA4LS4wMjItLjAxLS4wMjItLjAxLS4wMjMtLjAxMi0uMDIzLS4wMTMtLjAyMy0uMDE0LS4wMjMtLjAxNS0uMDI0LS4wMTctLjAyMy0uMDE4LS4wMjQtLjAxOS0uMDI0LS4wMi0uMDI1LS4wMjEtLjAyNC0uMDIzLS4wMjQtLjAyMy0uMDI1LS4wNTEtLjA1LS4wNTYtLjA1LS4wNi0uMDUtLjA2NS0uMDUxLS4wNy0uMDUyLS4wNzQtLjA1MS0uMDc5LS4wNTItLjA4My0uMDUyLS4wODgtLjA1Mi0uMDkyLS4wNTItLjA5Ni0uMDUyLS4xMDEtLjA1Mi0uMTA2LS4wNTItLjExLS4wNTItLjExNC0uMDUyLS4xMTgtLjA1MS0uMTIzLS4wNTEtLjEyNi0uMDUxLS4xMzEtLjA1LS4xMzYtLjA1LS4xMzktLjA0OS0uMTQzLS4wNDgtLjE0OC0uMDQ4LS4xNTItLjA0OC0uMTU1LS4wNDYtLjE2LS4wNDYtLjE2My0uMDQ1LS4xNjgtLjA0My0uMTcyLS4wNDMtLjE3NS0uMDQyLS4xNzktLjA0MS0uMTgzLS4wNC0uMTg3LS4wMzgtLjE5MS0uMDM4LS4xOTQtLjAzNi0uMTk4LS4wMzQtLjIwMi0uMDMzLS4yMDUtLjAzMi0uMjEtLjAzMS0uMjEyLS4wMjgtLjIxNi0uMDI3LS4yMi0uMDI2LS4yMjQtLjAyMy0uMjI2LS4wMjItLjIzMS0uMDIxLS4yMzMtLjAxOC0uMjM3LS4wMTYtLjI0MS0uMDE0LS4yNDQtLjAxMi0uMjQ3LS4wMTEtLjI1LS4wMDgtLjI1NC0uMDA1LS4yNTctLjAwNC0uMjYtLjAwMS0uMjYuMDAxeiIvPjwvc3ltYm9sPjwvZGVmcz48ZGVmcz48c3ltYm9sIGlkPSJjbG9jayIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCB0cmFuc2Zvcm09InNjYWxlKC41KSIgZD0iTTEyIDJjNS41MTQgMCAxMCA0LjQ4NiAxMCAxMHMtNC40ODYgMTAtMTAgMTAtMTAtNC40ODYtMTAtMTAgNC40ODYtMTAgMTAtMTB6bTAtMmMtNi42MjcgMC0xMiA1LjM3My0xMiAxMnM1LjM3MyAxMiAxMiAxMiAxMi01LjM3MyAxMi0xMi01LjM3My0xMi0xMi0xMnptNS44NDggMTIuNDU5Yy4yMDIuMDM4LjIwMi4zMzMuMDAxLjM3Mi0xLjkwNy4zNjEtNi4wNDUgMS4xMTEtNi41NDcgMS4xMTEtLjcxOSAwLTEuMzAxLS41ODItMS4zMDEtMS4zMDEgMC0uNTEyLjc3LTUuNDQ3IDEuMTI1LTcuNDQ1LjAzNC0uMTkyLjMxMi0uMTgxLjM0My4wMTRsLjk4NSA2LjIzOCA1LjM5NCAxLjAxMXoiLz48L3N5bWJvbD48L2RlZnM+PGRlZnM+PG1hcmtlciBpZD0iYXJyb3doZWFkIiByZWZYPSI3LjkiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSI+PHBhdGggZD0iTSAtMSAwIEwgMTAgNSBMIDAgMTAgeiIvPjwvbWFya2VyPjwvZGVmcz48ZGVmcz48bWFya2VyIGlkPSJjcm9zc2hlYWQiIG1hcmtlcldpZHRoPSIxNSIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iIHJlZlg9IjQiIHJlZlk9IjQuNSI+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjFwdCIgZD0iTSAxLDIgTCA2LDcgTSA2LDIgTCAxLDciIHN0eWxlPSJzdHJva2UtZGFzaGFycmF5OiAwLCAwOyIvPjwvbWFya2VyPjwvZGVmcz48ZGVmcz48bWFya2VyIGlkPSJmaWxsZWQtaGVhZCIgcmVmWD0iMTUuNSIgcmVmWT0iNyIgbWFya2VyV2lkdGg9IjIwIiBtYXJrZXJIZWlnaHQ9IjI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMTgsNyBMOSwxMyBMMTQsNyBMOSwxIFoiLz48L21hcmtlcj48L2RlZnM+PGRlZnM+PG1hcmtlciBpZD0ic2VxdWVuY2VudW1iZXIiIHJlZlg9IjE1IiByZWZZPSIxNSIgbWFya2VyV2lkdGg9IjYwIiBtYXJrZXJIZWlnaHQ9IjQwIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjE1IiBjeT0iMTUiIHI9IjYiLz48L21hcmtlcj48L2RlZnM+PGRlZnM+PG1hcmtlciBpZD0ic29saWRUb3BBcnJvd0hlYWQiIHJlZlg9IjcuOSIgcmVmWT0iNy4yNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0by1zdGFydC1yZXZlcnNlIj48cGF0aCBkPSJNIDAgMCBMIDEwIDggTCAwIDggeiIvPjwvbWFya2VyPjwvZGVmcz48ZGVmcz48bWFya2VyIGlkPSJzb2xpZEJvdHRvbUFycm93SGVhZCIgcmVmWD0iNy45IiByZWZZPSIwLjc1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvLXN0YXJ0LXJldmVyc2UiPjxwYXRoIGQ9Ik0gMCAwIEwgMTAgMCBMIDAgOCB6Ii8+PC9tYXJrZXI+PC9kZWZzPjxkZWZzPjxtYXJrZXIgaWQ9InN0aWNrVG9wQXJyb3dIZWFkIiByZWZYPSI3LjUiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSI+PHBhdGggZD0iTSAwIDAgTCA3IDciIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMS41IiBmaWxsPSJub25lIi8+PC9tYXJrZXI+PC9kZWZzPjxkZWZzPjxtYXJrZXIgaWQ9InN0aWNrQm90dG9tQXJyb3dIZWFkIiByZWZYPSI3LjUiIHJlZlk9IjAiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSI+PHBhdGggZD0iTSAwIDcgTCA3IDAiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMS41IiBmaWxsPSJub25lIi8+PC9tYXJrZXI+PC9kZWZzPjx0ZXh0IHg9IjE5NiIgeT0iODAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5QT1NUIGNsZWFudXAgZW5kcG9pbnQ8L3RleHQ+PGxpbmUgeDE9IjgyIiB5MT0iMTE5IiB4Mj0iMzE2IiB5Mj0iMTE5IiBjbGFzcz0ibWVzc2FnZUxpbmUwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0ibm9uZSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiBzdHlsZT0iZmlsbDogbm9uZTsiLz48bGluZSB4MT0iNzUiIHkxPSIxMTkiIHgyPSI3NSIgeTI9IjExOSIgc3Ryb2tlLXdpZHRoPSIwIiBtYXJrZXItc3RhcnQ9InVybCgjc2VxdWVuY2VudW1iZXIpIi8+PHRleHQgeD0iNzUiIHk9IjEyMyIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgY2xhc3M9InNlcXVlbmNlTnVtYmVyIj4xPC90ZXh0Pjx0ZXh0IHg9IjQ4MiIgeT0iMTM0IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgY2xhc3M9Im1lc3NhZ2VUZXh0IiBkeT0iMWVtIiBzdHlsZT0iZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyI+c3RhcnRDbGVhbnVwLi4uKGRyeVJ1biwgLi4uKTwvdGV4dD48bGluZSB4MT0iMzI3IiB5MT0iMTczIiB4Mj0iNjQyLjUiIHkyPSIxNzMiIGNsYXNzPSJtZXNzYWdlTGluZTAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJub25lIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIHN0eWxlPSJmaWxsOiBub25lOyIvPjxsaW5lIHgxPSIzMjAiIHkxPSIxNzMiIHgyPSIzMjAiIHkyPSIxNzMiIHN0cm9rZS13aWR0aD0iMCIgbWFya2VyLXN0YXJ0PSJ1cmwoI3NlcXVlbmNlbnVtYmVyKSIvPjx0ZXh0IHg9IjMyMCIgeT0iMTc3IiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ic2VxdWVuY2VOdW1iZXIiPjI8L3RleHQ+PHRleHQgeD0iODkwIiB5PSIxODgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5zdWJtaXQob3BlcmF0aW9uS2V5LCByZXRlbnRpb25Nb2RlLCBpbml0aWFsUGhhc2UsIHdvcmspPC90ZXh0PjxsaW5lIHgxPSI2NTMuNSIgeTE9IjIyNyIgeDI9IjExMzIuNSIgeTI9IjIyNyIgY2xhc3M9Im1lc3NhZ2VMaW5lMCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9Im5vbmUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgc3R5bGU9ImZpbGw6IG5vbmU7Ii8+PGxpbmUgeDE9IjY0Ni41IiB5MT0iMjI3IiB4Mj0iNjQ2LjUiIHkyPSIyMjciIHN0cm9rZS13aWR0aD0iMCIgbWFya2VyLXN0YXJ0PSJ1cmwoI3NlcXVlbmNlbnVtYmVyKSIvPjx0ZXh0IHg9IjY0Ni41IiB5PSIyMzEiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJzZXF1ZW5jZU51bWJlciI+MzwvdGV4dD48dGV4dCB4PSIxMzY5IiB5PSIyNDIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5pbnNlcnQgcXVldWVkIHJvdzwvdGV4dD48bGluZSB4MT0iMTE0My41IiB5MT0iMjgxIiB4Mj0iMTYwMC41IiB5Mj0iMjgxIiBjbGFzcz0ibWVzc2FnZUxpbmUwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0ibm9uZSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiBzdHlsZT0iZmlsbDogbm9uZTsiLz48bGluZSB4MT0iMTEzNi41IiB5MT0iMjgxIiB4Mj0iMTEzNi41IiB5Mj0iMjgxIiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSIxMTM2LjUiIHk9IjI4NSIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgY2xhc3M9InNlcXVlbmNlTnVtYmVyIj40PC90ZXh0Pjx0ZXh0IHg9IjczMCIgeT0iMjk2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgY2xhc3M9Im1lc3NhZ2VUZXh0IiBkeT0iMWVtIiBzdHlsZT0iZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyI+cXVldWVkIHNuYXBzaG90PC90ZXh0PjxsaW5lIHgxPSIxMTQxLjUiIHkxPSIzMzUiIHgyPSIzMjQiIHkyPSIzMzUiIGNsYXNzPSJtZXNzYWdlTGluZTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJub25lIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIHN0eWxlPSJzdHJva2UtZGFzaGFycmF5OiAzLCAzOyBmaWxsOiBub25lOyIvPjxsaW5lIHgxPSIxMTM2LjUiIHkxPSIzMzUiIHgyPSIxMTM2LjUiIHkyPSIzMzUiIHN0cm9rZS13aWR0aD0iMCIgbWFya2VyLXN0YXJ0PSJ1cmwoI3NlcXVlbmNlbnVtYmVyKSIvPjx0ZXh0IHg9IjExMzYuNSIgeT0iMzM5IiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ic2VxdWVuY2VOdW1iZXIiPjU8L3RleHQ+PHRleHQgeD0iMTk5IiB5PSIzNTAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5vcGVyYXRpb24gc25hcHNob3Q8L3RleHQ+PGxpbmUgeDE9IjMyNSIgeTE9IjM4OSIgeDI9Ijc5IiB5Mj0iMzg5IiBjbGFzcz0ibWVzc2FnZUxpbmUxIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0ibm9uZSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiBzdHlsZT0ic3Ryb2tlLWRhc2hhcnJheTogMywgMzsgZmlsbDogbm9uZTsiLz48bGluZSB4MT0iMzIwIiB5MT0iMzg5IiB4Mj0iMzIwIiB5Mj0iMzg5IiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSIzMjAiIHk9IjM5MyIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgY2xhc3M9InNlcXVlbmNlTnVtYmVyIj42PC90ZXh0Pjx0ZXh0IHg9IjE0ODkiIHk9IjQwNCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgYWxpZ25tZW50LWJhc2VsaW5lPSJtaWRkbGUiIGNsYXNzPSJtZXNzYWdlVGV4dCIgZHk9IjFlbSIgc3R5bGU9ImZvbnQtZmFtaWx5OiAmcXVvdDtPcGVuIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsiPmV4ZWN1dGUgYXN5bmMgd29yazwvdGV4dD48bGluZSB4MT0iMTE0My41IiB5MT0iNDQzIiB4Mj0iMTgzOS41IiB5Mj0iNDQzIiBjbGFzcz0ibWVzc2FnZUxpbmUwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0ibm9uZSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiBzdHlsZT0iZmlsbDogbm9uZTsiLz48bGluZSB4MT0iMTEzNi41IiB5MT0iNDQzIiB4Mj0iMTEzNi41IiB5Mj0iNDQzIiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSIxMTM2LjUiIHk9IjQ0NyIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgY2xhc3M9InNlcXVlbmNlTnVtYmVyIj43PC90ZXh0Pjx0ZXh0IHg9IjE0OTIiIHk9IjQ1OCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgYWxpZ25tZW50LWJhc2VsaW5lPSJtaWRkbGUiIGNsYXNzPSJtZXNzYWdlVGV4dCIgZHk9IjFlbSIgc3R5bGU9ImZvbnQtZmFtaWx5OiAmcXVvdDtPcGVuIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsiPm1hcmtSdW5uaW5nKCk8L3RleHQ+PGxpbmUgeDE9IjE4NDguNSIgeTE9IjQ5NyIgeDI9IjExNDAuNSIgeTI9IjQ5NyIgY2xhc3M9Im1lc3NhZ2VMaW5lMCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9Im5vbmUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgc3R5bGU9ImZpbGw6IG5vbmU7Ii8+PGxpbmUgeDE9IjE4NDMuNSIgeTE9IjQ5NyIgeDI9IjE4NDMuNSIgeTI9IjQ5NyIgc3Ryb2tlLXdpZHRoPSIwIiBtYXJrZXItc3RhcnQ9InVybCgjc2VxdWVuY2VudW1iZXIpIi8+PHRleHQgeD0iMTg0My41IiB5PSI1MDEiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJzZXF1ZW5jZU51bWJlciI+ODwvdGV4dD48dGV4dCB4PSIxMzY5IiB5PSI1MTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij51cGRhdGUgc3RhdHVzIFJVTk5JTkcsIHJldmlzaW9uKys8L3RleHQ+PGxpbmUgeDE9IjExNDMuNSIgeTE9IjU1MSIgeDI9IjE2MDAuNSIgeTI9IjU1MSIgY2xhc3M9Im1lc3NhZ2VMaW5lMCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9Im5vbmUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgc3R5bGU9ImZpbGw6IG5vbmU7Ii8+PGxpbmUgeDE9IjExMzYuNSIgeTE9IjU1MSIgeDI9IjExMzYuNSIgeTI9IjU1MSIgc3Ryb2tlLXdpZHRoPSIwIiBtYXJrZXItc3RhcnQ9InVybCgjc2VxdWVuY2VudW1iZXIpIi8+PHRleHQgeD0iMTEzNi41IiB5PSI1NTUiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJzZXF1ZW5jZU51bWJlciI+OTwvdGV4dD48dGV4dCB4PSIxODA1IiB5PSI1NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5wdWJsaXNoIGR1cmFibGUgc25hcHNob3QgZXZlbnQ8L3RleHQ+PGxpbmUgeDE9IjExNDMuNSIgeTE9IjYwNSIgeDI9IjI0NzMiIHkyPSI2MDUiIGNsYXNzPSJtZXNzYWdlTGluZTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJub25lIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIHN0eWxlPSJzdHJva2UtZGFzaGFycmF5OiAzLCAzOyBmaWxsOiBub25lOyIvPjxsaW5lIHgxPSIxMTM2LjUiIHkxPSI2MDUiIHgyPSIxMTM2LjUiIHkyPSI2MDUiIHN0cm9rZS13aWR0aD0iMCIgbWFya2VyLXN0YXJ0PSJ1cmwoI3NlcXVlbmNlbnVtYmVyKSIvPjx0ZXh0IHg9IjExMzYuNSIgeT0iNjA5IiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ic2VxdWVuY2VOdW1iZXIiPjEwPC90ZXh0Pjx0ZXh0IHg9IjEyNDciIHk9IjYyMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgYWxpZ25tZW50LWJhc2VsaW5lPSJtaWRkbGUiIGNsYXNzPSJtZXNzYWdlVGV4dCIgZHk9IjFlbSIgc3R5bGU9ImZvbnQtZmFtaWx5OiAmcXVvdDtPcGVuIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsiPmludm9rZSBjbGVhbnVwIHdvcmsgY2FsbGJhY2s8L3RleHQ+PGxpbmUgeDE9IjE4NDguNSIgeTE9IjY1OSIgeDI9IjY1MC41IiB5Mj0iNjU5IiBjbGFzcz0ibWVzc2FnZUxpbmUwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0ibm9uZSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiBzdHlsZT0iZmlsbDogbm9uZTsiLz48bGluZSB4MT0iMTg0My41IiB5MT0iNjU5IiB4Mj0iMTg0My41IiB5Mj0iNjU5IiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSIxODQzLjUiIHk9IjY2MyIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgY2xhc3M9InNlcXVlbmNlTnVtYmVyIj4xMTwvdGV4dD48dGV4dCB4PSIxMzgzIiB5PSI2NzQiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5jbGVhbnVwLi4uKCk8L3RleHQ+PGxpbmUgeDE9IjY1My41IiB5MT0iNzEzIiB4Mj0iMjExOCIgeTI9IjcxMyIgY2xhc3M9Im1lc3NhZ2VMaW5lMCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9Im5vbmUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgc3R5bGU9ImZpbGw6IG5vbmU7Ii8+PGxpbmUgeDE9IjY0Ni41IiB5MT0iNzEzIiB4Mj0iNjQ2LjUiIHkyPSI3MTMiIHN0cm9rZS13aWR0aD0iMCIgbWFya2VyLXN0YXJ0PSJ1cmwoI3NlcXVlbmNlbnVtYmVyKSIvPjx0ZXh0IHg9IjY0Ni41IiB5PSI3MTciIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJzZXF1ZW5jZU51bWJlciI+MTI8L3RleHQ+PHRleHQgeD0iMTM4NiIgeT0iNzI4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgY2xhc3M9Im1lc3NhZ2VUZXh0IiBkeT0iMWVtIiBzdHlsZT0iZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyI+YWZmZWN0ZWQgaWRzIG9yIHJlc3VsdHM8L3RleHQ+PGxpbmUgeDE9IjIxMjciIHkxPSI3NjciIHgyPSI2NTAuNSIgeTI9Ijc2NyIgY2xhc3M9Im1lc3NhZ2VMaW5lMSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9Im5vbmUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgc3R5bGU9InN0cm9rZS1kYXNoYXJyYXk6IDMsIDM7IGZpbGw6IG5vbmU7Ii8+PGxpbmUgeDE9IjIxMjIiIHkxPSI3NjciIHgyPSIyMTIyIiB5Mj0iNzY3IiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSIyMTIyIiB5PSI3NzEiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJzZXF1ZW5jZU51bWJlciI+MTM8L3RleHQ+PHRleHQgeD0iODkwIiB5PSI3ODIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5kdXJhYmxlIHByb2dyZXNzIGNhbGxiYWNrPC90ZXh0PjxsaW5lIHgxPSI2NTMuNSIgeTE9IjgyMSIgeDI9IjExMzIuNSIgeTI9IjgyMSIgY2xhc3M9Im1lc3NhZ2VMaW5lMCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9Im5vbmUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgc3R5bGU9ImZpbGw6IG5vbmU7Ii8+PGxpbmUgeDE9IjY0Ni41IiB5MT0iODIxIiB4Mj0iNjQ2LjUiIHkyPSI4MjEiIHN0cm9rZS13aWR0aD0iMCIgbWFya2VyLXN0YXJ0PSJ1cmwoI3NlcXVlbmNlbnVtYmVyKSIvPjx0ZXh0IHg9IjY0Ni41IiB5PSI4MjUiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJzZXF1ZW5jZU51bWJlciI+MTQ8L3RleHQ+PHRleHQgeD0iMTM2OSIgeT0iODM2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgY2xhc3M9Im1lc3NhZ2VUZXh0IiBkeT0iMWVtIiBzdHlsZT0iZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyI+dXBkYXRlIHBoYXNlIG9yIGNvdW50cywgcmV2aXNpb24rKzwvdGV4dD48bGluZSB4MT0iMTE0My41IiB5MT0iODc1IiB4Mj0iMTYwMC41IiB5Mj0iODc1IiBjbGFzcz0ibWVzc2FnZUxpbmUwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0ibm9uZSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiBzdHlsZT0iZmlsbDogbm9uZTsiLz48bGluZSB4MT0iMTEzNi41IiB5MT0iODc1IiB4Mj0iMTEzNi41IiB5Mj0iODc1IiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSIxMTM2LjUiIHk9Ijg3OSIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgY2xhc3M9InNlcXVlbmNlTnVtYmVyIj4xNTwvdGV4dD48dGV4dCB4PSIxODA1IiB5PSI4OTAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5wdWJsaXNoIGR1cmFibGUgcHJvZ3Jlc3MgZXZlbnQ8L3RleHQ+PGxpbmUgeDE9IjExNDMuNSIgeTE9IjkyOSIgeDI9IjI0NzMiIHkyPSI5MjkiIGNsYXNzPSJtZXNzYWdlTGluZTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJub25lIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIHN0eWxlPSJzdHJva2UtZGFzaGFycmF5OiAzLCAzOyBmaWxsOiBub25lOyIvPjxsaW5lIHgxPSIxMTM2LjUiIHkxPSI5MjkiIHgyPSIxMTM2LjUiIHkyPSI5MjkiIHN0cm9rZS13aWR0aD0iMCIgbWFya2VyLXN0YXJ0PSJ1cmwoI3NlcXVlbmNlbnVtYmVyKSIvPjx0ZXh0IHg9IjExMzYuNSIgeT0iOTMzIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ic2VxdWVuY2VOdW1iZXIiPjE2PC90ZXh0Pjx0ZXh0IHg9Ijg5MCIgeT0iOTQ0IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgY2xhc3M9Im1lc3NhZ2VUZXh0IiBkeT0iMWVtIiBzdHlsZT0iZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyI+Y29tcGxldGUoLi4uKTwvdGV4dD48bGluZSB4MT0iNjUzLjUiIHkxPSI5ODMiIHgyPSIxMTMyLjUiIHkyPSI5ODMiIGNsYXNzPSJtZXNzYWdlTGluZTAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJub25lIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIHN0eWxlPSJmaWxsOiBub25lOyIvPjxsaW5lIHgxPSI2NDYuNSIgeTE9Ijk4MyIgeDI9IjY0Ni41IiB5Mj0iOTgzIiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSI2NDYuNSIgeT0iOTg3IiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ic2VxdWVuY2VOdW1iZXIiPjE3PC90ZXh0Pjx0ZXh0IHg9IjEzNjkiIHk9Ijk5OCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgYWxpZ25tZW50LWJhc2VsaW5lPSJtaWRkbGUiIGNsYXNzPSJtZXNzYWdlVGV4dCIgZHk9IjFlbSIgc3R5bGU9ImZvbnQtZmFtaWx5OiAmcXVvdDtPcGVuIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTZweDsgZm9udC13ZWlnaHQ6IDQwMDsiPnVwZGF0ZSBzdGF0dXMgQ09NUExFVEVELCByZXRlbnRpb25VbnRpbCwgcmV2aXNpb24rKzwvdGV4dD48bGluZSB4MT0iMTE0My41IiB5MT0iMTAzNyIgeDI9IjE2MDAuNSIgeTI9IjEwMzciIGNsYXNzPSJtZXNzYWdlTGluZTAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJub25lIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIHN0eWxlPSJmaWxsOiBub25lOyIvPjxsaW5lIHgxPSIxMTM2LjUiIHkxPSIxMDM3IiB4Mj0iMTEzNi41IiB5Mj0iMTAzNyIgc3Ryb2tlLXdpZHRoPSIwIiBtYXJrZXItc3RhcnQ9InVybCgjc2VxdWVuY2VudW1iZXIpIi8+PHRleHQgeD0iMTEzNi41IiB5PSIxMDQxIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ic2VxdWVuY2VOdW1iZXIiPjE4PC90ZXh0Pjx0ZXh0IHg9IjE4MDUiIHk9IjEwNTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBjbGFzcz0ibWVzc2FnZVRleHQiIGR5PSIxZW0iIHN0eWxlPSJmb250LWZhbWlseTogJnF1b3Q7T3BlbiBTYW5zJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7IGZvbnQtd2VpZ2h0OiA0MDA7Ij5wdWJsaXNoIGNvbXBsZXRlZCBldmVudDwvdGV4dD48bGluZSB4MT0iMTE0My41IiB5MT0iMTA5MSIgeDI9IjI0NzMiIHkyPSIxMDkxIiBjbGFzcz0ibWVzc2FnZUxpbmUxIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0ibm9uZSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiBzdHlsZT0ic3Ryb2tlLWRhc2hhcnJheTogMywgMzsgZmlsbDogbm9uZTsiLz48bGluZSB4MT0iMTEzNi41IiB5MT0iMTA5MSIgeDI9IjExMzYuNSIgeTI9IjEwOTEiIHN0cm9rZS13aWR0aD0iMCIgbWFya2VyLXN0YXJ0PSJ1cmwoI3NlcXVlbmNlbnVtYmVyKSIvPjx0ZXh0IHg9IjExMzYuNSIgeT0iMTA5NSIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgY2xhc3M9InNlcXVlbmNlTnVtYmVyIj4xOTwvdGV4dD48dGV4dCB4PSIxMjc1IiB5PSIxMTA2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgY2xhc3M9Im1lc3NhZ2VUZXh0IiBkeT0iMWVtIiBzdHlsZT0iZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyI+R0VUIC9hcHAtc3RyZWFtaW5nL2FzeW5jLW9wZXJhdGlvbnMve2lkfS9ldmVudHM8L3RleHQ+PGxpbmUgeDE9IjgyIiB5MT0iMTE0NSIgeDI9IjI0NzMiIHkyPSIxMTQ1IiBjbGFzcz0ibWVzc2FnZUxpbmUwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0ibm9uZSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiBzdHlsZT0iZmlsbDogbm9uZTsiLz48bGluZSB4MT0iNzUiIHkxPSIxMTQ1IiB4Mj0iNzUiIHkyPSIxMTQ1IiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSI3NSIgeT0iMTE0OSIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgY2xhc3M9InNlcXVlbmNlTnVtYmVyIj4yMDwvdGV4dD48dGV4dCB4PSIxMjc4IiB5PSIxMTYwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgY2xhc3M9Im1lc3NhZ2VUZXh0IiBkeT0iMWVtIiBzdHlsZT0iZm9udC1mYW1pbHk6ICZxdW90O09wZW4gU2FucyZxdW90Oywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNnB4OyBmb250LXdlaWdodDogNDAwOyI+c25hcHNob3QsIHByb2dyZXNzLCBjb21wbGV0ZWQgU1NFIGV2ZW50czwvdGV4dD48bGluZSB4MT0iMjQ4MiIgeTE9IjExOTkiIHgyPSI3OSIgeTI9IjExOTkiIGNsYXNzPSJtZXNzYWdlTGluZTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJub25lIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIHN0eWxlPSJzdHJva2UtZGFzaGFycmF5OiAzLCAzOyBmaWxsOiBub25lOyIvPjxsaW5lIHgxPSIyNDc3IiB5MT0iMTE5OSIgeDI9IjI0NzciIHkyPSIxMTk5IiBzdHJva2Utd2lkdGg9IjAiIG1hcmtlci1zdGFydD0idXJsKCNzZXF1ZW5jZW51bWJlcikiLz48dGV4dCB4PSIyNDc3IiB5PSIxMjAzIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ic2VxdWVuY2VOdW1iZXIiPjIxPC90ZXh0Pjwvc3ZnPg==" alt="diagram" class="mermaid-diagram" style="width: 1091px" /></p>
<div id="figure-11-2" class="figure-caption figure-kind-diagram">Figure 11.2: Current process-support cleanup integration using the durable async-operation lane</div>
</div>
<p><a class="object-xref" href="#figure-11-2">Figure 11.2</a> reflects
the current state at HEAD:</p>
<ul>
<li>the four process-support adopters already exercise the durable
async-operation lane</li>
<li>the UI can subscribe through the generic app-streaming endpoint</li>
<li>no current adopter in this sequence emits
<code>publishVolatileProgress(...)</code> yet</li>
</ul>
</section>
<section id="recommended-adoption-pattern-for-import-and-export-jobs" class="level2" data-number="11.15">
<h2 data-number="11.15"><span class="header-section-number">11.15</span>
Recommended Adoption Pattern For Import And Export Jobs</h2>
<p>The following pattern is the intended next adopter shape, but it is
not yet implemented by a concrete export or import job at HEAD.</p>
<div class="sourceCode" id="cb64"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><span id="cb64-1"><a href="#cb64-1" aria-hidden="true" tabindex="-1"></a><span class="kw">val</span> <span class="va">queued</span> <span class="op">=</span> asyncOperationService<span class="op">.</span>submit<span class="op">(</span></span>
<span id="cb64-2"><a href="#cb64-2" aria-hidden="true" tabindex="-1"></a> operationKey <span class="op">=</span> <span class="st">&quot;exports.customer-data&quot;</span><span class="op">,</span></span>
<span id="cb64-3"><a href="#cb64-3" aria-hidden="true" tabindex="-1"></a> submittedBy <span class="op">=</span> submittedBy<span class="op">,</span></span>
<span id="cb64-4"><a href="#cb64-4" aria-hidden="true" tabindex="-1"></a> attributes <span class="op">=</span> mapOf<span class="op">(</span><span class="st">&quot;format&quot;</span> to <span class="st">&quot;zip&quot;</span><span class="op">),</span></span>
<span id="cb64-5"><a href="#cb64-5" aria-hidden="true" tabindex="-1"></a> retentionMode <span class="op">=</span> AsyncOperationRetentionMode<span class="op">.</span>SHORT_LIVED<span class="op">,</span></span>
<span id="cb64-6"><a href="#cb64-6" aria-hidden="true" tabindex="-1"></a> initialPhase <span class="op">=</span> <span class="st">&quot;Queued customer export&quot;</span></span>
<span id="cb64-7"><a href="#cb64-7" aria-hidden="true" tabindex="-1"></a><span class="op">)</span> <span class="op">{</span> _ <span class="op">-&gt;</span></span>
<span id="cb64-8"><a href="#cb64-8" aria-hidden="true" tabindex="-1"></a> AsyncOperationResult<span class="op">(</span></span>
<span id="cb64-9"><a href="#cb64-9" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;placeholder&quot;</span><span class="op">,</span></span>
<span id="cb64-10"><a href="#cb64-10" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> <span class="dv">0</span><span class="op">,</span></span>
<span id="cb64-11"><a href="#cb64-11" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb64-12"><a href="#cb64-12" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb64-13"><a href="#cb64-13" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb64-14"><a href="#cb64-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-15"><a href="#cb64-15" aria-hidden="true" tabindex="-1"></a>asyncOperationPublisher<span class="op">.</span>publishProgress<span class="op">(</span></span>
<span id="cb64-16"><a href="#cb64-16" aria-hidden="true" tabindex="-1"></a> queued<span class="op">.</span>operationId<span class="op">,</span></span>
<span id="cb64-17"><a href="#cb64-17" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Collecting customers&quot;</span><span class="op">,</span></span>
<span id="cb64-18"><a href="#cb64-18" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Loading export candidates&quot;</span></span>
<span id="cb64-19"><a href="#cb64-19" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span>
<span id="cb64-20"><a href="#cb64-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-21"><a href="#cb64-21" aria-hidden="true" tabindex="-1"></a>repeat<span class="op">(</span><span class="dv">100</span><span class="op">)</span> <span class="op">{</span> batch <span class="op">-&gt;</span></span>
<span id="cb64-22"><a href="#cb64-22" aria-hidden="true" tabindex="-1"></a> asyncOperationPublisher<span class="op">.</span>publishVolatileProgress<span class="op">(</span></span>
<span id="cb64-23"><a href="#cb64-23" aria-hidden="true" tabindex="-1"></a> queued<span class="op">.</span>operationId<span class="op">,</span></span>
<span id="cb64-24"><a href="#cb64-24" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> batch <span class="op">*</span> <span class="dv">100</span><span class="op">,</span></span>
<span id="cb64-25"><a href="#cb64-25" aria-hidden="true" tabindex="-1"></a> runtimeContext <span class="op">=</span> mapOf<span class="op">(</span><span class="st">&quot;export&quot;</span> to mapOf<span class="op">(</span><span class="st">&quot;currentBatch&quot;</span> to batch<span class="op">))</span></span>
<span id="cb64-26"><a href="#cb64-26" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb64-27"><a href="#cb64-27" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb64-28"><a href="#cb64-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-29"><a href="#cb64-29" aria-hidden="true" tabindex="-1"></a>asyncOperationPublisher<span class="op">.</span>publishProgress<span class="op">(</span></span>
<span id="cb64-30"><a href="#cb64-30" aria-hidden="true" tabindex="-1"></a> queued<span class="op">.</span>operationId<span class="op">,</span></span>
<span id="cb64-31"><a href="#cb64-31" aria-hidden="true" tabindex="-1"></a> phase <span class="op">=</span> <span class="st">&quot;Generating archive&quot;</span><span class="op">,</span></span>
<span id="cb64-32"><a href="#cb64-32" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Assembling final ZIP&quot;</span></span>
<span id="cb64-33"><a href="#cb64-33" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span>
<span id="cb64-34"><a href="#cb64-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-35"><a href="#cb64-35" aria-hidden="true" tabindex="-1"></a>asyncOperationPublisher<span class="op">.</span>complete<span class="op">(</span></span>
<span id="cb64-36"><a href="#cb64-36" aria-hidden="true" tabindex="-1"></a> queued<span class="op">.</span>operationId<span class="op">,</span></span>
<span id="cb64-37"><a href="#cb64-37" aria-hidden="true" tabindex="-1"></a> summary <span class="op">=</span> <span class="st">&quot;Customer export completed&quot;</span><span class="op">,</span></span>
<span id="cb64-38"><a href="#cb64-38" aria-hidden="true" tabindex="-1"></a> processedCount <span class="op">=</span> <span class="dv">10_000</span><span class="op">,</span></span>
<span id="cb64-39"><a href="#cb64-39" aria-hidden="true" tabindex="-1"></a> successCount <span class="op">=</span> <span class="dv">10_000</span><span class="op">,</span></span>
<span id="cb64-40"><a href="#cb64-40" aria-hidden="true" tabindex="-1"></a> payload <span class="op">=</span> mapOf<span class="op">(</span><span class="st">&quot;fileId&quot;</span> to <span class="st">&quot;...&quot;</span><span class="op">)</span></span>
<span id="cb64-41"><a href="#cb64-41" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span></code></pre></div>
<p>That example matches the intended separation precisely:</p>
<ul>
<li>durable state transitions through
<code>publishProgress(...)</code></li>
<li>high-frequency UI-only movement through
<code>publishVolatileProgress(...)</code></li>
<li>one generic subscription contract for both exact and topic
subscribers</li>
</ul>
<p>Again, the important current-status note is:</p>
<ul>
<li>the pattern is supported by the framework now</li>
<li>the process-support cleanup adopters already use the durable
half</li>
<li>a concrete export/import adopter using
<code>publishVolatileProgress(...)</code> has not yet been added at
HEAD</li>
</ul>
</section>
<section id="exact-streams-versus-topic-streams" class="level2" data-number="11.16">
<h2 data-number="11.16"><span class="header-section-number">11.16</span>
Exact Streams Versus Topic Streams</h2>
<p>There is one event model and one broadcaster, but two subscription
selectors:</p>
<ul>
<li>exact operation stream</li>
<li>topic-prefix stream</li>
</ul>
<p>The exact-operation stream is the stronger contract:</p>
<ul>
<li>durable latest snapshot</li>
<li>durable <code>revision</code></li>
<li>reconnect-safe replay of the latest authoritative state</li>
</ul>
<p>The topic stream is a live discovery channel:</p>
<ul>
<li>good for dashboards and domain-level live awareness</li>
<li>good for &quot;something changed in exports&quot; views</li>
<li>not a durable replay log for every missed topic event during
disconnect gaps</li>
</ul>
<p>That difference is why the recommended UI strategy is:</p>
<ol type="1">
<li>use topic streams to discover or monitor activity where useful</li>
<li>switch to exact operation streams once the UI cares about one
concrete operation</li>
</ol>
</section>
<section id="operational-posture" class="level2" data-number="11.17">
<h2 data-number="11.17"><span class="header-section-number">11.17</span>
Operational Posture</h2>
<p>This runtime is intentionally much smaller than a second job
framework:</p>
<ul>
<li>no persisted event log</li>
<li>no workflow engine inside the async-operation layer</li>
<li>no multi-step batch DSL</li>
<li>no broker dependency</li>
<li>no cross-node live fan-out yet</li>
</ul>
<p>The implementation remains lightweight because it solves a smaller
problem:</p>
<ul>
<li>durable async operation state</li>
<li>live SSE delivery</li>
<li>volatile progress overlays</li>
<li>reconnect safety for exact operation tracking</li>
<li>retention and stale-operation cleanup</li>
</ul>
<p>That is why Sigma can reuse it under BPMN jobs, admin actions,
exports, and imports without rebuilding the semantics that Camunda or
Spring Batch already own elsewhere.</p>
<div class="chapter-break"></div>
</section>
</section>
</main>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment