1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 06:58:51 +03:00

docs sync

This commit is contained in:
Anton Afanasyeu
2026-06-23 11:02:33 +02:00
parent 0b41f5f980
commit 2adfb2fe2a
42 changed files with 4333 additions and 691 deletions

View File

@@ -279,7 +279,7 @@ endobj
endobj
46 0 obj
<<
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Author (Android Cast project) /CreationDate (D:20260623103622+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260623103622+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (URL shortener service \204 design review \(DR\)) /Trapped /False
>>
endobj
@@ -553,7 +553,7 @@ xref
trailer
<<
/ID
[<83c7ff4a46a2644aace09ed6f7e081cb><83c7ff4a46a2644aace09ed6f7e081cb>]
[<0c13123401870dc3636a59ca6043fbfa><0c13123401870dc3636a59ca6043fbfa>]
% ReportLab generated PDF document -- digest (opensource)
/Info 46 0 R

View File

@@ -498,7 +498,7 @@ endobj
endobj
79 0 obj
<<
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Author (Android Cast project) /CreationDate (D:20260623103623+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260623103623+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Platform scaling and multi-environment architecture \204 design review \(DR\)) /Trapped /False
>>
endobj
@@ -933,7 +933,7 @@ xref
trailer
<<
/ID
[<7d99cda1a76db27f24dc235d81baf7e5><7d99cda1a76db27f24dc235d81baf7e5>]
[<633c08311ac4fc3baffc5b7f2ceda12e><633c08311ac4fc3baffc5b7f2ceda12e>]
% ReportLab generated PDF document -- digest (opensource)
/Info 79 0 R

View File

@@ -324,7 +324,7 @@ endobj
endobj
51 0 obj
<<
/Author (Android Cast project) /CreationDate (D:20260618174832+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174832+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Author (Android Cast project) /CreationDate (D:20260623103623+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260623103623+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (RSSH routed egress \(dev-only\) \204 design review \(DR\)) /Trapped /False
>>
endobj
@@ -663,7 +663,7 @@ xref
trailer
<<
/ID
[<ed6e2cbfecdbe9d49d63d210ed176bb8><ed6e2cbfecdbe9d49d63d210ed176bb8>]
[<007c0311bfb71c5beb7d2970ce25523e><007c0311bfb71c5beb7d2970ce25523e>]
% ReportLab generated PDF document -- digest (opensource)
/Info 51 0 R

View File

@@ -319,7 +319,7 @@ endobj
endobj
51 0 obj
<<
/Author (Android Cast project) /CreationDate (D:20260618174832+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174832+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Author (Android Cast project) /CreationDate (D:20260623103623+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260623103623+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Codec2 / ultra-low-bandwidth voice \204 design review \(DR\)) /Trapped /False
>>
endobj
@@ -657,7 +657,7 @@ xref
trailer
<<
/ID
[<c52c2dec229f7f4121897008cb63143d><c52c2dec229f7f4121897008cb63143d>]
[<c9beadc7d306c78320e68b037811d0b8><c9beadc7d306c78320e68b037811d0b8>]
% ReportLab generated PDF document -- digest (opensource)
/Info 51 0 R

View File

@@ -4,34 +4,30 @@
| Field | Value |
|---|---|
| Author | Anton Afanasyeu |
| Revision | R0 |
| Revision | R1 |
| Creation date | 2026-06-18 |
| Last modification date | 2026-06-18 |
| Last modification date | 2026-06-22 |
| Co-authored | Cursor Agent (project assistant) |
| Severity | high |
| State | approved for planning |
| State | frozen — superseded by [SPEC R1](../specs/20260618_repos_reorganizing.md) |
| Document type | DR |
| Pre-requisite to | Gitea org `ac` repo creation; post-alpha full MS split (alpha may stay on monolith until Step 8+) |
| Superseded by | [docs/specs/20260618_repos_reorganizing.md](../specs/20260618_repos_reorganizing.md) |
<!-- doc-meta:end -->
\newpage
\newpage
---
**Document type:** DR (Design Review)
**Document type:** DR (Design Review)**FROZEN**
**Specification:** [docs/specs/20260618_repos_reorganizing.md](../specs/20260618_repos_reorganizing.md) (normative implementation doc)
**Source draft:** [docs/drafts/20260618_repos_reorganizing.txt](../drafts/20260618_repos_reorganizing.txt)
**PDF:** [20260618_repos_reorganizing.pdf](20260618_repos_reorganizing.pdf) · Regenerate: `bash scripts/build-all-docs-pdf.sh`
**Status:** PO-approved (2026-06-18) — **implementation phased**; monolith remains deployable until each step completes
**Scope:** Split `git://f0xx.org/android_cast` monolith into Gitea org **`ac`** — full microservice end-state, VM-first cloud path
**Status:** Frozen R1.1 (2026-06-22) — accepted; **do not edit** except typo fixes; implement per SPEC
**Scope:** Split `git://f0xx.org/android_cast` monolith into git org **`ac`** on the **f0xx.org git server** — full microservice end-state, VM-first cloud path
**Related:** [INFRA.md](../INFRA.md) · [BUILD_DEPLOY.md](../BUILD_DEPLOY.md) · [specs/20100612_1_scaling.md](../specs/20100612_1_scaling.md) · [orchestration/sim/cluster0/ARCHITECTURE.md](../../orchestration/sim/cluster0/ARCHITECTURE.md) · [examples/crash_reporter/backend/scripts/gitea/README.md](../../examples/crash_reporter/backend/scripts/gitea/README.md)
**Related:** [INFRA.md](../INFRA.md) · [BUILD_DEPLOY.md](../BUILD_DEPLOY.md) · [specs/20100612_1_scaling.md](../specs/20100612_1_scaling.md) · [orchestration/sim/cluster0/ARCHITECTURE.md](../../orchestration/sim/cluster0/ARCHITECTURE.md)
**Documentation index:** [README.md](../README.md)
---
---
**VCS clarification (R1):** Canonical remotes are **`git://f0xx.org/ac/<repo>`** — repos are **created and owned on the POs git server** (bare repos, hooks, ACLs). **Gitea** at `…/git/` is a **read-only browse UI** over those mirrors, not the system of record and **not** where new repos are provisioned. Ops mirror scripts (`examples/crash_reporter/backend/scripts/gitea/`) sync **server → Gitea** for HTML UI only.
---
@@ -43,7 +39,7 @@
- [3. PO decisions (locked)](#3-po-decisions-locked)
- [4. Current state](#4-current-state)
- [5. Target architecture](#5-target-architecture)
- [6. Gitea repo catalog](#6-gitea-repo-catalog)
- [6. Git repository catalog](#6-git-repository-catalog)
- [7. Public URL map](#7-public-url-map)
- [8. Dependency graphs](#8-dependency-graphs)
- [9. Monolith conversion steps](#9-monolith-conversion-steps)
@@ -52,6 +48,8 @@
- [12. ac-workspace and OTA versioning](#12-ac-workspace-and-ota-versioning)
- [13. Risks and mitigations](#13-risks-and-mitigations)
- [14. Changelog](#14-changelog)
- [Appendix A — Git disk paths and remotes](#appendix-a-git-disk-paths-and-remotes)
- [Appendix B — Optional HTTPS read-only git access](#appendix-b-optional-https-read-only-git-access)
<!-- /toc -->
---
@@ -60,19 +58,21 @@
**Question:** How should the androidcast monolith (`git://f0xx.org/android_cast`) be split into independent repos and deployable microservices?
**Short answer:** Create Gitea org **`ac`** with **platform libraries**, **microservice APIs**, **thin backend UIs**, **client repos**, and **`ac-deploy`** (submodules **`ac-scripts`**). End-state is **full microservice** architecture. Delivery is **strictly ordered****[§9 Monolith conversion steps](#9-monolith-conversion-steps)** — with **`ac-ms-identity` as the gate** before any domain service split.
**Short answer:** Create git org **`ac`** on the **f0xx.org git server** with **platform libraries**, **microservice APIs**, **thin backend UIs**, **client repos**, and **`ac-deploy`** (submodules **`ac-scripts`**). End-state is **full microservice** architecture. Delivery is **strictly ordered****[§9 Monolith conversion steps](#9-monolith-conversion-steps)** — with **`ac-ms-identity` as the gate** before any domain service split.
**DR decision (R0):**
**DR decision (R1):**
| Item | Decision |
|------|----------|
| **Gitea org** | **`ac`** — `git://f0xx.org/ac/<repo>` |
| **Git org** | **`ac`** on f0xx.org git server`git://f0xx.org/ac/<repo>` |
| **Gitea** | Browse-only UI at `…/git/`; **not** repo provisioning; PO creates bare repos on git server |
| **Architecture** | Full microservice end-state; phased migration from monolith |
| **URL prefix** | **`/app/androidcast_project/`** (full path, locked) |
| **Path-per-service** | `/issues/`, `/tickets/`, `/access/`, etc. — replace `/crashes/?view=` |
| **Path-per-microproject** | **One URI root per surface** `/issues/`, `/tickets/`, `/graphs/`, … — **no** `/crashes/` and **no** `?view=` in target URLs |
| **Scripts** | **`ac-scripts`** separate; **`ac-deploy`** consumes it (submodule or PATH) |
| **Workspace** | Optional **`ac-workspace`** — selective clone only; **not** build-all-in-one |
| **Identity gate** | **Step 6** (`ac-ms-identity`) before domain MS extraction |
| **Repo catalog (§6)** | **Approved** — PO minor naming deltas acceptable; structure kept as R1 table |
| **Cloud** | VM lift-and-shift first; K8s optional later |
| **Alpha** | Monolith may run in prod until Step 8+; no forced big-bang |
@@ -85,7 +85,7 @@ The monolith bundles Android app, PHP backend (crashes, tickets, graphs, RBAC, r
- Independent CI/CD and rollback per service
- Clear ownership and OpenAPI contracts
- Mapping repos to VM roles ([scaling SPEC §6.3](../specs/20100612_1_scaling.md))
- Product URLs such as [issues](https://apps.f0xx.org/app/androidcast_project/crashes/?view=reports) vs [tickets](https://apps.f0xx.org/app/androidcast_project/crashes/?view=tickets) under distinct paths
- Product URLs such as [issues](https://apps.f0xx.org/app/androidcast_project/issues/) vs [tickets](https://apps.f0xx.org/app/androidcast_project/tickets/) **distinct path roots**, not `?view=` under a shared `/crashes/` console
Todays coupling: **one MariaDB**, **one session cookie** (`ac_crash_sess`, path `/app/androidcast_project`), **shared Auth.php** across consoles ([BUILD_DEPLOY.md](../BUILD_DEPLOY.md)).
@@ -95,13 +95,16 @@ Todays coupling: **one MariaDB**, **one session cookie** (`ac_crash_sess`, pa
| # | Topic | Decision |
|---|-------|----------|
| 1 | Gitea org | **`ac`** |
| 1 | Git org | **`ac`** on f0xx.org **git server** (bare repos); Gitea = browse UI only |
| 2 | Architecture | **Full microservice** (phased delivery) |
| 3 | ac-scripts | Separate repo; **ac-deploy utilizes ac-scripts** |
| 4 | Public URLs | Path-per-service under **`/app/androidcast_project/`** |
| 4 | Public URLs | **One path root per microproject** under **`/app/androidcast_project/`** |
| 5 | ac-workspace | Optional manifest; **not** unified build; supports multi-repo OTA |
| 6 | Cloud | VM lift-and-shift; no K8s requirement at R0 |
| 6 | Cloud | VM lift-and-shift; no K8s requirement at R1 |
| 7 | Identity gate | **Approved** — identity MS before other MS splits |
| 8 | Legacy `/crashes/` | **301 only** during migration; **removed** from docs, hub, mobile defaults |
| 9 | `?view=` routing | **Forbidden** in target state — each console is its own nginx location + repo |
| 10 | Git submodules | **Only** `third-party/*` and `backend/url-shortener` — no other nested git trees in product repos |
---
@@ -118,14 +121,22 @@ Todays coupling: **one MariaDB**, **one session cookie** (`ac_crash_sess`, pa
| `orchestration/` | ac-deploy | cluster0, docker |
| `examples/crash_reporter/backend/` | ac-ms-* + ac-be-* | **32 PHP classes**, 24 APIs |
| `examples/build_console/` | ac-ms-build, ac-be-builder | Same DB `users` |
| `examples/app_hub/` | ac-be-hub | Loads `/crashes/assets/` today |
| `examples/app_hub/` | ac-be-hub | Loads shared platform-web assets (not `/crashes/assets/`) |
| `backend/url-shortener/` | ac-ms-url-shortener | Existing submodule |
### 4.2 What is not a git repo
- **Gitea** (infra on BE; scripts in ac-deploy)
- **Gitea** — read-only web UI over git mirrors on BE (`:3000`); **not** where repos are created
- **MariaDB**, **Janus** (:8089)
- `examples/gentoo-portage-experimental/` (exclude or separate lab repo)
### 4.3 Git submodules (monolith — only these)
| Path | Role after split |
|------|------------------|
| `third-party/*` | Native/codec deps (submodules of **ac-mobile-android**) |
| `backend/url-shortener/` | URL shortener → **ac-ms-url-shortener** |
No other directories in the monolith carry git submodules. Post-split, **ac-deploy** may submodule **ac-scripts** (repo-to-repo pin, not monolith legacy).
---
@@ -162,15 +173,26 @@ Backend UI (thin → MS APIs)
| ac-ms-vpn-rssh + ac-ms-vpn-wireguard | **ac-ms-remote-access** (single control plane) |
| ac-ms-analytics | **ac-ms-graphs** (+ BI later) |
| ac-ms-orchestration | **ac-ms-build** + **ac-deploy** |
| ac-ms-vcs | Not a repo — Gitea infra |
| ac-ms-vcs | **Not a repo** — Gitea is infra/UI only; git server is source of truth |
---
## 6. Gitea repo catalog
## 6. Git repository catalog
Canonical: **`git://f0xx.org/ac/<repo>`**
HTTPS (Gitea UI): **`https://apps.f0xx.org/app/androidcast_project/git/ac/<repo>.git`**
Legacy: **`git://f0xx.org/android_cast`** → read-only mirror until retired.
**Canonical remote:** **`git://f0xx.org/ac/<repo>`** — PO provisions **bare repos on the git server** (org namespace `ac`).
**Browse UI (optional):** **`https://apps.f0xx.org/app/androidcast_project/git/ac/<repo>`** — Gitea mirror for humans; push/fetch use git server.
**Legacy:** **`git://f0xx.org/android_cast`** → read-only mirror until retired.
### 6.1 Structure rationale (R1)
The catalog below is the **recommended end-state** for this project:
- **Platform libs (P\*)** before microservices — avoids auth/DB duplication across 10+ PHP trees.
- **Thin BE UI (B\*)** separate from **MS APIs (S\*)** — matches POs **one public URI per microproject** (§7).
- **ac-deploy + ac-scripts** as the only deploy orchestration entry — fits VM roles in scaling SPEC.
- **No `ac-ms-vcs` repo** — VCS is infrastructure, not application code.
POs initial sketch used deeper folder names (`ac-backend/*`, `ac-microservice/*`). **R1 keeps the flatter `ac-<name>` list** — easier permissions, clone URLs, and OTA manifest entries. Minor renames are acceptable; **dependency order and URI split are locked**.
| ID | Repo | Git URL | Role |
|----|------|---------|------|
@@ -208,7 +230,21 @@ Legacy: **`git://f0xx.org/android_cast`** → read-only mirror until retired.
| F1 | ac-ms-sfu-signaling | `git://f0xx.org/ac/ac-ms-sfu-signaling` | Future F1 |
| F2 | ac-ms-media-transcode | `git://f0xx.org/ac/ac-ms-media-transcode` | Future VOD |
Third-party codecs remain **submodules of C0** (upstream URLs); Gitea mirrors under `ac/`.
Third-party codecs remain **submodules of C0** (upstream URLs); optional **git-server mirrors** under `ac/` for backup (sync scripts, not Gitea-as-SoT).
### 6.2 Repo creation waves (git server)
PO creates bare repos on the **git server** in waves (Gitea mirror sync follows). Agent/docs do **not** “create repos in Gitea.”
```mermaid
flowchart LR
W1[Wave 1: docs scripts deploy session-studio url-shortener] --> W2[Wave 2: platform-php platform-db platform-web]
W2 --> W3[Wave 3: ms-identity]
W3 --> W4[Wave 4: ms-rbac ms-devices]
W4 --> W5[Wave 5: ms-issues ms-tickets + be UI]
W5 --> W6[Wave 6: ms-graphs ms-remote-access ms-build + UI]
W6 --> W7[Wave 7: platform-edge mobile-android ms-ota]
```
---
@@ -216,25 +252,56 @@ Third-party codecs remain **submodules of C0** (upstream URLs); Gitea mirrors un
**Base prefix (locked):** `https://apps.f0xx.org/app/androidcast_project`
| Today | Target URL |
|-------|------------|
### 7.1 Rules (R1 — PO locked)
1. **No `/crashes/` in target URLs** — legacy path **301 →** correct microproject root for ≥ one release, then drop from nginx/docs/mobile.
2. **No `?view=` routing** — each product surface has its **own path root** and (eventually) its **own BE UI repo + nginx `location`**.
3. **Auth at project root**`/login`, `/logout`, `/register`, `/two-factor`, `/verify-email` (not under `/issues/`).
4. **Shared cookie path**`/app/androidcast_project` at **ac-platform-edge** until SSO tokens replace shared PHP session.
### 7.2 Target path catalog (one URI per microproject)
| Microproject | Target URL root | Repo (UI / API) | Notes |
|--------------|-----------------|-----------------|-------|
| Hub / landing | `…/` | ac-be-hub | Entry cards link to path roots only |
| Auth | `…/login`, `…/logout`, … | ac-be-auth → ac-ms-identity | Project-root paths |
| Issues (reports) | `…/issues/` | ac-be-issues / ac-ms-issues | Was `?view=reports` |
| Issue detail | `…/issues/{id}` | ac-be-issues | Was `?view=report&id=` |
| Tickets | `…/tickets/` | ac-be-tickets / ac-ms-tickets | Was `?view=tickets` |
| Ticket detail | `…/tickets/{id}` | ac-be-tickets | Was `?view=ticket&id=` |
| Analytics / graphs | `…/graphs/` | ac-be-graphs / ac-ms-graphs | Already separate |
| RBAC / access | `…/access/` | ac-be-access / ac-ms-rbac | Was `?view=rbac` |
| Remote access | `…/remote-access/` | ac-be-remote-access / ac-ms-remote-access | Was `?view=remote_access` |
| Short links | `…/short-links/` | ac-be-hub or thin UI / ac-ms-url-shortener | Was `?view=short_links` |
| Live sessions | `…/live-sessions/` | ac-be-issues (or ac-be-live) / live_cast API | Was `?view=live_sessions` |
| Live join / education | `…/live/join`, `…/live/education` | shared live pages | Under issues until split |
| Builder | `…/build/` | ac-be-builder / ac-ms-build | Unchanged |
| Git browse | `…/git/` | Gitea UI (mirror) | Not a microservice repo |
| OTA | `/v0/ota/` | ac-ms-ota | Top-level on apps host |
**API examples:** `…/issues/api/upload` (not `…/crashes/api/upload.php`).
### 7.3 Migration table (legacy → target)
| Legacy (forbidden in target) | Target URL |
|----------------------------|------------|
| `…/crashes/?view=reports` | `…/issues/` |
| `…/crashes/?view=report&id=N` | `…/issues/N` |
| `…/crashes/?view=tickets` | `…/tickets/` |
| `…/crashes/?view=ticket&id=N` | `…/tickets/N` |
| `…/crashes/?view=home` | `…/issues/` or `…/` (hub) |
| `…/crashes/?view=rbac` | `…/access/` |
| `…/crashes/?view=remote_access` | `…/remote-access/` |
| `…/crashes/?view=short_links` | `…/short-links/` |
| `…/crashes/?view=live_sessions` | `…/live-sessions/` |
| `…/crashes/?view=graphs` | `…/graphs/` |
| `…/crashes/login` | `…/login` |
| `…/crashes/api/upload.php` | `…/issues/api/upload` |
| `…/graphs/` | `…/graphs/` (unchanged) |
| `…/build/` | `…/build/` (unchanged) |
| `…/` (hub) | `…/` (unchanged) |
| `…/git/` | `…/git/` (Gitea) |
| `/v0/ota/` | `/v0/ota/` (unchanged) |
| `…/crashes/` (any) | **301** → matching row above |
**Cookie path:** remain `/app/androidcast_project` at **ac-platform-edge** until SSO tokens replace shared PHP session.
**Monolith shim (Step 5 only):** nginx may **internally** rewrite `/issues/` → legacy PHP `?view=reports` until ac-ms-issues exists — **not** exposed in links, docs, or mobile defaults.
**Migration:** 301 redirects from old `?view=` URLs for ≥ one release; update `BackendEndpoints.java`, `CrashSettings.java`.
**Code updates:** `BackendEndpoints.java`, hub `index.php`, all console nav, OpenAPI base paths, deploy nginx fragments.
---
@@ -349,21 +416,13 @@ flowchart TB
| ac-ms-graphs | ac-ms-identity | — | ac-be-graphs, mobile |
| ac-ms-remote-access | ac-ms-rbac, ac-ms-devices | ac-ms-issues | ac-be-remote-access, mobile |
| ac-ms-url-shortener | ac-ms-rbac | — | hub/admin UI |
| ac-ms-build | ac-ms-identity | Gitea infra | ac-be-builder, ac-ms-ota |
| ac-ms-build | ac-ms-identity | git server mirrors | ac-be-builder, ac-ms-ota |
| ac-ms-ota | ac-ms-build | C0, C1 SHAs | mobile OTA clients |
| ac-mobile-android | ac-ms-issues, ac-ms-graphs, ac-ms-remote-access APIs | ac-ms-ota | — |
### 8.5 Repo creation waves (Gitea)
### 8.5 Repo creation waves
```mermaid
flowchart LR
W1[Wave 1: docs scripts deploy session-studio url-shortener] --> W2[Wave 2: platform-php platform-db platform-web]
W2 --> W3[Wave 3: ms-identity]
W3 --> W4[Wave 4: ms-rbac ms-devices]
W4 --> W5[Wave 5: ms-issues ms-tickets + be UI]
W5 --> W6[Wave 6: ms-graphs ms-remote-access ms-build + UI]
W6 --> W7[Wave 7: platform-edge mobile-android ms-ota]
```
See **[§6.2](#62-repo-creation-waves-git-server)** for the ordered wave diagram.
---
@@ -371,14 +430,15 @@ flowchart LR
Ordered steps for converting the monolith. **Do not skip gates.** Each step lists repos created/moved, monolith paths affected, and verification.
### Step 1 — Gitea org and mirrors (no code move)
### Step 1 — Git org `ac` on f0xx.org server (no code move)
**Goal:** Org **`ac`** exists; legacy mirror preserved.
**Goal:** Namespace **`ac`** exists on the **git server**; legacy monolith mirror preserved; Gitea browse UI synced.
| Action | Detail |
|--------|--------|
| Create org | `ac` on f0xx.org Gitea |
| Create org / paths | PO: bare repos under `git://f0xx.org/ac/<repo>` on git server |
| Mirror | `android_cast` → read-only; plan `ac-workspace` or retire later |
| Gitea (optional) | Sync mirrors for `…/git/` browse UI only — **not** repo creation |
| Update docs | Pointer in INFRA.md, AGENTS.md |
**Verify:** `git ls-remote git://f0xx.org/ac/ac-docs` (after Step 2) ; legacy push still works.
@@ -536,7 +596,7 @@ Monolith/builder **call identity** for auth (HTTP or shared session bridge durin
|------|--------|
| ac-be-hub | Extract app_hub; use **ac-platform-web** assets |
| ac-be-access | RBAC UI → calls ac-ms-rbac |
| ac-platform-edge | Enable **301** from `/crashes/?view=*` to new paths |
| ac-platform-edge | Enable **301** from `/crashes/` and `?view=*` to §7 path roots |
**Verify:** Hub cards link to `/issues/`, `/tickets/`; no broken CSS.
@@ -567,7 +627,7 @@ Monolith/builder **call identity** for auth (HTTP or shared session bridge durin
|--------|--------|
| ac-workspace | Publish optional `.gitmodules` manifest |
| Retire | Stop commits to `android_cast` monolith; archive mirror |
| Gitea migrate | Update scripts for org `ac` ([gitea README](../../examples/crash_reporter/backend/scripts/gitea/README.md)) |
| Gitea mirror sync | Update mirror scripts for org `ac` (server → Gitea UI; [gitea README](../../examples/crash_reporter/backend/scripts/gitea/README.md)) |
**Verify:** Fresh clone via ac-workspace or individual repos; prod deploy from ac-deploy only.
@@ -592,7 +652,7 @@ Monolith/builder **call identity** for auth (HTTP or shared session bridge durin
| Step | Name | Gate |
|------|------|------|
| 1 | Gitea org `ac` | — |
| 1 | Git org `ac` on server | — |
| 2 | docs, scripts, deploy, session-studio, url-shortener | — |
| 3 | platform-php, platform-db, platform-web | — |
| 4 | ms-template, OpenAPI | — |
@@ -615,13 +675,13 @@ Monolith/builder **call identity** for auth (HTTP or shared session bridge durin
| Independent deploy | Yes — per MS/UI repo | Single PHP deploy |
| Failure isolation | Stronger | Shared FPM pool |
| Auth complexity | Needs identity MS + edge early | Works today |
| URL clarity | `/issues/`, `/tickets/` | `?view=` parameters |
| URL clarity | `/issues/`, `/tickets/`, … — one URI per microproject | `?view=` under `/crashes/` |
| Ops load | More nginx, repos, logs | One sync path ([INFRA.md](../INFRA.md)) |
| Alpha risk | High if before Step 6 | Lower |
| Cloud mapping | 1:1 to VM roles | Lift entire tree |
| Rollback | Revert one repo SHA | Revert one commit |
PO choice is approved with **Step 6 gate** enforced in [§9](#9-monolith-conversion-steps).
PO choice is approved with **Step 6 gate** enforced in [§9](#9-monolith-conversion-steps). **R1:** URL model is **path-per-microproject** (§7), not query-parameter routing.
---
@@ -674,8 +734,310 @@ PO choice is approved with **Step 6 gate** enforced in [§9](#9-monolith-convers
---
## Appendix A — Git disk paths and remotes
**Convention:** disk paths are **relative to the git server root** (example: `/var/git/`). PO may choose another root; keep the `ac/<repo>` layout.
**Canonical clone/fetch (write + read):** `git://f0xx.org/ac/<repo>`
**HTTPS read-only (optional):** see [Appendix B](#appendix-b-optional-https-read-only-git-access) — separate vhost; **not** `https://f0xx.org/...` app URLs.
| # | Disk path (relative) | Git repo URL |
|---|----------------------|--------------|
| 1 | `ac/ac-workspace` | `git://f0xx.org/ac/ac-workspace` |
| 2 | `ac/ac-platform-php` | `git://f0xx.org/ac/ac-platform-php` |
| 3 | `ac/ac-platform-db` | `git://f0xx.org/ac/ac-platform-db` |
| 4 | `ac/ac-platform-web` | `git://f0xx.org/ac/ac-platform-web` |
| 5 | `ac/ac-platform-edge` | `git://f0xx.org/ac/ac-platform-edge` |
| 6 | `ac/ac-scripts` | `git://f0xx.org/ac/ac-scripts` |
| 7 | `ac/ac-docs` | `git://f0xx.org/ac/ac-docs` |
| 8 | `ac/ac-deploy` | `git://f0xx.org/ac/ac-deploy` |
| 9 | `ac/ac-mobile-android` | `git://f0xx.org/ac/ac-mobile-android` |
| 10 | `ac/ac-mobile-ios` | `git://f0xx.org/ac/ac-mobile-ios` |
| 11 | `ac/ac-session-studio` | `git://f0xx.org/ac/ac-session-studio` |
| 12 | `ac/ac-ms-template` | `git://f0xx.org/ac/ac-ms-template` |
| 13 | `ac/ac-ms-identity` | `git://f0xx.org/ac/ac-ms-identity` |
| 14 | `ac/ac-ms-rbac` | `git://f0xx.org/ac/ac-ms-rbac` |
| 15 | `ac/ac-ms-devices` | `git://f0xx.org/ac/ac-ms-devices` |
| 16 | `ac/ac-ms-issues` | `git://f0xx.org/ac/ac-ms-issues` |
| 17 | `ac/ac-ms-tickets` | `git://f0xx.org/ac/ac-ms-tickets` |
| 18 | `ac/ac-ms-graphs` | `git://f0xx.org/ac/ac-ms-graphs` |
| 19 | `ac/ac-ms-remote-access` | `git://f0xx.org/ac/ac-ms-remote-access` |
| 20 | `ac/ac-ms-url-shortener` | `git://f0xx.org/ac/ac-ms-url-shortener` |
| 21 | `ac/ac-ms-build` | `git://f0xx.org/ac/ac-ms-build` |
| 22 | `ac/ac-ms-ota` | `git://f0xx.org/ac/ac-ms-ota` |
| 23 | `ac/ac-ms-notifications` | `git://f0xx.org/ac/ac-ms-notifications` |
| 24 | `ac/ac-be-hub` | `git://f0xx.org/ac/ac-be-hub` |
| 25 | `ac/ac-be-issues` | `git://f0xx.org/ac/ac-be-issues` |
| 26 | `ac/ac-be-tickets` | `git://f0xx.org/ac/ac-be-tickets` |
| 27 | `ac/ac-be-graphs` | `git://f0xx.org/ac/ac-be-graphs` |
| 28 | `ac/ac-be-remote-access` | `git://f0xx.org/ac/ac-be-remote-access` |
| 29 | `ac/ac-be-access` | `git://f0xx.org/ac/ac-be-access` |
| 30 | `ac/ac-be-builder` | `git://f0xx.org/ac/ac-be-builder` |
| 31 | `ac/ac-be-auth` | `git://f0xx.org/ac/ac-be-auth` |
| 32 | `ac/ac-ms-sfu-signaling` | `git://f0xx.org/ac/ac-ms-sfu-signaling` |
| 33 | `ac/ac-ms-media-transcode` | `git://f0xx.org/ac/ac-ms-media-transcode` |
| 34 | `android_cast` | `git://f0xx.org/android_cast` |
**Bare repo on server (PO):** `git init --bare /var/git/ac/ac-workspace` (repeat per row; path = `{GIT_ROOT}/` + disk path).
---
## Appendix B — Optional HTTPS read-only git access
**Scope:** Standalone **git infrastructure** on the PO git host. **Not** part of the AndroidCast monorepo, BE PHP tree, Gitea product UI, or `apps.f0xx.org` consoles.
**Goal:** Keep **`git://f0xx.org/...`** for normal push/fetch; add **`https://`** for **read-only** clones (anonymous and/or token), with TLS certs and ACL files **outside** the AndroidCast project.
**Suggested hostname:** `git-r.f0xx.org` (read-only smart HTTP). Do **not** reuse `https://f0xx.org/` landing or `https://apps.f0xx.org/` app paths for git smart HTTP.
**Suggested paths on git server:**
| Path | Purpose |
|------|---------|
| `/var/git/` | Bare repos (Appendix A disk layout) |
| `/etc/git-ro/` | nginx snippets, token maps, install scripts — **not** in android_cast repo |
| `/etc/git-ro/tokens/` | Per-token permission files (see step 8) |
| `/etc/letsencrypt/live/git-r.f0xx.org/` | Certbot certificates |
---
### 1) DNS
Add an **A** (or **AAAA**) record for the read-only git vhost pointing at the **git server** public or edge IP (same host that serves `git://` today, or FE DNAT to it):
```text
git-r.f0xx.org. IN A <GIT_SERVER_PUBLIC_IP>
```
Verify:
```bash
dig +short git-r.f0xx.org
```
---
### 2) Install packages (git server — example: Gentoo/Alpine)
On the machine that holds `/var/git/`:
```bash
# Gentoo (example)
emerge -av nginx git fcgiwrap certbot
# Alpine (example)
apk add nginx git git-daemon fcgiwrap certbot certbot-nginx openssl
```
Enable `fcgiwrap` (smart HTTP via `git http-backend`):
```bash
# systemd example
systemctl enable --now fcgiwrap
```
---
### 3) Issue free TLS certificates (Certbot)
Run on the git server (or on FE if it terminates TLS and proxies to git — adjust plugin):
```bash
certbot certonly --standalone -d git-r.f0xx.org \
--agree-tos -m admin@f0xx.org --non-interactive
```
If nginx is already bound to :80/:443, use the nginx plugin instead:
```bash
certbot certonly --nginx -d git-r.f0xx.org \
--agree-tos -m admin@f0xx.org --non-interactive
```
Certificates land under `/etc/letsencrypt/live/git-r.f0xx.org/fullchain.pem` and `privkey.pem`.
---
### 4) nginx — HTTPS smart HTTP, read-only
Create `/etc/git-ro/nginx-git-ro.conf` (outside AndroidCast):
```nginx
server {
listen 443 ssl;
server_name git-r.f0xx.org;
ssl_certificate /etc/letsencrypt/live/git-r.f0xx.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git-r.f0xx.org/privkey.pem;
root /var/git;
location ~ ^/ac/(.+?)(\.git)?/(HEAD|info/refs|objects/info/.*|objects/([0-9a-f]{2}/[0-9a-f]{38}|pack/pack-.*))$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/libexec/git-core/git-http-backend;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROJECT_ROOT /var/git;
fastcgi_param PATH_INFO /$1.git/$2;
fastcgi_pass unix:/run/fcgiwrap.socket;
}
location ~ ^/ac/(.+?)\.git$ {
return 301 /ac/$1.git/info/refs?service=git-upload-pack;
}
}
```
Include it from the main nginx config:
```bash
ln -s /etc/git-ro/nginx-git-ro.conf /etc/nginx/conf.d/git-ro.conf
nginx -t && rc-service nginx reload # Gentoo
# nginx -t && systemctl reload nginx # other
```
**Read-only guarantee:** do **not** enable `receive-pack`; only `git-upload-pack` (clone/fetch) is exposed. Push stays on **`git://f0xx.org`** (SSH or authenticated path), not this vhost.
Test anonymous clone:
```bash
git clone https://git-r.f0xx.org/ac/ac-docs.git /tmp/ac-docs-ro-test
```
---
### 5) Mark public repos for anonymous HTTPS read
For each bare repo that may be cloned without a token:
```bash
GIT_ROOT=/var/git
repo=ac/ac-docs
touch "${GIT_ROOT}/${repo}.git/git-daemon-export-ok"
git -C "${GIT_ROOT}/${repo}.git" config http.receivepack false
git -C "${GIT_ROOT}/${repo}.git" config http.uploadpack true
```
Repeat for every Appendix A row you want world-readable over HTTPS.
---
### 6) Optional — require token for private HTTPS read
Add HTTP basic auth for selected locations. Store credentials **outside** AndroidCast:
```bash
install -d -m 0750 /etc/git-ro/htpasswd
htpasswd -c /etc/git-ro/htpasswd/ro-ci-user
```
In `/etc/git-ro/nginx-git-ro.conf`, inside the `location` block for smart HTTP:
```nginx
auth_basic "git read-only";
auth_basic_user_file /etc/git-ro/htpasswd/ro-users;
```
Clone with token (password = token):
```bash
git clone https://ro-ci-user:<TOKEN>@git-r.f0xx.org/ac/ac-ms-identity.git
```
---
### 7) Token-wise permissions (outside AndroidCast)
Keep a simple ACL file per token under `/etc/git-ro/tokens/`**not** referenced by android_cast code:
```bash
install -d -m 0700 /etc/git-ro/tokens
cat > /etc/git-ro/tokens/ci-read-issues.env <<'EOF'
# token name: ci-read-issues
# htpasswd user: ro-ci-issues
# allowed repo prefixes (one per line, relative to /var/git)
ac/ac-ms-issues
ac/ac-be-issues
ac/ac-platform-php
EOF
chmod 0600 /etc/git-ro/tokens/*.env
```
Enforcement options (pick one on the git server):
- **Simple:** separate htpasswd users per token + nginx `location` blocks per repo prefix.
- **Scalable:** small `git-ro-auth` script (not in AndroidCast repo) consulted by nginx `auth_request`.
AndroidCast **never** reads `/etc/git-ro/`; CI machines hold tokens in their own secret stores.
---
### 8) Certbot renewal (crontab)
Edit root crontab on the git server:
```bash
crontab -e
```
Add:
```cron
17 3 * * * certbot renew --quiet --deploy-hook "nginx -t && rc-service nginx reload"
```
Or install a one-shot renew script `/etc/git-ro/certbot-renew.sh`:
```bash
#!/bin/sh
set -eu
certbot renew --quiet
nginx -t
rc-service nginx reload
```
```bash
chmod 0755 /etc/git-ro/certbot-renew.sh
crontab -e
```
```cron
17 3 * * * /etc/git-ro/certbot-renew.sh >> /var/log/git-ro-certbot.log 2>&1
```
---
### 9) Client remote examples
| Use case | Remote URL |
|----------|------------|
| Developer push/fetch (canonical) | `git://f0xx.org/ac/ac-mobile-android` |
| Anonymous read (HTTPS) | `https://git-r.f0xx.org/ac/ac-docs.git` |
| Token read (HTTPS) | `https://<user>:<token>@git-r.f0xx.org/ac/ac-ms-issues.git` |
| Browse HTML (optional) | `https://apps.f0xx.org/app/androidcast_project/git/` (Gitea mirror only) |
Do **not** document `https://f0xx.org/...` as a git smart HTTP endpoint; landing stays human/PDF only.
---
### 10) Verification checklist
```bash
# TLS
curl -sSI https://git-r.f0xx.org/ac/ac-docs.git/info/refs?service=git-upload-pack | head
# git:// still works (unchanged)
git ls-remote git://f0xx.org/ac/ac-docs
# HTTPS read-only (no push)
git clone https://git-r.f0xx.org/ac/ac-docs.git /tmp/ro-clone-test
cd /tmp/ro-clone-test && git push origin HEAD # must fail on this vhost
```
---
## 14. Changelog
| Rev | Date | Change |
|-----|------|--------|
| R0 | 2026-06-18 | Initial DR from draft; PO locked org `ac`, full MS, URL prefix, identity gate; §9 conversion steps; dependency graphs |
| R1 | 2026-06-20 | **VCS:** git server = SoT; Gitea = browse UI only — not repo provisioning. **§6:** structure rationale; flat `ac-*` catalog approved. **§7:** no `/crashes/` or `?view=` in target; full path-per-microproject catalog. **PDF:** removed spurious `\newpage` blank sheets. PO review before SPEC. |
| R1.1 | 2026-06-22 | **Appendix A:** disk path ↔ `git://` URL index table (§6 catalog + legacy). **Appendix B:** optional HTTPS read-only git (`git-r.f0xx.org`), certbot, nginx smart HTTP, token ACL outside AndroidCast. |
| R1.1-frozen | 2026-06-22 | **Frozen** — superseded by [SPEC R1](../specs/20260618_repos_reorganizing.md) |

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,178 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 8 0 R /F4 9 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Border [ 0 0 0 ] /Contents () /Dest [ 10 0 R /Fit ] /Rect [ 45.68504 762.0394 116.381 773.0394 ] /Subtype /Link /Type /Annot
>>
endobj
5 0 obj
<<
/Border [ 0 0 0 ] /Contents () /Dest [ 10 0 R /Fit ] /Rect [ 45.68504 751.0394 124.813 762.0394 ] /Subtype /Link /Type /Annot
>>
endobj
6 0 obj
<<
/Border [ 0 0 0 ] /Contents () /Dest [ 10 0 R /Fit ] /Rect [ 45.68504 740.0394 108.381 751.0394 ] /Subtype /Link /Type /Annot
>>
endobj
7 0 obj
<<
/Border [ 0 0 0 ] /Contents () /Dest [ 10 0 R /Fit ] /Rect [ 45.68504 729.0394 108.373 740.0394 ] /Subtype /Link /Type /Annot
>>
endobj
8 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
9 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
10 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Annots [ 4 0 R 5 0 R 6 0 R 7 0 R ] /Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0
/Trans <<
>> /Type /Page
>>
endobj
12 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Outlines 15 0 R /PageMode /UseNone /Pages 20 0 R /Type /Catalog
>>
endobj
14 0 obj
<<
/Author (Android Cast project) /CreationDate (D:20260623103624+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260623103624+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Live Cast Control Plane \(P0-P2 kickoff\)) /Trapped /False
>>
endobj
15 0 obj
<<
/Count 4 /First 16 0 R /Last 19 0 R /Type /Outlines
>>
endobj
16 0 obj
<<
/Dest [ 10 0 R /Fit ] /Next 17 0 R /Parent 15 0 R /Title (Scope implemented)
>>
endobj
17 0 obj
<<
/Dest [ 10 0 R /Fit ] /Next 18 0 R /Parent 15 0 R /Prev 16 0 R /Title (Non-goals \(remaining\))
>>
endobj
18 0 obj
<<
/Dest [ 10 0 R /Fit ] /Next 19 0 R /Parent 15 0 R /Prev 17 0 R /Title (Data model notes)
>>
endobj
19 0 obj
<<
/Dest [ 10 0 R /Fit ] /Parent 15 0 R /Prev 18 0 R /Title (Follow-up phases)
>>
endobj
20 0 obj
<<
/Count 3 /Kids [ 10 0 R 11 0 R 12 0 R ] /Type /Pages
>>
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 350
>>
stream
Gas309kseb&-h(iregH[(*#.+hJ6skH*ASb@?K(l<nD1gqYN?Oqk95ea?YoVH'0kG$pMp1#M^\c-4JHr/_H4TRc6%:mfd&n>":b;AJ%6OAL]3UZ:uf`isa".?A/Xl:03adIjq0?J%#_TS[?aOQWn-o)_`4O;6gqb''466Z,1a*3+C\3Nr+9`fljW)@:%7/E2KmC2k.NukCWf`/(,Gmf*0+i,i^PaB6$4KVmu;VgN[M"g1N9U^:]BH$WE:RIGa#Bq(U5hHf_Z8[mI8;U"@PW?GC&4FiuH(5<'HMa)JoW!u*;-n@4Hj?Im&rWQ8F1kni'A5TJl-Zm3U2:DYDs,FBteMGNHDF+j~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 491
>>
stream
Gatn"?#SI?'Sc)J/)JHk;CMj0Z$/3N\XKBio4=_R3]-Aa_)%n_qnl0.D/Co"W]Cb`S-.)a4s9?21p:a,HtdQ/,*5(]#V&r-jr;#mMa%S17i$uIQ/VtSer7e`aS3!0nOc=u_.6hl?%H]9l@L_X(AU[N_%9,cB8/B%KU<%g`]Q5=n;'!tA:9=oBRZ`P1pMOB]>QAu48AB"-WG/A1t-r^@Betl_:_#imIgm5;1B;YHgnMV?sLWr3SN>3^@Su0bnV.-ohcXq9etqMeiNSflo!l@mqMejpaS6>NjjcE_qr>ti5DGR1r1gM&80NR+KJG2lB:X_J"r%'P<K:9lf6I11a(a2[e;K[(F:^<hmi+-DH[_&AfB;V\BWrj\#eIr/VHU#D<c_.lhj]"0oSol4FZ(hnD)]l*@'"h,fISSKJ@%FZD0>g`jbh!7eW\^9G^+.`9[Ad_Fm=5<h=g-5D^hg)-+3t68:o.bt0MkmNDPi@.>&E$M(k~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1744
>>
stream
Gatm<D/\-!&H:Nn0d"Lpf\"gn;H9V?Qh7:NHYGgs01aLE*q6W3Y3WLSOS\!Uei&UL&DVet0]66_1G@'1ZWPSLQW:a)$,FLsb\IS;)obg\*5d?t%:"nMr-W#).im4Y%,IKWT'%!)rlCd]FIP0llXs?9N"`F4^"L',bO<pND@a#jFRB7!,4Hk;0gUq3GQQpo0`FFDqEhB&ecrSADR2f/l&N2bp%7pSFC=mni5G1Kpl#Tj\:X>,O&4OdYN`<<?0Cq=qm#tUC(,i*Qn7?2biea40U3SkPh(cYLN^sHJ9C8HTkP%GJM>0,jnQV?=<_Y`3c-L0OCbsu)]B)mdM_Zkr,Y%^H+)nJjPg4'X/E`!Smre0MhZ?pHPY[IMYHs&a'VNEGa\.grDKR.h=q_+eHT783]-K<Kcfo:JgD\\ElQ)ZjK&Zt#.6)tNMo$IM.$uMmIBY@g3bX(0*fMb*Jm$!VGcqhB$2On)p6q^Z0#r;U03$TSk$SR>]s*Y@D^`[M-q?j9P\f9e7hnZo!mAXaf*o(I4uQ[)Y:$0;GMegMAXZI"h+o8/94kU+/Udq9l;$TR:$[mlDN,e]5c;5iPBL*./5</gKdrHGi_lc_0mT8*+COL.!Q,,mNBJbjU%&C#F'ZJ$"&ci:12?bkh=?R!'oe^h.j.1CsE-!"54\8)YWcfr'8DjZ","d$K<6t[Bh]YV67$:[!1R6`\GEUa*>#FXr"4b]HT.aiLu\8L-E.,6e-dB"4oW?k*JfneEMJ6VJP"^3ktn6$5"joT#-5*OCp->_8YaTd[2V#$!dUkA/o(0_@L;"e8h3(CH:;;N8%(:-uoGTP7+5QT2Zk#=%+Z-#VhGnOS8uMcG6&5`p_(PhB@OeqA!.S_G#obrY0OPLaapPP%Qc!:dpS3(-^B'6_f,[jU[@_&6N3I4qiP-3U&B4BY!K'?=nS'H:CL_UGf12_mol'aE\2J:(.L39QSa=J<p'\+0QmH-g>^#obOu^mZFPneZ(8K&PU3R0b_&[q)Pi"F5:M'Q-0l=R7sP1"B!Gih*u]m[M%d>IF0&q7kWng41C%!=:7O!"\io5)9:&K]G`f&lZYdSG;.KN"^bm&/eAO[Xo_090;72qlL$^FlouqIQD-I)N%f&(-&`40^,AZ+SR]$^]CD_CJ,L)p%&khV\k%@.O@DKMc@:m>h709eq\8N8N,eaOTo?N6<!5PMI*1d>3CGH,P`&+'pTnmk$.kN9-7<XQg"rE2qN.sB,Ee-g;F2uiK_gHHb4I5S+;]])f1HufENR'N;7^+q9b>ZKL>HL87k.()/??WW6W]Dhh/^:tXe;]`',f5cf`Im(gqhnH"oT6+#07A#6BoJJ@c;Pnk5X:ZXf*,N0t2AV-[h3.]g+1u#\Jlr_+^lnS-J9><3%*`D&PD;SS.TgdqU4`YBMqt3?UfnoAU]W1pXP]Y2eq0&cA4Vbu;\!Io7!BE+R&1jp9MslMS&1cO;#R4_(f:^0qqdR4A%"1IW[!-;(O9:m@)Xj.<qo9&*bjf;-W2d4J6e,GnWZL=P2!h;3%@=9a-u8n^7.OMmQUU@la@Le-r,am2ujk>VA4#e8="(G=GFU1`p<nI*uc""pT6d7h[hiMkg2RHWqR++;YX:j(t;R//')p$q;d)khU_Fi<^F,O43`e9Y>W>^pG#5!G!(.9riVQ,%+*Wq)C)qP]]Qct$9TY3US$SjP:ra0N>Y1p7X_[IKA6W;d3QKMaZbCZl5DVGY^GL*b\OT/o:<M&gj45>cB;I/~>endstream
endobj
xref
0 24
0000000000 65535 f
0000000061 00000 n
0000000122 00000 n
0000000229 00000 n
0000000341 00000 n
0000000488 00000 n
0000000635 00000 n
0000000782 00000 n
0000000929 00000 n
0000001044 00000 n
0000001149 00000 n
0000001355 00000 n
0000001597 00000 n
0000001803 00000 n
0000001890 00000 n
0000002206 00000 n
0000002280 00000 n
0000002379 00000 n
0000002497 00000 n
0000002608 00000 n
0000002706 00000 n
0000002781 00000 n
0000003222 00000 n
0000003804 00000 n
trailer
<<
/ID
[<dab4f5eab97be7c79d196b8cb2e530e9><dab4f5eab97be7c79d196b8cb2e530e9>]
% ReportLab generated PDF document -- digest (opensource)
/Info 14 0 R
/Root 13 0 R
/Size 24
>>
startxref
5640
%%EOF