Nội bộ — tài liệu vận hành. Sơ đồ end-to-end từng luồng dưới Management Hub, để duyệt, không phải bản phân phối. Bản nháp, có thể thay đổi. Một số định danh hạ tầng đã được lược bỏ. noindex.

_system / notes · companion to kế hoạch tích hợp

Management Hub — sơ đồ vận hành end-to-end từng luồng

Tám bảng, bốn hệ thống, một vòng vận hành. Bản nháp 2026-05-31.

Status: reference, for Editor · 2026-05-31 · author: Claude session Companion to: _system/plans/management-hub-integration-plan-v1.md (the build plan), _system/notes/management-hub-mockup-handoff-2026-05-31.md, memory project_management-hub-integration-plan.

This is the operating view the plan does not spell out: for each hub panel, who triggers it, every step, which real tool / agent / cloud service / app route does the work, where the data lives, and exactly where the one human gate sits. Grounded in the actual code (not the mockup). Phase A throughout: the hub reads and routes; the only thing the system never does on its own is post.

Legend — maturity: 🟢 deployed-live · 🟡 deployed · 🟠 built-not-deployed · 🔵 new read-view (unbuilt) · 🟣 new surface + new data (unbuilt) · ⚪ placeholder (nothing built)


The one-paragraph picture

There are four real subsystems (Media&Story, Care, Donor/Finance, Community) behind eight hub panels, sitting on three privacy-separated SQLites that are never SQL-joined (registry.sqlite = bundles, so-dang-ky.sqlite = care, so-tai-chinh.sqlite = finance). The hub is a new parent app (app/hub.py, not built yet) that puts one login in front of all of them, mounts the three dashboards, adds three native read-views, and aggregates a daily action strip by reading all three DBs in Python. The operating loop is FIELD → EDITORIAL → AUDIENCE → RESOURCES, and "everything closes the transparency loop." Today the left half is live (field intake, the media pipeline, the care subsystem, finance recording); the editorial-intelligence top edge (content-plan, trips, presence) and the audience-feedback return edge (community) are unbuilt, and the connective tissue (app/hub.py + the Overview) is still only mock HTML.

        FIELD                 EDITORIAL              AUDIENCE             RESOURCES
  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────┐  ┌──────────────────┐
  │ brief-form +     │  │ Kế hoạch nội dung│  │ Hiện diện XH │  │ Tài trợ & Tài chính│
  │ photos → Drive   │  │  (🟣 plan)       │  │  (🔵 presence)│  │  (🟠 donor/finance)│
  │  → ingest        │  │   ↑ pulls trips, │  │              │  │   → thank-you +   │
  │  → pipeline      │──┼──▶ holidays,     │  │ Cộng đồng    │◀─┤     receipt →     │
  │  → Hàng đợi duyệt│  │   journey miles  │  │  (⚪ Op1)    │  │   transparency    │
  │  🟢 review queue │  │                  │  │              │  │                   │
  └────────┬─────────┘  └────────┬─────────┘  └──────┬───────┘  └────────┬──────────┘
   CARE (parallel, 🟢):          │                   │                    │
   intake→promote→profile        │  Hoạt động &      │                    │
   +benefits+watchlist ──miles──▶│  Chuyến đi (🔵)──▶│ (orphan-trip flag) │
   🌱 Thụ hưởng                  │  trip ledger      │                    │
                                 ▼                   ▼                    ▼
                       🌻 Tổng quan (Overview, 🟣) = one login, action strip,
                          three independent DB reads stitched in Python

1. 🟢 Hàng đợi duyệt (Review queue) + the Media & Story pipeline — Stream A

One line. Coordinator submits a brief form and drops photos in Drive; cloud Jobs ingest → sanitise → cull → retouch → caption into a post-bundle that rests at awaiting-approval; the administrator approves with a required dignity_ok confirmation (gate-8 folded in); that triggers a Stage-1 manual posting pack. Nothing auto-publishes.

Triggers. Brief-form submit (webhook) · photos land in Drive · daily 09:00 ICT pipeline · administrator opens the queue.

Walkthrough.

event-folder Service (event_folder_core.create_event_folder) makes 01_Tai-lieu-thuc-dia/<date>__<slug>/{photos,videos}, writes brief.yaml inside it, appends a Dashboard row state=cho-upload.

downloads new photos, dedups by SHA-256, writes the untouched original to Sandbox/posts/<slug>/source/, registers an asset row in registry.sqlite.

exiftool, hard-fails if absent — never silently downgraded).

selected/ (media_triage).

(gradient wash + logo + tagline) with recipe sidecars. AI polish (Gemini Nano Banana Pro) is opt-in/paid, not in the default render.

