Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/recruiting/redact.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/recruiting/redact.go |
| Original SHA-256 | 3b7a894242e82f88f200abe10ffa2dea19ee8fc0eea65d7204dcafa6ac853ced |
| Git blob SHA | 076b0404e54ab83377413a6ea2735ca8899d7848 |
| Reading-copy SHA-256 | 3b7a894242e82f88f200abe10ffa2dea19ee8fc0eea65d7204dcafa6ac853ced |
Recovered source transcript
Download complete source text ↓180 lines · 6.9 KB
package recruiting
import (
"context"
"log"
"net/http"
"github.com/missionworkforce/missionmatch/internal/platform/orgsettings"
)
// RATE AND MARGIN SENSITIVITY, ENFORCED ON THE WAY OUT.
//
// The organization can already say who sees pay data and who sees the margin, and until this file existed
// that setting did nothing at all. It was read in one place, GET /org/policy, which REPORTS the answer so an
// interface can hide a column. Every offer, placement and requisition still carried its bill rate, its pay
// rate and its margin to every role that could open the record, so the entire control was a curl away from
// being irrelevant and the settings screen was promising something the API did not do.
//
// THE MONEY IS REMOVED, NOT ZEROED. Every money field is a pointer and is omitempty, so a redacted rate is
// ABSENT from the response rather than present as zero. That distinction is the whole point: zero is a
// number, and a bill rate of zero is a statement about a placement that would be false. Absent is the
// truthful rendering of "you are not shown this".
//
// TWO SETTINGS, NOT ONE. Rates and margin are governed separately, because they are different disclosures: a
// recruiter often needs the pay rate to have a conversation, and almost never needs to know what the agency
// makes. Redacting them together would force an owner to choose between blinding their desk and publishing
// their economics.
// redactOffer removes what the caller may not see from one offer.
func redactOffer(o *Offer, p orgsettings.Policy) {
if !p.CanSeeRates {
o.BillRate = nil
o.PayRate = nil
o.Salary = nil
}
if !p.CanSeeMargins {
// FeePct is the direct hire margin expressed as a percentage, so it belongs to the margin setting
// rather than the rate one. An owner who hid the margin and kept a visible fee percentage would have
// published the same fact in a different unit.
o.FeePct = nil
redactEconomics(&o.Economics)
}
}
// redactPlacement removes what the caller may not see from one placement.
func redactPlacement(pl *Placement, p orgsettings.Policy) {
if !p.CanSeeRates {
pl.BillRate = nil
pl.PayRate = nil
pl.Salary = nil
}
if !p.CanSeeMargins {
pl.FeeAmount = nil
redactEconomics(&pl.Economics)
}
}
// redactEconomics removes the COST SIDE and leaves the spread.
//
// The split is deliberate and it is not the same cut as before. The spread is bill minus pay, so anybody who
// may see the two rates can do that subtraction on paper; hiding it while showing its two inputs would be
// theatre. What the margin setting actually protects is the wrap, which is the firm's own cost structure and
// appears nowhere else, and everything computed from it.
//
// Unburdened is CLEARED too. Left set, it would tell somebody who may not see the wrap whether one exists,
// which is a small fact about the firm's finances leaking through a field that exists to explain a blank.
func redactEconomics(e *Economics) {
e.BurdenPct = nil
e.BurdenSource = ""
e.CostPerHour = nil
e.MarginPerHour = nil
e.MarginPct = nil
e.Unburdened = false
e.Fee = nil
}
// redactRequisition removes the advertised rate ranges from one job order.
//
// The ranges are RATE data, not margin data, even though a bill range and a pay range together imply the
// spread. That implication is the reason both ranges move under the same setting: hiding one and showing the
// other would leak the same fact with an extra subtraction.
func redactRequisition(rq *Requisition, p orgsettings.Policy) {
if p.CanSeeRates {
return
}
rq.BillRateMin, rq.BillRateMax = nil, nil
rq.PayRateMin, rq.PayRateMax = nil, nil
rq.SalaryMin, rq.SalaryMax = nil, nil
}
// redactAssignment walks the whole picture hanging off one application. It exists because Assignment nests
// the offers and the placement, and a redaction that only covered the top level would have published every
// rate in the product through one route.
func redactAssignment(a *Assignment, p orgsettings.Policy) {
if p.Unrestricted() {
return
}
for i := range a.Offers {
redactOffer(&a.Offers[i], p)
}
for i := range a.Placements {
redactPlacement(&a.Placements[i], p)
}
}
// redactPlacementList and redactRequisitionList apply the same rule to a page. They take the wrapper rather
// than the slice so a caller cannot pass the wrapper by value, redact a copy, and ship the original.
func redactPlacementList(l *PlacementList, p orgsettings.Policy) {
if p.Unrestricted() {
return
}
for i := range l.Placements {
redactPlacement(&l.Placements[i], p)
}
}
func redactRequisitionList(l *RequisitionList, p orgsettings.Policy) {
if p.CanSeeRates {
return
}
for i := range l.Requisitions {
redactRequisition(&l.Requisitions[i], p)
}
}
// FieldPolicy resolves what the caller may see. It is on the service rather than the handler because the
// settings store is a service dependency, and a handler reaching around its service for a database is the
// shape that produces two answers to the same question.
func (s *Service) FieldPolicy(ctx context.Context) orgsettings.Policy {
return s.settings.ResolvePolicy(ctx)
}
// writeMoney is the ONLY way a money bearing response leaves this module. It resolves the caller's policy
// once, applies it, and then writes.
//
// A TYPE IT DOES NOT KNOW IS LOGGED AND WRITTEN UNCHANGED, because a request path must not crash a server.
// That is a weak guarantee on its own, which is why it is not the guarantee this relies on: TestEveryMoneyDTO
// walks the package with reflection, finds every type carrying a decimal field, and fails if one is neither
// handled here nor recorded as deliberately not routed through here. The compiler cannot enforce this, so a
// test does, and it runs on every build rather than only on the routes somebody remembered to exercise.
func (h *Handler) writeMoney(w http.ResponseWriter, r *http.Request, code int, out any) {
p := h.svc.FieldPolicy(r.Context())
switch v := out.(type) {
case Assignment:
redactAssignment(&v, p)
writeJSON(w, code, v)
case Offer:
redactOffer(&v, p)
writeJSON(w, code, v)
case Placement:
redactPlacement(&v, p)
writeJSON(w, code, v)
case PlacementList:
redactPlacementList(&v, p)
writeJSON(w, code, v)
case Requisition:
redactRequisition(&v, p)
writeJSON(w, code, v)
case RequisitionDetail:
redactRequisition(&v.Requisition, p)
writeJSON(w, code, v)
case RequisitionList:
redactRequisitionList(&v, p)
writeJSON(w, code, v)
default:
log.Printf("recruiting: writeMoney has no redaction rule for %T, response written unredacted", out)
writeJSON(w, code, out)
}
}
// moneyTypesHandled names every type writeMoney redacts. It exists so the guard test can compare what is
// handled against what the package actually declares, rather than against a list somebody keeps by hand in
// two places.
func moneyTypesHandled() []any {
return []any{
Assignment{}, Offer{}, Placement{}, PlacementList{},
Requisition{}, RequisitionDetail{}, RequisitionList{},
}
}