_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
Cập nhật từ comments của administrator (2026-06-02). Hai quyết định định hình lại phần FIELD→EDITORIAL: (a) tách hẳn việc đưa hoạt động về Hub khỏi việc viết bài; (b) viết bài là theo yêu cầu, có hộp chỉ đạo và chọn ảnh. Mục này mô tả hướng mục tiêu; các mục 1–8 bên dưới vẫn ghi đúng trạng thái build hiện tại để đối chiếu. Hai điểm chưa khớp code được nêu rõ ở cuối mục.
photos/, videos/), và lưu phiếu đã điền vào thư mục đó (brief.yaml).2a · Câu chuyện từ một hoạt động (theo yêu cầu).
10_Cau-chuyen/ — dùng lại kho hiện có; band mới 09_Mang-xa-hoi/ đã bị hủy 2026-06-02, phân biệt bản đăng vs bản ghi bằng thẻ registry.post).2b · Các nguồn nội dung khác ngoài hoạt động thực địa (gương em, tin trường, ngày lễ, cột mốc Quỹ, thường kỳ/theo mùa, minh bạch tài trợ) — xem §6 Kế hoạch nội dung.
Chi tiết end-to-end: §1 luồng thu nhận hoạt động · §2 media & story workflow (viết bài theo yêu cầu).
Hai điểm chưa khớp code hiện tại (việc cần làm):
event-folder Service hôm nay tạo thư mục + brief.yaml + dòng Dashboard state=cho-upload, nhưng không gửi email link về cho người điền phiếu. Cần thêm bước gửi mail (qua onFormSubmit.gs — Apps Script đã giữ email người gửi — hoặc từ Service). 🟣pipeline Job chạy 09:00 hằng ngày và tự soạn caption cho mọi bundle có ảnh. Hướng mục tiêu: pipeline hằng ngày chỉ ingest + sanitise; bước cull→retouch→caption chuyển sang chạy theo yêu cầu khi bấm “Yêu cầu viết bài”, nhận thêm hộp chỉ đạo + danh sách ảnh do người dùng chọn làm input cho caption_draft. 🔵One line. A coordinator submits the Activity Form; a Service creates the activity's own folder (photos/videos subfolders), saves the filled form inside it, and emails the folder link back to the submitter; the coordinator uploads media; a watch Job ingests + sanitises it and updates the activity (with its folder link) onto the Management Hub. The workflow ends here — no story is drafted. (This is Workflow 1 from the 2026-06-02 comments; §0.)
Triggers. Activity-Form submit (webhook) · photos land in Drive · ingest-watch Job (every 3 min) · daily pipeline Job — target: ingest + sanitise only.
Walkthrough.
onFormSubmit.gs → the event-folder Service (event_folder_core.create_event_folder) makes 01_Tai-lieu-thuc-dia/<date>__<slug>/{photos,videos}, writes the filled form as brief.yaml inside it, appends a Dashboard row state=cho-upload.onFormSubmit.gs, which already holds the submitter's email) sends the new folder's link to whoever filled the form, so they know where to upload. Not built yet — this is a new step.photos/ · videos/ subfolders.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.sanitised/ (gate 2, exiftool, hard-fails if absent — never silently downgraded). Target: this is the last automatic media step; cull→retouch→caption no longer run on their own (they move to §2, on story-request).status-reconcile Job (~20 min) is the self-healing backstop.Cloud: event-folder · ingest-watch · status-reconcile (all deployed-live). Feeds: Trips read-view (§7) · Content-plan orphan/petal flags (§6) · the story-request entry point (§2). Gaps: email-link-back not built (🟣); decoupling not done — today the daily pipeline Job continues straight on into cull→caption (the old coupled chain, now §2) instead of stopping at sanitise; petal_anchor in brief.yaml is free-text, not validated against the 8 petals.
Một luồng thu nhận thực địa thứ ba, song song với §1 (Hoạt động): cùng khuôn form → thư mục → watch job → Hub, nhưng dữ liệu là ứng viên chương trình (hồ sơ gia đình, nhạy cảm, nội bộ — không bao giờ đăng) và đầu ra là một học sinh mới ở §3 (Quản lý học sinh), không phải một bài đăng.
One line. A coordinator submits the Survey Form (candidate / family details for the Foundation's programmes); a Service creates the candidate's own folder under a new restricted band 11_Khao-sat/ (with an anh/ photo subfolder), saves the filled form inside it, and emails the folder link back; the coordinator uploads home-visit photos; a watch Job sanitises them and updates the candidate (with its folder link) onto the Hub and a candidate register for later review; after an internal human promotion (the "admit" button on the Hub), an admitted candidate is minted as a new journey-NNNN with its own folder in the care band and moves to the Quản lý học sinh workflow (§3). Sensitive, internal-only — nothing here ever becomes a published post.
Triggers. Survey-Form submit (webhook) · photos land in a candidate anh/ folder · a survey watch Job (sanitise + Hub/register upsert) · the administrator's promotion decision on the Hub Khảo sát panel.
Walkthrough.
onSurveySubmit.gs → a survey-folder Service (the §1 event-folder pattern, re-pointed at the restricted band) makes 11_Khao-sat/<date>__ung-vien-<NNNN>/{anh}, writes the filled form as phieu-khao-sat.yaml inside it, and appends a candidate register row state=cho-xet-chon. Folder code, not a real name — the slug is a non-identifying case id (ung-vien-NNNN), per the no-real-name-in-identifiers rule.onSurveySubmit.gs, which already holds the submitter's email) sends the new candidate folder's link to the coordinator so they know where to upload the home-visit photos. Same new step as §1 — not built.anh/ subfolder.watch Job upserts the Khảo sát row with the candidate's key details + the folder link + photo count, and writes/updates the candidate register (the index for future review). A status-reconcile-style pass is the self-healing backstop.journey-NNNN (monotonic, never reused), creates the beneficiary's designated folder under the care band 10_Ho-so-thu-huong/Ho-so/journey-NNNN/, seeds the profile from phieu-khao-sat.yaml (a beneficiary_promote-style first Block-3 note), and stamps the candidate register state=da-chon with the new journey-id link. The candidate now lives in the Quản lý học sinh workflow (§3); the survey record stays in 11_Khao-sat/ as intake provenance.Cloud: a new survey-folder Service (the §1 event-folder pattern re-pointed at 11_Khao-sat/) · a survey watch/sanitise Job · the §3 care subsystem on admission · a new candidate register (markdown-canonical + SQLite projection, mirroring care; its own store in the new band, separate from so-dang-ky.sqlite until admission). Feeds: the Care subsystem (§3) — admitted candidates become journey-NNNN · the Quản lý học sinh ledger · the Overview action strip ("khảo sát chờ xét chọn"). Does not feed the media/story streams (internal-only). Gaps: everything here is unbuilt (🟣) — the Phiếu khảo sát Form + onSurveySubmit.gs, the 11_Khao-sat/ restricted band + its permissions (chosen 2026-06-02: a new band, kept separate from the beneficiary store until admission), the survey-folder Service path, the candidate register, the watch-job Hub/register upsert, the email-link-back (the same gap as §1), and the admission → journey-NNNN handoff (no candidate→care promote exists). The mockup #survey panel already exists (a read/write-light UI in the Hub), but nothing backs it with data or services yet. Naming: a candidate id scheme (ung-vien-NNNN) must be reserved alongside journey-NNNN so the two never collide.
One line. On a story-request for an activity, the system reads its form + photos, takes an optional direction box and a chosen-photo set (or auto-culls), and drafts FB + Website copy into a post-bundle at awaiting-approval; the administrator approves with a required dignity_ok (gate-8 folded in); a Stage-1 manual pack is emitted and the approved final is archived to the 10_Cau-chuyen/ story archive. Nothing auto-publishes. (Workflow 2a; §0.)
Triggers. 🔵 “Yêu cầu viết bài” button on a Hub activity (target primary path) · today: the daily 09:00 ICT pipeline still auto-drafts every bundle with photos (to be narrowed — §1) · administrator opens the queue.
Walkthrough.
selected/ (media_triage). Target: a user-supplied photo list overrides the auto-pick.STATUS.md + publish.yaml; draft VN copy with Claude Opus vision from brief.yaml (incl. the outcome beat) + the direction box + prior revise notes, prompt-cached brand-voice corpus. New input: the direction box steers selection + framing before the draft.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; satisfies the non-empty gate_findings rule). Clean → awaiting-approval; blocking → in-review.dignity_ok ticked. decisions_core.apply_decision 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/).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.10_Cau-chuyen/<slug>/ (reuse the existing story archive). Decision revised 2026-06-02: the provisional 09_Mang-xa-hoi/ band is not provisioned; the social-final vs story-record distinction is carried by registry.post tags, not a separate Drive band._system/runs/qc-log-YYYY-MM.jsonl; an approve feeds the accepted caption into few-shot.yaml. Periodically a self-improve summary of the administrator's edits is sent for review — nothing is applied to prompts/few-shot until the administrator agrees. Admin later types the posted_id → state=published.Cloud: pipeline · decisions · review (all deployed-live) · Anthropic Opus. Feeds: Presence (§7) · Community (§8) · quality ratchet · the 10_Cau-chuyen/ finals archive. Gaps: the on-demand trigger + direction box + user photo-pick are unbuilt (🔵); the finals → 10_Cau-chuyen/ write on approval is unbuilt (🟣) (no new 09_ band — decision revised 2026-06-02); the human-gated self-improve summary cadence is unbuilt (🟣) — auto-optimize exists but proposes on its own cadence, not a per-edit summary for review; not yet mounted under /review (P4); the headless pipeline runs only gate 1 + records gate-8-pending (agent gates 3–7 are not invoked in the cloud path, so 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. A new child enters via the §1b survey-selection intake (admission → journey-NNNN) · 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 10_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. Everything about the donor relationship: a donor self-registers from a website/Facebook donate link → form → a personalised VietQR; the cash gift arrives by bank; a transactional thank-you + PDF receipt goes out (auto-sent, scoped relaxation); the donor joins the distribution list + watch list; refined-format newsletters and holiday/achievement appreciation keep the relationship warm; repeat donors get history-aware wording. (Workflow 2·Tài trợ from the 2026-06-02 comments; mockup: Donor engagement & communications.)
Triggers. Website/FB donate link → Activity-style donor Form submit (webhook) · 🔓 bank-credit notification (target: a SePay Open-Banking webhook on bank credit — candidate, see Gaps; today: NCB CSV import) · scheduled newsletter · special event (holiday / Foundation milestone / donor anniversary).
Walkthrough.
donor-input Service (OIDC; never writes the registry). The Service maps → DonorInput (name, phone/email, province, privacy_choice, opt_in_updates), registers HMT-D<NNNN>, and runs the returning-donor detector (donor_match: email→phone→name+province). Advisory — never auto-merges.qr_donor mints a per-donor NAPAS-247 QR with HMT-D<NNNN> pre-filled in the transfer note, on a branded 5:4 canvas (offline, no bank credentials). Shown after the form / shareable by the donor — so the inbound transfer self-identifies.contribution_import_statement, which hard-refuses on a header mismatch). The credit is classified + matched to the donor by the transfer-note code; contribution_promote writes con-YYYY-NNNNN.md + a contribution row (idempotent on bank_txn_ref).thank_you_draft (Opus, prompt-cached Coordinator voice) drafts a personal letter; for a returning donor it acknowledges the history (gift count, prior support) and thanks more warmly; the matching per-contribution PDF (contribution_receipt) is attached; thank_you_record stamps thanked + receipted together. 🔓 Scoped relaxation (administrator-approved): the templated transactional thank-you+receipt auto-sends on the bank match (receipts only). The claims rules still hold — no fabricated number, no extra donation ask, anonymised where the donor didn't opt in.update_recipient_register adds the donor (by opt-in) to the distribution list (newsletter) and a watch list (repeat / major-donor attention); update_recipient_unsubscribe honours opt-out./approve, then edited + sent by a human (not auto).Cloud: donor-input Service (built, deploy-ready) · qr_donor / thank_you_draft / thank_you_record / update_recipient_* (code-complete) · so-tai-chinh.sqlite (donor + contribution + update_recipient). Feeds: Finance (§5, shares the contribution table) · the newsletter pulls from approved media posts (§2). Gaps: 🔓 transactional auto-send needs an SMTP path + the scoped-relaxation override recorded (today nothing auto-sends, no SMTP); the bank-credit trigger is unbuilt (today = manual CSV import) — candidate under review (2026-06-02, not decided): SePay Open-Banking webhook (sepay.vn) watches the account and fires a signed webhook (~10s) with amount + transfer note (HMT-D<NNNN>) + a stable txn id; a contribution_webhook endpoint would verify HMAC-SHA256, adapt the payload to the shape contribution_import_statement already emits, then run donor_match → contribution_promote (idempotent on bank_txn_ref; SePay id = dedup key). A confirmed match is exactly the trigger the scoped receipt auto-send is defined against (receipts-only; SMTP still needed). Our qr_donor already mints the VietQR offline, so SePay's free QR generator is not needed. Confirm before adopting: Foundation owns the SePay account + bank connection (third-party processor of financial data, external legal/data handling), NCB supported, webhook-tier pricing. Newsletter + appreciation send is unbuilt (recipient lists are opt-in metadata only); Drive band 08_Quan-he-nha-tai-tro/ not provisioned (code still emits old 02_).
One line. An initial input sets the opening financial position; thereafter financial statements are uploaded periodically, extracted, and turned into detailed internal reports on the hub; a high-level public summary (with supporting statements) is published to the website for transparency through the media QC + /approve gate, and can seed a media story. In-kind contributions sit in a separate ledger (items received, where/when distributed). Cash (VND) and in-kind (units) are never summed. (Workflow 2·Tài chính; §0.)
Triggers. Initial financial-position input · periodic statement upload (bank CSV / financial statement) · month-end reconciliation · at-confidence transparency publish · in-kind handover / disposal.
Walkthrough.
contribution_import_statement (NCB CSV, hard-refuses on header mismatch) classifies each credit; the custodian resolves unknowns (confirm / assign / register / anonymous / non-donation); contribution_promote writes the cash rows.finance_report_monthly builds income by channel/intent/donor-type, expenditure = Area-1 cash benefits (app-layer join, never SQL-ATTACH) + operating costs, bank reconciliation (opening + income − benefits − costs = closing; discrepancies flagged, never rounded), and 8-petal + per-school coverage. Audience filter: internal / trustee / published-named / published-anon.inkind_record tracks items received (units, e.g. “300 bộ đồ dùng”, estimated_retail_vnd internal-only, handover location); inkind_disposal tracks each drawdown (how many, where, when → which Area-1 benefit). inkind_report_monthly shows opening / received / disposed / closing. Never summed with cash.finance_report_publish composes a Stream-D bundle: Phần A cash + Phần B in-kind side by side + a “one number that grows” line + the supporting statements. 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 before website + FB.Cloud: finance_report_monthly / finance_report_publish / inkind_record / inkind_report_monthly / finance_anon_check (code-complete) · finance_dashboard (factory ready, mounts at /finance, not deployed) · so-tai-chinh.sqlite. Feeds: website transparency · media story seed (§6) · Care (in-kind disposals + cash funding). Gaps: whole subsystem unprovisioned/undeployed; the initial-position input + a general financial-statement extractor are unbuilt (today extract = NCB-CSV only); finance_dashboard has no login of its own (relies on the parent-hub gate — the literal “~50 lines”); receipt PDFs need weasyprint + GTK; F-zero/D-zero hand pilots before scale; sponsorship + payment-aggregator + SMTP parked.
One line. A 12-month theme roadmap + 3-month editorial board + a wide content-source palette, refreshed by a periodic planning agent that proposes, with computed self-flagging intelligence (orphan trips, 8-petal gaps, 80/20 value:ask drift, observance countdown). The Editor confirms slot-by-slot; picking a confirmed slot routes into the normal on-demand draft→QC→approve path. The /plan surface itself is read + route only — it never drafts and never posts.
Reality check — the least-built panel. The mockup /plan is static HTML with hardcoded numbers. None of these exist: content-plan.yaml (the only new data store), the planning agent, the hub /plan route + reader, the computed flags, registry.post rows (so every count reads zero), and Knowledge/Brand/facts.md (the agent's declared fact base, missing). Live today: content-calendar can draft individual evergreen bundles (none on disk). Phase 3, allowed to lag.
Decomposition (reconciled with the newsroom split + the 2026-06-02 on-demand reshape): a new content-strategist agent plans (proposes/refreshes content-plan.yaml periodically); the existing content-calendar drafts one picked slot into a bundle. Splitting keeps planning (synthesis, no vision) apart from drafting (Opus vision).
Cadence. A monthly anchor run rewrites the rolling 3-month board + re-checks the 12-month roadmap; a cheap weekly refresh only recomputes the intelligence flags. Phase-A safe — it writes a proposal, never a bundle.
Sourcing palette (the content scout, wider than trips+holidays, every source already in the workspace): (1) orphan-trip feed — events with media but no story, highest priority; (2) VN observances; (3) 8-petal / 8-activity coverage gaps; (4) journey milestones (Stream C, anonymised, no PII); (5) Foundation fact base facts.md (missing — prerequisite); (6) donor "where it went" follow-ups; (7) evergreen series (explainers, "one number that grows", spotlights, partner-school news, seasonal).
Steering. A directions: block of Editor-owned plain prose at the top of content-plan.yaml ("Q3 nhấn mạnh hướng nghiệp", "ít bài kêu gọi hơn"), read as a steering prompt before the agent proposes; every slot records what produced it (source · directed_by). Same pattern as the on-demand draft direction box, lifted to plan altitude.
Propose → confirm (per-slot gate). The agent only ever writes slots as status: idea. The hub shows them as a reviewable diff. The Editor confirms slot-by-slot, promoting idea → planned with a per-slot tick (the same human-tick discipline as dignity_ok). An unconfirmed idea slot cannot be drafted. Nothing auto-advances. Artifact: Sandbox/_content-plan/content-plan.yaml (directions + roadmap_12mo themes + board_3mo slots with type/source/status/petal; flags computed at read time, never stored).
Pick = the "Yêu cầu viết bài" button on a planned slot — the 2026-06-02 reshape generalised from field-trips-only to any plan item. The slot's type routes the producer: A (field event / orphan trip) → caption_draft (brief + culled renditions + direction box + optional photo-pick); B (evergreen / observance / explainer) → content-calendar (fact base + direction box + photo or quote-card template); C (journey milestone) → /journey (anonymised, no photo-pick by default).
Save & register (closing today's open loop): /approve → publish_pack manual pack (Phase A) → story_archive writes the human-readable record to 10_Cau-chuyen/<slug>/ — reuse the existing story archive; the provisional 09_Mang-xa-hoi/ band is NOT provisioned, the social-final vs story-record distinction is carried by registry.post tags, not a separate band → on posted_id, write a registry.post row (INSERT INTO post does not exist today — the gap that makes Presence read zeros) → flip the slot → posted, link posted_id back.
Ingest only reads 01_Tai-lieu-thuc-dia. Stream-B and reuse need photos that never came from a field trip: reuse of past renditions (already in registry.asset, pick by reference); brand assets / graphics (03_Tai-san-thuong-hieu; quote-cards often need no photo); partner / third-party supplied (needs a permission / public-domain / attribution basis — blocking if absent); coordinator ad-hoc. The lane: an "Đính kèm ảnh" action drops files into source/ and must run media_sanitise (EXIF/GPS strip, gate 2) + a per-asset provenance sidecar (origin, supplied_by, license_basis, attribution_required). claim-checker already blocks reposted third-party media without a recorded basis. Firewall untouched — nothing crosses out; sanitised working media in-repo is allowed (workspace-ethics.md §1).
Two layers: the human-readable 10_Cau-chuyen/<slug>/ record, and the lineage/query layer registry.sqlite post table (powers the rest, once the 4.B writer exists). Each post row carries tags — activity_type · petal · program · school · journey_id · theme · observance (petal_anchor must be validated against the 8 canonical petals). Three read-only features fall out: (1) "related stories" at draft time (prior posts sharing school/petal/program/journey-id — guards the "single story"); (2) suggestions back into the plan (content-strategist queries the rows for under-covered petals/activities → new idea slots, the intelligence flags now backed by real data); (3) reminders / follow-up loop (time queries → the Overview today-strip countdown).
Feeds: Stream-B production · Overview (countdown + suggestions) · Presence (shares the 80/20 + petal computation, and the registry.post writer it needs) · consumes the Trips orphan-trip feed. Gaps (build order): (1) facts.md fact base; (2) registry.post writer + tag columns + petal validation (unblocks Presence and 4.D); (3) content-plan.yaml + content-strategist; (4) hub /plan route + reader + flags; (5) external-photo lane. (3)/(4) depend on the unbuilt P2 trip/presence read-views.
Lưu ý: đây là khung nhìn đọc trên Hub hiển thị các hoạt động — khác với §1 là luồng thu nhận tạo ra chúng. §1 ghi dữ liệu; §7 đọc và tổng hợp.
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 savedinside it + a cho-upload Dashboard row (the upstream record Trips reads). Target (§0): the Service also emails the folder link back to the submitter so they know where to upload (🟣 not built yet).
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-strategist agent emits/refreshes content-plan.yaml (proposes idea slots; content-calendar still drafts a picked slot); build /plan + per-slot promote + flags + the external-photo lane;create the missing facts.md.
management-hub Dockerfile;two-bucket gcsfuse deploy; replace the old services after a full approve-flow smoke test.