Skip to content

test(exp-repid): adversarial proof of the t12-exp-v1 owner priorities (inert, D6) - #1519

Merged
DealAppSeo merged 1 commit into
mainfrom
claude/d6-exp-repid-inert-6d48c4
Nov 8, 2026
Merged

DealAppSeo merged 1 commit into
mainfrom
claude/d6-exp-repid-inert-6d48c4

Conversation

@DealAppSeo

Copy link
Copy Markdown
Owner

What

Test-first adversarial hardening of the existing experimental RepID namespace (t12-exp-v1) against the six owner-independence priorities. This is inert development only (D6 lane, owner-authorized 2026-11-07): one new test file, no source change, no migration, no mode flip, no canonical write, no DB touched.

Finding

Every attack is already refused by code on main. No new guard or scoring math was needed — the namespace is sound as built. So this PR is a proof of each guard plus coverage of the gaps the existing suites missed. Per LESSONS 5, every attack carries a positive control that returns the opposite verdict through the same code path, so a green run is a measurement, not a vacuous pass.

New: tests/experimental-repid/experimental-repid-adversarial.test.ts — 17 tests, 6 blocks.

# Priority What it proves Guard (where)
P1 Evidence eligibility verified and authoritative only; authoritative must be === true, not merely truthy ("true"/1/{}/undefined refused) settleImmuneReceipt gate 1
P2 Owner independence the eligibilityFor → partyIndependence → verifierIndependence path inherits D-148 (#1516): two administrative builder_ids are sameness, never independence; both sides need a proven binding in a shared namespace bounty-authorization.ts
P3 Negative evidence an adverse outcome writes a real negative delta, the chain carries it, and replay (already_consumed) / a clone (subject_mismatch) can neither flip nor erase it experimental-repid.ts + migration unique/chain
P4 Replay protection verbatim replay (same evidence, same mode ⇒ already_consumed) vs signature transplant (re-seal onto a new subject ⇒ subject_mismatch) — named distinctly settleExperimental
P5 Controller lineage reputation is bound to the agent uuid and the source row's own worker: content.claimed_by must equal the subject's agent_id, so "credit X's work to Y" ⇒ subject_mismatch even re-sealed settleExperimental
P6 Two-number model the scores view/route keep same_controller_measured and independent_counterparty as two separate counts; independent_reputation:false and the label are fixed; a negative same-controller score is reported as-is, never laundered into the independent count routes/experimental-repid.ts + view

Design clarification (not a hole)

The brief asked that a duplicate be rejected "even across shadow/active". The one-time UNIQUE key is (domain, dimension, evidence_id|evidence_hash, **mode**) by design — shadow and active are separate ledgers, and the same evidence settles once in each. This is not a replay hole: only active rows can gate anything (experimentalTier filters mode==='active'; shadow never changes a tier). Adding a cross-mode global nullifier would break the intended shadow-vs-active A/B design and the existing "shadow and active produce identical deltas" test, so per LESSONS 9 I did not invent it — I pinned the real invariant (the gating ledger admits each receipt once) and documented the cross-mode semantics in a test.

NOT covered (honest)

  • Append-only UPDATE/DELETE/TRUNCATE refusal is a DB trigger; the in-memory fake can't exercise it (the existing migration test asserts the trigger SQL exists; real refusal is only provable against a live DB this inert lane does not touch).
  • All proofs run against the in-memory fake + pure functions. No live Supabase row was read or written; mode was never active against a real store.
  • P1 immune vectors use a stub receipt (gate 1 returns before the body is read); full receipt verification is covered by the existing immune suite.

Verification [V 2026-11-07]

SUPABASE_URL=http://localhost:54321 SUPABASE_SERVICE_KEY=dummy \
  npx jest --config jest.config.js tests/experimental-repid src/immune/__tests__/experimental-settlement.test.ts
# Tests: 48 passed, 48 total  (31 existing + 17 new)

🤖 Generated with Claude Code

… (inert)

Test-first hardening of the EXPERIMENTAL RepID namespace against the six
owner-independence priorities. FINDING: every attack is already refused by
code on main -- no new guard or scoring math was needed. This pins each guard
and the honest cross-mode ledger semantics so a later weakening turns red.

New: tests/experimental-repid/experimental-repid-adversarial.test.ts (17 tests)
  P1 eligibility     verified+authoritative only (authoritative===true, not truthy)
  P2 independence    the eligibilityFor path inherits D-148 (#1516): admin-only
                     builder_ids are sameness, never independence
  P3 negative        adverse delta persists, can't be flipped/erased by replay or clone
  P4 replay          verbatim replay (already_consumed) vs transplant (subject_mismatch)
  P5 lineage         content.claimed_by bound to subject agent; wrong-worker refused
  P6 two-number      same_controller vs independent counts stay separate; label/flag fixed

Each attack carries a positive control (LESSONS 5) so green is a measurement.
Inert: in-memory fake store only, mode never active against a real store, no DB
touched, no migration. 48 pass combined with the two existing exp suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DealAppSeo
DealAppSeo marked this pull request as ready for review November 8, 2026 04:11
@strix-security

strix-security Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Strix Security Review

✅ No open security findings.

PR summary: Adds one inert test file that pins the existing experimental RepID (t12-exp-v1) owner-independence guards with adversarial cases and positive controls. No production source, migration, or configuration is changed.

Review summary

This PR adds a single inert test file, tests/experimental-repid/experimental-repid-adversarial.test.ts (+271/-0), with no source, migration, config or runtime change. It exercises the existing experimental RepID guards (settleImmuneReceipt gate 1, eligibilityFor → partyIndependence → verifierIndependence, settleExperimental replay/lineage refusals, and the two-number scores route) against an in-memory Supabase fake and stubbed owner resolvers.

I reviewed the full diff and triaged the repository lint inventory against the test's import graph. Every lint item that surfaced sits in code this PR neither changes nor reaches: src/routes/arena.ts ownerSide() (fabricated proven_wallet claims), src/scoring/grounding.ts (SAME_OWNER_SETTLEMENT_MODE default off, GROUNDING_EVIDENCE_UNIQUE=agent), src/db.ts service-role client, src/immune/combined-receipt.ts verifyCombinedReceipt, src/auth/api-keys.ts, and two migrations. The test imports only types from combined-receipt, injects a fake client everywhere, and stubs the resolver, so none of those paths are executed or newly exposed. The test file itself contains no credentials or secrets — only agent UUIDs and display names.

No security vulnerability is introduced or exposed by this change.

Ruled out (7)
  • Does the test's stub-receipt plus hand-built CombinedReceiptVerification pattern expose the never-throws/attacker-controlled signer dereference in verifyCombin…
    The test imports only types from src/immune/combined-receipt.ts and passes a `{}` stub receipt with hand-built verification objects; it never calls verifyCombinedReceipt, so the flagged dereference is neither changed nor reached by this PR.
  • Does the test's P2 positive control mask fabricated proven_wallet claims from request-body wallets in arena.ts ownerSide() (L364)?
    src/routes/arena.ts is not imported, called, or modified by the test file, which stubs the resolver and drives partyIndependence directly, so the arena fabrication path is outside this PR's reach.
  • Does the test prove owner independence is live on the payment-grounding path despite SAME_OWNER_SETTLEMENT_MODE defaulting to off (L327/L398/L469)?
    The test exercises the independence classifier in isolation and never touches src/scoring/grounding.ts or the mode gate that decides whether the classifier runs; the gap is pre-existing and not introduced or exposed here.
  • Does the test's P4 replay proof cover the GROUNDING_EVIDENCE_UNIQUE=agent anti-replay collapse (L399)?
    The test's replay proof targets the experimental ledger's unique key, a different mechanism from the grounding evidence uniqueness setting in src/scoring/grounding.ts, which the test never imports or exercises.
  • Is the api-keys env allowlist truncation (L31) reachable through the test's eligibility path?
    verifierIndependence does not call validateAgentApiKey; only authorizeBountyVerification does, and the test never invokes that path.
  • Do the service-role client items in src/db.ts (L195/L347/L454) apply to the experimental routes exercised by the test?
    The test always injects an in-memory fake client into supabaseExperimentalStore and experimentalScoresRouter, so the default service-role client is never constructed or used.
  • Do the migration items (L95 agent_delegations, L96 builder_registry) become exploitable through this PR?
    The test stubs the owner resolver entirely and asserts classification outcomes only; no migration, RLS policy, or row-integrity path is changed or exercised.

Review 1 on this PR · 8604368 · requested Nov 8 04:11 UTC when the PR was opened


Reviewed by Strix
Re-run review · Configure security review settings

@strix-security strix-security Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a single inert test file, tests/experimental-repid/experimental-repid-adversarial.test.ts (+271/-0), with no source, migration, config or runtime change. It exercises the existing experimental RepID guards (settleImmuneReceipt gate 1, eligibilityFor → partyIndependence → verifierIndependence, settleExperimental replay/lineage refusals, and the two-number scores route) against an in-memory Supabase fake and stubbed owner resolvers.

I reviewed the full diff and triaged the repository lint inventory against the test's import graph. Every lint item that surfaced sits in code this PR neither changes nor reaches: src/routes/arena.ts ownerSide() (fabricated proven_wallet claims), src/scoring/grounding.ts (SAME_OWNER_SETTLEMENT_MODE default off, GROUNDING_EVIDENCE_UNIQUE=agent), src/db.ts service-role client, src/immune/combined-receipt.ts verifyCombinedReceipt, src/auth/api-keys.ts, and two migrations. The test imports only types from combined-receipt, injects a fake client everywhere, and stubs the resolver, so none of those paths are executed or newly exposed. The test file itself contains no credentials or secrets — only agent UUIDs and display names.

No security vulnerability is introduced or exposed by this change.


Reviewed by Strix
Configure security review settings

@DealAppSeo
DealAppSeo merged commit 1869bd4 into main Nov 8, 2026
18 checks passed
@DealAppSeo
DealAppSeo deleted the claude/d6-exp-repid-inert-6d48c4 branch November 8, 2026 04:22
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