Opus vision from brief.yaml (incl. the outcome beat) + prior revise notes, prompt-cached brand-voice corpus.

alt-text, IG-deferred block). Always records a "gate-8 dignity pending" finding (never auto-cleared; also satisfies the non-empty gate_findings rule). Clean → awaiting-approval; blocking → in-review.

~20-min self-healing backstop.

a near-final FB/web post + full gallery + gate findings.

writes any edited caption back, stamps SAFEGUARDING: cleared by <admin> at <ISO>, flips state=approved. (gate-8 + editor approval folded into one human click; refuses if dignity_ok unticked.) Alternatives: request-changes (revising, re-drafted next pass) or reject (→ _rejected/).

emit a human-readable post sheet per channel under Sandbox/_review-prints/, state=packed. No API push — a human posts to Facebook by hand.

_system/runs/qc-log-YYYY-MM.jsonl; an approve feeds the accepted caption into few-shot.yaml. Admin later types the posted_idstate=published.

Cloud: event-folder · ingest-watch · pipeline · decisions · status-reconcile · review (all deployed-live) · Anthropic Opus. Feeds: Presence · Community · Content-plan (orphan/petal flags) · Trips · quality ratchet. Gaps: not yet mounted under /review (P4 template-link audit + two-bucket deploy is the heavy lift); the headless pipeline runs only gate 1 + records gate-8-pending — the brand-voice/claim/dignity agent gates 3–7 are not invoked in the cloud path, so the human approval is the real substantive check; the daily chain processes photos only (video_edit is invoked separately).


2. 🟢 Thụ hưởng (Care / Beneficiary) — Area 1

One line. Raw coordinator drops about a child (prose notes or school PDFs) are auto-converted into structured Block-3 progress notes + a benefits ledger + a safeguarding watchlist, all projected into so-dang-ky.sqlite and browsed through the private care-dashboard. The canonical record is the markdown under Ho-so/; the SQLite is a rebuildable projection. Humans own escalation + gate-8.

Triggers. Coordinator uploads a drop to Tai-lieu-vao/<week>/<journey-id>/ · hourly intake-convert Job · manual dashboard add · daily 07:00 keyword + monthly full watchlist sweep · 28th transparency-CSV emit.

Walkthrough.

__hoc-ba/__so-diem/__chuyen-can/__hop-phu-huynh, or image) into Tai-lieu-vao/.

sanitises any image (EXIF/GPS, fail-closed), idempotent via _done__ rename.

PromoteEntry (date/title/source/petal/body, verbatim quotes only), prepended newest-first to the profile + a profile_entry row.

SchoolDocExtract; bounds-guard catches OCR slips (gpa 0–10, attendance 0–100, rank ≤ size, multi-child split); clean single-child auto-commits confirmed_by='auto' as a school_report row + linked note.

out-of-bounds is not written — appended to _needs-review.md for the weekly human pass.

watchword still writes, but its journey-id surfaces same-day.

flagged "promote" in the weekly review; if a watchword/disclosure warrants it the Coordinator escalates to safeguarding and passes --sg-cleared-by before invoking (a human gate, not a tool block).

validates the funding-source enum, mints a benefit_id, writes a ledger md + a benefit row.

benefit-anomaly, keyword, grade-drop, attendance-dip) → watchlist_item rows (action_taken NULL = open) + a monthly md.

re-surface) with a note. The human owns gate 8; signals never auto-resolve.

monthly spend, by-school) behind the single-admin login.

sync with the canonical markdown; 28th emits the HXL-CSV transparency export.

Cloud: care-dashboard (Service) · care-jobs (one image, HMT_CARE_JOB selects the tool) · 8 Scheduler entries · care-db bucket · shared care_auth secrets. Feeds: journey-writer (Stream C "journey miles") · Overview tiles · Finance (benefit.funding_source app-layer join — the "where it went" loop). Gaps: not yet mounted under /care; path drift — tools reference 01_Ho-so-thu-huong/ but the live cloud uses 10_Ho-so-thu-huong/ (stale literals); report_child/cohort/school.py are CLI-only, wired into no Job (the /reports page only lists pre-generated markdown).


3. 🟠 Tài trợ & Tài chính (Donor & Finance) — Areas 3+4

One line. Donor intake (form or bank statement) → cash contribution / in-kind handover in separate ledgers → Coordinator-voice thank-you + per-contribution PDF receipt → monthly cash + in-kind transparency reports. Cash (VND) and in-kind (units) are never summed, and a hard anonymisation linter gates any published report. Code-complete but not deployed (so-tai-chinh.sqlite not on disk; services not in deploy.ps1).

Triggers. Donor submits the form (webhook) · bank transfer arrives (CSV import) · manual cash/in-kind record · month-end report · at-confidence publish.

