Skip to content

M6 Phase C′ — shadow buyer-reputation gate (grey_two) - #32

Merged
Mayakovsky merged 2 commits into
mainfrom
m6-cprime-shadow-gate
Jul 25, 2026
Merged

M6 Phase C′ — shadow buyer-reputation gate (grey_two)#32
Mayakovsky merged 2 commits into
mainfrom
m6-cprime-shadow-gate

Conversation

@Mayakovsky

Copy link
Copy Markdown
Owner

Base 8ee26d6. Fills the adapter's nullable reputationGate seam (shipped null in Phase C) with a concrete impl backed by the Phase B grey_two tables, in shadow mode — records buyer behavior but never blocks, so history accumulates for the eventual flip-to-enforce.

Port-mapping rationale

  • Table remap: autognostic.wpv_buyer_recordsgrey_two.buyer_records, autognostic.wpv_tracked_jobsgrey_two.tracked_jobs (identical columns — Phase B mirrored them), pure schema-qualifier swap.
  • Data access: drizzle → raw parameterized SQL over pg.Pool, mirroring grey-sweeper/log.ts (the existing grey_two writer). FDQ-65 compliant — every statement SELECT/INSERT/INSERT…ON CONFLICT DO UPDATE; no DELETE/TRUNCATE; transitions are upserts, never delete-reinsert.
  • Byte-ported semantics: the clean→warned→timeout_1h→timeout_12h→blocked ladder (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.
  • Deliberate changes: (1) unified shadow flagevaluateAcceptance computes the full would-be verdict, logs it, returns accept:true unless blockEnabled (the source enforced timeouts always + shadowed only blocked; the C′ directive says never block while the flag is false); (2) dropped getMetricsSnapshot (no heartbeat) and getTrackedSubmittedJobs (no reconciliation loop — FDQ-73), no adapter caller; (3) evaluateAcceptance fails open on any DB error; cross-provider is an optional injected collaborator (skipped when BASE_RPC_URL unset).

Behavior

  • Shadow (BUYER_GATING_BLOCK_ENABLED=false, default): always accepts; records the would-reject. onJobSubmitted/onJobTerminal record fully regardless of the flag. Empty tables → unknown → clean → accept. DB error → accept, logged. Flip-to-enforce = one env change to true.
  • Dedicated small pg.Pool (max 3) keeps the memory-tight VPS light; the cache-read handlers keep their own pool. Pool closed on shutdown.

Gates

vitest run 40/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 dispatches FUNDED only). Being determined actively (dist check → controlled expiry test) in parallel with the soak; if the SDK drops expiries, the getTrackedSubmittedJobs reconciliation 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 → verify reputationGate: 'shadow', seller healthy on 0xa966…, grey stopped.

Mayakovsky and others added 2 commits July 25, 2026 15:00
… + 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>
@Mayakovsky
Mayakovsky merged commit f123b02 into main Jul 25, 2026
3 checks passed
@Mayakovsky
Mayakovsky deleted the m6-cprime-shadow-gate branch August 14, 2026 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant