Skip to content

Reviewer Phase 3: three-way diff with canonical path normalization#48

Merged
dadachi merged 1 commit intomainfrom
reviewer-phase3-three-way-diff
May 3, 2026
Merged

Reviewer Phase 3: three-way diff with canonical path normalization#48
dadachi merged 1 commit intomainfrom
reviewer-phase3-three-way-diff

Conversation

@dadachi
Copy link
Copy Markdown
Contributor

@dadachi dadachi commented May 3, 2026

Summary

Closes the reviewer arc. With canonicalization, the three platforms' different URL encodings reduce to the same comparable string:

Rails OpenAPI:    /clinics                       (server = /api/v1/vet)
iOS Request:      /vet/clinics/\(id)
Android Retrofit: {account_id}/api/v1/vet/clinics/{id}
                  → all canonicalize to /clinics/{*}

The Rails-side {account_id} is handled transparently by AccountMiddleware, which path-info-rewrites the URL before the rest of the routing layer sees it — same logical endpoint, three different framings.

canonicalizeEndpoint(endpoint, {role})

Strips:

  1. Tenant segment ({account_id} placeholder or runtime UUID).
  2. /api/v<N>/ prefix.
  3. Role segment (lowercased Shopkeeper rename target — vet, shopkeeper, etc.).
  4. Path-param names — {id}, {shopId}, \(id) all become {*}.

diffContracts(rails, ios, android, ctx)ContractDiff

Bucket Meaning Fail?
railsOnly in Rails, no mobile client uses ❌ informational (admin/server-only)
iosOrphan iOS calls endpoint not in Rails ✅ FAIL — 404 risk
androidOrphan Android calls endpoint not in Rails ✅ FAIL
iosOnly in Rails + iOS but not Android ✅ FAIL — mobile clients must agree
androidOnly in Rails + Android but not iOS ✅ FAIL

reviewer.contractParity = "fail" iff any of orphan/only counts > 0. runJudge.overallPass now requires reviewer.contractParity === "pass" in addition to layer1+layer2+visual.

Real finding against out/vet-clinic-queue/

ios-orphan: DELETE /clinics/{*}/reset

The iOS substrate calls a reset endpoint Rails OpenAPI doesn't document — either Rails is missing the spec for that route, or iOS expects an endpoint that doesn't exist. Exactly the contract-drift class of bug the reviewer is supposed to catch and the project's "no contract drift" USP from ROADMAP.md depends on.

This is a substrate-side finding — recommend investigating the Rails route table for DELETE /shops/{shopId}/reset and adding it to docs/openapi.yaml if it exists, or removing the iOS call if it doesn't.

Test plan

  • npm run ci — 19/19 green.
  • canonicalizeEndpoint reduces three encodings to same canonical (new test).
  • diffContracts surfaces ios-orphan correctly (new test).
  • Existing dispatch e2e still passes (stub-mode reviewer returns synthetic pass).
  • After merge: investigate the iOS /clinics/{*}/reset finding in the substrate.

🤖 Generated with Claude Code

Closes the reviewer arc. With canonicalization, the three platforms'
different URL encodings reduce to the same comparable string:

  Rails OpenAPI:   /clinics                    (server = /api/v1/vet)
  iOS Request:     /vet/clinics/\(id)
  Android Retrofit: {account_id}/api/v1/vet/clinics/{id}
  → all canonicalize to /clinics/{*}

canonicalizeEndpoint(endpoint, {role}) strips:
  1. Tenant segment ({account_id} placeholder, or runtime UUID — see
     /Users/adachi/pg/ruby/pg/nativeapptemplateapi/lib/middleware/
     account_middleware.rb for the Rails side that handles this
     transparently via path-info rewrite)
  2. /api/v<N>/ prefix
  3. role segment (lowercased Shopkeeper rename target — vet, etc.)
  4. Path-param names: {id}, {shopId}, \(id) all become {*}

diffContracts(rails, ios, android, ctx) produces a ContractDiff:
  - railsOnly:     in Rails, no mobile client uses (informational —
                   admin/server-only routes are expected)
  - iosOrphan:     iOS calls endpoint not in Rails (FAIL — 404 risk)
  - androidOrphan: Android calls endpoint not in Rails (FAIL)
  - iosOnly:       in Rails + iOS but not Android (FAIL — mobile
                   clients must agree per project USP)
  - androidOnly:   in Rails + Android but not iOS (FAIL)

reviewer.contractParity = "fail" iff any of orphan/only counts > 0.
runJudge.overallPass now requires reviewer.contractParity === "pass"
in addition to layer1+layer2+visual.

Real finding against out/vet-clinic-queue/:
  ios-orphan: DELETE /clinics/{*}/reset
The iOS substrate calls a reset endpoint Rails OpenAPI doesn't
document — either Rails is missing the spec for that route, or iOS
expects an endpoint that doesn't exist. This is exactly the
contract-drift class of bug the reviewer is supposed to catch and
the project's "no contract drift" USP from ROADMAP.md depends on.

Tests: 19/19 npm run ci green.
  - canonicalizeEndpoint reduces three encodings to same canonical ✓
  - diffContracts surfaces ios-orphan correctly ✓
  - existing dispatch e2e still passes (stub-mode reviewer) ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dadachi dadachi merged commit 802e61d into main May 3, 2026
1 check passed
@dadachi dadachi deleted the reviewer-phase3-three-way-diff branch May 3, 2026 00:16
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