Module: services/approvalService

Approval-decision business logic for AUTO-003b.

Extracted from routes/tests.js so the route handlers stay thin (HTTP shape only) and the provenance contract has a single home.

Three provenance shapes flow through this module:

PROVENANCE_CLEAR — null-out the four columns; written by every "return to draft" path (single restore, bulk restore, revoke). Previously duplicated in three handlers, where the single-restore copy went stale and shipped as a bug.

humanApproval() — provenance for a manual approve click. Carries approvalSource: "human", approvedBy from the actor, approvedAt from Date.now(), and a null approvalThreshold (no threshold was consulted).

computeStats() — counts (human/auto/draft/rejected) plus the 7-day revert rate, computed off the activity log because the tests row only carries current state and revoked tests have their provenance cleared.

Source:

Members

(static, constant) APPROVAL_SOURCE

tests.approvalSource enumeration.

Source:

(static, constant) PROVENANCE_CLEAR

Provenance-clearing shape — pass to testRepo.update(...) together with { reviewStatus: "draft", reviewedAt: null } to fully revert an approval.

Frozen so a caller can't accidentally mutate it (the same object is reused across every restore/revoke path; mutation would leak between requests).

Source:

Methods

(static) computeStats(projectId) → {Object}

Compute approval-decision counts and the 7-day revert rate for a project.

Counts are derived from the live tests table. The revert rate is derived from the activity log because revoked tests have their provenance cleared — only the audit trail can answer "was this auto-approval pulled back?".

The revert rate is deduped by testId so a test that round-trips auto-approve → revoke → re-approve → revoke within the 7-day window counts as a single revert (and a single auto-approval). The metric answers "what fraction of auto-approved tests did humans pull back?", not "how many revoke events fired?".

Defensive clamp: ratio capped at 1 so a backfill that produced more revokes than auto-approvals in the window can't render "117% revert rate".

Parameters:
Name Type Description
projectId string
Source:
Returns:

{ human, auto, draft, rejected, total, revertRate7d, autoApprovals7d, reverts7d } — all number.

Type
Object

(static) humanApproval(actorInfo) → {Object}

Build the provenance fields for a human approval, attributed to actorInfo. The threshold is always null for human approvals (no threshold was consulted).

Parameters:
Name Type Description
actorInfo Object

— caller identity from actor(req).

Properties
Name Type Attributes Description
userName string <optional>

— preferred attribution.

userId string <optional>

— fallback when userName is unset.

Source:
Returns:

{ approvalSource, approvalThreshold, approvedAt, approvedBy }.

Type
Object