Skip to content

promote: staging → main (2026-10-18-3) — b2e147338 - #2541

Draft
fedcourtsai-dev[bot] wants to merge 15 commits into
mainfrom
promote/2026-10-18-3
Draft

fedcourtsai-dev[bot] wants to merge 15 commits into
mainfrom
promote/2026-10-18-3

Conversation

@fedcourtsai-dev

@fedcourtsai-dev fedcourtsai-dev Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Drafted by the promotion train at 2026-10-18T18:35Z for promote/2026-10-18-3 at b2e147338 over main a2b9d414d. The branch was cut from staging and never moves, so the integration evidence and an approval of this head stay valid while staging carries on. The train regenerates the block between the markers while this PR is open; write anything else outside it.

Gate status (promote/2026-10-18-3 at b2e147338):

  • freshness: not yet — freshness: no green 'ranged-reads' integration-test run at b2e1473;freshness: no green 'corpus-service' integration-test run at b2e1473;freshness: no green 'stub-cascade' integration-test run at b2e1473
  • registry: clean

Train: dispatched the integration gate suite at b2e1473 (gh workflow run integration-test.yml --ref promote/2026-10-18-3 -f scenario=gate)

Next (maintainer): read the batch and approve this PR at b2e147338. The train then merges it with a merge commit once the integration gate run, the required checks and the registry gate hold at this head and no predict/evaluate/back-test round is in flight. Merging it yourself instead is fine. To stop it, request changes or close it: a closed candidate is not re-cut at the same staging tip, and the next one is cut once staging moves.

The batch

Effect checks

#2522: fix(security): bind the production read-write role's jobs to a corpus-write environment

Once promoted, and with step 1 done:

  • A writer run's jobs show environment corpus-write. The run page shows a corpus-write deployment per writer job, and this lists fresh deployments dated after the promotion:

    gh api 'repos/ModelMirrorAI/fedcourtsai/deployments?environment=corpus-write&per_page=5' \
      --jq '.[] | [.created_at, .ref, .sha] | @tsv'
    

    The matching query for environment=prod no longer gains entries from run-pull pull/live windows.

  • The window succeeds: its pointer commit lands on main.

  • scripts/promotion-gate.sh contexts (maintainer's admin token, from main once promoted) passes, including the new "the corpus-write environment admits main alone and holds only the writers' names" line.

  • After step 4, an executed check: a prod-bound job that tries the RW role is refused. Nothing in the repo can show this, so it is the maintainer's IAM reading of the RW trust (sub names only environment:corpus-write).

#2536: feat(promotion-gate): say when a promotion owes the admin-read contexts stage, and when to run it

  • The next promotion PR's promotion-gate job log ends with a contexts: due — … or contexts: not due — … line, and the job's conclusion is unchanged by it.
  • The train's generated block carries the contexts (admin read, run by hand) line exactly once.

To check:

gh run view <promotion-gate run> --log | grep 'contexts:'

Generated by Claude Code

#2540: feat(promotion-train): cut a candidate only on a dispatch

Once promoted, no tick cuts unless it is a dispatch.

  1. After this batch merges and the sync lands, the next scheduled promotion-prep run's summary should read promotion train: wait — staging <sha> carries a batch over main, ready to cut; … gh workflow run promotion-prep.yml --ref main, and no new promote/ branch should appear:
    gh run list --workflow promotion-prep.yml --limit 3
    gh api repos/ModelMirrorAI/fedcourtsai/git/matching-refs/heads/promote/ --jq '.[].ref'
    
  2. Then gh workflow run promotion-prep.yml --ref main should cut promote/<date> and open its draft PR (gh pr list --base main --search head:promote/).

Generated by Claude Code

No effect-check note found. Add one to the feature PR's description (the next refresh picks it up) or write it below the end marker, or say there why none applies:

After the merge

  1. Tag promotion/<YYYY-MM-DD> on the merge commit (-2 for a same-day second batch).
  2. Run the effect checks above and record what they printed.

claude and others added 15 commits October 17, 2026 16:21
…-write environment

The agent cell jobs bind `prod` with `id-token: write`, and the production
read-write corpus role's OIDC trust names `environment:prod`, so a
prompt-injected cell could mint a token that role accepts. Move exactly the
jobs that assume the read-write role (run-pull pull/live/enrich, run-seed
seed/ocr-sweep, run-repair repair/applications/decision-record/opinion-record)
to a dedicated `corpus-write` environment, so the role's trust can name that
environment alone.

- tests/test_workflow_corpus_write.py: every job naming the read-write role
  binds the literal `corpus-write`, no other job binds it, no agent job binds
  it, and the gate's admitted names equal what those jobs read.
- environment_checks + assert-corpus-write-environment + promotion-gate.sh
  contexts stage: the environment exists, admits `main` alone, and holds
  nothing outside the writers' names.
- docs/security.md, SECURITY.md, docs/data-pipeline.md, docs/pipeline.md,
  docs/cli.md and the workflow comments: the new trust design, and the Claude
  GitHub App's OIDC exchange recorded as an accepted residual.

Part of #2511: the repo half. The environment and IAM steps are the maintainer's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126DwPpqinYddgnRmh7Ljfn
Review round on the corpus-write environment change.

- Docs and comments that stated the read-write role's narrowed trust as fact
  now tie it to the maintainer's IAM step the gate cannot read (SECURITY.md,
  docs/security.md, docs/data-pipeline.md, run-predict, run-evaluate,
  run-pull, promotion-gate.sh, environment_checks).
