_system / notes · companion to kế hoạch tích hợp
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)
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
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.
onFormSubmit.gs → theevent-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.
ingest-watch Job polls Drive changes.list from a stored page token,downloads new photos, dedups by SHA-256, writes the untouched original to Sandbox/posts/<slug>/source/, registers an asset row in registry.sqlite.
pipeline Job — sanitise. EXIF/GPS strip into sanitised/ (gate 2,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.
STATUS.md + publish.yaml; draft VN copy with ClaudeOpus vision from brief.yaml (incl. the outcome beat) + prior revise notes, prompt-cached brand-voice corpus.
bundle_validate (gate 1: schema, route-regression, DRAFT-vs-state,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.
status-reconcile Job is the~20-min self-healing backstop.
a near-final FB/web post + full gallery + gate findings.
dignity_ok ticked. decisions_core.apply_decisionwrites 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/).
publish_pack): strip the DRAFT marker, copy renditions +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_id → state=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).
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/.
care-intake-convert Job (hourly) routes each by classify_drop,sanitises any image (EXIF/GPS, fail-closed), idempotent via _done__ rename.
beneficiary_promote. Opus returns a pydanticPromoteEntry (date/title/source/petal/body, verbatim quotes only), prepended newest-first to the profile + a profile_entry row.
school_doc_extract. Opus returns aSchoolDocExtract; 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.
/record path (skill → beneficiary-recorder agent): for an itemflagged "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.
safeguarding-watchlist Jobs run detectors (absence > 60d, followup-overdue,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).
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.
donor-input Service maps values → DonorInput, runs the returning-donordetector (email→phone→name+province), registers a new donor row. Detection is advisory — never silently merges.
contribution_import_statement on the NCB CSV.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.
con-YYYY-NNNNN.md + a contribution row(cash only; FK blocks an unknown donor). Idempotent on bank_txn_ref.
inkind_record): own units,estimated_retail_vnd internal-only; drawn down into Area-1 benefits via inkind_disposal. Distinct table so VND and counts can never be summed.
/thank-you (thank-you-composer): Opus drafts a personal Coordinator-voiceletter (returning/first-time/overseas-aware), DRAFT-marked, no em-dash, no fabricated number, no donation ask.
attached. No SMTP, nothing auto-sent.
thank_you_record.stamp_thanked stamps thanked + receipted together sothey 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.
finance_report_publish composes a Stream-D bundle layingcash (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.
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).
/calendar to plan/draft Stream-B content (the current real entry).content-calendar plans coverage across the 8 activity types, enforces 80/20,drafts evergreen bundles — but produces no roadmap/board today.
content-plan.yaml from VN observances+ orphan-trip feed + journey milestones.
/plan route reads the artifact and computes flags(orphan trips, petal gaps, 80/20 drift, observance countdown) at read time.
/qc-run → /review-queue → /approve. Theplan 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.
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.
brief.yaml + a cho-uploadDashboard row (the upstream record Trips reads).
registry.asset rows + each job upserts the Dashboardrow.
brief.yaml (activity_type, school,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.
registry.post + performance + trailing-monthrecaps 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.
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.
/community (no API in Phase A).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.
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).
Sandbox/posts/*/STATUS.md, countawaiting-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=...).)
dignity_ok (folded gate-8 +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.
Built and load-bearing (the spine that works):
decisions_core.apply_decision(dignity_ok) →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.
care_auth is live and shared; financejust needs the param plumbing.
The four open edges (ranked by importance):
post-row writer → the AUDIENCE leg has no data. posts_published: 0,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.
app/hub.py + Overview + login middleware don't exist — the only connectivecode in the whole loop is mock HTML; everything "feeds Overview" feeds nothing yet.
content-plan.yaml, no agentboard-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.
app/hub.py: login + middleware + Overview (mock numbers) + mount financeat /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).
content-calendar to emit content-plan.yaml; build /plan + flags;create the missing facts.md.
management-hub Dockerfile;two-bucket gcsfuse deploy; replace the old services after a full approve-flow smoke test.