Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/sourcing/service.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 09015496d565a172bcaa15cfe9a8e4b072b13df0 |
| Branch | workstream-b-screenmatch |
| Path or archive member | internal/modules/sourcing/service.go |
| Original SHA-256 | 4d58031ed1fa9e459c74d54b9fde12380bb2c9e5b50644ae6e630f63794bbc23 |
| Git blob SHA | e2220566bb996245e12578ebd738207860ca4dda |
| Reading-copy SHA-256 | 4d58031ed1fa9e459c74d54b9fde12380bb2c9e5b50644ae6e630f63794bbc23 |
Every recorded occurrence
These 21 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-b-screenmatch | 09015496d565a172bcaa15cfe9a8e4b072b13df0 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-a-opportunity-intelligence | 1675031db74316e100782d063e9eda1a597fe3a1 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-d-command-center | 255e7d1d420abf5e277d0cd8c9b14f6f72b6acb9 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | w0-aedt-compliance | 39df94f92b2a33f852481fe1954dabed841524bb | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-d-missionmatch | 3be46ba89edb737182075a2b320509b42dc916f8 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-e-dataflow-deploy | 433815a042f9741411e60fa553fe72143e9b10db | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-e-commercial-stubs | 4437da660539284a400ee380b9b427a300c369ee | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-c-slatematch | 5cd60c918301b46ecd6ed3e03e20c395da0062e2 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | ruthless-debug-sweep | b7d742f8f032984be3817efffa3eb8b13b651c1f | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-b-sam-contract-awards | d5101cceb98e70000cebb31d3e7ac73d747d3a6d | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-a-shared-core | e0de03ac3a1ec826bd15d0e048e3b02baa23eeff | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-c-slatematch | 5cd60c918301b46ecd6ed3e03e20c395da0062e2 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-d-command-center | 255e7d1d420abf5e277d0cd8c9b14f6f72b6acb9 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-e-dataflow-deploy | 433815a042f9741411e60fa553fe72143e9b10db | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | ruthless-debug-sweep | b7d742f8f032984be3817efffa3eb8b13b651c1f | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-a-opportunity-intelligence | 1675031db74316e100782d063e9eda1a597fe3a1 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-b-screenmatch | 09015496d565a172bcaa15cfe9a8e4b072b13df0 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-e-commercial-stubs | 4437da660539284a400ee380b9b427a300c369ee | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-d-missionmatch | 3be46ba89edb737182075a2b320509b42dc916f8 | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | workstream-b-sam-contract-awards | d5101cceb98e70000cebb31d3e7ac73d747d3a6d | Open original |
| Janady13/missionmatch / internal/modules/sourcing/service.go | w0-aedt-compliance | 39df94f92b2a33f852481fe1954dabed841524bb | Open original |
Recovered source transcript
Download complete source text ↓176 lines · 5.8 KB
package sourcing
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/missionworkforce/missionmatch/internal/platform/audit"
"github.com/missionworkforce/missionmatch/internal/platform/database/dbgen"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// ErrNoDatabase is returned when no pool is configured. ErrNotFound when a delete matches nothing.
var (
ErrNoDatabase = errors.New("sourcing: no database configured")
ErrNotFound = errors.New("sourcing: external candidate not found")
)
const listLimit = 200
// Service reads externally sourced candidates and the compliance log, and deletes sourced records.
type Service struct {
db *tenant.DB
aud *audit.Writer
}
// NewService builds the service. db may be nil (then calls return ErrNoDatabase). The audit writer
// records the PII view and erasure events on the same tenant transaction as the work.
func NewService(db *tenant.DB, aud *audit.Writer) *Service { return &Service{db: db, aud: aud} }
// ListExternal returns the org's externally sourced candidates.
func (s *Service) ListExternal(ctx context.Context) ([]ExternalCandidate, error) {
if s.db == nil || s.db.Pool == nil {
return nil, ErrNoDatabase
}
var out []ExternalCandidate
err := s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
rows, err := dbgen.New(tx).ListExternalCandidates(ctx, listLimit)
if err != nil {
return err
}
out = make([]ExternalCandidate, 0, len(rows))
for _, r := range rows {
out = append(out, ExternalCandidate{
ID: r.ID.String(),
FullName: r.FullName,
Email: deref(r.Email),
Headline: deref(r.Headline),
Location: deref(r.Location),
YearsExperience: int(r.YearsExperience),
Available: r.Available,
ExternalID: deref(r.ExternalID),
Source: "External",
Clearance: "Unknown",
CreatedAt: r.CreatedAt.Format(time.RFC3339),
})
}
// Viewing externally sourced candidates surfaces contact PII, so record the access.
return s.audit(ctx, tx, audit.Event{Action: "candidate.pii.view", EntityType: "sourcing", NewValues: countJSON(len(out))})
})
return out, err
}
func countJSON(n int) []byte {
return []byte(fmt.Sprintf(`{"count":%d}`, n))
}
// Log returns the org's sourcing compliance log.
func (s *Service) Log(ctx context.Context) ([]SourcingLogEntry, error) {
if s.db == nil || s.db.Pool == nil {
return nil, ErrNoDatabase
}
var out []SourcingLogEntry
err := s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
rows, err := dbgen.New(tx).ListSourcingLog(ctx, listLimit)
if err != nil {
return err
}
out = make([]SourcingLogEntry, 0, len(rows))
for _, r := range rows {
cid := ""
if r.CandidateID != nil {
cid = r.CandidateID.String()
}
out = append(out, SourcingLogEntry{
ID: r.ID.String(),
CandidateID: cid,
ExternalID: r.ExternalID,
Source: r.Source,
Provider: r.Provider,
Query: r.Query,
LegalBasis: r.LegalBasis,
CollectedAt: r.CollectedAt.Format(time.RFC3339),
})
}
return nil
})
return out, err
}
// Delete hard deletes a sourced candidate (removing the personal data) and detaches its compliance row
// so the audit that a record was collected and then deleted is retained without the personal data.
func (s *Service) Delete(ctx context.Context, id uuid.UUID) error {
if s.db == nil || s.db.Pool == nil {
return ErrNoDatabase
}
return s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
q := dbgen.New(tx)
// Detach the compliance log so the audit that a record was collected survives without the
// candidate link.
if err := q.DetachSourcingLog(ctx, &id); err != nil {
return err
}
// Anonymize in place rather than hard delete: the PII is wiped but the de identified scored
// outcomes (slate_members) and the demographic bucket are KEPT for the adverse impact record, so
// erasure does not destroy the AEDT decision history. Once the name, email, contact, and resume
// vector are gone the person is no longer identifiable.
rows, err := q.AnonymizeCandidate(ctx, id)
if err != nil {
return err
}
if rows == 0 {
// Distinguish an already anonymized candidate (idempotent success) from one that does not
// exist in this org (not found).
if _, gerr := q.GetCandidate(ctx, id); errors.Is(gerr, pgx.ErrNoRows) {
return ErrNotFound
}
return nil
}
// Purge the qualification detail too (skills, certifications, customer agency experience) so no
// child PII survives the erasure. The frozen scored outcomes in slate_members are untouched.
if err := purgeCandidateChildData(ctx, tx, id); err != nil {
return err
}
return s.audit(ctx, tx, audit.Event{Action: "privacy.candidate.anonymized", EntityType: "candidate", EntityID: id.String()})
})
}
// purgeCandidateChildData removes a candidate's qualification rows. RLS scopes each delete to the org.
func purgeCandidateChildData(ctx context.Context, tx pgx.Tx, id uuid.UUID) error {
for _, stmt := range []string{
`DELETE FROM candidate_skills WHERE candidate_id = $1`,
`DELETE FROM candidate_certifications WHERE candidate_id = $1`,
`DELETE FROM candidate_customer_experience WHERE candidate_id = $1`,
} {
if _, err := tx.Exec(ctx, stmt, id); err != nil {
return err
}
}
return nil
}
// audit records an event on the tenant tx with the actor taken from the validated session.
func (s *Service) audit(ctx context.Context, tx pgx.Tx, e audit.Event) error {
if s.aud == nil {
return nil
}
if p, ok := tenant.UserFromContext(ctx); ok {
if id, err := uuid.Parse(p.UserID); err == nil {
e.ActorUserID = &id
}
}
return s.aud.OnTx(ctx, tx, e)
}
func deref(s *string) string {
if s == nil {
return ""
}
return *s
}