Walkthrough.

donor-input Service. Apps Script never writes the registry.

detector (email→phone→name+province), registers a new donor row. Detection is advisory — never silently merges.

Hard-refuses on a header mismatch (pinned _ncb_csv_schema.yaml), classifies each credit row, writes a _review.md + per-row YAML stubs. Writes nothing to the registry.

anonymous / non-donation). Unknown transfers can park on HMT-D0000.

(cash only; FK blocks an unknown donor). Idempotent on bank_txn_ref.

estimated_retail_vnd internal-only; drawn down into Area-1 benefits via inkind_disposal. Distinct table so VND and counts can never be summed.

letter (returning/first-time/overseas-aware), DRAFT-marked, no em-dash, no fabricated number, no donation ask.

attached. No SMTP, nothing auto-sent.

they can never drift apart.

expenditure = cash benefits joined from Area-1 at the app layer + operating costs, bank reconciliation, petal + per-school coverage, thank-you/receipt SLA) and a separate in-kind report (units, never a VND equivalent). Custodian signs.

cash (Phần A) and in-kind (Phần B) side by side, never summed. The finance_anon_check linter is a hard pre-publish gate with no Editor override — any non-opted-in name/contact/bank-ref blocks emission. The bundle then travels the normal media QC chain and waits for /approve.

Cloud: donor-input (built, not in deploy.ps1) · finance-dashboard (factory ready, not deployed) · Apps Script doorbell · GCS FUSE /data/finance. Feeds: Care (in-kind disposals + cash funding) · Stream-D media pipeline · Overview tiles · update-recipient send batch. Gaps: whole subsystem unprovisioned/undeployed; finance_dashboard has no login of its own (relies on the parent hub gate — this is the literal "~50 lines" to wire); receipt PDFs need weasyprint + GTK; receipt issuer block is placeholder (needs registration number + signature image); F-zero/D-zero hand pilots required before scale; sponsorship + payment-aggregator + SMTP are parked.


4. 🟣 Kế hoạch nội dung (Content plan / editorial intelligence) — Stream B

One line. A 12-month theme roadmap + 3-month editorial board + content-source palette with computed self-flagging intelligence (orphan trips, 8-petal gaps, 80/20 value:ask drift, observance countdown). Decision LOCKED: the content-calendar agent generates the plan artifact and the hub reads it.

Reality check — this is the least-built panel. The mockup /plan is static HTML with hardcoded numbers. None of these exist: content-plan.yaml (the only genuinely new data store), the agent's board/roadmap-generation capability, the hub /plan route + reader, the computed flags, and Knowledge/Brand/facts.md (the agent's declared fact base, missing). What is live: the content-calendar agent can draft individual evergreen post-bundles today (none currently on disk). Plan §4.3/§5 mark this Phase 3, allowed to lag.

Intended walkthrough (P3).

drafts evergreen bundles — but produces no roadmap/board today.

+ orphan-trip feed + journey milestones.

(orphan trips, petal gaps, 80/20 drift, observance countdown) at read time.

plan surface itself is read+route only.

Feeds: Stream-B production · Overview · Presence (shares the 80/20+petal computation) · consumes the Trips orphan-trip feed. Gaps: see reality check — the entire editorial-intelligence leg is unwritten, and its inputs depend on the unbuilt P2 trip/presence read-views.


5. 🔵 Hoạt động & Chuyến đi (Trips) + 📣 Hiện diện xã hội (Presence) — two native read-views

One line. Trips reconstructs a field-record ledger (one row per event over the Dashboard Sheet + brief.yaml + registry.sqlite photo counts) and flags orphan trips (media landed but no story shipped). Presence aggregates cadence / 80-20 value:ask / 8-petal coverage over the registry.post table + monthly recaps. Both compute, never store; nothing publishes.

Reality check. Neither route exists (app/hub.py unbuilt). The underlying field data (Dashboard Sheet, brief.yaml, registry.asset) is live, but registry.post + performance are empty (posts_published: 0) and no recap bundle exists — so Presence has near-zero real input today.

Walkthrough.

Dashboard row (the upstream record Trips reads).

row.

date, outcome, petal) + registry.asset counts at the application layer (no ATTACH) into one ledger row per event.

reached approval/packed/published; shows photo count vs story state.

recaps to aggregate cadence (2–3/week), the 80/20 mix, per-petal coverage.

gaps — the same trailing-30-day logic quality-auditor already runs offline.

action stays in the review surface, not here.

