Skip to content
Merged
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,38 @@ The same rule makes the combinations explicit rather than order-dependent:
| `llm: hosted` + any provider key | refused: pick one |
| `llm: hosted` + `license_key` | refused: use `llm: license` |
| `llm: license` without `license_key` | refused: names the secret to add |
| `llm: hosted` or `license` + `model`, `agent_model` or `parsing_model` | refused: CodeBoarding picks the model on its own key; bring your own key to choose one |
| `llm: anthropic` + `anthropic_api_key` | Anthropic, directly |
| `llm: anthropic`, key empty or absent | refused: names the input and the secret |
| `llm: anthropic` + `openai_api_key` | refused: a second provider's key |
| `llm: anthropic` + key + `license_key` | Anthropic, on a CodeBoarding plan |

A licence alongside your own key is deliberately allowed: it says "my CodeBoarding plan,
my own tokens". **Your key always wins.** A direct provider call never reaches
CodeBoarding, so the licence is recorded and reported but not spent, and nothing meters
that combination today. The job summary says so on every run, rather than leaving you to
infer it from the tier name.
my own tokens". **Your key always wins.** The analysis calls your provider directly, so
the licence is recorded and reported but not spent, and nothing meters that combination
today. The job summary says so on every run, rather than leaving you to infer it from the
tier name.

The review guide and architecture checks are the exception to "directly": their code
runs on CodeBoarding's service, so your key travels with that request, is used for that run
and is neither stored nor logged. A provider the service cannot call, such as a custom base
URL, LiteLLM or a self-hosted Ollama, keeps its key on the runner: those features make no
model call, and the run never falls back to CodeBoarding's model.

The action independently calls `POST /v1/review-guide` and `POST /v1/architecture` on the
same `review_guide_url` base URL. There is no discovery request. Both receive the same
required analysis inputs. The guide endpoint returns `{guide, usage, timingsMs}`; the action
saves the bare guide as `review_guide.json`. Architecture returns `{envelope, usage}`; its
envelope is saved unchanged as `closed/architecture.json`, including partial, skipped and
failed results. Failure of either request does not prevent the other or fail the review.

Only architecture receives optional `source.json`: tracked Python, JavaScript/TypeScript
and C# files (`.py`, `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.cs`, `.csproj`), excluding
dependency/build directories, docs/public/fixtures, minified JS and TypeScript declarations.
Symlinks are not read. Individual files are capped at 300 KB and source at 16 MB. If adding
source exceeds the 4.3 MB compressed request limit, it is omitted with a reason; architecture
still runs with the required inputs. `closed_features_source: false` also omits source.
An unsupported provider skips the guide, but architecture can still return measured findings.

### Providers

Expand Down Expand Up @@ -230,7 +252,9 @@ a CodeBoarding service issue. Own-provider quota failures retain their provider

## Model selection

All model inputs are optional and are passed directly to Core without action-side validation:
All model inputs are optional and are passed directly to Core. The action checks one thing: with
`llm: hosted` or `license` it refuses them, because CodeBoarding picks the model on its own key.
Bring your own key to choose one:

```yaml
with:
Expand Down Expand Up @@ -347,7 +371,8 @@ The first sync after the merge creates `codeboarding/baseline`, catching up from
| `github_token` | both | `${{ github.token }}` | Token for comments and sync delivery. |
| `codeboarding_analysis_location` | both | `codeboarding_branch` | Where the synced branch's analysis is stored: `codeboarding_branch` (on `codeboarding/baseline`, for the default branch; the code is never written) or `in_place` (committed as `.codeboarding/` on the synced branch). Sync saves it there and review reads it from there. |
| `warmstart_retention_days` | review | `1` | Days to keep the reusable analysis. Only the next run reads it. |
| `review_guide_url` | review | `https://jxivqwx4or6oip3krgdmdaiunm0iaybf.lambda-url.us-east-1.on.aws/` | Experimental. Production service base URL for the pull request's review guide. The run sends it this analysis and stores `review_guide.json` in the review artifact. Set to `''` to disable. Never fails the run. |
| `review_guide_url` | review | `https://jxivqwx4or6oip3krgdmdaiunm0iaybf.lambda-url.us-east-1.on.aws/` | Experimental. Shared base URL for `/v1/review-guide` and `/v1/architecture`. Saves `review_guide.json` and `closed/architecture.json` in the review artifact. Set to `''` to disable both. Never fails the run. |
| `closed_features_source` | review | `true` | Whether architecture checks may read this repository's source. Only the architecture request carries it, capped in size, and the service keeps nothing. The model sees code around a finding, never the tree. With `false`, source-dependent checks and model review do not run, and the result says so. |

