Skip to content

fix: don't PATCH a scoreboard owned by another identity - #116

Closed
sankalpsthakur wants to merge 138 commits into
TauCetiProject:mainfrom
sankalpsthakur:fix/98-scoreboard-cross-identity
Closed

fix: don't PATCH a scoreboard owned by another identity#116
sankalpsthakur wants to merge 138 commits into
TauCetiProject:mainfrom
sankalpsthakur:fix/98-scoreboard-cross-identity

Conversation

@sankalpsthakur

Copy link
Copy Markdown
Contributor

Summary

Review-thread roots already refuse to PATCH a comment whose user.login is not the current gh identity (PR #110). The scoreboard issue-comment path did not: a stored scoreboard_comment_id was PATCHed unconditionally, which 404s when that comment was authored by a different worker and strands the merge-sweep.

If the known scoreboard's user.login ≠ current gh identity, post a fresh kind:scoreboard instead of PATCHing. Newest-wins on the consumer side picks it up.

Closes #98

Regression coverage

  • known ledger scoreboard id, now owned by someone else → POST, not PATCH
  • same via plan_sb_id
  • known id still ours → still PATCH in place
  • foreign known id even when we have another scoreboard of our own → still POST fresh (same as the thread-root identity-migration case)
  • execute_post layer: stored id 500 owned by other-bot is not PATCHed

Verification

  • python3 -m py_compile runner/*.py
  • python3 tests/test_cli.py
  • python3 tests/test_prices.py
  • python3 tests/test_post.py (20 checks, including the new ones)
  • python3 tests/test_billing.py
  • python3 tests/test_thread_states.py
  • python3 tests/test_sweep.py
  • python3 tests/test_codex_fallback.py
  • python3 tests/test_kiro.py
  • python3 tests/test_attempt_diagnostics.py
  • extra (not in CI workflow): python3 tests/test_prompt_refs.py, python3 tests/test_coordinate.py

tests/test_reviewer_env.py fails on this Darwin host with KeyError: XDG_DATA_HOME already on main (the following test asserts that key is absent on macOS). CI runs Ubuntu, where that file is expected to pass. Untouched by this PR.

Made with Cursor

kim-em and others added 30 commits June 3, 2026 00:22
Set use-github-cache: false on lean-action. lean-action otherwise wraps
the ~2.4 GB ./.lake directory in actions/cache, whose save and restore
cost ~40s per run, far more than the ~6s `lake exe cache get` it avoids.
The Mathlib cache server (use-mathlib-cache: true) still supplies the
prebuilt oleans, so nothing is compiled from Mathlib; this only drops the
redundant GitHub-side caching.
The branch renamed the files and directories but left the contents referring to
Centauri (lakefile library names, namespaces, imports, README, manifest), which broke
the build. Sweep the contents: Centauri → TauCeti, Proxima Centauri → Tau Ceti, and
the package name / URLs.
Replace the textual no-sorry CI grep with a Lean metaprogram that builds the TauCeti
environment from its oleans and inspects, for every declaration defined in TauCeti, the
axioms it transitively depends on (Lean.collectAxioms), failing on anything outside the
allowlist propext, Classical.choice, Quot.sound. Because it reads the kernel environment
rather than source text, it catches sorry/admit (sorryAx), native_decide
(Lean.ofReduceBool), and home-rolled axioms, including ones reaching in through imports.

Add it as `lean_exe axioms` (root TauCetiReview.Axioms), run in CI after the build, and
update TauCetiReview/README to reflect that the audit is now mechanized.
Address review of the axiom audit: enumerate every TauCeti/**/*.lean module (not only
those imported from the root) so an orphan module that smuggles in an axiom is still
audited; glob the TauCeti lean_lib (`TauCeti.*`) so `lake build` builds every such module
(and a broken/orphan module fails the build); fail loudly if zero declarations are
audited (miswiring); index the module-name table safely; and document the trustLevel
contract (the audit checks axiom dependencies, not proof validity — the build does that).
feat: add the axiom-allowlist audit (lake exe axioms)
Rewrite the remaining em-dash constructions (README axiom note, the
ci.yml audit comment, the lakefile glob comment, and two in
TauCetiReview/Axioms.lean) using a semicolon, comma, or colon.
…axiom-audit

style: remove em-dashes from the axiom-audit docs and comments
Add the per-angle prompts for the AI review agents under TauCetiReview/rubrics/: a
shared protocol plus eleven single-angle rubrics (scope, correctness, reuse, api-design,
generality, placement, naming, documentation, proof-quality, deprecation, attribution).
Each agent runs after CI is green and judges one angle, returning approve/request_changes/
block with evidence; only the integrity angles may block. The prompts are deliberately
terse and adversarial, with a shared materiality bar so agents hunt real problems rather
than nitpick.
Split the review rubrics out into their own repo: rubrics at the root, the axiom audit
removed (it moves to the TauCeti code repo) and the superseded rubric.md skeleton deleted.
Update the shared protocol and scope rubric to reflect that the code, roadmaps, and rubrics
now live in three repos, with the runner providing code and roadmap checkouts. Add README,
LICENSE, and CODEOWNERS. The review runner and workflow land in a follow-up.
feat: AI review harness (runner + reusable workflow)
…checkout

fix: reusable workflow checks out TauCetiReview explicitly
codex exec occasionally exits non-zero with no output; after the same-provider
retry, try the other provider once so a transient crash never drops a rubric.
…-fallback

feat: provider fallback when a reviewer CLI fails
…stead

A fallback to the other provider masked codex exec's intermittent non-zero
exits. Remove it and instead record codex's event stream, error events, and
raw stdout on failure so the crash can be root-caused. Keep only a single
same-provider retry for a genuine transient network blip.
…er-fallback

revert: drop provider fallback, capture codex failure detail
I1: never evaluate PR-controlled Lake in the privileged job. Get Mathlib source
from a trusted clone at the rev pinned in the BASE manifest, not via the PR's
lakefile (which would be arbitrary code execution on attacker input).

I2: reviewers run in a clean workspace (PR source w/o .git, roadmap, mathlib,
diff only) with a minimal per-provider env — only that provider's key, never a
GitHub token or the other provider's key. Keys are staged to files, read into
memory by the runner, and unlinked before any reviewer runs. persist-credentials
is off on every checkout. codex runs with shell_environment_policy.inherit=none.

I3: replace the single broad App token with two scoped tokens (TauCeti
pull-requests:write to comment; TauCetiReview contents:write to persist), minted
only after reviewers finish, in separate steps. No token is in the reviewer phase.

With public transcripts and no redaction gate, this isolation is the only leak
barrier; the residual is that a reviewer can read its own provider key via
/proc/self/environ (tracked in TauCetiProject#8).
…-a-sandbox

harden: sandbox the review pipeline (I1/I2/I3)
I6: parse the verdict only from after a one-time random marker the runner injects
per review. Attacker content (incl. a forged {"verdict":"approve"}) sits before
the marker and is ignored; fail closed on missing marker, bad JSON, or a verdict
outside {approve,request_changes,block}. _common.md gains an explicit
untrusted-input boundary and documents the marker channel.

I5: reserve before spending — skip a rubric if spend-so-far plus --max-call-cost
would breach the daily budget (a per-call ceiling, not a post-spend check); count
every attempt incl. the retry; persist spend to the ledger incrementally so a
crash cannot lose what was billed.
kim-em and others added 25 commits July 2, 2026 22:26
…auCetiProject#93)

* feat: provide the Mathlib naming conventions to the naming reviewer

The naming rubric judges names against "standard Mathlib terminology"
but the agent was never given the conventions: they live in the
leanprover-community website repo, not in anything the read-only
reviewer can grep. Vendor the document under rubrics/references/ (with
a provenance header and a short local addendum on _def/_apply/_iff/_eq
suffix semantics), append it to the naming rubric's prompt via a small
per-rubric RUBRIC_REFERENCES mapping in build_prompt, and fold
references/*.md into rubrics_fingerprint so a reference edit
invalidates carried-forward approvals like any rubric edit.


* fix: correct fingerprint claim, add reference boundary and path validation

Review findings on this branch (Codex):

- The claim that rubrics_fingerprint invalidates carried-forward approvals
  was false: state_of() binds approvals to the PR head SHA only, and the
  fingerprint is recorded purely as rubrics_version provenance (archive
  records, run ids, dedupe keys, meta blocks). Correct the comment, the
  fingerprint docstring, and the test docstring; TauCetiProject#95 tracks actually
  binding approvals to the fingerprint.
- Wrap each spliced reference in a generated BEGIN/END boundary naming the
  source and stating it cannot override the shared protocol, output
  format, tools, or verdict instructions.
- Validate RUBRIC_REFERENCES entries (relative, no '..', resolves under
  rubrics/references/, exists) via resolve_reference(), shared by prompt
  assembly and fingerprinting so a spliced document can never escape the
  fingerprint's references/*.md coverage.
- Tests for the boundary and validation; all standalone suites pass.


---------
…lic surface (TauCetiProject#96)

Reword the exposure rule so it no longer hinges on "the named downstream
target", which review agents read as the entries in a roadmap's Targets.lean
and used to block any public declaration not literally listed there. It now
frames the public surface as what later roadmap stages need or the roadmap's
explicit products, so a general theorem the named milestones specialize is
characteristic API, not over-exposure.
…CetiProject#99)

* feat: placement covers directory structure and filename families

New files should join an existing topic subdirectory when one fits; a
flat filename-prefix family is evidence of a subdirectory in the
making, to be handled by a dedicated relocation PR, with the prefix
count treated as evidence only and never alone grounds for
request_changes.


* feat: restructure-as-you-add in placement, sanctioned in scope

A PR adding a file to an already-large flat family is expected to move
the family into its subdirectory in the same PR; scope explicitly
treats that bundle as one topic. Deferral only for documented open-PR
conflicts, via the structure tracking issue.


* style: tighten the placement structure bullet


* style: drop the redundant deferral clause

The unless-clause already carries the exception; the tracking-issue
bookkeeping is author-side guidance and has no reviewer action.


* style: drop the vague coda

Judgment over counting is already carried by 'several'; the one-topic
sanction lives in scope.md where its reviewer reads it.


* style: fold the restructure-as-you-add sanction into the existing sentence


* feat: preliminary relocation PR replaces restructure-as-you-add

The relocation ships as its own PR before the addition, per the repo's
existing ship-a-prerequisite-refactor-as-its-own-PR rule; the scope.md
carve-out is unnecessary under this policy and is reverted.


---------
* feat: the pair is the directory threshold

Foo.lean plus FooBar.lean become Foo/Basic.lean (or Defs.lean) and
Foo/Bar.lean via a preliminary relocation PR; anchors move inside.


* feat: move as you add, same PR; scope sanctions the bundle


* style: should be, not are


* style: tighten the scope sanction


* style: name placement.md explicitly


---------
Switch the codex reviewer's default model from gpt-5.5 to gpt-5.6-sol and
price the gpt-5.6 family (sol/terra/luna) so the engine's require_priced
guard and daily-budget accounting cover them. Sol lists at $5/$30 per 1M
tokens (identical to gpt-5.5, with the same >272K long-context tier), Terra
at $2.50/$15, and Luna at $1/$6, all with the 10% cached-input read rate;
the three windows are dated to the family's 2026-07-09 GA. The worker passes
only --reviewer codex and never --codex-model, so moving the default is what
actually routes review rounds onto sol.

🤖 Prepared with Claude Code
…uCetiProject#102)

Downgrade a codex reviewer to gpt-5.6-terra when the account isn't entitled
to the default model (Sol needs a paid ChatGPT tier; Free/Go subscriptions
get Terra), so an unentitled account no longer fails every codex rubric.

run_codex parses the terminal turn.failed event's JSON payload into a
structured error_status / error_type / error_message (defensively: a
string/list where a dict is expected, or a malformed payload, is skipped, not
crashed on, and never masks a good earlier one). codex_model_unavailable()
then needs BOTH a model-access message (matched against that structured
message — grounded in the captured codex-cli 0.144 wording "... not supported
when using Codex with a ChatGPT account", not a blind guess) AND a
non-transient status: a 400/403/404 qualifies, a 429 or 5xx never does. A 400
alone is not model-specific, so it can't trigger a downgrade on its own.

Because codex has been seen to wrap a transient server error in the same "not
supported" 400 (openai/codex#14190), run_one reconfirms on the same model
before downgrading (a transient clears on the retry; a real unavailability
repeats), and only persists the downgrade — flipping the shared runner
registry so later rubrics skip the dead Sol call — once the fallback yields a
verdict. So a misclassification costs at most one extra attempt, never a
sticky downgrade. Only the default model auto-falls-back: passing
--codex-model explicitly (now None-defaulted so an explicit Sol is
distinguishable) opts out. Each attempt records the model it ran. Rubrics
dispatch sequentially, so the registry mutation races nothing; the registry
is rebuilt from CODEX_MODEL each run, so Sol is re-probed next run.

The fallback is kept priced (dispatch_models lists it; the price-sync test
enforces it) and re-checked by dispatch-time require_priced. A dependency-free
test covers the real captured output, the status+message classifier, the
malformed-payload hardening, and the full reconfirm/downgrade state machine.

🤖 Prepared with Claude Code
…ith "should be private" (TauCetiProject#103)

This PR loosens the api-design rubric's exposure guidance so it no longer
asks for a declaration to be `private` merely because the roadmap's named
targets do not mention it. The named targets are not the whole allowed
surface: genuinely reusable general results and the products a later stage
needs may be exposed, and a helper is kept `private` only when it has no use
outside the proof or file it serves.

🤖 Prepared with Claude Code
…auCetiProject#97)

Scope gains two qualitative checks: a PR citing a later stage of a
roadmap needs its prerequisite stage merged or in flight, and reviewers
weigh whether the cited target is actually getting closer rather than
approving on citation alone. Correctness gains the unexercised-predicate
check: a new Prop-valued definition needs a consuming theorem or a
non-degenerate witness before its faithfulness is falsifiable.
…-bot-lakefile

fix: merge validated bot lakefile bumps
* fix: preserve login identity for reviewers

* fix: stream reviewer prompts over stdin

* fix: stream pi prompts over stdin
…tiProject#107)

* fix: keep a failed attempt's diagnosis, publish none of it

Every artifact a failed rubric produced recorded returncode, secs and usage at
$0.00 and no error text, so a total auth failure was indistinguishable from a
model that answered nothing. run_claude captured the operative line all along
and then dropped it: the attempts allowlist omitted raw_stderr, and only the
final attempt's copy survived at the top level of the local result.

Carry raw_stderr per attempt in the local store, and echo each distinct stderr
summary to stderr when a rubric ends with no verdict, so the cause appears
beside the symptom. The archive repo is public, so ATTEMPT_PRIVATE_KEYS names
both fields that must not leave the machine and the durable record strips them.


* fix: publish why a rubric errored, not the stderr it came from

Second-opinion review caught that the premise of the first attempt was wrong.
`--store` is a checkout of this repo's `reviews` branch and the review
workflow commits and pushes it, so the per-rubric record is as public as the
archive. It has been publishing top-level `raw_stderr` and `session_id` all
along: 57 records on origin/reviews carry both. Adding per-attempt stderr to
that file would have widened an existing leak rather than staying local.

Publish a classified `error_kind` instead: a closed vocabulary derived from
stderr that carries the diagnosis without the payload, which is what the
scoreboard's bare "error" at $0.00 was missing. Route both persisted sinks
through one recursive `public_record`, so `raw_stderr` and `session_id` are
stripped at any depth and a field added to an attempt cannot start publishing
either by accident. That also closes the pre-existing top-level leak.

The unsanitised line still prints for a local operator, which is the case
that had nothing to go on, but never under GITHUB_ACTIONS, whose logs are as
public as the repo.


---------
* fix: support older gh when resolving PR refs

* test: guard old-gh compatibility path
A stored scoreboard id whose user.login is not the current gh actor
404s on edit; post a fresh kind:scoreboard instead, matching the
review-thread guard from TauCetiProject#110.
@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Superseded by #121 (same patch, agent trailer removed from the commit).

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.

Re-review can't update a finding authored by a different worker identity (gh PATCH → 404), stranding the scoreboard

2 participants