* feat(agent): move every model call to Sonnet 5
Sonnet 5 is verified enabled on our Bedrock account already: a live probe of
eu.anthropic.claude-sonnet-5 in eu-north-1 answered normally, so no model-access
request was needed. The bare anthropic.claude-sonnet-5 is rejected (on-demand
throughput needs the cross-region inference profile), so the eu. prefix we
already use stays.
This is not a model-string swap. Sonnet 5 REJECTS the fixed thinking budget
outright: thinking {type:'enabled', budget_tokens} returns 400 "not supported
for this model. Use thinking.type.adaptive and output_config.effort". Every
chat intent set a budget, so the assistant would have failed on the first turn
after a bare ID change. Reasoning depth is now an effort level (STANDARD high,
DEEP xhigh), and max_tokens is explicit per tier rather than derived from a
budget that no longer exists.
display:'summarized' is load-bearing, not cosmetic. The default is 'omitted',
which still emits thinking blocks but with empty text. Measured on our own
account at xhigh effort: summarized returned ~1k characters of reasoning, the
default returned none. Without it the collapsible "Tänker ..." block in the
chat would have gone silently empty, which no mocked test would have caught.
Ceilings are raised (16k standard, 24k deep) because Sonnet 5's tokenizer
produces roughly 30% more tokens for the same text and max_tokens now caps
thinking and the visible reply together.
Also resolves the Opus 4.7 landmine recorded in the readiness doc: the composer
comment told ops to flip BEDROCK_OPUS_MODEL_ID to Opus 4.7, which would have
400d every thinking intent against the legacy budget shape. Both model
constants now point at Sonnet 5 and the stale instruction is gone.
Checked but deliberately unchanged: forced tool_choice in atom-selection. The
Sonnet 5 docs require thinking:{type:'disabled'} alongside a forced tool_choice
on Bedrock; probed against our account, the forced call succeeds without it, so
no change was made rather than adding a guard we cannot show is needed.
Other call sites moved too: invoice-inbox extraction, document extraction, the
compliance config, and the CI/CD workflows (pr-agent MODEL and MODEL_WEAK,
swedish-compliance-review, compliance-swarm).
Verified: 11315 tests pass, lint and tsc clean on every touched file, guards
pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(agent): review triage: keep the no-thinking output ceiling, finish the model sweep
max_tokens now caps thinking and the visible reply together, so collapsing the
two tiers into one made every non-thinking intent inherit a 16000 ceiling where
it used to have 4096. Give it its own MAX_TOKENS_NO_THINKING instead, set to the
old 4096 scaled ~30% for Sonnet 5's tokenizer so the effective reply length is
unchanged rather than quietly cut.
scripts/swedish-compliance-review.mjs still fell back to Sonnet 4.6 when
REVIEW_MODEL was unset, so a manual run silently used the old model. The initial
sweep only covered .ts and .yml.
pr-agent's FALLBACK_MODELS listed the primary model as its own fallback, which is
not a fallback; dropped it and rewrote the surrounding comments, which still
described Opus 4.8 and a 200k window.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
115 lines
5.6 KiB
YAML
115 lines
5.6 KiB
YAML
name: Swedish Accounting Compliance Review
|
|
|
|
# Stage 2 of the fork-safe compliance review (stage 1 is swedish-compliance-diff.yml).
|
|
#
|
|
# SECURITY: this is the privileged half: it has the AWS Bedrock secrets and a
|
|
# write token. It is triggered by `workflow_run` (NOT pull_request_target) and
|
|
# checks out ONLY the base repo, so it never executes fork PR code. The untrusted
|
|
# input (the PR diff) arrives as a downloaded artifact and is fed to the model
|
|
# as DATA: never run. This is the pattern GitHub recommends instead of
|
|
# `pull_request_target` + checking out the PR head.
|
|
|
|
on:
|
|
workflow_run:
|
|
# Must match the `name:` field in swedish-compliance-diff.yml exactly.
|
|
# A rename there silently stops this trigger from firing on all subsequent PRs.
|
|
workflows: ["Compliance diff"]
|
|
types: [completed]
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read # required to download the artifact from the triggering run
|
|
pull-requests: write # required to post the review comment
|
|
|
|
jobs:
|
|
review:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
# Only act on PR-triggered diffs that actually produced an artifact.
|
|
if: >
|
|
github.event.workflow_run.event == 'pull_request' &&
|
|
github.event.workflow_run.conclusion == 'success'
|
|
steps:
|
|
# Base repo only: the TRUSTED copy of the script and .claude/skills/.
|
|
# persist-credentials: false, no later step needs git push creds, so don't
|
|
# leave the token in .git/config for the steps that handle untrusted input.
|
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
persist-credentials: false
|
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: 20
|
|
- name: Download diff artifact
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: compliance-input
|
|
run-id: ${{ github.event.workflow_run.id }}
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Resolve PR number
|
|
id: pr
|
|
run: |
|
|
set -euo pipefail
|
|
NUM=$(cat pr-number.txt)
|
|
# Guard: pr-number.txt must be a plain integer (artifact is untrusted input).
|
|
if ! [[ "$NUM" =~ ^[0-9]+$ ]]; then
|
|
echo "Refusing to continue: pr-number.txt is not a number" >&2
|
|
exit 1
|
|
fi
|
|
echo "number=$NUM" >> "$GITHUB_OUTPUT"
|
|
- name: Install Anthropic Bedrock SDK
|
|
# Installed ONE DIRECTORY ABOVE the checkout on purpose. Running this
|
|
# inside the repo makes npm re-resolve the whole dependency tree from
|
|
# package.json (--no-package-lock throws away the pinned resolutions),
|
|
# and that re-resolution dies on an unrelated floating peer conflict
|
|
# (@hookform/resolvers -> valibot), which silently killed this review on
|
|
# every PR. Out of tree npm resolves this one package and nothing else,
|
|
# so an unrelated peer conflict can never take the compliance gate down
|
|
# again. Node still finds it: ESM bare specifiers walk up the parent
|
|
# directories' node_modules. NODE_PATH is not an alternative here, the
|
|
# ESM loader ignores it (CommonJS only).
|
|
# Version tracks the exact package.json pin: 0.32.0 broke Bedrock
|
|
# streaming in prod (DECISIONS.md 2026-07-08) and check:guards enforces
|
|
# 0.29.1 repo-wide, so this job must not be the one place running an
|
|
# unvetted build. Pinned exact + --ignore-scripts because this is the
|
|
# privileged job (write token in env): no floating @latest, and no
|
|
# dependency lifecycle scripts may execute here.
|
|
run: npm install --prefix "$GITHUB_WORKSPACE/.." --no-save --no-package-lock --ignore-scripts @anthropic-ai/bedrock-sdk@0.29.1
|
|
- name: Run compliance review
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
AWS_REGION: ${{ secrets.AWS_REGION || 'eu-north-1' }}
|
|
REVIEW_MODEL: eu.anthropic.claude-sonnet-5
|
|
# Two-stage mode: read the diff from the artifact instead of git-diffing.
|
|
DIFF_FILE: diff.patch
|
|
FILES_FILE: files.txt
|
|
run: node scripts/swedish-compliance-review.mjs
|
|
- name: Assert review produced output
|
|
# This job once produced no compliance signal for 10 consecutive PR
|
|
# runs (the in-tree npm install died on an unrelated peer conflict, see
|
|
# the install step above) and nobody noticed: workflow_run-triggered
|
|
# jobs do not appear on the PR checks list, so a red or silently empty
|
|
# run is invisible from the PR. Fail loudly whenever the review script
|
|
# finishes without writing a non-empty review.md, so "no output" can
|
|
# never again pass as a green run.
|
|
run: |
|
|
set -euo pipefail
|
|
if [ ! -s review.md ]; then
|
|
echo "::error::Compliance review produced no output (review.md missing or empty)"
|
|
exit 1
|
|
fi
|
|
- name: Find previous compliance comment
|
|
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
|
id: find-comment
|
|
with:
|
|
issue-number: ${{ steps.pr.outputs.number }}
|
|
comment-author: 'github-actions[bot]'
|
|
body-includes: '<!-- swedish-compliance-review-bot -->'
|
|
- name: Post or update PR comment
|
|
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5
|
|
with:
|
|
issue-number: ${{ steps.pr.outputs.number }}
|
|
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
|
body-path: review.md
|
|
edit-mode: replace
|