`target_branch` and `sync_strategy` are deprecated, and read as the `codeboarding_analysis_location` they stand for, with a warning: `sync_strategy: push` as `in_place`, `sync_strategy: pull_request` as `codeboarding_branch`. `target_branch` is accepted only when it names the branch the run is on: the push trigger picks the branch. `force_full: true` is ignored with a warning. Anything the new input cannot express fails, linking to [moving an existing setup](#moving-an-existing-setup).

Expand Down
55 changes: 44 additions & 11 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,19 @@ inputs:
default: '2'
review_guide_url:
description: >-
Experimental. The CodeBoarding service that writes a pull request's review guide (main
changes, each change as numbered steps on the map). Defaults to the production service.
A review run sends it this run's analysis and stores the guide in the review artifact for
the platform to show. Set to an empty string to disable.
Experimental. Service base URL for POST /v1/review-guide and POST /v1/architecture.
Defaults to production. Both receive this run's analysis independently; results are stored
as review_guide.json and closed/architecture.json. Set to an empty string to disable both.
required: false
default: 'https://jxivqwx4or6oip3krgdmdaiunm0iaybf.lambda-url.us-east-1.on.aws/'
closed_features_source:
description: >-
Whether architecture checks may read this repository's source, for checks that
need the whole tree and to verify a finding. Only the architecture request carries it, capped in
size, and the service keeps nothing. The model sees the code around a finding, never the tree.
With 'false' those checks and the model review do not run, and the result says so.
required: false
default: 'true'
github_token:
description: 'Token used for comments and sync delivery.'
required: false
Expand Down Expand Up @@ -301,6 +308,9 @@ runs:
ACTION_PATH: ${{ github.action_path }}
CB_IN_LLM: ${{ inputs.llm }}
CB_IN_LICENSE_KEY: ${{ inputs.license_key }}
CB_IN_MODEL: ${{ inputs.model }}
CB_IN_AGENT_MODEL: ${{ inputs.agent_model }}
CB_IN_PARSING_MODEL: ${{ inputs.parsing_model }}
CB_IN_ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CB_IN_AWS_BEDROCK_API_KEY: ${{ inputs.aws_bedrock_api_key }}
CB_IN_AWS_BEDROCK_REGION: ${{ inputs.aws_bedrock_region }}
Expand Down Expand Up @@ -694,15 +704,16 @@ runs:
ANALYSED_FILES_CHANGED: ${{ steps.review_render.outputs.analysed_files_changed }}
run: "$GITHUB_ACTION_PATH/scripts/action/build-review-artifact.sh"

