Created
April 24, 2026 09:17
-
-
Save moreaki/33d4072d65c5933b495941c707e723d8 to your computer and use it in GitHub Desktop.
Sigma Backend: embedded HTML for Async Operations And SSE Runtime chapter (temporary)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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'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><<span class="va">String</span><span class="op">,</span> <span class="va">String</span>><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><<span class="va">String</span>><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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?><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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?></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">&&</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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?><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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?></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">"snapshot"</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">"progress"</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">"volatile-progress"</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">"completed"</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">"failed"</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">"heartbeat"</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">-></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">-></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">-></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">-></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 "what is going on right now"</li> | |
| <li><code>payload</code> means "what came out of this operation"</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: "Customer export"</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 "what is happening?"</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>"8,400 processed"</code></li> | |
| <li>success/error split: <code>"8,350 succeeded, 50 failed"</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>"Started after 2.1s in queue"</code></li> | |
| <li>execution time: <code>"Running for 43s"</code></li> | |
| <li>total duration after completion: | |
| <code>"Completed in 45s total"</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">"export"</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">"currentBatch"</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">"totalBatches"</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">"currentStep"</span><span class="fu">:</span> <span class="st">"collect-signatures"</span><span class="fu">,</span></span> | |
| <span id="cb52-6"><a href="#cb52-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">"currentLegalEntity"</span><span class="fu">:</span> <span class="st">"LE-2048"</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>"currently processing legal entity LE-2048"</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">"fileId"</span><span class="fu">:</span> <span class="st">"7c2d..."</span><span class="fu">,</span></span> | |
| <span id="cb53-3"><a href="#cb53-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">"fileName"</span><span class="fu">:</span> <span class="st">"customer-export-2026-04-11.zip"</span><span class="fu">,</span></span> | |
| <span id="cb53-4"><a href="#cb53-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">"downloadSizeBytes"</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 "what came out of the operation" rather | |
| than "what is happening right now."</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 = "Collecting customers"</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><<span class="va">String</span>>? <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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?><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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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><<span class="va">String</span>> <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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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">"Failed"</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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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">"Async operation </span><span class="ss">$operationId</span><span class="st"> not found"</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">></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">-></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">&&</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">>=</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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?><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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?></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">"Async operation </span><span class="ss">$operationId</span><span class="st"> not found"</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">"Ignoring volatile progress for terminal async operation. operationId={}"</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><<span class="va">String</span><span class="op">,</span> <span class="va">String</span>><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">-></span> <span class="dt">Unit</span><span class="op">)</span> -> <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">"Submitting async operation unexpectedly returned null"</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">"Async operation deduplicated. operationKey={}, requestHash={}, operationId={}"</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">"Async operation rejected. operationKey={}, operationId={}"</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">"Rejected"</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">"Operation could not be started"</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">"ASYNC_OPERATION_REJECTED"</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">"Operation executor rejected the task"</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">-></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">-></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">-></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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?> <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">-></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">"Async operation </span><span class="ss">$operationId</span><span class="st"> not found"</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">-></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">"Mutating async operation unexpectedly returned null"</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><<span class="va">String</span><span class="op">,</span> <span class="va">String</span>><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"><</span><span class="dt">String</span>, <span class="dt">String</span><span class="op">></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"><</span><span class="dt">Map</span><span class="op"><</span><span class="dt">String</span>, <span class="dt">String</span><span class="op">>>()</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><<span class="va">String</span>><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"><</span><span class="dt">String</span><span class="op">></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"><</span><span class="dt">List</span><span class="op"><</span><span class="dt">String</span><span class="op">>>()</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><<span class="va">String</span><span class="op">,</span> <span class="va">Any</span>?><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"><</span><span class="dt">String</span>, <span class="dt">Any</span><span class="op">?></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"><</span><span class="dt">Map</span><span class="op"><</span><span class="dt">String</span>, <span class="dt">Any</span><span class="op">?>>()</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><<span class="va">String</span><span class="op">,</span> <span class="va">String</span>></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">"operationKey"</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">"attributes"</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">"SHA-256"</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">""</span><span class="op">)</span> <span class="op">{</span> <span class="st">"%02x"</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">-></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">-></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">-></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">-></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"><</span><span class="dt">String</span><span class="op">></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">'.'</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">-></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">"."</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">"async-operations"</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">"general"</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">"operations"</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">"."</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">"."</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"><</span><span class="dt">String</span><span class="op">></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">'.'</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">"[^A-Za-z0-9]+"</span><span class="op">),</span> <span class="st">"-"</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">'-'</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.<id></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">"sigma-app"</span><span class="op">,</span> description <span class="op">=</span> <span class="st">"Sigma System/App APIs"</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">"</span><span class="sc">\$</span><span class="st">{app.openapi.controller-prefixes.app}/async-operations"</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">"hasRole('ADMIN')"</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">"/{operationId}"</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">"Async operation </span><span class="ss">$operationId</span><span class="st"> not found"</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">"</span><span class="sc">\$</span><span class="st">{app.openapi.controller-prefixes.app-streaming}/async-operations"</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">"hasRole('ADMIN')"</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">"/{operationId}/events"</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">"Last-Event-ID"</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"><</span><span class="dt">SseEmitter</span><span class="op">></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">"Async operation </span><span class="ss">$operationId</span><span class="st"> not found"</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"><</span>AsyncOperationSnapshot<span class="op">?>(</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">-></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">&&</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">-></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">"hasRole('ADMIN')"</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">"/events"</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"><</span><span class="dt">SseEmitter</span><span class="op">></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"><</span>AsyncOperationSnapshot<span class="op">?>(</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">-></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">-></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">"topicPrefix"</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">"topic"</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">"serverTime"</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">"operationId"</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">"revision"</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"><</span><span class="dt">SseEmitter</span><span class="op">></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">"X-Accel-Buffering"</span><span class="op">,</span> <span class="st">"no"</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">"keep-alive"</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">"</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">"</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">"Async operation housekeeping skipped; disabled by configuration"</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">"Timed out"</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">"Operation expired before starting"</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">"Housekeeping marked the queued async operation as stale before execution started."</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">"Timed out"</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">"Operation timed out while running"</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">"Housekeeping marked the running async operation as stale because it stopped reporting progress."</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">></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"><=</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">"Async operation housekeeping skipped stale {} scan; batchSize={}"</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">-></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">"housekeeping"</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">"reason"</span> to <span class="st">"stale-operation"</span><span class="op">,</span></span> | |
| <span id="cb60-93"><a href="#cb60-93" aria-hidden="true" tabindex="-1"></a> <span class="st">"previousStatus"</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">"cutoff"</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">"markedAt"</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">"housekeeping"</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">"reason"</span> to <span class="st">"stale-operation"</span><span class="op">,</span></span> | |
| <span id="cb60-101"><a href="#cb60-101" aria-hidden="true" tabindex="-1"></a> <span class="st">"previousStatus"</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"><</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 = "ASYNC_OPERATION_STALE"</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">"async-operations"</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">"0 0/15 * * * ?"</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">"0 0/15 * * * ?"</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 "flush now" 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">"dryRun"</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">"includeMissingCases"</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">"Queued cleanup for orphaned process instances"</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">-></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">"Scanning orphaned process instances"</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">"Querying active runtime instances"</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">"Dry run complete"</span> <span class="cf">else</span> <span class="st">"Cleanup complete"</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">"Matched </span><span class="ss">${</span>processInstanceIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> orphaned process instances (dry run)"</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">"Cleaned </span><span class="ss">${</span>processInstanceIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> orphaned process instances"</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">"referenceIds"</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">"dryRun"</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">"Queued cleanup for finished-case runtime mismatches"</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">-></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">"Scanning runtime instances linked to finished cases"</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">"Querying runtime state and linked cases"</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">"Dry run complete"</span> <span class="cf">else</span> <span class="st">"Cleanup complete"</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">"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)"</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">"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"</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">"referenceIds"</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">"dryRun"</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">"close"</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">"Queued cleanup for cases missing process key"</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">-></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">"Scanning running cases missing process key"</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">"Querying running cases with NULL process key"</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">"Dry run complete"</span> <span class="cf">else</span> <span class="st">"Cleanup complete"</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">"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)"</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">"Updated </span><span class="ss">${</span>caseIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> cases missing process key"</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">"referenceIds"</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">"dryRun"</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">"close"</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">"Queued cleanup for running cases missing active process"</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">-></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">"Scanning running cases missing active process"</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">"Checking runtime and business-key liveness"</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">"Dry run complete"</span> <span class="cf">else</span> <span class="st">"Cleanup complete"</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">"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)"</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">"Updated </span><span class="ss">${</span>caseIds<span class="op">.</span>size<span class="ss">}</span><span class="st"> running cases missing active process"</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">"referenceIds"</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">"process-support.cleanup-orphan-process-instances"</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">"process-support.cleanup-process-instances-for-finished-cases"</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">"process-support.cleanup-cases-missing-process-key"</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">"process-support.cleanup-running-cases-missing-active-process"</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">"exports.customer-data"</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">"format"</span> to <span class="st">"zip"</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">"Queued customer export"</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">-></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">"placeholder"</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">"Collecting customers"</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">"Loading export candidates"</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">-></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">"export"</span> to mapOf<span class="op">(</span><span class="st">"currentBatch"</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">"Generating archive"</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">"Assembling final ZIP"</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">"Customer export completed"</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">"fileId"</span> to <span class="st">"..."</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 "something changed in exports" 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