GN Gerege Nexus
GitHub Нэвтрэх

Тенант дамнасан тайлан — report_grants

Уурхай 100 тээврийн компанийн тайланг нэг дэлгэцээс харах. Тенант тусгаарлалтыг сулруулахгүйгээр.

Баримт бичгийн төв рүү буцах · Тайлангийн хөдөлгүүр · Дизайны санал §3.5


1. Асуудал

Нүүрсний уурхай 100 тээврийн компанитай гэрээтэй. Компани бүр өөрийн тенант дотроо рейс, түлш, хүргэлтээ бүртгэдэг. Уурхай "Тээвэр" гэсэн ганц нэгдсэн тайлан харахыг хүснэ.

Энэ бол платформын бүх бүтэц эсрэг чиглэлд ажилладаг хүсэлт: тенант тусгаарлалт нь яг үүнээс сэргийлэхийн тулд бий. Тиймээс шийдэл нь тусгаарлалтыг сулруулах биш, зөвшөөрөлд суурилсан тусдаа зам нэмэх явдал.


2. Таван зарчим

Зарчим Хэрхэн хэрэгжсэн
Default deny Grant байхгүй бол RunConsolidated хоосон буцаана. RLS хэвээрээ, ямар ч бодлого сулрахгүй
Тодорхой, буцаагдах Эзэмшигч талын админ зөвшөөрөх хүртэл accepted_at NULL, ActiveGrants түүнийг үл тоомсорлоно. revoked_at бичигдмэгц дараагийн гүйлтэд хаагдана
Counterparty scope Тайланд p.Counterparty()-оор хүлээн авагчийн регистрийн дугаар өгөгдөнө. Тайлан түүгээр шүүнэ
Түүхий мөр биш, үр дүн Хөдөлгүүр эзэмшигч бүрийн тенант контекст дотор ижил Run-ыг дуудна. Хүлээн авагч тал өөр хүснэгтээс уншдаггүй
Хоёр талын audit Гүйлт бүр эзэмшигч талд reports.data_shared, хүлээн авагч талд reports.consolidated_read

3. Механизм

Grant үүсэх

  1. Хүлээн авагч (уурхай) хүсэлт үүсгэнэ: эзэмшигчийн улсын бүртгэлийн дугаар, тайлангийн түлхүүр, хүрээ, хугацаа, тайлбар. → POST /api/v1/reports/grants
  2. Эзэмшигч (тээврийн компани)-ийн админ хүсэлтийг харна (Тохиргоо → Тайлан хуваалцах) ба зөвшөөрнө эсвэл татгалзана. → POST /api/v1/reports/grants/{id}/accept
  3. Аль ч тал хэдийд ч цуцалж болно. → POST /api/v1/reports/grants/{id}/revoke

Яагаад регистрийн дугаараар? Tenant ID бол дотоод танигч бөгөөд хүсэлт гаргагч түүнийг мэдэх зөв зам байхгүй; түүнийг гараар бичих боломжтой болгох нь энэ формыг суулгац дээрх байгууллагуудыг тоолох хэрэгсэл болгоно. Нэрээр биш: нэр давхардаж, өөрчлөгддөг.

counterparty_ref нь grant үүсэх үед нэг удаа шийдэгдэж хадгалагдана. Гүйлт бүрд дахин тулгах нь эзэмшигчийн профайл өөрчлөгдөхөд grant чимээгүй өөр өгөгдөл руу заахад хүргэнэ.

Нэгдсэн тайлан ажиллах

Уурхайн хэрэглэгч → POST /api/v1/reports/{key}/run-consolidated
  ├─ ActiveGrants(уурхай, key)      → идэвхтэй grant-ууд
  ├─ grant бүрд:
  │    Engine.Run(ctx, ЭЗЭМШИГЧИЙН tenant_id, report, params+counterparty)
  │      └─ dbguard холболтыг ЭЗЭМШИГЧИД холбоно
  │           └─ RLS бодлого ЭЗЭМШИГЧИЙНХ
  │                └─ тайлангийн өөрийн WHERE tenant_id = $1
  │    └─ audit ×2 (хоёр талд)
  └─ мөрүүдийг "Байгууллага" баганатайгаар нэгтгэнэ

Тайлан өөрөө нэгдсэн гүйлтэд байгаагаа мэдэхгүй. Ганц ялгаа нь p.Counterparty() хоосон биш байх явдал.

Алдаа

Нэг grantor унавал бусад нь үргэлжилнэ. 100 тээврийн компани гэдэг нь нэг удаан query гарах 100 боломж бөгөөд 97 дахь байгууллага timeout болсон учраас юу ч үзүүлэхгүй нэгдсэн тайлан бол хэн ч ашиглахгүй тайлан. Оронд нь тухайн компани үр дүнгийн notes-д "алдаатай" гэж тэмдэглэгдэнэ — чимээгүй дутуу нийлбэрээс дээр.


4. Тайлангаа хуваалцах боломжтой болгох

Тайлан өөрөө зөвшөөрөх ёстой. Энэ нь Go-гийн түвшин дэх default deny:

// Хоёуланг нь дэмждэг тайлан.
func (transportTrips) Scopes() []string {
    return []string{reporting.ScopeCounterparty, reporting.ScopeFull}
}