# After the artifact holds the analysis and before it is uploaded: the guide is one more file
# in it. Uses production by default; an empty URL disables it. It never fails the run.
- name: Build review guide
id: review_guide
# After the artifact holds the analysis and before it is uploaded: each feature is one more
# file in it. Uses production by default; an empty URL disables it. It never fails the run.
- name: Run closed features
id: closed_features
if: steps.guard.outputs.skip != 'true' && steps.guard.outputs.mode == 'review' && inputs.review_guide_url != ''
continue-on-error: true
shell: bash
env:
REVIEW_GUIDE_URL: ${{ inputs.review_guide_url }}
CLOSED_FUNCTIONS_URL: ${{ inputs.review_guide_url }}
CLOSED_FEATURES_SOURCE: ${{ inputs.closed_features_source }}
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ steps.guard.outputs.pr_number }}
ARTIFACT_DIR: ${{ steps.review_artifact.outputs.artifact_dir }}
Expand All @@ -711,10 +722,32 @@ runs:
CHECKOUT_DIR: ${{ github.workspace }}/.codeboarding-target
GITHUB_TOKEN: ${{ inputs.github_token }}
LICENSE_KEY: ${{ inputs.license_key }}
LLM: ${{ inputs.llm }}
MODEL: ${{ inputs.model }}
AGENT_MODEL: ${{ inputs.agent_model }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
AWS_BEDROCK_API_KEY: ${{ inputs.aws_bedrock_api_key }}
AWS_BEDROCK_REGION: ${{ inputs.aws_bedrock_region }}
CEREBRAS_API_KEY: ${{ inputs.cerebras_api_key }}
DEEPSEEK_API_KEY: ${{ inputs.deepseek_api_key }}
DEEPSEEK_BASE_URL: ${{ inputs.deepseek_base_url }}
GLM_API_KEY: ${{ inputs.glm_api_key }}
GLM_BASE_URL: ${{ inputs.glm_base_url }}
GOOGLE_API_KEY: ${{ inputs.google_api_key }}
KIMI_API_KEY: ${{ inputs.kimi_api_key }}
KIMI_BASE_URL: ${{ inputs.kimi_base_url }}
OLLAMA_API_KEY: ${{ inputs.ollama_api_key }}
OPENROUTER_API_KEY: ${{ inputs.openrouter_api_key }}
OLLAMA_BASE_URL: ${{ inputs.ollama_base_url }}
OPENAI_API_KEY: ${{ inputs.openai_api_key }}
run: python3 "$GITHUB_ACTION_PATH/scripts/review_guide.py"
OPENAI_BASE_URL: ${{ inputs.openai_base_url }}
OPENROUTER_API_KEY: ${{ inputs.openrouter_api_key }}
OPPER_API_KEY: ${{ inputs.opper_api_key }}
ORCAROUTER_API_KEY: ${{ inputs.orcarouter_api_key }}
REQUESTY_API_KEY: ${{ inputs.requesty_api_key }}
REQUESTY_BASE_URL: ${{ inputs.requesty_base_url }}
VERCEL_API_KEY: ${{ inputs.vercel_api_key }}
VERCEL_BASE_URL: ${{ inputs.vercel_base_url }}
run: python3 "$GITHUB_ACTION_PATH/scripts/closed_features.py"

- name: Upload review artifact
id: upload_review_artifact_dotcom
Expand Down
17 changes: 17 additions & 0 deletions scripts/action/credential_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"hosted_with_provider_key",
"hosted_with_license",
"license_with_provider_key",
"hosted_with_model",
"license_with_model",
"foreign_provider_key",
}
)
Expand Down Expand Up @@ -174,6 +176,19 @@ def _reject_provider_inputs(table: dict, given: dict[str, str], llm: str, tier:
)


def _reject_model(llm: str, tier: str, environ: dict[str, str]) -> None:
"""On CodeBoarding's key, CodeBoarding picks the model and its settings. Choosing one takes a
key of your own, and a workflow that names one here would otherwise run on another without
saying so."""
for name in ("model", "agent_model", "parsing_model"):
if environ.get(f"CB_IN_{name.upper()}", "").strip():
raise ConfigError(
f"{tier}_with_model",
f"`llm: {llm}` runs on CodeBoarding's model, so `{name}` cannot be set. "
"Remove it, or set `llm` to a provider and bring your own key to choose a model.",
)


def _require_id_token(llm: str, environ: dict[str, str]) -> None:
# Both, because the relay needs both (oidc_relay.py refuses to start without either)
# and a runner can expose one without the other. Checking only the URL let that case
Expand Down Expand Up @@ -259,6 +274,7 @@ def resolve(table: dict, environ: dict[str, str]) -> dict:

if llm == "hosted":
_reject_provider_inputs(table, given, llm, "hosted")
_reject_model(llm, "hosted", environ)
if license_key:
raise ConfigError(
"hosted_with_license",
Expand All @@ -270,6 +286,7 @@ def resolve(table: dict, environ: dict[str, str]) -> dict:

if llm == "license":
_reject_provider_inputs(table, given, llm, "license")
_reject_model(llm, "license", environ)
if not license_key:
raise ConfigError(
"missing_license_key",
Expand Down
Loading
Loading