Feeds: Content-plan (orphan + petal + 80/20 signals) · Overview today-strip · review queue (an orphan points back to the bundle). Gaps: routes unbuilt (P2); critical — nothing writes a post row (no INSERT INTO post anywhere; the manual-publish loop ends at publish.yaml + posted_id and never feeds back into SQLite), so Presence reads zeros until a post-row writer is built; petal_anchor in brief.yaml is free-text, not validated against the 8 canonical petals.


6. ⚪ Cộng đồng (Community) — Op1

One line. Approval-gated handling of FB comments/messages: cluster engagement, draft brand-voice replies a human must approve, hide+escalate safeguarding-sensitive comments, monthly engagement report. Intended only — nothing is implemented: no tool, no service, no data store, no app route, no FB ingest, no send path.

Reality check. The only real artifacts are the community-manager agent prompt, the /community skill markdown, community-ops.md (R1–R6), and Knowledge/Brand/faq-replies.md — itself a 6-stub placeholder with donate/ volunteer wording still pending Foundation P0. Zero engagement-report files exist.

Intended walkthrough.

library (never verbatim).

debate publicly.

press to leadership only.

escalations) + the R6 accountability follow-up loop.

Feeds: the AUDIENCE→RESOURCES "where it went / follow-ups" return edge of the loop; safeguarding escalations → the gate-8 owner. Gaps: the entire workflow is unbuilt (Phase 5, only when Op1 is built); the return edge of the operating loop is drawn but unwired — the loop does not actually close back to the audience today.


7. 🟣 Tổng quan (Overview) + the single-login parent shell

One line. The hub's front door: a single-login parent (app/hub.py) that aggregates summary counts and a "Cần bạn xử lý hôm nay" action strip by three independent DB reads stitched in Python (never ATTACH-joined), and routes the administrator into each area's own surface where the gates live. Not built — only mock HTML exists. The three sub-apps are real (review live, care deployed, finance built); the single-login primitive (care_auth) is live and already shared by review+care.

Walkthrough (planned).

fail-closed 303 to /login (no secrets configured = locked).

(care_auth.make_session; one account, username cosmetic).

awaiting-approval ("posts to approve") + revising. (FIELD→EDITORIAL leg.)

open watchlist (action_taken IS NULL) + needs-review intake backlog.

needs_thanks (thanked_at IS NULL). (RESOURCES leg closes the loop.)

intake) + per-area tiles. Cross-area facts stitched in Python, never by SQL JOIN across files. (The one proven cross-DB seam today is finance_report_monthly.build_report(care_db=...).)

editor approval) → publish_pack. The Overview can only count and link, never approve.

Cloud (planned): one management-hub Service, two gcsfuse mounts (/data=bundles bucket, /care-data=care-db), one service account on both, mounting review /review + care /care + finance /finance, replacing the standalone review + care services. Gaps: app/hub.py + Overview + login middleware don't exist (P1); /trips, /plan, /presence don't exist (P2/P3); finance_dashboard.create_finance_app has no auth params (the literal "~50 lines"); the two-bucket single-container deploy + Dockerfile is the real heavy lift (P4); template absolute-link audit (href="/bundle/..", /children/.., /donors/..request.url_for) is required before any mount.


8. Loop integrity — what holds, what is open

Built and load-bearing (the spine that works):

publish_pack** is the one complete, Phase-A-correct hand-off. Every panel agrees the single non-overridable gate is approve-with-dignity_ok, and the read-only panels correctly keep it out of the hub.

ATTACH-joined**; the one proven cross-DB read is finance_report_monthly reading the care DB read-only.

just needs the param plumbing.

The four open edges (ranked by importance):

no INSERT INTO post anywhere; the manual-publish loop ends at publish.yaml + posted_id and never back-fills registry.sqlite. Presence reads zeros indefinitely until this is built. This is the single most important gap.

code in the whole loop is mock HTML; everything "feeds Overview" feeds nothing yet.

board-emission, facts.md missing. The EDITORIAL leg the loop pivots on is the least real.

"where it went / follow-ups" loop-back does not close.

Smaller hazards: the headless pipeline runs only gate 1 + records gate-8-pending (agent gates 3–7 not invoked in the cloud path — automated QC is thinner than qc-enforcement.md implies; the human approval is the real check); Care path drift 01_ vs live 10_Ho-so-thu-huong/; two parallel decision write-paths (review app + Dashboard Sheet onEdit, both calling decisions_core, Sheet now secondary); video_edit orphaned from the daily photo-only chain.


Build order (from plan §7, with the operating reality folded in)

at /finance + finance auth-param wiring + finance template-link audit.

Add: a post-row writer so Presence/AUDIENCE has data (the #1 gap above — not currently called out in the plan).

create the missing facts.md.

two-bucket gcsfuse deploy; replace the old services after a full approve-flow smoke test.