Ecosystem Audit
Answer one question with evidence: is this ecosystem actually what its documentation says it is?
A sprawling multi-repo project fails quietly. Nothing breaks, no test goes red, no build fails. Two files simply stop agreeing, a canonical document keeps rendering a spec that was replaced, and a repo the docs call load-bearing stops receiving commits. None of that is visible in a star count or a CI run. This skill makes it cheap enough to check on a schedule.
Inputs
Treat the user's request or $ARGUMENTS as the target. Derive:
OWNER— the GitHub user or org. Required.FAMILY— a regex matched against repo name and description to define the family under audit (e.g.rapp|brainstem). Optional; omit to audit everything.DAYS— window size. Default 30.CORE_REPOS— the repos the documentation calls canonical, Tier 1, or load-bearing. These are the claims being tested.MIRRORS— URLs that some document asserts are byte-identical copies of one another. This is the highest-value input; supply it whenever the project claims a mirrored spec, registry, or manifest.
Make reasonable assumptions and proceed. Ask only when the owner is genuinely ambiguous.
Honesty contract
- Report what was measured, never what was inferred from the measurement.
A mirror that returns 404 is
unreachable, notdrifted— those are different failures with different fixes. - Never call a claim verified because the request succeeded. Verify the content, not the status code.
- An unprobed repo is reported as
not probed, never as inactive. - Distinguish public from total.
gh repo listincludes private repos for the authenticated owner; the REST users endpoint does not. Inflating a public count with private repos is the most common reporting error here. - State the token situation. An unauthenticated run has a 60 req/hr ceiling and may silently truncate; say so rather than presenting a partial sweep as full.
Step 1 — Locate the runtime agent
The deterministic implementation ships beside this skill at
references/github_ecosystem_agent.py. It is a single-file RAPP agent: stdlib
only, no LLM, no API key required, read-only network. Prefer it over hand-rolled
gh one-liners — a hand-rolled sweep is how the failure modes below get missed.
SKILL_DIR="$(dirname "$0")" # or the directory this SKILL.md lives in
AGENT="${SKILL_DIR}/references/github_ecosystem_agent.py"
If the agent is absent, fall back to gh api calls that reproduce the same four
stages, and say in the report that you did so.
Step 2 — Run the audit
python3 - <<'PY'
import json, sys
sys.path.insert(0, "SKILL_DIR/references")
from github_ecosystem_agent import GitHubEcosystem
report = GitHubEcosystem().perform(
owner="OWNER",
family="FAMILY",
days=30,
velocity_limit=12,
core_repos=["CORE_REPO_1", "CORE_REPO_2"],
mirrors=[
{"label": "MIRROR_A", "url": "https://raw.githubusercontent.com/.../spec.json"},
{"label": "MIRROR_B", "url": "https://raw.githubusercontent.com/.../spec.json"},
],
)
print(json.dumps(report, indent=2))
PY
Substitute SKILL_DIR, OWNER, FAMILY, and the core/mirror lists. Omit
mirrors entirely when the project asserts no mirrored document; the drift stage
degrades to a clean skip rather than inventing a finding.
The agent returns four stages plus a one-line headline:
| stage | proves |
|---|---|
inventory |
public repo count, family match, created-in-window, dormancy |
velocity |
commits per repo in the window; pinned core repos always probed |
traction |
star distribution, and declared-core versus actual-effort |
drift |
whether asserted-identical mirrors are in fact identical |
On failure it returns {"status": "error", "failed_stage": ..., "detail": ..., "completed_stages": [...]}. Report the failed stage. Never let an HTTP error
become a finding.
Step 3 — Read the drift verdict first
drift.verdict is the highest-signal field in the whole report. It takes one of
four shapes:
ALIGNED: N mirrors are byte-identical— the invariant holds.DRIFTED: N mirrors resolved to M distinct hashes— the documents diverged. Find the newest one and treat the others as stale.BROKEN: byte-identical claim is unverifiable -- K of N mirrors unreachable— a mirror is missing. The invariant cannot hold; it was never testable.BROKEN: no asserted mirror is reachable— the claim is entirely unfounded.
Then read drift.declared_identity. Each reachable mirror reports its own
schema, version, and status fields. A document that has silently changed
its schema or flipped status to something like quarantined-candidate,
deprecated, or disabled is the finding — regardless of what any human-facing
doc says about it.
Step 4 — Test the documentation's own claims
For every CORE_REPO, traction.declared_core_check reports one of:
declared core, active— the docs and the effort agree.declared core, but near-zero attention and low velocity -- docs and effort disagree— the documentation is describing an aspiration, not the project.declared core, but velocity could not be measured— fix the probe before concluding anything.declared core repo not found— the docs reference a repo that does not exist publicly. Either it is private or the name is wrong; both matter.
Where a project states a conflict rule — for example "where this document and that JSON disagree, the JSON wins" — apply it literally. If the JSON now declares the ecosystem quarantined, then by the project's own rule the human-facing document is currently overruled. Say that plainly.
Known failure modes
These are the traps that make a hand-rolled audit wrong. The agent handles each;
if you fall back to gh, handle them yourself.
The 404 sentinel. raw.githubusercontent.com serves missing files as
HTTP 200 with a 14-byte 404: Not Found body. Any check that trusts the
status code records a missing mirror as present and identical. Always inspect the
body. This single trap is what hides a deleted mirror indefinitely.
Private-repo inflation. gh repo list <owner> includes private repos when
you are that owner. GET /users/{owner}/repos returns public only. Mixing them
overstates the public footprint. Pick one and label it.
The unprobed core. Velocity sampling by most-recently-pushed will skip a dormant repo — which is exactly the repo a core-repo check exists to catch. Pinned repos must jump the probe queue, or the check is vacuous.
Commit-count saturation. The commits endpoint caps at 100 per page. Past
that the exact number stops carrying information. Report 100+, never a
paginated total that implies precision the window does not support.
Rate-limit truncation. Unauthenticated, the sweep dies partway and looks
like a small ecosystem. Check report.authenticated before believing a low
count.
Completion checks
Do not present the audit as complete until all of these hold:
statusisokandcompleted_stagescontains all four stages.authenticatedis reported, and any unauthenticated run is disclosed.- Every supplied
CORE_REPOhas acommits_in_windowthat is notnot probed. - Every supplied mirror appears in
drift.mirrorswith an explicitreachableboolean and, where reachable, asha256. - The report distinguishes public counts from total counts in words.
Healing actions
An audit that only describes is half the job. When a finding lands, state the specific repair:
- Mirror unreachable — republish the file at the asserted path, or amend the document to stop asserting a mirror that does not exist. Do not leave the claim standing.
- Mirrors drifted — identify the newest, republish it to the others, and add
a CI job that fails when their hashes differ. The check is one
sha256per mirror; there is no excuse for it being manual. - Status flipped — propagate the new
status/schemainto every document that renders it, and date-stamp the render so staleness is visible. - Declared core is dormant — either revive the repo or demote it in the documentation. A doc that calls a dead repo Tier 1 teaches every new reader something false.
- Declared core not found — correct the name, or mark it private explicitly so readers stop looking for it.
Re-run the audit after the repair and show the verdict changing. A fix that does
not move drift.verdict did not fix anything.
Reporting
Lead with headline — it is one line and carries inventory, velocity, traction,
and the drift verdict together. Then report findings in severity order: drift
first, declared-core mismatches second, inventory and traction shape last.
Keep the numbers exact and attributed. 385 public repos is a finding;
hundreds of repos is not. When a count comes from a capped probe, carry the
cap into the sentence.
Run this — do not improvise
This capability's deterministic implementation is a RAPP single-file agent, linked beside this file as ecosystem_audit_agent.py and embedded as the fenced Python below (sha256 5d784f7abfcb21fe…; a byte-exact copy is also vaulted in the capsule comment at the end of this file). On a host with sandbox execution, run the linked file directly — if it is missing, write the fence contents verbatim to ecosystem_audit_agent.py first:
python3 ecosystem_audit_agent.py '{"key": "value"}' # arguments as one JSON object
echo '{"key": "value"}' | python3 ecosystem_audit_agent.py # or on stdin
python3 ecosystem_audit_agent.py --tool # emit the JSON tool contract
Treat stdout as a tool result. If it reports missing or unresolved inputs, stop and collect them. If it returns steps, execute those steps in order exactly as returned; if it returns instructions, follow them with the supplied inputs. Otherwise use the result verbatim. Do not invent behavior beyond that output. On a host without code execution, treat the Parameters schema and the code below as the exact specification and never paraphrase a step. Never edit inside the generated markers; a converter-equipped host can instead restore the original file checksum-verified with the installed rapp-agent-converter/scripts/toast.py convert SKILL.md --to agent.
"""EcosystemAudit -- Audit a GitHub owner's whole ecosystem and prove or disprove the invariants its own docs assert: repo inventory and family shape, commit velocity in a window, star traction, declared-core-versus-actual-effort mismatch, and a mirror drift audit that catches silently diverged canonical documents. Use when asked to audit, map, heal, or health-check a multi-repo ecosystem, to check whether a spec's mirrors still agree, or when a canonical doc may have drifted from the authority file it renders.
Generated by the rapp skill from ecosystem-audit. The RCI capsule at the bottom of this file carries the full original; `toast.py convert` restores it byte-exact."""
import json
import re
import sys
try:
from agents.basic_agent import BasicAgent
except ImportError: # running OUTSIDE a brainstem -- stay executable anyway.
class BasicAgent: # noqa: D101 - minimal stand-in, same contract
def __init__(self, name=None, metadata=None):
if name:
self.name = name
if metadata:
self.metadata = metadata
def perform(self, **kwargs):
return "Not implemented."
def system_context(self):
return None
def to_tool(self):
return {"type": "function", "function": {
"name": self.name,
"description": self.metadata.get("description", ""),
"parameters": self.metadata.get("parameters", {})}}
# The procedural layer, verbatim from the source capability.
INSTRUCTIONS = '# Ecosystem Audit\n\nAnswer one question with evidence: **is this ecosystem actually what its\ndocumentation says it is?**\n\nA sprawling multi-repo project fails quietly. Nothing breaks, no test goes red,\nno build fails. Two files simply stop agreeing, a canonical document keeps\nrendering a spec that was replaced, and a repo the docs call load-bearing stops\nreceiving commits. None of that is visible in a star count or a CI run. This\nskill makes it cheap enough to check on a schedule.\n\n## Inputs\n\nTreat the user's request or `$ARGUMENTS` as the target. Derive:\n\n- `OWNER` — the GitHub user or org. Required.\n- `FAMILY` — a regex matched against repo name and description to define the\n family under audit (e.g. `rapp|brainstem`). Optional; omit to audit everything.\n- `DAYS` — window size. Default 30.\n- `CORE_REPOS` — the repos the documentation calls canonical, Tier 1, or\n load-bearing. These are the claims being tested.\n- `MIRRORS` — URLs that some document asserts are byte-identical copies of one\n another. This is the highest-value input; supply it whenever the project\n claims a mirrored spec, registry, or manifest.\n\nMake reasonable assumptions and proceed. Ask only when the owner is genuinely\nambiguous.\n\n## Honesty contract\n\n1. Report what was **measured**, never what was inferred from the measurement.\n A mirror that returns 404 is `unreachable`, not `drifted` — those are\n different failures with different fixes.\n2. Never call a claim verified because the request succeeded. Verify the\n content, not the status code.\n3. An unprobed repo is reported as `not probed`, never as inactive.\n4. Distinguish public from total. `gh repo list` includes private repos for the\n authenticated owner; the REST users endpoint does not. Inflating a public\n count with private repos is the most common reporting error here.\n5. State the token situation. An unauthenticated run has a 60 req/hr ceiling and\n may silently truncate; say so rather than presenting a partial sweep as full.\n\n## Step 1 — Locate the runtime agent\n\nThe deterministic implementation ships beside this skill at\n`references/github_ecosystem_agent.py`. It is a single-file RAPP agent: stdlib\nonly, no LLM, no API key required, read-only network. Prefer it over hand-rolled\n`gh` one-liners — a hand-rolled sweep is how the failure modes below get missed.\n\n```bash\nSKILL_DIR="$(dirname "$0")" # or the directory this SKILL.md lives in\nAGENT="${SKILL_DIR}/references/github_ecosystem_agent.py"\n```\n\nIf the agent is absent, fall back to `gh api` calls that reproduce the same four\nstages, and say in the report that you did so.\n\n## Step 2 — Run the audit\n\n```bash\npython3 - <<'PY'\nimport json, sys\nsys.path.insert(0, "SKILL_DIR/references")\nfrom github_ecosystem_agent import GitHubEcosystem\n\nreport = GitHubEcosystem().perform(\n owner="OWNER",\n family="FAMILY",\n days=30,\n velocity_limit=12,\n core_repos=["CORE_REPO_1", "CORE_REPO_2"],\n mirrors=[\n {"label": "MIRROR_A", "url": "https://raw.githubusercontent.com/.../spec.json"},\n {"label": "MIRROR_B", "url": "https://raw.githubusercontent.com/.../spec.json"},\n ],\n)\nprint(json.dumps(report, indent=2))\nPY\n```\n\nSubstitute `SKILL_DIR`, `OWNER`, `FAMILY`, and the core/mirror lists. Omit\n`mirrors` entirely when the project asserts no mirrored document; the drift stage\ndegrades to a clean skip rather than inventing a finding.\n\nThe agent returns four stages plus a one-line `headline`:\n\n| stage | proves |\n|---|---|\n| `inventory` | public repo count, family match, created-in-window, dormancy |\n| `velocity` | commits per repo in the window; pinned core repos always probed |\n| `traction` | star distribution, and declared-core versus actual-effort |\n| `drift` | whether asserted-identical mirrors are in fact identical |\n\nOn failure it returns `{"status": "error", "failed_stage": ..., "detail": ...,\n"completed_stages": [...]}`. Report the failed stage. Never let an HTTP error\nbecome a finding.\n\n## Step 3 — Read the drift verdict first\n\n`drift.verdict` is the highest-signal field in the whole report. It takes one of\nfour shapes:\n\n- `ALIGNED: N mirrors are byte-identical` — the invariant holds.\n- `DRIFTED: N mirrors resolved to M distinct hashes` — the documents diverged.\n Find the newest one and treat the others as stale.\n- `BROKEN: byte-identical claim is unverifiable -- K of N mirrors unreachable` —\n a mirror is missing. The invariant cannot hold; it was never testable.\n- `BROKEN: no asserted mirror is reachable` — the claim is entirely unfounded.\n\nThen read `drift.declared_identity`. Each reachable mirror reports its own\n`schema`, `version`, and `status` fields. A document that has silently changed\nits `schema` or flipped `status` to something like `quarantined-candidate`,\n`deprecated`, or `disabled` is the finding — regardless of what any human-facing\ndoc says about it.\n\n## Step 4 — Test the documentation's own claims\n\nFor every `CORE_REPO`, `traction.declared_core_check` reports one of:\n\n- `declared core, active` — the docs and the effort agree.\n- `declared core, but near-zero attention and low velocity -- docs and effort\n disagree` — the documentation is describing an aspiration, not the project.\n- `declared core, but velocity could not be measured` — fix the probe before\n concluding anything.\n- `declared core repo not found` — the docs reference a repo that does not exist\n publicly. Either it is private or the name is wrong; both matter.\n\nWhere a project states a conflict rule — for example *"where this document and\nthat JSON disagree, the JSON wins"* — apply it literally. If the JSON now\ndeclares the ecosystem quarantined, then by the project's own rule the\nhuman-facing document is currently overruled. Say that plainly.\n\n## Known failure modes\n\nThese are the traps that make a hand-rolled audit wrong. The agent handles each;\nif you fall back to `gh`, handle them yourself.\n\n**The 404 sentinel.** `raw.githubusercontent.com` serves missing files as\n**HTTP 200 with a 14-byte `404: Not Found` body**. Any check that trusts the\nstatus code records a missing mirror as present and identical. Always inspect the\nbody. This single trap is what hides a deleted mirror indefinitely.\n\n**Private-repo inflation.** `gh repo list <owner>` includes private repos when\nyou are that owner. `GET /users/{owner}/repos` returns public only. Mixing them\noverstates the public footprint. Pick one and label it.\n\n**The unprobed core.** Velocity sampling by most-recently-pushed will skip a\ndormant repo — which is exactly the repo a core-repo check exists to catch.\nPinned repos must jump the probe queue, or the check is vacuous.\n\n**Commit-count saturation.** The commits endpoint caps at 100 per page. Past\nthat the exact number stops carrying information. Report `100+`, never a\npaginated total that implies precision the window does not support.\n\n**Rate-limit truncation.** Unauthenticated, the sweep dies partway and looks\nlike a small ecosystem. Check `report.authenticated` before believing a low\ncount.\n\n## Completion checks\n\nDo not present the audit as complete until all of these hold:\n\n- `status` is `ok` and `completed_stages` contains all four stages.\n- `authenticated` is reported, and any unauthenticated run is disclosed.\n- Every supplied `CORE_REPO` has a `commits_in_window` that is not\n `not probed`.\n- Every supplied mirror appears in `drift.mirrors` with an explicit\n `reachable` boolean and, where reachable, a `sha256`.\n- The report distinguishes public counts from total counts in words.\n\n## Healing actions\n\nAn audit that only describes is half the job. When a finding lands, state the\nspecific repair:\n\n- **Mirror unreachable** — republish the file at the asserted path, or amend the\n document to stop asserting a mirror that does not exist. Do not leave the claim\n standing.\n- **Mirrors drifted** — identify the newest, republish it to the others, and add\n a CI job that fails when their hashes differ. The check is one `sha256` per\n mirror; there is no excuse for it being manual.\n- **Status flipped** — propagate the new `status`/`schema` into every document\n that renders it, and date-stamp the render so staleness is visible.\n- **Declared core is dormant** — either revive the repo or demote it in the\n documentation. A doc that calls a dead repo Tier 1 teaches every new reader\n something false.\n- **Declared core not found** — correct the name, or mark it private explicitly\n so readers stop looking for it.\n\nRe-run the audit after the repair and show the verdict changing. A fix that does\nnot move `drift.verdict` did not fix anything.\n\n## Reporting\n\nLead with `headline` — it is one line and carries inventory, velocity, traction,\nand the drift verdict together. Then report findings in severity order: drift\nfirst, declared-core mismatches second, inventory and traction shape last.\n\nKeep the numbers exact and attributed. `385 public repos` is a finding;\n`hundreds of repos` is not. When a count comes from a capped probe, carry the\ncap into the sentence.'
# Ordered commands lifted verbatim from the capability's own documentation.
STEPS = []
class EcosystemAuditAgent(BasicAgent):
def __init__(self):
self.name = 'EcosystemAudit'
self.metadata = {
"name": "EcosystemAudit",
"description": "Audit a GitHub owner's whole ecosystem and prove or disprove the invariants its own docs assert: repo inventory and family shape, commit velocity in a window, star traction, declared-core-versus-actual-effort mismatch, and a mirror drift audit that catches silently diverged canonical documents. Use when asked to audit, map, heal, or health-check a multi-repo ecosystem, to check whether a spec's mirrors still agree, or when a canonical doc may have drifted from the authority file it renders.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
super().__init__(name=self.name, metadata=self.metadata)
def perform(self, **kwargs): # toaster:generated-perform
return json.dumps({"status": "ok", "instructions": INSTRUCTIONS,
"inputs": kwargs,
"note": "Prose-only capability: follow INSTRUCTIONS "
"with the given inputs."}, indent=2)
if __name__ == "__main__":
# echo '{"arg": "value"}' | python3 ecosystem_audit_agent.py
# python3 ecosystem_audit_agent.py '{"arg": "value"}'
# python3 ecosystem_audit_agent.py --tool # emit the JSON tool contract
_a = sys.argv[1:]
if _a and _a[0] == "--tool":
print(json.dumps(EcosystemAuditAgent().to_tool(), indent=2))
else:
_raw = _a[0] if _a else (sys.stdin.read().strip() or "{}")
print(EcosystemAuditAgent().perform(**json.loads(_raw)))
# rci-capsule:v1:H4sIAAAAAAAC/617aZOj2tHmXyHKb4TtpqokBNrar2cCIbSAkJCENlyOW6wCsYpFgGz/98lzQKrqa78x82E6ou+tYsmTJ5cnn8xD/+NFyzMnSl5+hrnvv76YVmokbpy5Ufjy84XNTTcjNGLqZrNcJ6IitJI/pkThRL5FWEaUVmlmBYQWmkScRDeLiBLCdNP658yxCDe8aYmrhVlKuPAXBBBmZKSElqZWkv0kEiuO0ENWmEVJhQXZWuD6FZE6Wmy9EkYUBKDCzfIjw80qeBbUKdzQjIpXIs20hMgSzUDqvhKmZfhaYplvRpRYbzcrSfP0DW7mmv9m2XaUZETgpoGWGc4rXkqD35ME6Zy4NuwT7zZztIww0ENWSqSuD6qBOqYL8s6WCXfCKHQNzUcbyQO4m74Tu9QCo1igW+rBM1lUy3olAi1+JRxL81+RadAPmfMGkg0PLZ77mfuGTfC05St6uX4ABIIJE3gwjS0DzF4rC0plru8T2jmxLCy2XvlXzWDlinA0cAPeGyhlJ1GAfVI7HBnTht2BX8ALoQnWen95fbFKLYh9K335+be/v7648PMjMtwwzZIcmxruvvyB4J/+x2HyEX6EbJgWoHEUWsQ1t1L0LDgrcwjr5ppWaFg/iR8/3BTUgP98ix/sI7BygWwPgfIRPoyrYRmpVqEAItz0f//4gRcCmyRa4bvh+bsZIfAulpFBDLl+Ciq4FvjunVhGsCA8qSeW5qWvRBgRGWhHnCNwMQTM60cIl/Tc9c361XdCKSJsHhQCYASIxyyKa5uDpNffmxvrSniWFYPutTnRgrXr6pAqNLRW7GsGLNiEH1Ya+QQnBcjyCT/SzDfd0vD7aFEs0LDcG7pQp0OKtgQ2juxaNBjz5qaujryJIgHnhRHloFKE4oebE0kewqbA7B9h6qHwCTTPwjaFYNNiwgqj/Ox8BV+E5cDPZu5b78jkf/gDMQ/jHDnnI1TAkhnWPE8xJCQWdjha7/O/2M10J/FLZfsJCYGfAoXOVvZOjMEuN+snEvFGfK4OS37zSXzknTbF4OcaoEFCkagoOb8TGxDtgpfe8TsTVpovTs+XkA3PVkngrIYo184aCtTasqEWWNjS31ANbdG0bDfEAPUREg/AyZHTGgz4k/UOC38mWhz/U0+wRCv4/PM7scIyNP8vRIRg6ZHoEN9WUuEgq7Ucs6ftU8cariCQ7haygK1BwBJ0u36SW2343za8vNr+Ygikf/oIjW+ZgGIk/Yq9V0JxQWsK4QDay/foQf62AJgAErEgAEc3SAkdBTCO/4dJpflms9p8rb/bLNI6sNIo+FKgAe0UC9SrzHpDOZ3hFDCi2IVwgoCEuESKgIIIvOqYI9x6K457Bo2yt5vm5yhUIZr+QqR5jPILjIhwDBkSP9ukMpLVKP5Aa/AyyqpX5HkXMKnCIBhooWuDcByrEgQ33NZS8BXKCtA8D7Dr0kexMizYPsGmKNQx8gCGonVxjUMKn60whyjxqw8II90951GePhJhBrtMAUCNKMTlB12mUKTGqMgUj3T/8SMAHXLQ+McPQB28t+dNN7StJPkOzM3DyNbvaN8E+6hP2BmJleUJbIBpM0i/zxxwQTMctMFPhGkZ8dlg/bdIiuoAwOJM14YlkScRyMFKaQ3O3667pYU22QGAwdpiSNJqD0ARBvEuaKxbhgYp2gRqnfhpbiCTIqPu0XPVI72wkUB4rSJ6BeApyyGIIxMhCw1eCCH7wCc6yK4ZAYZKsCXKaNgqerO+//mwI7Ygqvw3JISBxIJYgMjO3dQh4lz3XaOxbJRpPiQzoBuW7cNzn/Cu4ecACyDWvWnZI+FsbOxab1Qn6/hGauC4+AvWf8NvFQxRUMNCM45csJyJSgmo+Q4gaftaVqN/rUdjBQTH2N6/LtnkRhCBERG8Q5LXe0ciLOx+SCS0ye47sc3QixhRIw8iNnWhcqK4boz4q84A+sABUOb02shRLQc8arm4akIaYL0QT3iyHCjwIXr1L6jiQvYTiYYpCMRfCGoDmoSPnWmgICQ+1HsrRs6wgSQ80mObwTXqEYSLyHgoDeIzF2EyJBdOGgUBnJVZSeCGyH8Ggaqt9a3yO26MMCsFrKlpQ12+NHj9M7Fw3BpW2jqDYXP9tyen+A0v8R5Xn+ARXCGhnIHqvvWGWc+GleVajZ8Qj6bv6h8hAgJMDhYLCf+fledQ0ysc46gCIcgBfMWAEVpZESXeOyFjLRB+RSguwVLmWxL5vgX2haD7RIj4BiZH4fIsWt+eakwIGjpQJJCVmuyEkEABqgP1LQCNMHlNMWaD4M9PXUudj3ArzheL38bzzV8/Xv7rT6ab4JoHP7c/Xv788UI8//yBqEMbsh3oBGbb2JxYwHtgQl7cECFA5GoKtRvJ+8dT+r9a/y+2/njBmiEF53bNNdEdbH09xRhgI0DRNWAYUDtRTmqx+9lUtQblINHN3KgDJkW7saMcihugxtlKa+aEotMNn4UyaVh7FeWwPbgd/RKJnYfdN3nYMOCGrz7NGEPxjkKaeCP++7//KJ/++BFCHCK5lxR1FrBPUKBK32PIh3egA1AH/9R+BTs/LfTNQMjyIcae/2wpopFdk50njUYKNbv56+/v/enP77GVADoFf8JZW8MR+AhTqI+X1/pqTWXgcs2SntdN4M9/pdvNb49m6jffBRLzV6rTXEdt028YlP76t4+XJzH5jQI5xPcLnY+XvzfvND0JvBA+Iu0fHy++BlH78fIT3qrJxW9sLSNPmstOlsXpz1YLSPx7bSUEqE2peAccbL2/v7dQnX9HLvh4+dfr/2WF0f+vFdDWwIGA0mH2J3Tr3QTykP6p9s0rBB6iPX/t/Bkekk/PiN/mOgBYlgPUfT6jAspVw3Jfn9S1DmHMyMDgrabEo7IEvH4VoMj8bMz6SSDATazvBOXR4zzYGODUkxc9qFpdp+quFucNNFTWOdEQniDOCgXdAkQHKI1/gfi6E68RHiiyWfPZGqbr0H2QEJSTtWgooX6O4PWBc8QndBQm+ukTE/1/1s8R/6xnBCnxT7j29vaG/6Lbn88BwCd6qK7duFbjovn6YOhN526g3gO6fDd8ewwCTEgMLTQqopb3CHAkrumYCEifx7QBG6d+9S9E7IYhauvBfk1B1vwCtZsNH6klPoYMSCLurkzEPF09rycPdY/xbfpA1NMH4tfpQy0LuwUJejb42JNoR08+/Wj1EdUGhW2QQ3zd/Scy6yp8Fgr3yy+fkBs1v6qzABOIOjPQ05b5Wx0PcBPCH12G8gs3HhdA8guYDGpw9ngWS/ob3Pz7vz6fHPdRp1D9Qg89CCO8B9YgZooi1+TlIwS2iNqIXyPqgc30E5shZr5FLcgyXdTJu0laQzW+/t5c//x9S5G6Z2jL4HELeviHi/GYqk5bzAIy3PPWnTMgNA5hNGZKHw0pu5hPl/z4J7H8xQO/Nju/9GnPARdUb99Mm+5vM58ov4oB7hT5t3o2JOHoAQaaIXYG6v8i8TlWes6ccDMwcRvQCK0CN9ph3dpmz04cN1xotIY8gpt2UGW0WYn88ue/9WuY0IMN87Bm9bhNensjRNTDfan9vcdolMTd3aMxcVPMSh7d5jdzQIuKWDuyyl9wcwd6Nd0dqI8E/qogwNgjD74J/7fVvzpZdPuJjjnyZmg27EhBWImoWpNs74/c/K22QIZYIQ+Sv+Q/1qyj5TmthLhDQ5BAQ/iNchphQJ3vn3WafdYxB9DNfnXKmI0g5v1k1rBKeEaUEAl+yESUzPbdOLa+iYMAQW13PbHyXWhkP6+5lmgIlxG6wNquCQD4+YqSAtiShcn+J+6CPyGy0HbMZ4I0SfcwH/TMWmL6VoqbddyMamFFODng5xvADDyKh2/1yE3ToxwN5H5JWeYhS0Fx+G8jij/WY966aUfvTUAvPB/5NuxA5nxg6pd3MAHB86fPpyPqbH0k6ONRjNevRN3//T590meBbXAXj+3e/5MAAHCISi15u1sJBGCGuAFqO5AARLufc2dIjafkWirKA2RtJPs/JnDdwIAb6umTXnddEOWxm2h13Xh0xE1N/x9VfKoBBRHwDb2mP+cFX+0+tO8PcXBbt0BN3MoC6UHdbq3A9zHVL0s1MzOQjXPp3636ZLhfk0vtq/MlrNJNsVXqCo4Gr7yLCxye3D7b3qYPwY0KXC6SKDz/hdABvlCFh14QR9sBNb2o0WzoDkoPC9EM2AzkDFxJckjbx85RjNWja+LHx0uBX8b9zdf0CjW8WGNhu1o+XfeKlcGXgBJArfvx7NIecynfBaXQfBqKiP31eBgViFZhC9a59jXR/paxeIEQAPi7p5skwVvA84bvCfilM+hv5ElSQwhqL9EL5jux1ara+DFkGXSjj/wUQyT1l/6xwcNvc0DIu7hpttAQ+HfdaD3PxE6pIb3mfegRNA1HePkXQDEbN1u/7+YgresH0UIBeiRJLd/G6v34gaSh4VU9RbD89x8/0Iz1f6Dnn/BcguhiU2CacbyWIlGYX3Ta7XqgohEU84YqHPEJ8n+ieT8xqWNYj8zqxw80HamayTbeeJbkQLdr038bR0FYQyaY9ayxXrWpC1r6mH9gCHhWUpBc00WInRjFKRaJVm1Gn/XQARsdRzuuDK6JQ9m0MM161rsQD6Yh2hqP/vgh1znz1jBXPFkCxESG+z7PIv4b94P/63+ca6Hu4SNELqujAJTAb7wTn1NeIVp4mtX6B76Gen145/PJKRtGjsYe74Tklnh+7KCGFYVkk5g4uJuxWxRluHt6J2QXnyXUVAX3bI9qUofDc+yHAAhta/9AuhQlMz60qfBs7A0dgqA8eIvzFE36CzQGwh2MhioWagCasf9j7O64UOERSyihTvjVc1yAQSRpjFoHBcYu3Bvhkz/QUK77gtp8AUQLcYEu8Bu8XnMrr8/fMCXBYtBBjGY8J8U/fnC4+3irh38pxFnydKCCG8C6OXmOEQ2UmuAcCkIbdSwxptayhoC1DlwEM2g/RJgHupXUR0TwXpJUyFoQI8gU9Uiw4eqfII38GpxCa6ud3RBPCPFwtDlEQvbGgWMZbopPSp5t0hfIo2k9otP19jYoNvEU4TE6bDa3+3USWYNsPegy8SJakkHaNHU28iCpMdXRiDRAmPJE0neCw5b9bGj8L2I/mxqHZmSudav7Vh/BMjb4AxW5up/BxydIGMbEcV3qHkn9HAuhTH80QAQaV/oEUggftSEURYT2wUYelA1N4yPgLJgW/r57+sQDcHSGhAV9a5zrGvy7HX0bfTenhGH1Hwe7qLa5qeFHaXOOw2OOhY9T0JD+G9lqhsCfTbj95oa/1X79fJ4fgi1Q6f4+af9PQh9oCJRVSxDoPRj2c2BRA3IIQQpvGG4t9RuN16MIzx5ga69EXaefd9Gx6id0ZJ1ur15d+RrwmV/jfesJSdjN6bc5/+MKKFYgIH8e2FhaPfWuz6/rs+rvB/54ptsQNQsP5R3Nr4v9JdLfiUN9xv5g0z6on77WpKSpIoD+0EfhwYXmJk2I/Pgh1Rb71kj9+PFFxvE+Uqdh6uikqgnFRyuEBo4YZIAt1aQW885noxE1B9P4+ToBvp8Z/UrO3okm6sEDt2+HgkgkbOXRnX+pnT6+HvjSua589fFO04u+fttIfSj61Y42MWyaddvIzZE5a93qQ/rHXMtNmma4OY6qyccTVlENeYQGQkYkrt4onnUlVh3EsE8DnUwhPuhmzVknVIZc85uNbetq37RdX/uCoAdYfBxUwMae6d16tmuA0FHTyjw8gPRo5tb4GwoCffWBx0EIGkFCUzPq2+hIBTfnIWrAvo7tG93Gv9BxTF5xWfvS0qoJdQJgd7O+Chr6hMUKogyPgurpx/cweZwQ4Q9Dmu9b0MAdMRCtOXKrz5GhOdfwhy/1LpEZUB9d2/urLQXel/5npZ/tw5fOcD1pmBEm/c1xbeIhZR805QEX6LiVwCdPeNm0Dm9UIvC62K04qTdQv7+P8wkN4jR5mAQysD4oeJypPOZJuA/H4wq2aZeaLPnA44oAfbj0+1ETOlLA+4Lnv/VPGFg2j6M69PsCWRMD4NcI9Jk32SOM8YwUKYeKtovPXJrx5+uz0Xv9+qzpI3x0s79OxrLobD2O2K3HmeEDoDACpsiJiEsBElrJz/r9jxCP1H73tdTzwyj0wQsUXwTOv36W9dCnnpgBADZH7SKq6di1mI80bKvO+qwekaKO5ZMedL+Pduuq+QRUaCk+HYga0AcPJr4ewWeqDfrWNApNExvMR1/h4OkJLlmvNQ2qo99AfDtssAiVeNS4oi+cQAMrTK2Xny/SXIHfUUjCL8+jFvwxE1wHigJ3IKbSl5//eEHwgBAWfRn1j3+9vjyOBOvvpLIqRjIiHfV2L3Ab+rIMETH0KpgZGDn6CZIuKsDiGdRA9A3VCADvFU8+X4kDeMp6+Rd6F1ES6HxM9Ao+7UQ/6D0G3pgx6Zyt/3Ct3l7rn/p6GS+G954ZiezE1arD6kaLZbpamsqqKqVOLsuqq9IcO19Epyy9HK9jfnfVZ4JHKxdWTtcko/SF0Jf3segKKeWcFySrB2Vr0stjJW5399q9ZwvdvZmTZsDc5r0+v0ioUNYnes8U5ruFZUl0og0SetwPNgd5eRi2yDZHSTeNMgbtdtGh9ubqshponXIf8JRC331l2dOLtd2ykvheUiqV3URaO43vZDFn496lOE0ZIenkVmCouQZir4mam9MJkOftkBau97RMKel4bR2N1WzlH495p+xYjpLpx/JImYHKaeVNWV4A9+9LJV9U7cv2FlhmhyFVKlk7dpAvQ9XIe4eqH8d38zrLDvSd2hWkas9u8XUmX/ZDN9QXmhnsda8lOZP2vRD21XjFHGa3TXZk+O5+f+/RwsbeJkeejlf6jBmxu9GwxS3Ubqz1dxTYvh+owT4lD1JkTexOtjP821K3kyI53Mkjz1ZlKE98e81OeWM+HTPasionHXOjrjKdSS86FYej1Z3M9+N7a+yHe1/V56t0vGOygAaw74h909/R49Vpex2syNPIurLZRBQYL6vo6wIUoY7rE8vMUp0X04XK5Cq5Dm8572/JrNyfLba76nc0YbZiRTsUQ7Y881LhVUd+n4UcCb7pJsfVaMVshhplT2k75PaKJymiQRacPKALR0z99pwUdJ2F92zVzQLW6WyqszprdYerdH/khdtU9zKbPt3H3HpM9m7zw0EW/EK2hkXZC7reapmQM2FjyakOcXq9nLLjWKKX+kkrBM/PF4vInoRRAI92stNW2ArObcunHfp6iU6W07E7kduTAyrxl9vxNmoJZR6xQqK1Al85+Heb63G7yT24e861mGziKosOA+ZwH1x6re0x7w5oe5CRKs/MojV9vMa+M5uuytbg7Gfiineq7mw3czpXrrc8xJoeidv2gvXo0YIT6SxRlxEbjjonqtybdyuR16NEOq1aE4qOd/m92pNauxMqJ49ci/zhPulWzFxQNiszDuwqYchbeOkzw1BwerfFmPUcZTA9TodW0L3sXCO3LGEQGioEFBmOKk8NW6201fI8wy2HLJQAZamsk/PJIgtpG9g2vRyYt4RU6c5orWdlyBSeNBhleq+6ra+5IyxMQeNNr/R3ez33EkY78sOxohjq0u1ehoWo99TlUbuTehmV4khXi6EyoCZSRxYn150/tY/RuLr2IbEUvjDtmUyRV6sdrwupaM1Ma5DNucCy43kYJtTAWnhkvvBaK9m3c6q9aq3XU3FqHlin70q05J8s+tINqYM0PwnL8jgqT1yL8UxJnNijvnRW2KMnnZwwLo3jrGq5MU0OrNFgB6pY/f7A7CjBPDUOuXlcBjqXniFBluAFdhWwspyylBcrXBU6ZikldiH4HTuwF5fgzKt+79Sedk5r9qyTp6vjdczLNRV25sILVau4tjv6NLQ1Rz/qnHnIdHK7T1mmG665Yd6JuGxuJY6pW4dwHQ83RmT6QWfF9vk0ZEW3y1u+t5zpU8qkCn7b3ZJljzmQvfycisMRWdxYTryvMpHzqq3MnYxlVbide28TDyVKzlojyvJ2Oick06Df0/JAU71qMDhMpytp37sfJ7YdrBfTzOyM92VrmJ9VGcCLC26LPVVqm2t8VTZafJPMZWJnyx2zt5ngZqzK+VF2xJ3O28fW1pplSnukMa69viwPZ6ZfcdAz3NyuocfTEa9utfZ1tpjuzOkgHw50x7hvp/Nxm4wMuhj72YguoAif3Z4xt+fHQXE5paeJl9xU1j6l7q7cJN0o68vkzRG6k8um3xlfpvp8ZhUDybz30ruo6pGc8Oelli3UKYRLUR4tsTsWW4F6stoOtW+pw5RMu6etOptq1K41UjiD57gWT55ypQLWNetuL9fsspt5fHIYXc4qm7iHrtbZK+pxeV9z8cQShit+dqrEVBtsZXNOGVR0VM7xcrkZqmfHJINusvCtgbgdbtvs9FKKc0Belr5EknI2TJWKB4k5o0XxMrH1raUF7HrcGZ5yz3b4fDFpXQKuq666Cz9OxLN4Entcm+Skq08enGM2N8xNlpE8uVjkFTWtGH0281NjnNsqTfdToeq1A6ri5l41OsZV/5Af7FwMuifd9wr6mrWTW8X7I5MTqLCdrfvqMk1PghJKx0vArO9Bt5QPLqf1p7YentTMm11nYrQ9cn3zyCpSac+6ORSWGXtOt8lwHqyoLZ2RLbkrt2YxaUfu/TZaSqk4Xe890SrW+VHTTdqIc9UIudGY2q1d1lekoB+lR5VnVcdTdYZh+GqinduqXIqXUb45zMPhVHDXZ4mfDxbc8pz1+JOncawqUMFhseVuXprRMdmyj/0BeTueu6Qdj5hNGh+7q9us3baU9F7ceqS0ds7qeMufT77nihwZ9ooFc+tIWdJdXLKeO5DkY/8kW3LUGi5MzmyLvL/yq2hr7ffube1umfatKg2Bm1zHA8m6pWVglWv+SK7d0/EIrIib3Pk072kjcVMwjHCd7RbrfVsWtN5Co7OdCXGhD9cOBJYyn1Dt/UbLo+rSDaZdxczD+NrNtX6/z3RbrUAfTbhLvh+Mp8U0XUhT22RXUVxdLuL0JM9SWlLnl7nIcYrDhEWl2pfTVTAnW2tc8Z1tT+4vSNqwZL6gk/PMTkaDYqLuz0evy/faZWB4wb4c5EIv9pn0xOW2HbY9u3fp01q+LcjhwF6UfJ+WslzVyNXRX3SXo41ftMJgFJP0gAzscjxjWlmRZrbndPlbeyhy1Wi1WWTKiCrswbCTe7s8ybIO322bs/ZyFW+iZTrlPXJ0Ss344PdK4WpIaaXIHb3lkrNbq0VO01M14w4upTmtwvCv5zwyZtPbWT3xm+XgthAKxhZIYyDSRpazZhyvhiNlGRQU52W7IFmO1m502mxPTsxbJZesuPVk09pIrrJgTzepp8zKiJ6sh0NjyEXCcDyPfL/Labtwk/nzLTunKQVIwmiylcVFuhqyt16nvBcp4PI2tdfXaBdtq8lhA1SXV52T5lj7JL/P3XG7G6xL7ZaPbSm9pPfWdUdq96GlcLY3aEWd1VrWA3Xc69Ilv+3cdl7PT1Oq52/KfjeurrNhx4EKOuQohu3pumi2dWlocH5muv5U3IxpUl3sZGU56eiXK5MMl6v5we2dIOfveQRQcaapu8LI2/ZouzuH/Y1mWLd52NoNN7eMBGJNVSSsvoxU0k1Um2RCfmYOnXbH2baMoyFKHbM1DLm1fWVmx9aR6afZAEhZZ25ew3wzHtimdKB0dpSzqxXjsqJQtaZ2td6M1tBMkAMzYka2QRqG3dHYDkkl3DRdhtZ5OqP6I3IwkPeU7l3ds+JeFIEcCx1LEQbXYD1X550xt81nqmCngTe5+Zpzvak3e2LLLK3NZ0XKL/v0ivTH83lb0IB8DoWN5ueayEfdsgi1wabTkgHiGEuhjJbszJfibGFopFhep5ueG6ubabvsR4x9XFIaO9dit38zV2UlnLkRFxSxl49pOouKxPGD1UZxdLr0zmmcxUcyy2b71CnUI+lkq2TkkMN0VjC3fHEhhyv5cm/taMmDKCt7VbynluLxvrKD2SaH6mdetF2r1eFt6nZi5cgZ9/yA2Y0W2xEl5VK5zuKIHG37onxendomyapaz+HJNKQZ5thJqlk6po67fXZlrhtxb3j2KfKD+TQ+dsijBY3D4Frx5cUMSKYV988nIRwKpR9ynVicQ/WWtDbtKAsp3NHc6My06Jt2no/YxFqbM3de7juL3kylXWXf1hftgGnrh7VkdZb29S7a7d5Iu9P3RD+M5ylQR3t43s0XajoiKfamOJuOEFLByCnLhXFYj+7TWJiW0fZAKT7XnQtVkYU9qqNmhxFHmuexcp/l6n51JSu3jyDrerxI/Um1McbDqxW2Nqnldc7tsNqkzJxeTdvWluWtSjX0gT0/Dz0jlpaRaUrU6p7MSkUMN2GWrvvVvCgpaSOG40WyXfQLCShVRx/LrbiTs2lX24cj0e5TcjS+LbXzbFAk/Ekxr9loIQ+6K32/WUsbylzIwGBVlr5dpmJu0/fDKS3olk9b7Eh0vLnbGWSOcxpk2Thyjxn0cWZhzAfDebwW8vZlOAxc0mhx+9X0yh2MYG/RvDFc7cnb1Kb72qYMB6MtKQiH45wJlP59Jwh+Ok4oz5fvW341rrJrkASlGbA71d3stHC0uwzLcW9L77OdsN62omrMijG7yZdCe8v76uI6a1f6wg1WvidWQ5Hv83ZL5EYTvtViWouL05nqai5OjvyiHMerOLu0hivw52C+vh+oLdu1+5trxgTSQdTWcur0q/OiPJ4mcwfCY1eMDLUEKHFvw7TPDfQymbq7iRmUg5vHtvSjofXP1JZjO1Y36/Rd9yQHl7mTDWfSmAso12hLQk+mp5fCJA3PEserRFhJl5AlldaE8Ww3XN5lKuDWQXJv8X3t3t+OJkbOmH57668M8nC+9NO4vcp30sAlPSPSw+n52j1wimsuo+lhNI8HK2UzL6n5QZaZjlpMqmBdtLr3sd5aTiP9GrlDql3dZtwi6vZUc5K12/xoO9jMvPJ+ntDnmznmILvuA92TWmth3J7l0Xks5luWOndKbp0N4qzcXi/LnNnoJ64t9YtspFsTfnxu769d0Vls2ADsY4ZBBnmycn2Rl7YCW06BhN1n9taSaf3Y7vHX1UnR430uX6r1hBKVm9LpSp5Rje5J76j1Sts4JZ3tmI68fjDgWEpt9ZTLxC2Twh9N5aXcHt1n++PR3AI9CELpdqIP++lyXml7aj1hq7t0my/8lkKHqcR5lLpahKdq103u0pxjwg7ZK+SQ9kNpp23NNIrng3sq+NOErkRpkA6EIQv9fN5bkBNBjU4Ke+AkQWqLxznvu3ooZhKlBSlbypUoByUf9IdUki3v4ohXTtE+5BVNbg+VeCgc2my4Mv3z7nwt/U5/d9WkVXvnJBP9frnz/qWY+plH6rzSCf2siM/iXpdEKhBjfUFVF6HaMMnB71xGQlFsLtGYo8Uee5+2gl1L3ZXnNmVUO3UpnC7l+KSeJ2LVp9cnZulEXf8+7BnqRC6MwDuOxFW7Z3e8xbafloNVNzyK0TjyRPK0yQ7uRkiEC6XvHD7anxXK3crnbakM2jEXeIe9fWApt31YMNXx1k825UamlsJyI9oi2S7V8WU0ibi+Th+KbBf27yP7tNPp2Sl0DHHQHvL7VcC7O1/oXYXjVSh3E2qstx0x75JSVxYc4RKKUyGgFhtxpXdoL5ukq1FP2uvpTbkM1XR2n1f3JAoMTbhyUveYs3Or2irMlW27BdcfZgeqH+w6M8FzxtfLmGnP2pP2vWOzZlfb3LaHKzlxqmAkBcxkv5ovY67MLlelE117PmMAgHqnRZ/mjg5zVvPrOqRb3eUt7FAzhtmTw4CedpxZdtEkJx8PJ7pLCfvZjC3K0NyeUvEyK9YzdVBwHeVeuVUoVcm+r+96Nyq57S5Lhb4G9Pxm8XFvFpyiBeTF2bysGcntFDdnslNEM5xenY19X5mblE/pyNz7y/kgiUK2R6e7cZ7zm+uos5/vt+K6m61HHfmY78uRnTDD1WZ9uO6CtVNubmVquhLv+g6nM316HCkD7zLsArtdjS/Z+XT31EWbk1n7MDgx+2jXTq3hKtjx1DYdInqz6fcE5eANfJ9XJYUNeu25PFoKVjtqFapw2a3mu2QSX+7OXHXviV2e2eGA7s3yKp7n2VGZ6a6bQ48xZezD2KrKeLuaXqLTjhsMtZKyLuOxY7v6xCTR5MwrNDVXx9SJYw9sQi+LYh9721tnY98u2uyYDE8TZpLxsbqNer2J0lq6XLnqG7edXGaOfzsmp1I3V+di7e1bW3fesuZuq0Oyg+Cy4EpXCmVlMoGG3R+VnZ581lhZa/OWObisE22y6Ef9Sr5Hiy3T2ZnufBUNZ96gndFWNdmkg0l3x5D8ZXCaSJZny4Gnc8vt8Cz41vieLB2N1eV4Ppf27fOyZy9u2/u0E0zVvVPMlrkasIVg7/t3836hu/erkUk5N5Bd8lZNwqO8me35sArPPBCVm8zfd3TH6SVS0PF2vap/PDM2cxmVy3Bjpy1aNroimd0LQ06O1MXKZG9CtbYZwEU80hihuxmdZsZRXhp0e+iBzW7Le2t8p2+MVVC73WKRMSdyYQXD9oLda15X9szeXUxjwxV6wrp9LXPTvM9LV+iT15Z9iSzvcBtMPWAWKpexI6k/30/HLWdUnDeesB8b6+5R2xStSOBLR93awel6OivhytqXEzWYOmoyVIVkMC72QNtFUliz7MvrCzoibeb1j3+3AlfrY0G4Zlm2ptM92jRsazjoDfS2plG6xfS6FAVXNGvQ6w97fcPUbWNoaX3TNLr9jt0edq2h1bW7zfg9usEioQGr/O0FHUP9xEP4n99WNCL02WxW33j7X/gTqZe/v74khgtqUO9tpJWfn5FOj3OFN605WGj+KQj+1KnMHv+8OtPOzT+6bj4zrQWBqH/9H/b1l1qkPwAA