M6 Phase C′ — shadow buyer-reputation gate (grey_two) - #32
Merged
Conversation
… + gate + wiring
Wires the adapter's nullable reputationGate seam (null in Phase C) to the Phase B grey_two
tables in SHADOW mode: records buyer behavior but never blocks, so history accumulates for
the eventual flip-to-enforce (BUYER_GATING_BLOCK_ENABLED=true).
- reputation/reputationDb.ts: grey_two.{buyer_records,tracked_jobs} data layer — raw
parameterized SQL over a pg.Pool (mirrors grey-sweeper's grey_two writer idiom). FDQ-65
compliant: only SELECT / INSERT / INSERT…ON CONFLICT DO UPDATE — no DELETE/TRUNCATE;
status transitions are upserts, never delete-reinsert. BuyerRecordStore/TrackedJobsRepo
interfaces let tests inject in-memory stores.
- reputation/buyerReputationGate.ts: the impl behind acpTypes' BuyerReputationGate. Ported
from plugin-wpv (drizzle→raw SQL; autognostic.wpv_*→grey_two.*). Byte-ported the
clean→warned→timeout_1h→timeout_12h→blocked ladder, stiff=submitted-then-expired,
idempotent terminal resolution, first-contact stub, cross-provider TTL cache. Shadow
unified under a single flag: evaluateAcceptance computes the would-be verdict, logs it,
returns accept:true unless blockEnabled; fail-open on any DB error. Dropped (no adapter
caller): getMetricsSnapshot heartbeat + getTrackedSubmittedJobs reconciliation (FDQ-73).
- reputation/crossProvider.ts: optional viem cross-provider history fetch (B.7), injected
only when BASE_RPC_URL is set; kept out of the gate core so the gate stays pure/testable.
- config.ts: buyerGating config (BUYER_GATING_BLOCK_ENABLED default false + timeouts/TTL) +
optional baseRpcUrl.
- main.ts: construct a dedicated small pg.Pool (max 3), build the gate, inject into the
seam; log the reputationGate mode; close the pool on shutdown.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- test/buyerReputationGate.test.ts: 13 deterministic tests — shadow never blocks (blocked + active-timeout buyers still accept), fail-open on empty tables and on DB error, the full ladder over four stiffs, idempotent terminal resolution (no double stiff), FDQ-65 grant compliance (asserts every data-layer statement is SELECT/INSERT/UPDATE and transitions go via ON CONFLICT DO UPDATE), and the earning path unaffected with the gate wired. - _fakes.ts + tier1-offline-smoke.ts: add the buyerGating field to their AcpAdapterConfig literals. - package.json + pnpm-lock.yaml: add pg ^8.22.0 (runtime) + @types/pg (dev), matching grey-sweeper; adds no native build. vitest run 40/40, tier-1 smoke, typecheck, build, lint all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Base
8ee26d6. Fills the adapter's nullablereputationGateseam (shippednullin Phase C) with a concrete impl backed by the Phase Bgrey_twotables, in shadow mode — records buyer behavior but never blocks, so history accumulates for the eventual flip-to-enforce.Port-mapping rationale
autognostic.wpv_buyer_records→grey_two.buyer_records,autognostic.wpv_tracked_jobs→grey_two.tracked_jobs(identical columns — Phase B mirrored them), pure schema-qualifier swap.pg.Pool, mirroringgrey-sweeper/log.ts(the existinggrey_twowriter). FDQ-65 compliant — every statementSELECT/INSERT/INSERT…ON CONFLICT DO UPDATE; noDELETE/TRUNCATE; transitions are upserts, never delete-reinsert.clean→warned→timeout_1h→timeout_12h→blockedladder (one step per stiff), stiff = submitted-then-expired, the idempotent terminal resolution (UPDATE … WHERE status='submitted' RETURNING→ first observer only), first-contact stub, cross-provider TTL cache.evaluateAcceptancecomputes the full would-be verdict, logs it, returnsaccept:trueunlessblockEnabled(the source enforced timeouts always + shadowed onlyblocked; the C′ directive says never block while the flag is false); (2) droppedgetMetricsSnapshot(no heartbeat) andgetTrackedSubmittedJobs(no reconciliation loop — FDQ-73), no adapter caller; (3)evaluateAcceptancefails open on any DB error; cross-provider is an optional injected collaborator (skipped whenBASE_RPC_URLunset).Behavior
BUYER_GATING_BLOCK_ENABLED=false, default): always accepts; records the would-reject.onJobSubmitted/onJobTerminalrecord fully regardless of the flag. Empty tables → unknown →clean→ accept. DB error → accept, logged. Flip-to-enforce = one env change totrue.pg.Pool(max 3) keeps the memory-tight VPS light; the cache-read handlers keep their own pool. Pool closed on shutdown.Gates
vitest run40/40 (13 new) · tier-1 offline smoke ✓ · typecheck ✓ · build ✓ · lint ✓Known follow-up (FDQ-73, not a merge gate)
Expiry-reconciliation loop omitted — stiffs record only when the SDK event-fires
job.expired(the poll backstop dispatchesFUNDEDonly). Being determined actively (dist check → controlled expiry test) in parallel with the soak; if the SDK drops expiries, thegetTrackedSubmittedJobsreconciliation gets its own PR before flip-to-enforce.Not in this PR
Redeploy is a separate authorized step (shadow + fail-open = low-risk): pull → filtered install → set
BUYER_GATING_BLOCK_ENABLED=false→ restart → verifyreputationGate: 'shadow', seller healthy on0xa966…, grey stopped.