{{.Title}}
{{.Description}}
- +{{tr $.Lang .Title}}
{{tr $.Lang .Description}}
+From 3a3f4d80148eba282b0ed8d43c77690d060b5f22 Mon Sep 17 00:00:00 2001
From: Guozhu Huang <1948788642@qq.com>
Date: Sat, 26 Sep 2026 18:11:58 +0800
Subject: [PATCH 01/13] feat(i18n): add Chinese demo guides and browser
language selection
---
demo/assets.go | 2 +-
demo/grok-codebase-exfil/README.md | 24 ++-
demo/grok-codebase-exfil/README.zh-CN.md | 105 +++++++++++++
demo/jev-judge/README.md | 2 +
demo/jev-judge/README.zh-CN.md | 172 +++++++++++++++++++++
demo/k8s-cross-pod-a2a/README.md | 7 +-
demo/k8s-cross-pod-a2a/README.zh-CN.md | 133 ++++++++++++++++
demo/k8s-substrate/README.md | 9 +-
demo/k8s-substrate/README.zh-CN.md | 93 +++++++++++
demo/llm-judge/README.md | 2 +
demo/llm-judge/README.zh-CN.md | 91 +++++++++++
demo/multiagent-provenance/README.md | 9 +-
demo/multiagent-provenance/README.zh-CN.md | 107 +++++++++++++
demo/snake-supply-chain/README.md | 2 +
demo/snake-supply-chain/README.zh-CN.md | 73 +++++++++
docs/release-start.md | 46 ++++++
docs/zh-CN/localization.md | 52 +++++++
docs/zh-CN/release-start.md | 52 +++++++
go.mod | 2 +-
internal/cli/demo_i18n_test.go | 162 +++++++++++++++++++
internal/cli/demo_ui.go | 88 +++++++++--
internal/cli/demo_ui/demo.css | 2 +
internal/cli/demo_ui/gallery.html | 31 ++--
internal/cli/demo_ui/guide.html | 24 +--
internal/cli/demo_ui/guide.js | 15 +-
internal/dashboard/dashboard.go | 2 +
internal/dashboard/theme.css | 5 +
internal/i18n/locale.go | 112 ++++++++++++++
internal/i18n/locale_test.go | 103 ++++++++++++
internal/i18n/web.go | 37 +++++
internal/i18n/zh-CN.json | 73 +++++++++
scripts/accept_release.py | 27 +++-
scripts/build-release.py | 53 ++-----
33 files changed, 1604 insertions(+), 113 deletions(-)
create mode 100644 demo/grok-codebase-exfil/README.zh-CN.md
create mode 100644 demo/jev-judge/README.zh-CN.md
create mode 100644 demo/k8s-cross-pod-a2a/README.zh-CN.md
create mode 100644 demo/k8s-substrate/README.zh-CN.md
create mode 100644 demo/llm-judge/README.zh-CN.md
create mode 100644 demo/multiagent-provenance/README.zh-CN.md
create mode 100644 demo/snake-supply-chain/README.zh-CN.md
create mode 100644 docs/release-start.md
create mode 100644 docs/zh-CN/localization.md
create mode 100644 docs/zh-CN/release-start.md
create mode 100644 internal/cli/demo_i18n_test.go
create mode 100644 internal/i18n/locale.go
create mode 100644 internal/i18n/locale_test.go
create mode 100644 internal/i18n/web.go
create mode 100644 internal/i18n/zh-CN.json
diff --git a/demo/assets.go b/demo/assets.go
index 63f5385..b196cf2 100644
--- a/demo/assets.go
+++ b/demo/assets.go
@@ -6,7 +6,7 @@ import "embed"
// Files preserves the original bundles, attestations and public keys unchanged.
// Evaluator source and capture scripts ship in the release archive, not the CLI.
//
-//go:embed */*.json.gz */*.dsse.json */*.pub */README.md */*.png jev-judge/validation-*.json
+//go:embed */*.json.gz */*.dsse.json */*.pub */README.md */README.zh-CN.md */*.png jev-judge/validation-*.json
var Files embed.FS
type Entry struct {
diff --git a/demo/grok-codebase-exfil/README.md b/demo/grok-codebase-exfil/README.md
index a461529..67ef67c 100644
--- a/demo/grok-codebase-exfil/README.md
+++ b/demo/grok-codebase-exfil/README.md
@@ -1,14 +1,18 @@
# Grok exfil demos — two signed captures of a real AI coding CLI
+English | [简体中文](README.zh-CN.md)
+
This folder holds **two real, signed, replayable AgentProvenance captures** of
`@xai-official/grok` 0.2.93 (the exact version the public repro
[`cereblab/grok-build-exfil-repro`](https://github.com/cereblab/grok-build-exfil-repro)
pins). Each is a content-addressed graph you can import and verify offline.
+All reproduction statements below refer to the July 2026 experiments, not current
+vendor behavior.
| Bundle | What it caught | Reproducible today? |
|---|---|---|
-| **`run-grok-exfil`** | Route ②: the CLI bundles the **whole repo + git history** and POSTs it to the vendor session-trace store (`grok-code-session-traces`) — every upload **BLOCKED**. | **No — not reproducible on 07-15** (appears disabled server-side; see Status). Historical capture. |
-| **`run-grok-3routes`** | Route ①: grok reads `.env` / `SECRET_DO_NOT_READ.md` / `.claude/` and the **secrets land in the model request** (`/responses`) — 3 canaries proven in-context. Plus telemetry (`/traces`) + product analytics (Mixpanel). | **Yes** — reproducible. |
+| **`run-grok-exfil`** | Route ②: historical investigation of repository uploads. The original log reported blocked uploads, but is no longer available; the committed graph preserves only `/traces` egress. | **No — not reproducible on 07-15** (appears disabled server-side; see Status). Historical capture. |
+| **`run-grok-3routes`** | Route ①: grok reads `.env` / `SECRET_DO_NOT_READ.md` / `.claude/` and the **secrets land in the model request** (`/responses`) — 3 canaries proven in-context. Plus telemetry (`/traces`) + product analytics (Mixpanel). | **Yes in the recorded July 2026 experiment**; current behavior is unverified. |
## Open from the portable CLI
@@ -26,7 +30,7 @@ and their formatted guides; manual import and live capture remain available belo
## Status — codebase upload not reproducible on 2026-07-15 (appears disabled server-side)
-The codebase-upload finding (route ②) is **no longer reproducible**, and grok's own
+The codebase-upload finding (route ②) was **not reproducible on 2026-07-15**, and grok's own
telemetry corroborates that nothing is being uploaded:
- **2026-07-15:** ~10 re-capture attempts with the version the public repro pins
@@ -48,7 +52,7 @@ Honest reading: on 07-15 the codebase does not leave the box, and grok's own que
counters say so. That it *did* on 07-14 rests on the (now-gone) capture log. **"xAI
disabled it server-side" is the most consistent explanation — a reasonable inference,
not a proven, xAI-confirmed fact.** Treat `run-grok-exfil` as a dated capture of a
-behavior that is not currently reproducible.
+behavior that is not reproducible in the 2026-07-15 checks.
> Note: `/sessions/ Follow what an agent did, from a single tool call to a team across Pods. {{tr $.Lang "Follow what an agent did, from a single tool call to a team across Pods."}} {{.Description}} {{tr $.Lang .Description}} {{tr $.Lang .Description}}
-
-
-
-
-
- "+i18n.T(lang, "Demo library")+"") {
+ t.Errorf("locale leaked for %s", lang)
+ }
+ }(n)
+ }
+ wg.Wait()
+}
+
+func TestDemoCopyHasChineseCatalogEntries(t *testing.T) {
+ // Catch new static template/JS copy and catalog cards missing translations.
+ patterns := []struct{ source, pattern string }{
+ {demoGalleryHTML, `\{\{tr \$?\.Lang "([^"]+)"`},
+ {demoGuideHTML, `\{\{tr \$?\.Lang "([^"]+)"`},
+ {string(demoGuideJS), `\bt\('([^']+)'\)`},
+ }
+ for _, p := range patterns {
+ for _, m := range regexp.MustCompile(p.pattern).FindAllStringSubmatch(p.source, -1) {
+ if !i18n.HasChinese(m[1]) {
+ t.Errorf("missing Chinese: %q", m[1])
+ }
+ }
+ }
+ for _, entry := range demo.Catalog() {
+ for _, s := range []string{entry.Title, entry.Description, entry.Requirements, demoCategory(entry)} {
+ if !i18n.HasChinese(s) {
+ t.Errorf("missing Chinese: %q", s)
+ }
+ }
+ }
+}
+
+func TestLocalizedGuideLinksKeepExplicitLanguage(t *testing.T) {
+ entries := demo.Catalog()
+ entry := entries[0]
+ source := []byte("# 指南\n\n[English](README.md)\n\n[中文](README.zh-CN.md)\n\n[团队](../multiagent-provenance)\n\n[外部](https://example.com/README.md)\n")
+ html, _, err := renderLocalizedDemoGuide(entry, entries, source, i18n.Chinese)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, want := range []string{
+ `/demos/docs/snake-supply-chain?lang=en`,
+ `/demos/docs/snake-supply-chain?lang=zh-CN`,
+ `/demos/docs/multiagent-provenance?lang=zh-CN`,
+ `href="https://example.com/README.md"`,
+ } {
+ if !strings.Contains(string(html), want) {
+ t.Errorf("missing link %s in %s", want, html)
+ }
+ }
+}
+
+func TestChineseHeadingAnchors(t *testing.T) {
+ entries := demo.Catalog()
+ source := []byte("# 中文指南\n\n[跳转](#证据范围)\n\n## 证据范围\n\n内容\n\n## 证据范围\n\n第二段\n\n## 证据范围-1\n")
+ rendered, headings, err := renderLocalizedDemoGuide(entries[0], entries, source, i18n.Chinese)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(headings) != 3 {
+ t.Fatalf("outline length: %d", len(headings))
+ }
+ for n, want := range []string{"证据范围", "证据范围-1", "证据范围-1-1"} {
+ if headings[n].ID != want || !strings.Contains(string(rendered), `id="`+want+`"`) {
+ t.Errorf("missing unique anchor %s: %s", want, rendered)
+ }
+ }
+}
+
+func TestLocalizedDemoErrors(t *testing.T) {
+ h := demoGallery(demo.Catalog())
+ for _, tc := range []struct {
+ method, path string
+ code int
+ message string
+ }{
+ {"GET", "/demos/docs/missing?lang=zh-CN", 404, "页面或资源不存在"},
+ {"GET", "/demos/assets/missing.png?lang=zh-CN", 404, "页面或资源不存在"},
+ {"POST", "/demos/?lang=zh-CN", 405, "不支持此请求方法"},
+ } {
+ w := httptest.NewRecorder()
+ h(w, httptest.NewRequest(tc.method, tc.path, nil))
+ if w.Code != tc.code || !strings.Contains(w.Body.String(), tc.message) {
+ t.Errorf("%s: %d %s", tc.path, w.Code, w.Body.String())
+ }
+ }
+}
+
+func TestSharedGuideKeepsSelectedCapture(t *testing.T) {
+ entries := demo.Catalog()
+ var current demo.Entry
+ for _, entry := range entries {
+ if entry.ID == "grok-3routes" {
+ current = entry
+ }
+ }
+ for _, file := range []string{"README.md", "README.zh-CN.md"} {
+ if got := demoGuideLink(current, entries, file); got != "/demos/docs/grok-3routes" {
+ t.Errorf("capture changed for %s: %s", file, got)
+ }
+ }
+}
diff --git a/internal/cli/demo_ui.go b/internal/cli/demo_ui.go
index 118bafe..19d10bc 100644
--- a/internal/cli/demo_ui.go
+++ b/internal/cli/demo_ui.go
@@ -9,9 +9,11 @@ import (
"net/url"
"path"
"strings"
+ "unicode"
"github.com/byteyellow/agentprovenance/demo"
"github.com/byteyellow/agentprovenance/internal/buildinfo"
+ "github.com/byteyellow/agentprovenance/internal/i18n"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
@@ -33,6 +35,8 @@ var demoGuideJS []byte
type demoHeading struct{ ID, Title string }
type demoPage struct {
+ Lang i18n.Locale
+ EnglishURL, ChineseURL string
Entries []demo.Entry
Entry demo.Entry
ReplayCount, EvaluatorCount int
@@ -56,7 +60,7 @@ func demoCategory(entry demo.Entry) string {
}
func demoGallery(entries []demo.Entry) http.HandlerFunc {
- functions := template.FuncMap{"runURL": demoRunURL, "category": demoCategory}
+ functions := template.FuncMap{"runURL": demoRunURL, "category": demoCategory, "tr": i18n.T, "localURL": i18n.URL}
gallery := template.Must(template.New("gallery").Funcs(functions).Parse(demoGalleryHTML))
guide := template.Must(template.New("guide").Funcs(functions).Parse(demoGuideHTML))
data := demoPage{Entries: entries}
@@ -68,10 +72,11 @@ func demoGallery(entries []demo.Entry) http.HandlerFunc {
}
}
return func(w http.ResponseWriter, r *http.Request) {
+ lang := i18n.FromRequest(r)
w.Header().Set("Cache-Control", "no-store")
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.Header().Set("Allow", "GET, HEAD")
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ http.Error(w, i18n.T(lang, "method not allowed"), http.StatusMethodNotAllowed)
return
}
switch r.URL.Path {
@@ -87,12 +92,12 @@ func demoGallery(entries []demo.Entry) http.HandlerFunc {
if name, ok := strings.CutPrefix(r.URL.Path, "/demos/assets/"); ok {
// Serve only the images and supplemental JSON embedded for these guides.
if path.Ext(name) != ".png" && path.Ext(name) != ".json" {
- http.NotFound(w, r)
+ http.Error(w, i18n.T(lang, "page or resource not found"), http.StatusNotFound)
return
}
b, err := demo.Files.ReadFile(name)
if err != nil {
- http.NotFound(w, r)
+ http.Error(w, i18n.T(lang, "page or resource not found"), http.StatusNotFound)
return
}
w.Header().Set("X-Content-Type-Options", "nosniff")
@@ -105,38 +110,51 @@ func demoGallery(entries []demo.Entry) http.HandlerFunc {
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.Header().Set("Content-Language", string(lang))
+ i18n.Remember(w, r)
+ page := data
+ page.Lang = lang
+ page.EnglishURL = i18n.URL(r.URL.RequestURI(), i18n.English)
+ page.ChineseURL = i18n.URL(r.URL.RequestURI(), i18n.Chinese)
if r.URL.Path == "/demos/" {
- _ = gallery.Execute(w, data)
+ _ = gallery.Execute(w, page)
return
}
for _, entry := range entries {
if r.URL.Path != "/demos/docs/"+entry.ID {
continue
}
- b, err := demo.Files.ReadFile(path.Join(entry.Directory, "README.md"))
+ b, err := demo.Files.ReadFile(path.Join(entry.Directory, i18n.GuideFilename(lang)))
if err != nil {
- http.Error(w, "guide unavailable", http.StatusInternalServerError)
+ http.Error(w, i18n.T(lang, "guide unavailable"), http.StatusInternalServerError)
return
}
- page := data
page.Entry = entry
- page.Content, page.Headings, err = renderDemoGuide(entry, entries, b)
+ page.Content, page.Headings, err = renderLocalizedDemoGuide(entry, entries, b, lang)
if err != nil {
- http.Error(w, "guide rendering failed", http.StatusInternalServerError)
+ http.Error(w, i18n.T(lang, "guide rendering failed"), http.StatusInternalServerError)
return
}
_ = guide.Execute(w, page)
return
}
- http.NotFound(w, r)
+ http.Error(w, i18n.T(lang, "page or resource not found"), http.StatusNotFound)
}
}
func renderDemoGuide(entry demo.Entry, entries []demo.Entry, source []byte) (template.HTML, []demoHeading, error) {
+ return renderLocalizedDemoGuide(entry, entries, source, i18n.English)
+}
+
+func renderLocalizedDemoGuide(entry demo.Entry, entries []demo.Entry, source []byte, lang i18n.Locale) (template.HTML, []demoHeading, error) {
// Leave unsafe HTML disabled: guide source cannot introduce scripts or raw
// HTML. Goldmark also rejects dangerous link/image URL schemes.
md := goldmark.New(goldmark.WithExtensions(extension.GFM), goldmark.WithParserOptions(parser.WithAutoHeadingID()))
- doc := md.Parser().Parse(text.NewReader(source))
+ context := parser.NewContext()
+ if lang == i18n.Chinese {
+ context = parser.NewContext(parser.WithIDs(&demoHeadingIDs{used: map[string]bool{}}))
+ }
+ doc := md.Parser().Parse(text.NewReader(source), parser.WithContext(context))
var headings []demoHeading
if err := ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
@@ -149,7 +167,19 @@ func renderDemoGuide(entry demo.Entry, entries []demo.Entry, source []byte) (tem
headings = append(headings, demoHeading{fmt.Sprintf("%s", id), string(node.Text(source))})
}
case *ast.Link:
+ linkLang := lang
+ if target, err := url.Parse(string(node.Destination)); err == nil {
+ switch path.Base(target.Path) {
+ case "README.md":
+ linkLang = i18n.English
+ case "README.zh-CN.md":
+ linkLang = i18n.Chinese
+ }
+ }
node.Destination = []byte(demoGuideLink(entry, entries, string(node.Destination)))
+ if strings.HasPrefix(string(node.Destination), "/demos/docs/") {
+ node.Destination = []byte(i18n.URL(string(node.Destination), linkLang))
+ }
case *ast.Image:
node.Destination = []byte(demoGuideLink(entry, entries, string(node.Destination)))
}
@@ -164,15 +194,45 @@ func renderDemoGuide(entry demo.Entry, entries []demo.Entry, source []byte) (tem
return template.HTML(out.String()), headings, nil
}
+// Goldmark's default heading IDs discard non-ASCII characters. Keep Chinese
+// titles addressable by their text, with deterministic suffixes for duplicates.
+type demoHeadingIDs struct{ used map[string]bool }
+
+func (ids *demoHeadingIDs) Generate(value []byte, _ ast.NodeKind) []byte {
+ var slug strings.Builder
+ for _, r := range strings.TrimSpace(string(value)) {
+ switch {
+ case unicode.IsLetter(r), unicode.IsNumber(r):
+ slug.WriteRune(unicode.ToLower(r))
+ case unicode.IsSpace(r), r == '-', r == '_':
+ slug.WriteByte('-')
+ }
+ }
+ base := slug.String()
+ if base == "" {
+ base = "heading"
+ }
+ id := base
+ for n := 1; ids.used[id]; n++ {
+ id = fmt.Sprintf("%s-%d", base, n)
+ }
+ ids.used[id] = true
+ return []byte(id)
+}
+
+func (ids *demoHeadingIDs) Put(value []byte) { ids.used[string(value)] = true }
+
func demoGuideLink(entry demo.Entry, entries []demo.Entry, href string) string {
u, err := url.Parse(href)
if err != nil || u.IsAbs() || u.Host != "" || u.Path == "" || strings.HasPrefix(u.Path, "/") {
return href
}
resolved := path.Clean(path.Join("demo", entry.Directory, u.Path))
- for _, candidate := range entries {
+ // Two historical Grok captures share a guide directory. A language switch
+ // in that guide must retain the selected capture and its replay link.
+ for _, candidate := range append([]demo.Entry{entry}, entries...) {
root := "demo/" + candidate.Directory
- if resolved == root || resolved == root+"/README.md" {
+ if resolved == root || resolved == root+"/README.md" || resolved == root+"/README.zh-CN.md" {
u.Path = "/demos/docs/" + candidate.ID
return u.String()
}
diff --git a/internal/cli/demo_ui/demo.css b/internal/cli/demo_ui/demo.css
index 4c17056..63aa00a 100644
--- a/internal/cli/demo_ui/demo.css
+++ b/internal/cli/demo_ui/demo.css
@@ -17,3 +17,5 @@ button{font:inherit}a:focus-visible,button:focus-visible,summary:focus-visible{o
@media(max-width:1000px){.demo-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.library-status{display:none}.docs-layout{gap:24px;grid-template-columns:190px minmax(0,1fr);padding:24px}.document{padding:30px}.evaluator-grid{grid-template-columns:1fr}.section-heading>span{display:none}}
@media(max-width:700px){.header-description,.header-divider{display:none}.demo-header{padding:14px 18px;gap:12px}.header-location{font-size:12px}.local-status{font-size:10px}.library{padding:27px 18px}.library-heading{margin-bottom:27px}.library-heading h2{font-size:30px}.library-heading p{font-size:13px}.demo-grid{grid-template-columns:1fr}.demo-card{padding:21px}.evaluator-card{padding:20px}.library-footer{flex-direction:column;gap:10px}.library-footer summary{text-align:left}.docs-layout{display:block;padding:20px 16px}.docs-sidebar{position:static;max-height:none;padding:0}.back-link{margin-bottom:17px}.docs-sidebar .page-nav{display:none}.docs-sidebar .guide-nav{border:1px solid var(--line);border-radius:10px;background:var(--panel);padding:12px;margin-bottom:22px}.guide-nav summary{margin:0}.guide-nav[open] summary{margin-bottom:12px}.guide-nav nav{display:grid;grid-template-columns:1fr 1fr}.docs-sidebar nav a{font-size:11px;padding:6px 7px}.doc-toolbar{margin-bottom:14px}.document{padding:25px 20px;border-radius:14px;font-size:13px}.document h1{font-size:24px}.document h2{font-size:19px}.doc-footer>span{display:none}.code-block pre{padding:15px}.document th,.document td{padding:10px}}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}.demo-card{transition:none}}
+
+@media(max-width:700px){.demo-header .local-status,.demo-header .header-location{display:none}.demo-header{gap:8px}.language-switch{margin-left:auto}}
diff --git a/internal/cli/demo_ui/gallery.html b/internal/cli/demo_ui/gallery.html
index effb46e..43eea8e 100644
--- a/internal/cli/demo_ui/gallery.html
+++ b/internal/cli/demo_ui/gallery.html
@@ -1,47 +1,48 @@
-
+
-
AgentProvenance
- verifiable agent observability
- Demos
- Local replay
+ AgentProvenance
+ {{tr $.Lang "verifiable agent observability"}}
+ {{tr $.Lang "Demos"}}
+ {{tr $.Lang "Local replay"}}
+
Demo library
{{tr $.Lang "Demo library"}}
Recorded executions {{.ReplayCount}}
Open a capture. Follow its evidence.{{tr $.Lang "Recorded executions"}} {{.ReplayCount}}
{{tr $.Lang "Open a capture. Follow its evidence."}}{{.Title}}
{{tr $.Lang .Title}}
External evaluators {{.EvaluatorCount}}
Build on the same execution evidence.{{tr $.Lang "External evaluators"}} {{.EvaluatorCount}}
{{tr $.Lang "Build on the same execution evidence."}}{{tr $.Lang .Title}}
{{tr $.Lang "Setup required"}}AgentProvenance
- verifiable agent observabilityDemos Local guide
+ AgentProvenance
+ {{tr $.Lang "verifiable agent observability"}}{{tr $.Lang "Demos"}} {{tr $.Lang "Local guide"}}
+
' in guide and '
' in guide
+ if language == 'zh-CN':
+ assert '本页目录' in guide and '证据' in guide
+ assert '"locale":"zh-CN"' in get('/assets/i18n.js?lang=zh-CN')
assert '--bg:#f5f5f7' in get('/assets/theme.css')
assert 'code-toolbar' in get('/demos/guide.js')
assert '.document' in get('/demos/demo.css')
@@ -90,7 +107,7 @@ def get(path):
if process.poll() is None:
process.kill()
process.wait()
- print(json.dumps({'archive': archive.name, 'platform': meta['os'] + '/' + meta['arch'], 'signed_replays': len(replay), 'guides': len(catalog), 'no_go_required': True, 'cleanup': 'passed'}))
+ print(json.dumps({'archive': archive.name, 'platform': meta['os'] + '/' + meta['arch'], 'signed_replays': len(replay), 'guides': len(catalog), 'guide_languages': ['en', 'zh-CN'], 'no_go_required': True, 'cleanup': 'passed'}))
if __name__ == '__main__':
diff --git a/scripts/build-release.py b/scripts/build-release.py
index f6ac626..25a4649 100644
--- a/scripts/build-release.py
+++ b/scripts/build-release.py
@@ -46,49 +46,16 @@ def main():
'go': output('go', 'version'), 'os': args.os, 'arch': args.arch,
'cgo_enabled': False, 'programs': programs,
}, indent=2) + '\n')
- (work / 'START_HERE.md').write_text('''# AgentProvenance
-
-From this extracted directory:
-
-```sh
-./agentprov --version
-./agentprov demo
-./agentprov demo --list
-./agentprov demo multiagent-provenance
-```
-
-The CLI embeds all six signed captures and all demo guides. Replay is offline,
-read-only, and uses a temporary store removed on Ctrl-C. The browser opens
-on a loopback address. Use `--no-browser` on a headless machine.
-
-Choose Open replay to view evidence, or Read guide for a formatted guide with
-a section outline, images, tables and code-copy buttons. Both use the dashboard
-theme. Guide content is embedded; additional repository/external links require
-a network connection when followed.
-
-The `demo/` directory contains every existing example, its original signatures,
-public keys, scripts and documentation. Replaying never executes captured commands.
-LLM Judge and Jev are optional Python examples, not prerecorded verdicts:
-
-```sh
-AGENTPROV_BIN="$PWD/agentprov" python3 demo/llm-judge/judge.py run --offline
-```
-
-Jev needs Python 3.9+ and separate live credentials/raw-evidence consent; see
-`demo/jev-judge/README.md`. Its commands use this archive's absolute
-`agentprov` path; Go is needed only for an optional source build. Reopening a completed Jev
-study is offline. Live capture scripts have additional requirements in their guides.
-
-Linux archives include the optional sensor; its kernel and privilege requirements
-still apply. macOS supports replay and application recording, not Linux eBPF.
-Windows users should use the matching Linux archive inside WSL.
-
-SHA256SUMS and per-archive .sha256 files verify download integrity; they are not
-publisher signatures. The CLI verifies example evidence using the bundled public
-keys. This proves evidence integrity, not capture completeness or causal certainty.
-
-These CLI binaries are not Apple Developer ID signed or notarized.
-''')
+ for source, guide_name in [
+ ('docs/release-start.md', 'START_HERE.md'),
+ ('docs/zh-CN/release-start.md', 'START_HERE.zh-CN.md'),
+ ]:
+ guide = (ROOT / source).read_text(encoding='utf-8')
+ # Source-document links also work after extraction at archive root.
+ guide = guide.replace('(zh-CN/release-start.md)', '(START_HERE.zh-CN.md)')
+ guide = guide.replace('(../release-start.md)', '(START_HERE.md)')
+ guide = guide.replace('(../../demo/', '(demo/')
+ (work / guide_name).write_text(guide, encoding='utf-8')
def normalize(info):
info.uid = info.gid = 0
info.uname = info.gname = ''
From dd0b4b18e4b1ca7abe7ae5611ea23a8451d9bf1f Mon Sep 17 00:00:00 2001
From: Guozhu Huang <1948788642@qq.com>
Date: Sat, 26 Sep 2026 18:12:55 +0800
Subject: [PATCH 02/13] feat(i18n): localize dashboard views and preserve
original evidence
---
README.md | 6 +
README.zh-CN.md | 24 +-
docs/graph-commands.md | 2 +
docs/img/README.md | 6 +
docs/img/README.zh-CN.md | 56 ++
.../dashboard-graph-explorer-taint-zh-CN.png | Bin 0 -> 175743 bytes
docs/img/dashboard-mobile-zh-CN.png | Bin 0 -> 60507 bytes
docs/img/dashboard-overview-zh-CN.png | Bin 0 -> 182310 bytes
.../dashboard-side-panel-preview-zh-CN.png | Bin 0 -> 86543 bytes
docs/img/demo-gallery-zh-CN.png | Bin 0 -> 107657 bytes
docs/img/demo-guide-zh-CN.png | Bin 0 -> 97367 bytes
...demo-k8s-a2a-substrate-dashboard-zh-CN.png | Bin 0 -> 182106 bytes
.../demo-multiagent-network-egress-zh-CN.png | Bin 0 -> 147366 bytes
.../demo-multiagent-orchestration-zh-CN.png | Bin 0 -> 207341 bytes
docs/img/demo-multiagent-risk-path-zh-CN.png | Bin 0 -> 138470 bytes
docs/native-capture-spool.md | 2 +
docs/zh-CN/graph-commands.md | 88 +++
docs/zh-CN/localization.md | 27 +-
docs/zh-CN/native-capture-spool.md | 100 +++
internal/dashboard/dashboard.go | 51 +-
internal/dashboard/i18n_test.go | 125 +++
internal/dashboard/index.html | 705 ++++++++++-------
internal/i18n/zh-CN.json | 720 +++++++++++++++++-
scripts/accept_dashboard_i18n.cjs | 110 +++
24 files changed, 1733 insertions(+), 289 deletions(-)
create mode 100644 docs/img/README.zh-CN.md
create mode 100644 docs/img/dashboard-graph-explorer-taint-zh-CN.png
create mode 100644 docs/img/dashboard-mobile-zh-CN.png
create mode 100644 docs/img/dashboard-overview-zh-CN.png
create mode 100644 docs/img/dashboard-side-panel-preview-zh-CN.png
create mode 100644 docs/img/demo-gallery-zh-CN.png
create mode 100644 docs/img/demo-guide-zh-CN.png
create mode 100644 docs/img/demo-k8s-a2a-substrate-dashboard-zh-CN.png
create mode 100644 docs/img/demo-multiagent-network-egress-zh-CN.png
create mode 100644 docs/img/demo-multiagent-orchestration-zh-CN.png
create mode 100644 docs/img/demo-multiagent-risk-path-zh-CN.png
create mode 100644 docs/zh-CN/graph-commands.md
create mode 100644 docs/zh-CN/native-capture-spool.md
create mode 100644 internal/dashboard/i18n_test.go
create mode 100644 scripts/accept_dashboard_i18n.cjs
diff --git a/README.md b/README.md
index 3d10ac0..0119df3 100644
--- a/README.md
+++ b/README.md
@@ -751,6 +751,12 @@ fixture so the pipeline still completes.
## Web Dashboard
+The interface follows the browser language on first visit, with English as the
+fallback. Use the English / 中文 switch to save a preference. Switching keeps the
+selected run, lens, detail level, node and overlays. Commands, paths, IDs and
+original evidence retain their recorded text.
+
+
+
@@ -693,6 +693,8 @@ Dashboard 是本地运行的只读单页界面,用于浏览和查询证据图
它与 CLI、AI 工具复用内部查询逻辑,HTML 和 JavaScript 内嵌在可执行文件中,
不依赖外部前端资源。
+首次访问按浏览器语言显示中文或英文,其他语言回退到英文。页面右上角可以切换语言,手动选择会保留。切换后会保留当前执行记录、图视图、展开层级、选中节点和叠加标记。界面说明随语言切换,命令、路径、ID 和原始证据保留原文。
+
面对大量事件,界面先展示摘要,再按问题逐步展开。
原始遥测仍可查询,但不会一次性全部绘制成图。
@@ -736,10 +738,10 @@ Dashboard 是本地运行的只读单页界面,用于浏览和查询证据图
并支持实时刷新。
+
+
+
+
+
-
+
SifG7X%Kv`P6)9HedHY!K$XBG{hPf}VIV=a+2nXyC{3&RT)KfD%Z}_u z0fP)1DNTgXr5%=(o0mv~g|%;T?_&}!-za?kQhR1JMWC?9oH&uN5)Phn$aK9tU_g*n zlE>`89eYMKr;{f8{zsDsXLc`9sYQSC{ F6sr#AwTseplk zDEgvgXI#U?d59DUc-a-%TuEC-pFT^Z7&8*% g-o&5xakw7EDrWjk zR~el@uTU2Q-I8MBHx#eFsPj7^p2BPP;#|g<6}Nnhr7n!HNfPM%1X3rHizacw$c9 zx>9*nY$^JDDyQc3H{!krjl}+`L&94G1ypTwL6Q?8JjEr!>2}jIXBMjDp>=&W|M)B% z j0nUOJ(H Seia6=NccsUJuP7MQJEr*iZ%rz&scpk^ z{>^=BorY+}Oq5B?X*pHn$rMZsoOw;2{zb>^@M>sN<~!Il!4apmZT$Y_T$GNS{OYjy zu^a0PtL|!nOc9A|sZp~7K0_drBg0v_h&_ y7D$^RZ#$Ecl%rdJ6ybVmxTg3uk~&1lJi$MJR}H=TSLPWeib&)2@0 RurWi3qx9lgVzOA-qTzsn7ZZ{2(U9AX>ZTGC_}?UNQz^YR{2p2%H3Ao-oCqo})>} z|6u`FIR->H-MyGtVyK?aM>l>v(4u&r`(PC`XXlAbpeLUnpj?NX>1lqBq&S5yj6fWN zFQ5{Jp_n40LBsSUBgaLycu;J+NM{eq^HmtIau_p=DFQ!?d!JRl_bJl^k&vWhX2R8& zsdQF)-;#SncxTJXWdM-h!!$swQ3Sf>x8!MTO5xygnM928GK!V6y}yE3RuthZWNgx- zp6x6Q1dnvhOTW+XxPDSnPT|+T&f|m?SLDs^pYZ<*qM78dPfuXUr7#gzovn7*&&tqb z&Xy? zHaO_~=IdNGUy6&1X-$5YAK?wN>U>{o!^g#?Iek?F!ib5933<6>aanZ(h7NgodB^h= z ;{9s@s6+=b?|d=aL~~$EiS4G2TAM*AX(+`06T_|fKVt;ae%Nt zAeM}QC-UxU?Z|X*M()NE<@etv613T9U%oSLbEoIM3Yp-{KrY}^x7vD(--HSzeQ3OH zBq9|R6+X6*d21CJdCbtX0}n|VX|}Zb;xDT|d4FbSJ03pmhTc(rTW&MxAU}*XdPZN& zGrA+$lBPm{6AOeD_J?>dLHw(z1)eYGj!_Qwbhj$x*MQ@q>9p szAzc@H8c=pHP$cSz1S#tIxQeHHjCgB zF*kEgexi~W!D>GHg_cI}s1|;x@S7#mnV&57jW+X;Q)ve_QMF7#5^%4U2nN}7XHuvD zy)gLhwC~(wtn-CeT^`Y@eWBy#ISv^NV$=Y1N6xYwT-=@c9T*u4cHKtf{Bs;VxD+JN zcRSs`g+d=OPrsRcGn0Lt77TJBu-{;~-f58ID5Nk4jc#qLi@Bj{;l; p&hnn5KD)oA{+%yFf`wwK7v{Fy<5>9^%s}?y0#P&)-anSh48TkQIT8#l Z+wEvAV6fBjum#98sPd2(|1Gt>Ipvc} zXii2=F+=+eod^_vR##B&3;GFc45pq>Gk*-w^e)QA07+bnFTMFmG4zQ(X9S03na^W> zC0YN1@i@R(@RJGB;J(P=1k!+fqPpc%{m$mLO2874Ky_dvVN|FT$;Z~VcqRTrFIQ;q z$Jcz}LitG~QsYDIzfDg;5M-y;o{qPb*Pz2AIfp5_l9wMpDAeSeS&%W zdY&6Sy;CW&AZN#_@zX^6uvXKX&$seAPu08v0!9R%C|9X-R=OqK_t&z#l~|QxDTO?5 zX%(L$ 7@%Wj&ODz| zZTx;}G5Y08)ft<@`>XTmJefp#KQ3d@9H7bq18)Z~DX7)0FGUHRS@$Z-ZFFzOrH9LZ z^X}be^fB51(IHfem{1IBy%=)A?m_k^8wix$taKtQKPl;`mb!;#C*@z7w(zCuToo!7 z+xU%2DSZTEuoSdMUsZTC=#ePh)2ybprnZgv8_?qKVXM`0neqd~ICI5SiVR|& zD82-+(%U@+wiyj)DHGXGr}m1UO%xyZXedCBXT89Ji6xnD757wT$i7O21Bxk)QWaLg zEejxfZ#)(JS0GWAH#j+8Z0Ne)jf?M#z}h>oWFuINUBIcfk^%1`cT5m%p)*eP-@eX$ z`d4orkNtvz4D}w%qOdaV@Of52b;pNY9?NGi|6Eu>US5}OVgvHa=ne$z4cKJ6?W3RX z*B5}0wp?eau-!;1gyQM1F75jft_VE*PVg-+KT&$gp;|j#jy~h4L}=wH;ZyzB#}slD z^$u}9&$XISdH|nrYhH!f`(h&dL2w(enXu|{O2Bk#g{1%MFDbEBRpl7Z=p-k37+YMl z-s)3^OaqlxrEWa|ZDFK>qpNrX;1Q?ScqWhgF|9hm0yTDUV4%f*eT{uf@+WX?Km;E_ zhDEvs-zO=9MahGAG}2#Iu+WImmiLHpO4E-^jvwvL^MZP^T=7#bM;!Nd1M; P{`OJA*)o#_XB6AmeI#vNltq8 zXGR^NAM)$#lVupR+!x;R*oU&uJ;hM)CuHdnm|st`ZG|ff1^)s?T^E0f9( %&< zigL`}X2(l20G9o*rGH&d(0_wVHVN#G7j&N 6))Py#F z${l!xxfptt{?vR+H(}@RHW^Sv#%`03f452d<`oCbcS)h?b9e= Ly1IKz#+r0n}FrAcEKLa&dw8 zfg#2_0>yiPEEmFnwA6dEJ_EB58WEv_(54}qK&z9Ve=>@+SO?>iM6ba9`h{T!d|tfr z(*MD1U-nMmWxF-U1M-<(Uk@Ry08bhMU3RxgA-j4BY9TZi@AX&kuE}KV@lbEr0e?hV zxtcIJ;a_D{i0>% z=+iDy!*{nFwvAP07bBv*7?AJbhIHU`dyW3C+gBL q*&vGt z3Usy|H;Wh+f3LRG7wT8UFF_t^e+#tx#N02@7pBk#Cb&=>ALF%)8ZMS LfN;@o|C z8=YML9Snr5R`UN8(y(&qlywRJugQ7)D3`E9O1IQ@T0~Q*#l3IHsS)jkn+ys7tk0aS z+-*jUjBYhZqV4i2gHQ$-DqMM!uhMJ~$9jzfAb?0IPQsd|@w8 q zt1=V=0D-S P?1b-5vN zp+a)a_JkI+VH|M3qY^fZz~z`NrOPhhF*xNE`!ru=7z!3=G*y*di0PENBDBtr%=wYZ zcbLB~HrWiT!iW}`$?IsE%%}+u58rI@0)^il^`6WUP)3DUs4{E^m__^J8Ag@*o@8@z zPakxn0M{8B^a*jhFm)Q*ST1Jfa>s2tTsp;#iz{9ryD!daEYqr@7MCTPQzC5MqT1fZ z1$+fVcyYKy%53Ir)S&mOr-HWooerzq33RD^qQj^j0t4%-UQfsn((^`d5kcn6M;H$r zi3Q4{6`r`|%{N_?T}D3Qf{o6UqGwvNn-@x{%Q$*h(&P2pv&0mjm 1cfR(6?2nAb=)1U+u?#!St`)=jnPVOW^MM zM1Y6`T)W4jor|;A)!j?NfNFnQc>c-ewi5i+b~lvr`MS0$U+^6i0)cediY5CVU;cYn z?_A?Gj?@>P`3?&u92^iJhLO?m1859qtDrf(=b8to3Gsf%dbeQ=BUKiSVe9M{`SGGv z^+c<`T0$zmQvo?$=at;0tL!E$53!=01IiAjdNFi7Bq=LPvlO$Kg`z+xCWo(Mx09uz zp@9zw&lHY(*Nflu8p(w{B(27{3MY$?4C*7?HCbcH1gW!_Lc*h?N&k6o?1UTH?5h`Y z?I1Ucuf<7trtNP|#v^wgf!6SSjGMjpDPlCsHB_?``-1ym+mT0{;lkTOgfX6>86_wp zmXLq_SA-IqscZ@%7A)8)j|ud0oIGmX=%IquCD%X&kjsU|D$z@Ug9AF6Jp_zB9w8e3 z fcpaIM=31Z(*7&yV4Q@HCiz0v%&z z+4NgLVJuRty2)YSeU^+myXpM*YsvbloyfHvsrVjPPH1a~m+V`QswgyzI9R4LN;vyl zz7amqR@aOH0j*2iFKkA>R|DgQ2HsRib5grLEH!15jNGQ+tGhuxQp0X%s!QhGYPUJv z9r>0u%~358G~e4%<(W;YuFKs%w%z6sWvBCr_V(601FW_f=gDm0(d}RZkbzl$WXLQp z;RQ0H;d-MtWoDvjf5h*n&HZ1~qh~;^`SBmJ%FdzkU9;a4v@`Us@}l#q2s4%8G(hA- z0*)V$o~$;TJ?8j5Z_iA2q~z+hfbGcwXNgbex{j5ze|UMN{Bd`fO3dl9 sm};QY3QmI7(jOkU3i>w{WK=$)Rv4_wbX z+RlhG0|6RuBhUo{7C}zjzuI%`Dpcs=^XAvKf0*FRMvtQO_ADU~b!hADNtx>E 8kS?&Mu496zAiY^5 zUX m0&m%8T(BEn`#WZ@q7(Q<<+FD6RV2`x*JL}ciFpS3fhbPj-5D5v|3VS{& zq6WG9jW2QUPyAf=b9jj^W8eQD7U1oE#^1`@+T3bVhtT#Ww%IVq%k^k3{xVSqZ|lYo z4IM%G;`HNYr(YwqES|%K?~A?mslji-Yu42k#hi;sjqhM5Iv-wGnX-GW?cIj2P*cgd zaxt!ZZW}#x2EUp_A^Tf&2_%QvxKI`qepv);Sd}hJe>zdEPWyG?cWB0k|Nd~{QjP)a z=5nJ>iZK+T2&>8LKX!l@SwcFOl{m358Os||5l~zH=uEQON{z+w_MkEuIr-iBdTfsI z`~01!=jV7bIDbmm`gjBA>dFcnoDyezATeO|1lV~1#D7X6U_bZ1J-L~dp^PTx(&Z z#hn2@BIWrGCMt~dSF5+tlr_)mTBG@kR$fVo^Z=7UUM1DmFF9gZa>} B(_)Wx5}YGGf#y^$%QvOd@IV@EFpqymbF#)6PJ` zI0nL3fB4YL6pn SWQ~_%<^{(8>H}b)F@uhDbj3_FscVMTXum zJaQ~1DIe&PFeiqX8?S9Z<&RFJvMdp&5A!8Dxg91Hdt)NYyNUWxoa9q+q|Xu>%MbDN z&$TSODp{2(KX8Q~Np$|A(2(C;mHjBoz8GBp`lcWub!(p>TRFS^%eNK}&y?=*(&ge1 z>5J0Ik>($9%*ATQLeC^7z=HiSHinn3E{j*{q%!0JTcO+7;`&0c5Cj+WaB~t$$fBRl zO}YSic$Dv6a{r3OXmVcJzJf^}{| }m5ddX!{Q`=kBz`HUan-wukLXQ9nY ziZc(W>RlzrG~bw+6uw`_a`+y+nN_4Lj&E9-@$nm6;P& $Q79j1>nsw#95^~| zKd!FJe4*)~>3BLBZ8%+`PL4N|-cshbn4MBpVMR3f$!}4rAxW~EX>bo@j-IunIp4E3 zdIciju5XZma? {emJ&;h1HO1gmiTDeZG2ZWf*^4-%)^VNm1 z(~>wVAD0v0@Ka+k8b%cY5qa^+odDEN(-_u(sq)u>sCCawS;RIZrI+I>9UpzWJw-__ z8Y-&aT2*U+==9mzE>Hpz8YO!+6E(pboc7Fx}64Ld4ZlT zWPtT8ody(rCcQu8ch%$02oDi;Cvkwat&fG;7URzJ5{^4PEb`VN7 3lz#j-gQ8XX-e6fbtu!!fEJ*DegE#%B)ul zd6yb|m#2GW7VK60y8j}2IMUERxj;U>d{=d(G}g0uoZoBt=pR7V5? ~hmM0sfEC||Z;td}Y+6eA)>Ds^7B5Q#C z(-ThAj4X97Ttj*UkMfcsXXADWEE!59 NilM*+BW~kL&B94r?F_3j*zwKTD z1a+nB2}{Iu`6c$uKcxKV_6-}Du^n5(nt+^O!=mHKwCA+-&ZF{2i5&4i(oe-jarnO= z#S8;66fvFfJ!8!C4WEz2;2)?PF-YfSc80>m@yDXPmG$?bF!uOT2Hj>sAK>4bAGeeR zh)KvqwR#+mT;Re!i6cCi{nJh%Chg7heF(1pg)c`_?b5!viSvtbhpM+XCx>7#K{c&H zk3018acuFg>LI8-gHI%6e^Bvv4B`v#zh!& !1y5fkwIRfgzkcZP%9Z{l-dAuP zjKV<&2|DAKJ(N$ +ox<;ANcAvq4!uyGkc;uaN-0a&dyhHhWVyA<>3(FDA8<1bV|fuz zh0 z%k{ %uoTRfWb;xE=kJ;pcnF %wf_aS-55m zXJQNx9khIW0@V&ql_k@F^!B68Y;X>fZX3=eV4rz8AU6^u*hocnH=4q-T&ya=!^3k3 z2Uwwwy3fGi0wU$F9fk0;DB1kh<3m3%j=4BFDTKW6@Lb5LUY_ra^l>ILymHXQw9c=8 z%d1;a?0D+^G2I2?J?O<(8d%1=7Y^-KlWRCQlY_C8p>YyF(8)PW^QDOlv(@Y#fwZ~Z zY*6Xzd(gcFKbG@G7qr}+G4xT2|B%_XeVhQPk@o4-sM4pIQL{|e-c1g50> (#=HPs6zM1?;^tSc9!Msq20vA6k%?()Xxj zq9|C~L-yGS-0A|jer?jTyDAJl!*Ymhohb2WO;_%mUt5l6WMMBP&fBY;?|4$t=YSw3 z)oLfla$pSb)(=7|=6o&2cqR%FS|bPDDzSD2F~R~DAS;+qM-T}V`LJnXa=QKvNXW$6 z9^O;>pVxO+CFKYxw;{+52i9Tl)h+Rf3EF ++XDv$8 zm88o_D&Vyz@^sCVqYc~avS@P(_Hi8S^%m LsMh%#O`%DUV7sTQHQmHLa!Qs zU>t$&?~7IpSF1>fV5f+IY7T&EyW#Hl?E*9qg*JD$nO *|}xkpvvK@SFl+dO9fN z-+zrH4Y2ZT*;giKpsB=u*>22faX+Ol@8R{8)Xfc?EuijtVKb7ZR_Ytu4%n?)ygL45 z2i>v$KrNHP>hL1z+uoBD`ePi2VxcG4nuP1O)=k;qB649;Kg$n5Rub-h??k@Tk)dPC zoZ&Rz#HO?Tr?K(sni zq)sFYQUHAry$$?6S*}dyZ{3-jtFj}V&F4yBaQEdL_3F=H6lJaZouMOn`OyKlV;_?J z8MOTY*`T#jUX(@SV~O}54`}YlT90&wKI_j;ZPRo(rEF9TjF6;>2j2(u;LRD+j89(& zCi$Jf;Oq1mHlxlRy;Ws=S4&}_0$44@C=0ovLEU2*`{LT<$3_c2;->Q;HFNl$0zMjh zPe;paP#2Xt;(gxk&2aG62gN0B<6@=74{Z%TRhEZ}6?&0J|L4yicrUD^N@{lS&&bFt z_jR9Gyw1l>0T0(yd;LAD?(=SJ>{#NCFg!g=sIw;_P# TFtIW01i@#j zwR_zFhV;PE6~Tpq46qU$w~=n 4HnQT4Uu{-u{Ob|$Rf+?r-!NT)ODN4t%qvqG=lT!r;!fwg20`>k&oyU%4R4g?Z3 zQXV+ OyP`!OTVU9D+kA(2S~_h zI?MO51KD(r@>7#{BP&{Jc{Ll|7>$2O&hg%v+fg5^11=DWA&-wgWF(`=0FotIKy8zo z(gt&%$U!yupJ)@Auos+-b*$Pi@jeXR3C)`-w(ChI$lN#DDO_F>A{XC<>~ mRs-tduU 3bI;DTIsFE!gzxo-x~Q<-&3b%ATfcq $E*M8s!9g=KvyKnwo lfXt&E+4TdnauV zwye8*0E#3b^NAhYJJyT70|OpR{eAc!_ywNcV*AU$`R x+Q 8IJx98|J8l-bR}L!vg4OJSxFgy;ve| z@5HUt*rT5X>H^3vvxIkcVi2Oou7r3;Y`=-2(qMAHbwk*T-}AYE!JT^0eo~{f_}{Mc za*p08_pMmx`cDGJV3 grNoo;3wF9#=glW6AeRa14B&x|+>`-KgXlFV z@H`*w6awKz!#CYdh923?%>c0H&*jE%RSY0x41#2=IBpOW$!hBwwL4OUJ4xOF11#!6 zx2an9Z2<|2B#2GN+K|IxyZ1A>42R;}Wk)hCgF(LfQ Z;$+FHosZsWYcL^sOZ1s*1}|71Yt8Cehz}vgoZMUuTum?bvfipks;7Tx*_oa &5X13w;`V z`sL#>tZ{es?Oz@Dgp?!szegkD>Ds?p{KGxW>2j&XX--&}1CqB!R3{>NM$(d!KD-YS zQ6`{Ihg>%kdJ*w?GHb2g_FFQzgvCkYw!R_}e* zV&ahM-;R 0kWz zQiH9)?lD8A<|dQJY48O>!^;pf+ray>eO$vf;n@d+!4Jro-xauLsCJ2%Qa>ZGby*n> zO;;LtV<;5J|AGr?|3u1d69zWf%2bPv%k+(d+YjR~8wma(EM*56Rn79cTx|6NMsQ=< zLZsKIpg=3w&;C$!S=1eqv9xK(b{_%}E-k;+gnk?+%7A&W6ae&!M!tsdo%G4QH11J? z*ehZj52x-~AcnqYWp$hSC6@Vq7!$C&SzB8JJTFp7yAuEpu8i} hqV0eXXoI$5g22>LN`=K+e59-orl> |DJ&R8KmJn9uZDwhz~ZyV$4-#QU(-uaHjvtBGEz1DnEcZaHTYLtw$JvaTCp@O;MV zsd56w4^3fnp;8|$1$L2$!?JJekgHqZ3A%rtfJ|MG92Xa6D^?x-V)yjCu)c}E(&m^L z9=mo0-Up)qSh!%jV2D;jdsb8TAUYMelN9G|%SJ9bu?N#mN~x D1fT7Y`J`}uHYN_kMZSf`9m+Ni;91 x%@LEHg$7i1a~Jp^DDP=TuS~4OW)V#|>*@o0_aYeM%^N`0 z?RGs?+3_3-=&Hw~a?aQJ?3hvDLeeCGI2*63+8=>`yk4~sqM+5~u5ndW -TOTKjO377=v&UPae<~CO zzVD`72w4@%SJMo2uWo#KtXg*n@SDiilhgGqGXA++HDt38A@jQ >XR%x6c4uU_HSc0JU@*zEe%; zu#ET0NK5x5{%i4NI$_c(?>iifAybG_CgOE5u2`zi7lhfebLKf5M$Q)YT&h-uXNVH) zAML4AGl`a4hxHvzzWd4NO{X+?Q@K_xpXsPe8Q${ Y~ zM((i%HqO?Gsau Zj;x+pG3Nh&pvS-2P>YWhXy08cVdZ1{#)x=Ss@!R zXmzvB^xmKNCJQDhQp(yh($$oSBSunYr64{4Y+Y5065g{gwB-j?e+AYg;Fr(sqzx3S zfK?5T&1~_P3SGd%${g^y>9YMgIP7{#Oh#e?(ZIu|ZZfov8Se0XL;~dUZYSD0va6n7 zP-jMZ00 B36Kf@y`tjvnw-zH(J&&Ke+KyK-9Z|Ar=`sHe5-7K<+M1*mx|#5 z0LyD)-+t{O`ly#@W3&5ao14d_PsIUY=?}*=hl4B2n{eX=xT*_onjS)Jh!+6|tzZBX z59>BYWFzC+rO$8V0WB~+>qS#=UP2=_9#L@o8@i4>0r$h}yTHyztfR?=zts5GO!>BH z{1%LBLo12*t^+QT!lu^_)ug;TM|%&)l%0XA8&ve4Gp`@7*W>3Wo&}`bB&OgekcZZn zLb1q@n%OO%h=4mGzgmln2M#1tFAMIUH+T|uAY|fpL}5yM5Zdxo88li~tdwmA+^e1~ zzuOB@AT)j|)F@HoMOc!(RP?ZGLI72EeXka5qmd=}mY#_9>j+^VOh^GG6LP)=`K3Kz z{6O8=52Axvn0jcRIATjKJ?Sw_E1IOEql+ecc5HgUow$^MM8+x*@%gYn?5``{a|lz# zD=!lL>%>X`-(BgoZP;HNmo83hh-w(4u6Z|VI{rcK4nCiSKCwd{?q_61lWwsWqVxyX zi7u`p55^QYhTO;F=)QunT6;P?4kLUYv8a?{)e7QXFtfbn#}Jhf7YE))+VW4(fEM2- zA6FNrgbqcB^W!drz}d~${kb0YG$ 7cj+DU10(-D1ra$E!4Siz^?sQsI;lwOk7+-E y7DCO{P@9#6QWE1)T2BV1fW8 IVsmMtnqiwc$xHd9@ zB@XzM@B8RRw5K^p_Y}0?lP8TKMhRRUM_hDKm20yjCa+W3s|q7CRc#^>Qa!d-puym- zgBjw93!u##zHhKO0RPsFThFUK8vDORntKyjgYZLl8&k4(z=avWvX+L5{p?~L=Wuj$ zlP*iBH+R`5MoPkGrM!;5Jr#Qb-0nI+Lb4QC@`nHd9ZA3vEIBX`MVvAEibJ6m>X5pM zx5^*I8DHH$J~&NTxHMlt`5jz3b2_U-@m`9N*G>d!*U}D-&~W)n9~C$@vSN53dPfzl zn?y-2t|8{RG}HMVU5{v7!ai-ghje}PF(mvEiD+tWwzH+(gBsL5IahIikr(paKl=DH zs29=p4T|g~cFlbr+>K1VJzGMH$hSC7dD3?vtLP*5Ol#7WT=KEnYzvVkl!Qkzh$EPU z`9M0Iqq>U?LU|b&Ddxzd^W!4M#Cn1~ERG5M;?2NoF?l8=NL!gJwM}3jo)Ir`NnEnh z`c>D61asO(y{0~nwuI$I+$2sCi*mO21q3-;J_lW2mJA3y{d)5&_61T~Ult-{ijWr= zVA|yRoZFdGeYS03zViZl!mRn;>yAk&1fn@;1NVPHY$fsdEM?x*+5?>aWU}ZrYTlHg zEx|q81jNP_u_}diHH!}lNFI1gXeFYQi%aFWebe;T7}u*Ny6>h;ka(i@qk6KNt<&`I z=%eHUrI?CFpuFtnze%vze!%~W9)BpJe06zwjliQjX84c&rt>pOt>Y>xvan&9vB~CU zQ!!wZQO3`^^uExb$A)~N)njx0j9M QDL9!+vVt+-2n5= kIX4I%uaGcd_&w ziASXJ h`przw0h=Wij_gAeo4l>1k &31deIWl`VP);1cngEV`kF&tb8Ck(#+t>fqkGBMf8 zeWts~_&DsMUdMuzEFzZxROF(<@Zz0GJ$a m*0e&EpAU1|0;t~I$wk( z1b}3m&angQ61Z_;b^<@NZn*eq1p)~MY{x~FWo4g$k`>Z{{{n5T0FHZnNYNyP%?8jJ z8h~4`LW7IL(DUp4UO TAD3`$Z zdBd1_vLa%s$I9YMxkkKRV92Qy{3GT;r2C(fa>b1~L8p&jQQp(uy-#b3bEYyHDp5kT zKaH1=p)RdhZj4w9LB$}MUB5n;{y^FGA||PgkcOi4b7*~9|A!Fn4{G_;?x_%#Nr~~L z`pT82G4;v9ERDmwfvDigTB`+$wyOIB%xohFK3a$gK9&8m_syWP3Zus(4G2BAe6b0s z60Pm0ZO<0ooF)wt0&Yjx^)}m`1VvX_2>@FEi{pZ|sze0XQAux~2u#}OCDTIU5A|0& zT`!|PV&~3|n7I)o&^jN&o3qm)ju~F2<2*m!@Vjok!;8A&y5K5+ 6Ydi}G3cV25Z}Q-K*y$mIegAk} z%NB}BS(MJ?d8C`-VGWNq&^DS}_6|D_ZXk+;b-W1Tu<{}9olgSdB^}6|cAu&LxXHKm z6Hw2<*{T#?KV}I~U-h^<)c)96|K?d{ig-%2<7t+e_7#ko!%mGg%<$It-bA;B @{V }R|j%H zx5L0jNG!74U_a{2gx(hB?NEm&j8X;hx!mvk{PKB(h@S!|x$x(IgqatWwdy@f-`pdg z_Luy&sZr?e%jAl>>YD80eJjX9i;RV|u%$8vn+h)-l*3ekz2|N7>jf7Vca(6iOJMQ( zoQ|%=R4^(<>UU!%AOO{>VZ!*jj7|(wYG{zA@=ioy84m1;?4$q`m1pZ%( Y)cBXGj6|Z_9KkGHYkr+fFF?H^X^jkAqZu6yJCQ9EWTg*1OgW@(ilN8D zi~1;gd}b)0&8mLh4XcK9*m0%g+`)`^3tZ>ADeeJsc#xs=4d!)3Ns{sL$#n4&7w-XF zUiokX>VQ0#c~a!1@)WqtMJY#kwb>asIKUwJc(B}PFDeRy2h<{?s2;I)-$#awHZo zJdMKsS?xm7+_Rk_etuaOOwieQhA)-aNH8Qk-{f#KZ4|p>?N_7jKhK2tkQ3 z?tzc!9>9^;8h&bU<5eYa;e{I1>bO(37#jaz4|u;1_FCf%TXivjVL6?9oXa~ZC6F;M zh;bENU^b#R3?}^mv=klW&R&c$oO&=*HA#ru`HC2N5rX;qhSTWv3z{E#YQA(b@l>J& zE9`&g)df^dzwy;jIQ$-dgV%WyJH *SkPO3jtgkj6O4BLzBRL8O@=+^qpNm z`Q$~rfim;s2~@%yadJkuxbEORA?z>CFT-#kUr*0Ql;m}K>5Pa&NhxCfk4OdH<2hgn z$W9Y`B#_k|Hm2f#_YMXk CE@cDe*!m8}g9Kbe>8BY{NWxi;eo@`eSaEvn{n+%bTp$TO0irp9OTf30$29 zbI|b@mcVln(H-)odR6>#HGmkHkmHv{^?s?&di##mz @o?R%F2Q zc}-k6T^O zJc3_D%Qxs&M3M6G$!*|nEvR+!o|kF>Ogt>Synd>stJavySrWx`2pX*q8+DW8)@w+y z*8WFIjk@KU4miiF8m*Dh)|Lb)+I3s6yi?V<$sCy>0`Fs(dzkxgB0gaDgJ` csl-3_Cv%wa zEP-`oX^Bb^f2+H2GT(kwm*IpInhvK$rH}NLlFFTvBJB88uZ31cj@1Mu5d9se`~H ?{)WZy-{yh$!=rhNT%hV+@M99JadSNd?0i @A+5uH@&kR%q(Ss2K(#( z@eRLlU?{c@fBm7BHcH1Sg!=x!C$7=w*p+Ts{1aj?7}x9w<6cSQ&Y9g}!Z3&VfVYUh zO=T*|noZm*Z&+Gu=O5e-JwWI*_K;bTmt;oYq^Egpd{m`ZOKqrKk`~0M KZBg zALH1-5BD8f?yC()WJ 52kl5Ehl^MAdhaA#wnHUiQhk+Y9>r4-i?v@&E zZy929{vU5|85KvjuI)l_cXxLW!CeCcm*DO$!QBZOoZ#**!QI{6-QD%8yz8vJ_8w>K z@7EdU7h^PCUDefHJ?DJtey)q8>w&|1k=x-`-W1C;kxHx8&TZ`&L4Mdhk$~MfIWiJ~ zY!@V`_ric!;yUP`@OE~p!)-9Y^6)Gga08A^{d0b?Ab1GBq?aBR*1?j`Sa?{}FQT)V z4z)9G_9+&f8as6dhoAjT3>xgV8`TH~- 1*#^F6HZ)tAD^}c46o Z>1x1s_xCW}V(-XY9SU>m9(>)#yNh!8eDy&3Zd*x;db_(n3RbHa3!73rMZ#$LB zG;6E%bDx2xtZReWc8Jj3kj##_8{9^SLb>#_nM$gUp`9&H*ecJVl4JHaFVA61H3luU z9ZCdOA&4~IT>Cca9-lup7i$**-4of=b0T~)?FN~DkrSxRzi(XGjUHl+k10{C%_r+m zWY%J#(l{9)Hl1#s*;6MMxA_?R$fhQ7#~d>Jcts@!28#h&E8+#*?$(=4x|Nl6BWW9` z%h@8H9ch+q+w!$l$4`&HKpbkvnK$(|lemzO7oe=S_f6 1w$M7(9W^MI~Ky(!rAF?80Y>K>1>PG8-HJvzV15QH-%m=9G`oNgRKc7vE`)f=E& z7Dxiyo12};4=xA6(5pA&a`-1vDXu ~gwRBeII2|eS8d8Cc14D!|A;=Gq9|7VNkw|}u8tVZf8rcH`r1j{O ze_CZ<^5K&H_Afdu5+k&vmosoL+G#D +w&GaRxGSwMx`xEk`C|2COQFQr z(zV)JYC6lB+QY#;Wh(IlFrBQ{*)+}TNN+|5Jd88OaR_I z*ZhI0_BRg(&6*lldsF4G-|VGyeg56X+cv5<<)@0P#+Q@tA2x3jQ$t_9+ls^zN>%qc zW@R2JQ(-c&z|J5~yRtWhi+6L|{~0T$a7LOSn0ui*rSE-!Q?|ksY75@W?DR;yC`66V z?>f8O-+bY_4zRwJVbMiy1vLMVbjECfO;sdbQ|}#Ig#UuE>2SJ&l|@-0zW-@`R6*c@ zJfxXGM+#=v(?nqXxH6WS0m}&nw~e*2Y66=;Y9^gj1`peMexiHkGvnhs;fodM$|#M# z7!zrLUF*Np00j2>uRwc4{BS^=jHfKEr}IGH=gXE>+fpZe*4x3}vpuf18D|P)=iP=A z%Xt9Qul@*61#E-KS>jd? Vm1TIY9L@U@bSDxI@s_LjTD?j~$oTic^=p{f&8OIN8R zaZgQETD(3_Km_BAr*2Q5QKvOx^yc+OQM=ZH?>#WH{P#&C6J)$;=MWnJNWZDrjO2E` z?A%h!mr8hGJWwc6=DKi@Y;#`w4IRMJHf1Pry4+M~Sa@G$whOyQwx(`<=}Cxcs#B(2 zPxwTy(yZJWru1mIePZ`GNFqsaMfN3L+{~;fBm`==;>;s0Ej{pBA7~GByR7o7>fF$7 zaHViaR!7>qz7t>yUUIC5k`PJ+g7!E241uhGt-?tda68+dX&`$)KLIKdZfJe~01-?E zjo)c$@Yj~Tq1pWW>zlQ9u!IPJC_7q!5J?T5;BVgSt7#iRbY0N(83+ZVe0#ntRw`8O zVuAwJvovB19@vLqn*`N1XZ;o0Jkl4?G AtWdJo5y9SM?%guzExtZhJWX9a=u&}EnSD--RJ$)nDDXV z%!B{U>~r%BXm{s(HBekOkNB`%y{+ujrB3P8y**2@Mie RA04pDp@V(k ZzdeimvecKUY-uaKDZpa#4VJqt+rVXpn;mkI6m#= zcJJ=##C)#m{|yZ=oVsS?)OvjqmJWe?W7v`|jxJ(EI)^w>xZ7ttkyBRdeL+dg=wz3$ zn5g*OEgL0>t&wBb6m*qg@8IsK1l$b>h%ienFCg9qf;xwhKCJWo^Ww*uWQ1qDdfy@6 zENFz!TDN@QH|0{E6JGggXUU`|C-HrF?Ro!DQBjm@*P t0NG8Mu4xUz_QC?G+ zbg-!p=DL*g5ao~22xE^a;=JHbQgd^_^u?oPsP5upz??WN9Q5_I^bQl|0x;2^uC}L! zotd9JXoyLO1#orO-ySWX8QRy+Dbsae?po|Tm*@7wE9I9C{=jLlx ;#-P-Z) zzDBDy&?A?p Ir*Tg?H nU&khnIJ`_5IdF-CSZ|$ZT2ExWy*iQG@AQvN=Sv5yxdN6$V3gZu$!Y-# zcYXk#etCXAI5S9O!{>uR0N`d(5)lz${f?5QwvgWi<{m`8dqxjeG|@?7d$hKG1oJYg zB)lb_xRf( %l5H r^f3gWU z9fb{r0S z-M<4c%GwbuaQ9Xd!~+a@xN0QQ;E1VZ&%I~yB3_d1c! zbApqxB2w#*QkR-0as9Erbe=Aq3}e7@^V4#vU8%1lOHIJ@yAKHx&x28Y(KtkAjo#LE z22pFY +YqWP#v6|Ur(Yl$;jA7knO!EbJ?aXR*Qrn zG9Bc5aq!oxZgB7=;Z}QlLZVYRtwo0gIu=5~Phq4o&@_S8GdRv4Hi8{UP6=Fb=j_AZ zTJUcdM5b@@04x|VsVuWYiwYF*KlUQ@MH&@b){gBRfnCuN~QwR`Yp%@^F$(WKeh8 zdNqFJIJ|<*-9Bc7?O*94*g$LfLo{W5^kQTooxq=IFJ$bY&p_B z@fDrWVaLW30@fR@@vTsNl*Ns}MJQn?T<$8;RvM^YmP5SPh9uztG$w=#! z_Z_s)fQuIg1rsxh{I6Ad9-dCYd`q@#pnC)8(wYP~{>2Kp&ohN`LZP8eDiHk&Krspn zi>f!6aSZrX*}d}pRb5{{u04bT9RwoP6L9FWVa%b8Xp4aV!`a!{#gWJ5+4JCmCu?m# z+8jXFH~K@>eOsBoL|*r*OrRAn$5d%yAmDW()*$RHwy>?g4qjM_I&0z2{47Mw+Pf6* zD!pyoy9OD78PA}(?>>ppN)LFY$~g+^>k&MW8b6!q1cF5TI9V5~wTfR#0-D&dAo(P+ zVsFOt8{5DA@!srIcx>jaQ+yjWScP6Qf7QR$Ngn7TUyw{G4*wZm%O-xImRrfFkvHYR zAJ#K8b=4IxARzaFmT}z3(lhz1LcIscP#ZQ8Cps%U`fQt`^hv=rOfHibOaK)VbE?iB zEQ68zW-vk#e`j|FhdiBzy%LVr{glt6Rlq8=s^L?07o^KW(#JPHR+n{DBP7J&1_&yN z_6V(xtxN?n$exo%7pA}vzlWT`0^TjxP~{+yDu%fd)u^98yS4~;9y&>P(QJ-;tW4}a zmsJuJ%D+!!&?K-ouJtX!P>lX)hmv@xAedr?baWRU>gAzkRs!bO ^<;i{H`wv352d)@T7f|G}Uf%z;N!>X$=hb`LR({WX5Zip_2j3 z+EFGZw*q>#u`9lq&xaleeGjrMm`rJQRgPiM J?cWcK;|5XgFObUuhA)Ao^xbH3n!Cf)>u6KHo5o=WLJ89yWVI+!^vt|jseI| z$9wY5o{IBel@NDNQBnqtRp3|5-2BCu^N#IUL*D*S66p!8Z@q(+!Wr8@S9D$zkpG#p zbByiE2(^wZR8x=FJe-l&_lo>}3hiqtfkPTbRQT52 05r2AWTpIw{HlZ+AtHQF~9Sx uF#&db (n3a%6^1p~#Lcx(Njh6-FwyF5@hT#Vp4zs;Q~oXfj%O1QF^w_ % z&)`0w<)e#zvoJgyZV+M;pL@a!7~#KsX#iIEzyKyEBh!EaiN@+z+o^tgwxY?-pvo)# zTXq_-q^3dBDT+FvQF@Xveku0a>K21H2JZFI-=QWTrCiR-1w+7oPX;VXEc%5R;g<7d zJ8=@9rzwzsf;+f2Biw?bZ)Ye41beHuHrB32&Bp@wAqWV&0YGQIGls2aU83g*9GiYA zeI`@KJG`wv<#VH}B^@DiJ5zA-w>9fx_YU~AcA@m<`s4Z11ol>1^46$}0`IYnmqM^{ zVEGfmwj*?`@QvE_(&O%J^Ytm5^Jy?U7nlq-VkR4-HjZfld$H2v0FE-TdCo*RVd0qWo|H`C`*c%Xy z2;uOFjCDsbhqL*|uPK&K3qv!g*IAR !HI zg>G!joP)u^effbSKwj-5jooLqh{Q8W 1~s((`!;+m)wzWOB6Z$H{&pDZeQw z!fgJCpTg7>AcMu@t|N--X0={cD;Y0NY{kN_(G%MLGNH$}?ony1>;%u0l9$)p %fNDozLg(iB0a|HHfZJXN$4O@skwkZkLF8 z)3<%pgvx@a7d0T=!q#!|DobPZ@Oz-aV$S`RLg3A5G%Edt|Je*(O3o5e0`V@!$cAF1 z8e&i`*%=8@AvFB{RvKd^1xM#l s^%8a j9fKBB&KaOBl0}vq6)d zkHRM0U^eG@5ViKI(J3YAw%8=17iFKX#;EJnp^4QiuW1!LIXd?2h#0&)p26>O{aAdT zzU=wbhi?DFx9SfyQoyUss}X94&eV6MmUt<*c*V5}tCIv+CbC@2aa~ef{vOMu=4R-d zn7%R%N3aL=l^@876?&)Z(u?d3Ny?OZW%lt@qc{B{M3}QF^`#toGDuFb@gesFEB=d+ zwx33hd~3a49=M$@&D?C6F6q0vx;$Kn>|9*HW&&5TjWm#Ay5wSt$wxpxjoJ8?=IxR^ zQS|s96A%)<UzBH6(f@d8D}S?IW@S32c8K~;-s-GaWMAsD21aqmx`K2>PLZNq>n z6~1#C^vc`XUI4aJNC_a7-ex#Z0T_TUA!xjwZ+{E;kestiwgFR>s~Bj %wI#@87=GuGW$NX2{^gk w+I2g5&~3Rt zv!2Xbd3I=;I*DDH^k%VqT5WY}5$bVw6#x_{?{ti#${rBGi2&;dLa7G-HwdBVxyeaP zxn?_ASs1eJ#V@AZwDdIA3B|V2gv)~tHM#3+f4nkfllFuBV9D1ei#f xzAq%7mPDyS`RU)p>tx z=CsnGQsT8_5Rya*G)=aK7^L>k_Y>pOB6ykg``Bvh8!|Zc)2iSPCfw|HfZyW+9TQSH zIyT!uc>D`f{VleS {S=YJCA$cKyBWrO^-f zAI{mS7J|p>2A!L{&-t#bYV_I&-P?b3-vvL2a?b~%)8mD6PDYO4U%!30^>+%rsaAX^ z^P+T~oCH1owL)*6efMOv9kNU#g^Cq)MU)0N4SKF|(ar}n3zdtH9lxd$e@O{HbUM(| zl*-@A(SXSE!W^N@z&A(~CeEaWMJBb|>9h%iyK#VGv1f!d^ptC4Qz=o^0g851tedSl z`8^B`we>qM<$}RKbrpVL(i%pTHn=bOi#Hp<*Wd0lqow6-qJ6$Z laz^SU)0_|8TZ}=hUAvW4h MTv8N~U@2x?EeS z^T0Yywn~rB@s&0n|CV~J1osLDX)%Okq%j 0pN=N8`QDQ+d&S;w zCK}qkJchc90H`hcvotU}a19l7Bv#1V1CoQX=kmcpK?_9+1nDVINQ6(%sv-}^i`6^O zl+t4QdR)Lw+@P`XTe1m$U-GB$T+Qf*tMi_bhy6%RGeiBrkj GOWp4}HUv@2o z1v-{ZfndKTFs4)Iby}DxXEFv#__Uae9hW%fgZGB5Zy=@6!#5Q7P*G48XG#a9S>Q}| zsI4_`1B6hhf#q*0OR5K^KvNFUc+321RJJ#AfotH}sx}L)Dpmm6*DxOEb2PO9=&KNZ zI53w^Q~b0JSBl#swx)`c=45(pA!0x@C`V4oA(0IH1+o-qfdcNkq6UhNfokf3_UtD> zT8v(^0Wt-p`OP5hl7;`xb#&U;G8TNgOSJ{wZ9R|+ZlZfKj_F{}+|WAGVX>VcLYzh( zJ3OhPn|=5Ku7Z)9*8TC)ujX2fz!`-D`_o&O3)|I0Wy6n?d$T32{W1@`qDt*YrxC#l zV9lXCMbXf}>)qSa%aGsGO(x&1m|?t-GmMv9{LygHf7c%IbGhxjvnm_Rilw(m{!*Qs zLE|&pJowN_GW|#Qfop8++L|`di2yYp0OROD_UE;hkK-=JDc}bL8;MLaREV(E2RW5w zG^0>mK;LEQ`OD61W(sEtQJWCa#q~vld7nqvc3mjSA!d%{Fdrl*EGNaIP8Bbq(>ETM zGiY5X0gaA@pFTdyri88&_>8~UiF&Zkz8a_<4dqfvD({6-Cjd=MKq&Ls=6J!?X={@B zw1}lA9h`JWT2tS$8UecJWm;y{Tyduvej%EWTQyhp2XE2GTdm<_%zIh}$Vk9!5`%j2 zHfK%R$Za7-eE$mpCX*JUS9;!D3qv3Ut--Hr7#cGO>=9f!o9pO)^fCmOo!8eA1RVa* z6?@(Lb1o18BzK@h&qe2F<2m-5eLRgToP;TZh_eza=*Vcoj-j ef{Zte nMMd- zp2u>J19notfojkl$m9w_lzF{5(#hm`H}?nNZEvEb+d a)&-OrTJ z-`+0}35J%=r&TGr1;ThN>-Y#nKkEbsWRfqrkd#`S83M1auXovmH0mutrS0sPKQ3V> z2rDGjjz?pzNq}TT86Oliyq61zD`?osL~UT$&xlVKo_vSFk&WSKI`|=ogj*+qVi_8) z9RTYDbC&D(bD>;Dg+?_dV$W31z+}aCdUvl& Dg>m4-4xd*hL~-Jn;6%i$y@6IR_>x5HpNAA%&13xEr&fEtFc z#>;c>IG%c%3Cgfmc8aL9H69|wAkZ$?FxIp@0rb|?vtMr B2FVZZ8)LI=P?Fa)#l_;9l)gj#ydZ2pR%gKoL z&?=|_hsk*IB&1gNQ~t38!H#3Q;o-M=HdQ4$_l=Z1_JACC8lMLVCFT4`t(AcRX*1F` zx|RMqtpE99wc4=b0OG|}P3$(x!%^}un^JA?NB_4EL8w%%3NN55&u3^v$^%lud9Sl( zoyq&McZdz)ctO2G|3uSPrK-AN3Xy3vo8R$$WGIu=Rx(F>WnMb50{2VZ$4JsI0F&0a z) PH>z;5+3Ih-E5>}S3DM@)^h6=QKRT27EJhLZ2oIF91i4F46zar zZ#=M)hPaZ(S &okF$7QSfUmF4gN0~R6pdj=`JC%~aIPC?La`Qb+pA5)hCVOlJl z8JwXgcd~h}TAoT*JHC0iSgoDm*mpy$K3}GK(bct>IJTnE;C^6C{=r&d@>R7$acFuP zRTN{;BwnBq)v~=UjuQ^j?R=r*kJDUQ5(*}3k>w|gaUO )#T))MZBmQeJe683Xohuao3WHk}uNwzY9n zF2qWs!+1A&Ytp3%2e$PhCnpDGVu|g5Wwq$?PnMi8`IL@5m$2rw weBz|Cu!W* z`w0dPPNJ56e7`YbL l-lK%k)@jH$-BTe{~AC+uB~R z*ayI%l9*hsDNc$${EAwa*Sa0vd_i&_loZ*!J>5NRy>1koYCnHM?W0s^)Mi^G3 7u7c3+ JPMmYq)VH0dbdw${t-B6)B>>Q7a&0^c{#CRZWtO zwiP3%1!Vps!$Ejkg4v%+!+`|cM+I*(Ww8f8<&2g7MX}7Ib98;jfK|E4w}Fj^JjS4P z{dm2JKE_m5!X=!T%nrEr{3}|i(8bEM0r;d;>7F27-o7%Xe}fDPGvB}gAC8eMsKP%u zNLf> oqobqB2S!o_#=r 4r^7^=buduXL)zwMK%BsSm81+m1&8&g)0W#m=;h#qkPar^0 zPoe(rkxNS?VlzfO!WPB=uM9mDZw45{fLBJv?_)?enBImck!Ubb`&A5tIRI|w{bJqY zwyLZQ6W~EnR8qp&=2FD}`+=cM&P-2V`m(kCgA8RVaTf6e0Yqr|qA)vc-88@{p%-Rl zmvZg=26Y1%j)QT)r2KluIy;=5xfB{KzNe_S%S1xX7~0wZ;yPgkP;uk91R6jOc?ZSD z!67Fri-|GgUHKc3Frv9uu(3`P;5#H|Nb!UC$&>46A$5+HCwyg^#8cGZrtY=Z{hK3% zNWm_Ul+#DtOYQ=USRgzWCMNwUir UGEcBO1#R2)pi{w6SM GrTZ)^k+nEWozTVXGIh7R;69j;(sZRW# jB*7p7^O&ZF1}8Tr8#C`;iPSsg3kdx_qO`P8-`ig^+9y|>L3*nc z!+fpu+kYf@qpPdkqcS87R5Ii;DzPFJBiS=H?*3rVKU)rrj^yXL3x})#GOyl_O?U2Y z1iEerY!mm%-Uy(crPI{Z{H!Ror3qic#G3VE$l{+bch(ue1F-`GR@i_B4KO(oqa*B& zLFqA7GSgw%4~e{QWf3Sf44S<@EbXYqfIBQ1Q)&Z#AzTvXvb#vzvutjYtv}(ELg0 zR+1*3P9D_#=E-PjVXa}tb^x~?Q%-K~mI(m!4o8$?{FlH=aDNF(567IIRx&i?A)M2C z80qS9j{C31%HPt*O=VKsGey0^WuERgIFy9lcHH5tYC%f)ziR=Dv8s$2U(Q?Wl_^C3 zo?5Fn1;xdk-6};@idO59s#a1;$}e18eSo0uGV&)NS`-rWhvwnY>`^N)3;4(Cm)ES| z38G&v Fyq!pHGpD z=;-Lk#)f7aC_{`~2N-8Sf!RpL@87T%J;TG(>+Adj7v1{+H&>GC?b|)*_iJg)hfrg3 zI)?P*avruns>v%xl#Ef>CxhsJPtJmsoE++@`4ByY1}L!o*Y5@<+JLiJENyc$R5m =97s z1-AMZ_@Mn;O!?O!NqtM~|6el7zlurWY$z1`|Hm!)uRpMSp__mI^Itkh`NRLWgA(XJ z|IZVELwEmgM$y09W>5axS^Dqi1rh%BL;tsp{=YgDIMvP=;4=np b_Agb2cn^zKZHx&9Gul=9hVgDiJ^Lf|G2GKUqHz@$&0Sy=aMZ@(E+#4cjQ*f%c zuWu<#FOUNx4Mbez2xwn1H0*IeDv<<7ERhD!^`%Aa`kvJ+$_!gXiV^|w_o2->I>jk+ zPI*#lEOb~i0ddzIzXNhg%3w#oChlLfs6t@>+uO~cLmh$g@3~El@PF;u+1lO~j)wB< zSzTS7nnJ1n^rPpp7T8DK|Mot1%SrdpWMu4oPs75)!JY=7!Eqi%{F}RyT=$?$Bm+ LK-6I0FBPS;ZjM6Ai5|faGsGrk|NP@u&eu_?l{V(s5SPg^b0~q21Y`+Sx zOe5ET{g$1L!B-aqc7^}H9U}JkU)LA_G2QOUZN(ztG8lxO*aG@E>|Ia!7c`guF=`ie z)=yDp%pyjVh)=)?HuU@Z5OWpj9vzTr&nqa<`2L;w6Nv|49n8%*L=wRW(}Vt>&;8Hl zBD9Kt8V}<$A76T+Ey`^VFo%g5a`?aQC+}bTF)}hrPEBR|B*Y{y=KwGGf7yvYKAmE_ ze*rcM?B^l`ihq4)|8rjd>uc;cL>C(-fF}JvuH4Jx|G%#G9)AqlK*V=u_ddvq7EexI z?*+ejkB4j?gyX$x&jxhLyQ!I)71e^(=9j}YBq^EqqDQQqy c$IC< nK!&P8r5rSW(pv+n((Xj zraUO5muo&j?phy2>A8*XaRLlVQ?#gvkE(fJzjkgp-Fnu;i>0L7-U-7f*9>1*@609q z{I1y`HnW&Y=eog(YQyM!rCW0CB0mSsYSPf8CUF%QIO)f?hy5#<;#~1lk5^7rZ`F_} z{upRxtK<(XUHm 1QVVuzZ{l%H}W(poh+>NmU~_jLk_7_XlWf-k5wN{`il4MY(8=MoTtQt z+S*b@LJoA2yLx}0AHm_N!1Qr;n&*z3&9D7whtbAaY5O>*jUcdbaH>QkaPj)E)3(+j zEzq>LpJ;pH-PkvGvBt)>Ox5l=dz%{fh0)k9N1~08@JGu~uDgRffv}XgP4V tb(1qiH}`3u(offIth603Yq+X}M3~JWk7ccR})a>7=9vnMrz8 zo#VFJZ}cZoDfBC{@|lh{wX!mSk7Bka1LKmTrXmi6R*-NtD=m*vjUjAzZ#tXI{%=zt z=TiZ>;dFKBuGV)FyyTT?*#}=bTJa_?Yx#suC{?{bT6&YB*>i-MmBm)GN=L-E<}F3T zc{Hnck~qG|nz-w0*5K3D29=Z1e(}CuNl!!%&n$wUuD>k3Ib&0j;v2w?H3TX4Y}9!z zFBlYmOwi_4yW?MVuyKCqJwU1%aZ3sI+7;6#uReP|YCrKgIofbqg37cYO80iUsS5xE zf$y!-^}&x%pZASR`*ah2&_2n1R1%a~=g$<@9kU&!6N-F!t3DX-I|D16WzdA7+NO?J zw!fCl!pDE_n2w^|;AG^gJ~__(M7TN`H`l*cB6ajh(#CKTrUI$-Vf~#aiBMcjFi97# zn>WhMuhvGK_q@(wnd)2f;b?4pvr|5|G>pVaf|qnIICPU32{C7$Y>P_JwXsV%n4A7e zk