From 5eca6803aed6b82ec6d696a7e62ff938d112dc60 Mon Sep 17 00:00:00 2001 From: thomasluizon Date: Sat, 8 Aug 2026 12:58:52 -0300 Subject: [PATCH 1/2] fix: pin .claude markdown to LF so the pr-review parity gate compares equal bytes tools/launch-worker.mjs in orbit-ui-mobile sha256s the working-tree bytes of .claude/skills/pr-review/{SKILL.md,rubric.md} in both repositories and refuses to launch an independent reviewer for repo ui or api when they differ. The committed blobs are already byte-identical: SKILL.md b374cfd63d3a1257da76870e2da00586f87183cf rubric.md 308d1c76fedec17cf0a1dae93bc0cc18925d88da Both repositories set core.autocrlf=true. orbit-ui-mobile pins `.claude/skills/**/*.md text eol=lf`; this repository pinned only *.mjs and two JSON artifacts. The same blob therefore checked out LF there and CRLF here, +230 bytes on SKILL.md and +289 on rubric.md. `git check-attr text eol` reported `eol: lf` in the UI repository and `eol: unspecified` here. `git status` was clean in both, which is why nothing looked wrong. Cost, measured on the overnight run of 2026-08-08: 76 tickets stood down rather than opened unreviewable, and 4 pull requests handed over with no independent review. This commit changes no blob. It pins `.claude/**/*.md text eol=lf`, a superset of the missing line that also covers every future mirrored document under .claude, and re-materializes the working tree from the unchanged blobs. Proof, both working trees after the change: SKILL.md 3fbf6d133b4a7bafbe8da8345ea10d45ff96ac6b83d6c78f348651fa3c830059 13388 B rubric.md 672f14194f2bc7834828d434babdad152d5b361b2059b75f34f5b0d370be72b6 20247 B Gate proof: with .gitattributes reverted and the tree re-materialized, the launcher prints "pr-review parity failed for .claude\skills\pr-review\SKILL.md". With this commit applied it passes that check and advances to the next one. Audit of every other path present in both repositories: 30 shared paths, 3 with an identical committed blob, and only these 2 drifted. The other 27 differ in content on purpose. Refs ORB-215 Co-Authored-By: Claude Opus 5 (1M context) --- .gitattributes | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitattributes b/.gitattributes index 66ca9ea4..ff751e7e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,12 @@ src/Orbit.Api/openapi.json text eol=lf +# Every markdown file under .claude/ checks out LF here, exactly as it does in +# orbit-ui-mobile. The pr-review skill is single-sourced in the UI repo and mirrored +# into this one, and tools/launch-worker.mjs refuses an API review when the two copies +# disagree. Without this line core.autocrlf=true materialized the identical committed +# blob as CRLF here and LF there, +230 bytes on SKILL.md and +289 on rubric.md, so the +# gate compared different bytes for the same content and stood 76 tickets down on +# 2026-08-08. `git status` stayed clean in both repos, which is why nothing looked wrong. +.claude/**/*.md text eol=lf .claude/skills/**/*.mjs text eol=lf tools/*.mjs text eol=lf architecture.json text eol=lf From 7750b001f752ce63101142b3977d238061135288 Mon Sep 17 00:00:00 2001 From: thomasluizon Date: Sat, 8 Aug 2026 15:00:40 -0300 Subject: [PATCH 2/2] chore: mirror the pr-review rubric provenance contract from orbit-ui-mobile The pr-review skill is single-sourced in orbit-ui-mobile and mirrored here, and tools/launch-worker.mjs refuses an API review when the two copies disagree by committed blob. This is the mirror half of orbit-ui-mobile#701. The contract change: a review artifact now carries rubricRepositoryKey, rubricCommitOid, rubricBlobOid and rubricArtifactPath instead of rubricBaseOid. record-readiness.mjs proves all four with git and refuses the receipt when they do not hold, so a review bound to the wrong or a stale rubric cannot reach READY. For this repository the binding is always own-base: orbit-api carries .claude/skills/pr-review/rubric.md, so rubricRepositoryKey is api and rubricCommitOid is the pull request's own base SHA. The canonical-main binding exists for orbit-landing-page, which has no .claude tree at any commit and could therefore never mint a READY receipt at all. Body copied verbatim. Verified byte-identical to the UI copy: 86d428358a2b460db68d1a5c7db99d8aa5a5472bb2ed7ac093f69a819044decf MERGE THIS TOGETHER WITH orbit-ui-mobile#701. The blob-parity gate refuses every ui and api review in the window where only one of the two has landed. Refs ORB-215 Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/pr-review/SKILL.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.claude/skills/pr-review/SKILL.md b/.claude/skills/pr-review/SKILL.md index b374cfd6..839471ec 100644 --- a/.claude/skills/pr-review/SKILL.md +++ b/.claude/skills/pr-review/SKILL.md @@ -163,13 +163,41 @@ orbit-api. A paired diff can be **both**. That classification gates which rubric ```json {"reviewerKind":"independent","verdict":"BLOCKING","rounds":1, "reviewedHeadOid":"","baseSha":"", - "rubricBaseOid":"","rubricArtifactPath":"", + "rubricRepositoryKey":"","rubricCommitOid":"", + "rubricBlobOid":"", + "rubricArtifactPath":"", "artifactPath":"", "frozenFindingIds":["F1"], "findings":[{"id":"F1","severity":"High","file":"apps/web/hooks/use-streak.ts","line":42, "claim":"one sentence: what is wrong and what goes wrong if it ships","blocking":true}]} ``` +### Rubric provenance: which rubric you read, and where it came from + +The four rubric fields are not bookkeeping. `record-readiness.mjs` proves them with git and refuses +the receipt when they do not hold, so a review bound to the wrong or a stale rubric cannot reach +READY. Fill them from the snapshot you were given: + +| Field | What it must be | +|---|---| +| `rubricRepositoryKey` | the repository the snapshot was materialized FROM | +| `rubricCommitOid` | the commit it was materialized from, full 40 characters | +| `rubricBlobOid` | `git rev-parse :.claude/skills/pr-review/rubric.md` | +| `rubricArtifactPath` | the absolute path of the materialized snapshot you actually read | + +Two bindings, decided by the repository under review, not by you: + +- **The repository carries the rubric at the pull request's base** (orbit-ui-mobile, orbit-api). + `rubricRepositoryKey` is that repository and `rubricCommitOid` is the pull request's own base SHA. +- **The repository carries no rubric** (orbit-landing-page has no `.claude` tree at any commit). + `rubricRepositoryKey` is `ui` and `rubricCommitOid` is orbit-ui-mobile's current `origin/main`. + Binding a landing review to its own base was impossible, and it is why four landing pull requests + reported `REVIEW_STALE` on 2026-08-08 while being complete on every other dimension. + +The snapshot is compared against the committed blob byte for byte, with line endings normalized. A +hand-edited snapshot fails, a wrong blob fails, and a stale rubric fails. Do not invent these +values: if you were not given a snapshot and its provenance, say so and stop. + `severity` is descriptive and comes from the rubric's ladder. A candidate below the target repository floor never enters this array. `blocking` is the decision for a surviving candidate: a High that does not break behaviour, security, or data integrity is `"blocking": false` and