func (transportTrips) Run(ctx context.Context, q reporting.Querier,
    p reporting.Params) (reporting.Result, error) {

    sql := `SELECT route, tonnes FROM transport_trips WHERE tenant_id = $1`
    args := []any{reporting.TenantOf(ctx)}

    // ScopeCounterparty-г зарласан тайлангийн ҮҮРЭГ. Хөдөлгүүр үүнийг
    // шалгах боломжгүй — шүүлт нь тайлангийн өөрийн SQL дотор байдаг —
    // тиймээс энэ нь opt-in бөгөөд тайлан тус бүрд тесттэй байх ёстой.
    if ref := p.Counterparty(); ref != "" {
        sql += ` AND counterparty_ref = $2`
        args = append(args, ref)
    }
    ...
}

Scopes() хэрэгжүүлээгүй тайланг огт хуваалцаж болохгүй: grant үүсгэх endpoint татгалзана, RunConsolidated алдаа буцаана.

Одоогийн байдал

Хүргэгдсэн 8 тайлангаас billing-ийн хоёр нь ScopeFull-ыг дэмжинэ (толгой байгууллага охин компанийг нэгтгэх — §3.5-ын (а) тохиолдол). Аль нь ч ScopeCounterparty-г дэмжихгүй, энэ нь шийдвэр биш схемийн байдал: billing_invoices нь харилцагчийн нэрийг хадгалдаг, регистрийн дугаарыг биш. Байгууллагуудыг гараар бичсэн нэрээр тулгах нь §3.5 яг зайлсхийж буй алдаа. Хүрээг зарлаад юу ч шүүхгүй тайлан нь гэрээнд огт хамаарахгүй бүх нэхэмжлэхийг гаргаж өгнө.

Уурхай/тээврийн бодит тохиолдол нь мөрөндөө counterparty-ийн регистрийн дугаартай модулийг шаардана. Тэр модуль энэ репод хараахан байхгүй; механизм нь бүрэн бэлэн бөгөөд grants_integration_test.go доторх fixture-ээр бодит өгөгдлийн сан дээр шалгагдсан.


5. Хамгаалалт

RLS хоёр талтай. report_grants дээрх бодлого нь grantor БА grantee хоёуланд нь мөрөө харуулна. Ердийн tenant_id = current_setting(...) бодлого нь хүлээн авагчид өөрт нь өгөгдсөн эрхийг харуулахгүй байх байсан — тэр мөр эзэмшигчийнх учраас — өөрөөр хэлбэл тал өөрийн эрхээ мэдэхгүй байх байв. Тиймээс энэ хүснэгтэд tenant_id багана зориудаар байхгүй: байсан бол 00029-ийн ерөнхий бодлого автоматаар хамаарч хоёр талын харагдацыг хаах байв.

Зөвшөөрөх нь зөвхөн эзэмшигчийнх. accept endpoint-ийн grantor_tenant_id = $2 заалт бол тэр endpoint-ийн бүх эрх шалгалт: RLS хоёр талд мөрийг харуулдаг тул энэ заалтгүй бол хүлээн авагч өөрийн хүсэлтээ өөрөө зөвшөөрөх байв.

Устгахгүй, цуцална. report_grants-д DELETE эрх олгогдоогүй. "Хэн бидний өгөгдлийг харж болох байсан, хэзээ" гэдэг нь дараа нь асуугдах асуулт.

Нэг хос, нэг тайлан, нэг амьд grant. Partial unique index. Хоёр амьд grant байвал аль нь үйлчлэхийг шийдэх дүрэм байхгүй бөгөөд нэгдсэн тайланд мөр давхардана.


6. Тестүүд

backend/internal/platform/reporting/grants_integration_test.go — бодит өгөгдлийн сан шаардлагатай (DATABASE_URL), эс бөгөөс алгасана.

Тест Юуг батлав
TestConsolidatedShowsNothingWithoutAGrant Grant-гүй бол юу ч харагдахгүй
TestRevokingAGrantClosesItAtOnce Цуцалсны дараа дараагийн гүйлтэд хаагдана
TestCounterpartyScopeExcludesAnotherMinesRows Өөр уурхайн мөр орохгүй
TestConsolidatedRunIsAuditedOnBothSides Хоёр талд бичигдэнэ
TestAReportThatIsNotShareableCannotBeConsolidated Зөвшөөрөөгүй тайлан хуваалцагдахгүй
TestAnExpiredGrantStopsWorking Хугацаа дуусахад өөрөө хаагдана
TestAnUnacceptedRequestGrantsNothing Хариу аваагүй хүсэлт бол эрх биш

7. Дэлгэц

Тохиргоо → Тайлан хуваалцах (/settings/report-sharing):

Сүүлийнх нь хамгийн чухал: тээврийн компани уурхайд рейсээ харуулахыг зөвшөөрөх нь уурхай юу үзсэнийг дараа нь харж чадах тохиолдолд л боломжтой.

Тайлан (/reports) дээр "Нэгдсэн" товч. Тусдаа товч бөгөөд горим биш: өөр асуултад хариулдаг тул хэн нэгний асаачихсан toggle нь тоонуудын утгыг чимээгүй өөрчлөх ёсгүй. Үр дүн дээр компаниар задлах/нэгтгэх сонголт бий.


8. Эрх

reports.share — хоёр чиглэл: өөр байгууллагаас тайлан хүсэх, өөрийнхийгөө харуулахыг зөвшөөрөх. reports.view болон reports.schedule-ээс тусдаа, учир нь энэ бол тенантын хилийг давдаг цорын ганц эрх.