- The gate requires the writers' names to be present, not only nothing extra
  (every secret and variable but the optional CourtListener rate caps), and a
  listing whose total_count disagrees with its items is a truncated page and
  reads as unread, for every environment the stage checks.
- docs/security.md: the Claude App residual names the full reach of a default
  installation token (unreviewed staging merges, gate-read labels, PR bodies,
  unprotected branches, public posts, Releases) and leaves its actual grant to
  the maintainer; corpus-write removes agents, not hostile parsers, from the
  write role's reach; the duplicated names rotate in both environments.
- tests: the agent detector moves to tests/agent_detector.py, code-owned with
  test_workflow_token_holder.py; the corpus-write invariant follows local
  composites, reads indexed secret/variable spellings, and pins the
  ref-name fall-through to the three branch-resolving workflows.

Part of #2511.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126DwPpqinYddgnRmh7Ljfn
…step everywhere; make copying the rate caps explicit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126DwPpqinYddgnRmh7Ljfn
chore: sync main into staging
…ironment

fix(security): bind the production read-write role's jobs to a corpus-write environment
…a request

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wn5rjyY9Z7Vhoc9QyGXToG
fix(buttondown): include Buttondown's bounded reason when it refuses a request
…ts stage

`contexts` needs an admin-read token no CI job holds, so it runs by hand. A
new `fedcourts contexts-due` names the paths in a batch that change what the
stage judges settings against (workflows, the gate script, the required-check,
ruleset and environment judgments and the configs they read). The gate's `all`
ends with the verdict, never failing on it, and the promotion train puts it in
the PR's generated block as due or not due.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019NtSiNeMSRGjEKSx7yTfdu
contexts reads main's own workflows, so a batch's workflow change is checked
once it merges; what it reads from the checkout is checked before. The verdict
and the promotion body now say which. Drops review-hold.yaml (nothing the stage
reads) and adds corpus_check.py (it parses the approver). Fixes the doubled
line on the train's candidate body, and every doc that still said contexts
runs on every promotion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019NtSiNeMSRGjEKSx7yTfdu
feat(promotion-gate): say when a promotion owes the admin-read contexts stage, and when to run it
)

The re-docketed-detector entry's staging merge commit, promotion merge commit
and merge date, and the live-cadence entry's boundary: promotion/2026-10-18 and
its first new-cadence live window, run-pull run 39091707696.


Claude-Session: https://claude.ai/code/session_019NtSiNeMSRGjEKSx7yTfdu

Co-authored-by: Claude <noreply@anthropic.com>
The hourly schedule cut a candidate whenever staging held something new
and the last promotion was synced, so a batch closed at whatever tip the
first eligible tick saw. Now only a dispatched promotion-prep tick cuts;
the schedule advances an open candidate to the merge as before and, with
none open, reports a batch ready to cut, naming the dispatch command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019NtSiNeMSRGjEKSx7yTfdu
…scribed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019NtSiNeMSRGjEKSx7yTfdu
…atch-cut

feat(promotion-train): cut a candidate only on a dispatch

This branch was successfully deployed

1 active deployment
staging — b2e14733 Deployed Oct 18, 2026 by modelmirror via scenario (engine-actions-smoke, gemini) #225
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.

3 participants