Skip to content

feat: save the diagram to an analysis branch of its own - #142

Open
Svilen-Stefanov wants to merge 2 commits into
feat/ancestor-base-seedfrom
feat/baseline-branch
Open

Svilen-Stefanov wants to merge 2 commits into
feat/ancestor-base-seedfrom
feat/baseline-branch

Conversation

@Svilen-Stefanov

@Svilen-Stefanov Svilen-Stefanov commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #140 (feat/ancestor-base-seed), which is stacked on #139. Review and merge those first.

What changed and why

Section 5 of the base-provenance contract: save the diagram to a branch of its own, so a team gets a saved diagram without committing generated files to its code branch. New setups will default to it through the webview's setup template (CodeBoarding-webview#202); this PR only adds the capability. Existing repositories keep whatever strategy they use; the README has a prompt to paste into a coding agent to move one.

  • sync_strategy: branch and a new analysis_branch input (default codeboarding/analysis). The first sync creates the branch as an orphan. Each sync adds one commit with the same .codeboarding/ files sync commits today plus .codeboarding/source.json (schema, source_branch, source_sha, generated_at, engine_version, config), titled chore(codeboarding): diagram of main @<sha7> with CodeBoarding-Source: <sha> and CodeBoarding-Config: <cfg hash> trailers. Engine output is never edited.
  • target_branch keeps its name (renaming it would break every v1 workflow), but its description now says what it is: the code branch sync analyzes, which push and pull_request also write to and branch only reads.
  • Delivery (deliver-sync.sh) builds the commit with a scratch index, never touches the target branch (no .gitattributes either), and pushes fast-forward only, never forced, onto the tip it fetched. It refuses an existing branch that is not an analysis branch (tip without the trailer, or holding anything besides .codeboarding/), so pointing analysis_branch at a code branch fails instead of emptying it. Races: a target that moved during analysis drops the result (as today); an analysis branch moved by a concurrent sync is built on once; a re-run for the same commit adds nothing. A push refused while the tip did not move fails with a message naming the bypass-actor fix.
  • Sync seeds from the branch tip when the tip was made under this configuration, replacing the generated state wholesale and keeping only the checkout's .codeboardingignore and health config, then runs incrementally. Otherwise it falls through to the ancestor-artifact lookup from feat: catch up the review base from the nearest saved ancestor #140, then full, and delivery recreates the branch.
  • Review base order: exact artifact, committed at merge base, analysis branch, ancestor artifacts, full. The branch's newest 100 commits are fetched without blobs into a scratch repo (messages only), and their trailers are matched against the merge base's first-parent history (100 deep). Only entries whose CodeBoarding-Config matches this run's configuration hash count. An exact entry is reused with no engine run; an ancestor entry is caught up and reported incremental. Entries found only under another configuration make a full run's reason "incompatible". A base read from the branch is published under the merge base's artifact name.
  • Guard: accepts branch; ignores pushes to the analysis branch itself; rejects an analysis_branch that git check-ref-format refuses, in both modes, before anything is analyzed.
  • Docs: README section, migration prompt and input rows; docs/COMMIT_STRATEGY.md "The analysis branch" (what lives where, how sync writes and reviews read it, the configuration rule, the non-atomic target check, what a deleted branch means, why to protect it); docs/analysis-branch-ruleset.json to import. It blocks creating, updating, deleting and force-pushing codeboarding/analysis for everyone but GitHub Actions (integration 15368, what the default github.token pushes as); the docs say to make a GitHub App the only bypass actor when sync pushes with one.

Since the first review

  • Renamed per @ivanmilevtues: baseline_branch → analysis_branch, codeboarding/baseline → codeboarding/analysis; target_branch described rather than renamed.
  • Codex P1s: exact entries are checked against the configuration (trailer); an existing non-analysis branch is never written; the ruleset restricts creation and updates.
  • Codex P2s: the parent is the fetched tip; restoring replaces generated state; invalid branch names fail in the guard. The target-head check stays separate from the push, now documented: a stale entry still names its commit, and the queued run replaces it.
  • Reporting follows feat: report how the review base analysis was obtained #139's base_analysis_method / base_analysis_reason.

What it looks like

This PR changes nothing visible in the web platform by itself; CodeBoarding-webview#202 reads the branch. On GitHub, the new branch's commits read:

chore(codeboarding): diagram of main @a1b2c3d

CodeBoarding-Source: a1b2c3d4e5f6...
CodeBoarding-Config: 3f9c...

How it was tested

tests/test_analysis_branch.py against real local remotes:

  • Delivery: the first sync creates an orphan with source.json (including config) and both trailers, and leaves main alone. The second sync appends a fast-forward commit, and a same-commit re-run adds nothing. A pre-receive hook refusing the branch produces the plain message. A deleted branch is recreated as a new orphan. A target that moved keeps the branch unchanged. An existing code branch under the analysis branch's name is refused and left untouched.
  • Review: an exact branch entry is reused and an older one is incremental (1 commit caught up), from a depth-1 clone. An entry under another configuration is never reused (full, incompatible), and without a configuration hash no entry is trusted. A missing branch falls back to full.
  • Sync: it continues from the branch tip; a switch from push replaces the old committed state but keeps .codeboardingignore; a tip under another configuration is not continued from.
  • Guard: it accepts the strategy, skips pushes to the branch, rejects invalid branch names in both modes, and rejects a target_branch equal to the analysis branch.

python -m unittest discover -s tests: 239 tests, all pass locally (macOS). Black 25.9.0 via the pre-commit hook; shellcheck on all action scripts.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-10T01:01:58.929791Z 608b37d New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codeboarding-review

codeboarding-review Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

CodeBoarding review

Status: 0 changed components (no analysed file changed)

See the full change in CodeBoarding.

graph LR
    n_action_scripts["action_scripts"]
    classDef added fill:#1f883d,stroke:#0b5d23,color:#ffffff;
    classDef modified fill:#bf8700,stroke:#7d4e00,color:#ffffff;
    classDef deleted fill:#cf222e,stroke:#82071e,color:#ffffff,stroke-dasharray:5 3;
Loading

download artifacts · run 38011193792

@Svilen-Stefanov
Svilen-Stefanov force-pushed the feat/ancestor-base-seed branch from 2012a60 to 32fdc42 Compare October 7, 2026 15:31

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db578a35b9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread scripts/action/analyze.sh Outdated
Comment on lines +369 to +370
elif [ "$BRANCH_DISTANCE" -eq 0 ]; then
REQUIRES_FULL=false base_source=saved base_from_sha="$BRANCH_SOURCE" catchup_commits=0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject exact baselines from incompatible configurations

When the baseline branch contains an entry for the merge base, this path accepts it after checking only depth_cap and skips the compatibility run. Baseline artifacts are otherwise keyed by CFG_HASH, which also covers the engine version, provider, endpoint, region, and model inputs; after an engine upgrade or when sync and review use different models, the review therefore compares an old-configuration base with a newly generated head and can report spurious architecture changes. Store and validate the configuration identity with each branch entry, or run the same compatibility check used for non-exact entries before treating it as saved.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7a65300. Each analysis-branch commit now carries a CodeBoarding-Config: <cfg hash> trailer (also in source.json), and an entry only counts when it matches this run's CFG_HASH, for the exact entry, ancestor entries and the sync tip alike. Entries found only under another configuration make the full run's reason "incompatible". With no CFG_HASH nothing on the branch is trusted, same as artifact reuse.

Comment thread scripts/action/deliver-sync.sh Outdated
Comment on lines +101 to +103
if [ -n "$tip" ]; then
git fetch -q --depth=1 "$REMOTE" "refs/heads/$branch"
parent=(-p "$tip")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Refuse to overwrite an existing non-baseline branch

If baseline_branch already names any ordinary branch other than target_branch—for example a pre-existing codeboarding/baseline, develop, or the old codeboarding/sync branch—this unconditionally makes its tip the parent of a commit whose tree contains only .codeboarding/. The subsequent fast-forward push succeeds and makes every other file disappear from that branch, rather than creating the promised orphan branch. Verify that an existing tip is a recognized CodeBoarding baseline before parenting it, and fail without pushing when it is not.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7a65300. Before building on an existing tip, delivery checks it has a CodeBoarding-Source trailer and that its tree is only .codeboarding/; otherwise it fails without pushing and says to pick an unused analysis_branch. Test: test_an_existing_branch_that_is_not_an_analysis_branch_is_never_written.

Comment on lines +114 to +115
# Never forced: the parent is the tip just read, so this only ever fast-forwards.
if git push -q "$REMOTE" "$commit:refs/heads/$branch"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Couple the target-head check to the baseline update

The target branch is checked by a separate fetch at the start of the loop, but this push updates only the baseline ref. If target_branch advances after that fetch and before this push, the push still succeeds and publishes an analysis of the old BASE_SHA as the latest baseline, contrary to the stale-result guarantee; the queued newer run may not repair it if that run later fails or is cancelled. The expected target SHA needs to be validated in the same remote transaction as the baseline update.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this one as is, now documented in COMMIT_STRATEGY.md. Git can't make the push conditional on another ref without also updating it, and the outcome is benign: the entry's trailer still names the older commit it describes, so no reader takes it for newer (reviews match by source sha), and the run queued for the newer commit replaces it. If that run fails, the next push fixes it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5c9e48f22

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +11 to +13
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict baseline writes to the bypass actor

In repositories where contributors may push branches but the main branch requires reviewed PRs, this ruleset still lets any contributor create or fast-forward codeboarding/baseline; these rules block only deletion and non-fast-forward updates. Review and sync runs later pass that branch's .codeboarding/static_analysis.pkl through restore_baseline to the engine, which loads pickle state in a privileged workflow, so a contributor can bypass code review with a crafted baseline commit. Add creation and update restrictions, leaving only the intended app/token as a bypass actor.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7a65300 (now docs/analysis-branch-ruleset.json): it adds creation and update rules, so only the bypass actor can write. The bypass is GitHub Actions (15368), since that's what the default github.token pushes as, and the docs explain the pickle risk and say to make a GitHub App the only bypass actor when sync pushes with one, since any workflow can use github.token.

Comment thread scripts/action/analyze.sh Outdated
mkdir -p "$scratch" "$state"
git -C "$CHECKOUT_DIR" archive "$commit" .codeboarding | tar -x -C "$scratch" || return 1
rm -f "$scratch/.codeboarding/source.json"
cp -a "$scratch/.codeboarding/." "$state/"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace generated state when restoring the branch

When a repository switches from push to branch, $state is initially seeded with the old committed .codeboarding directory, and this overlay leaves any generated files absent from the branch snapshot untouched. Optional artifacts such as static_analysis.sha, codeboarding_version.json, or a health report can therefore come from the old code-branch baseline while analysis.json and the pickle come from the baseline branch, and that mixed state is passed to incremental analysis. Clear the generated state before copying the snapshot, then restore only the current checkout's user configuration as seed_from_ancestor does.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7a65300. Restoring from the branch now replaces the generated state wholesale and only puts back the checkout's .codeboardingignore and health config (reusing #140's keep_user_config). Test: test_sync_replaces_the_committed_state_with_the_branch_tip.

Comment thread scripts/action/deliver-sync.sh Outdated
Comment on lines +99 to +102
tip="$(git ls-remote "$REMOTE" "refs/heads/$branch" | awk '{print $1; exit}')"
parent=()
if [ -n "$tip" ]; then
git fetch -q --depth=1 "$REMOTE" "refs/heads/$branch"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the fetched tip as the baseline parent

If another sync advances the baseline branch after ls-remote records tip but before this depth-one fetch, the fetch downloads the new tip while parent still names the old one. When that old commit is not already in the checkout—such as with force_full, or when the branch had already moved during analysis—git commit-tree -p "$tip" fails before the push and the advertised retry logic can run. git fetch -h describes --depth as producing shallow history, so derive tip from the fetched FETCH_HEAD or fetch the recorded SHA explicitly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7a65300. ls-remote is only used to tell whether the branch exists; the parent is now FETCH_HEAD from the depth-1 fetch.

Comment thread scripts/action/guard.sh Outdated
Comment on lines +30 to +34
if [ "$SYNC_STRATEGY" = branch ]; then
[ -n "${BASELINE_BRANCH:-}" ] || fail "baseline_branch is required with sync_strategy: branch."
# The baseline branch holds only analysis; a workflow that also fires on it must not analyze it.
[ "$REF_NAME" != "$BASELINE_BRANCH" ] || skip "Ignoring a push to the baseline branch $BASELINE_BRANCH."
[ "$target_branch" != "$BASELINE_BRANCH" ] || fail "target_branch must differ from baseline_branch."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the baseline branch before analysis

If baseline_branch contains an invalid Git refname, such as one with .., a space, or a trailing dot, this guard accepts it because it checks only that the value is nonempty. The analysis lookup then silently treats the failed fetch as a missing baseline and can perform a full paid analysis, after which delivery fails with an invalid refspec and misreports the unchanged empty tip as a branch-rule rejection. Validate refs/heads/$BASELINE_BRANCH with Git's refname validator in the guard so this configuration fails before checkout and analysis.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7a65300. guard.sh runs git check-ref-format refs/heads/$ANALYSIS_BRANCH in both modes and fails before checkout. Test: test_a_branch_name_git_cannot_use_fails_before_anything_runs.

@ivanmilevtues ivanmilevtues left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentally looked over the thing here. Looked through the logic and it makes sense and looks fine. no majore issues found from prompting just wording again + takea look at the codex comments. (there are some P1s)

Comment thread action.yml
description: 'Sync delivery method: push, pull_request, or branch (an orphan branch of its own, see baseline_branch).'
required: false
default: 'push'
baseline_branch:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe the word baseline is not very intuitive for our users as we call this syncing, so I suppose maybe we should name it syncing_branch

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on dropping "baseline". Renamed in 7a65300 to analysis_branch, default codeboarding/analysis. I avoided sync_branch because sync_strategy: pull_request already pushes to a branch called codeboarding/sync, so the two would be easy to mix up. analysis also matches .codeboarding/analysis.json. The webview side (#202) follows the same names.

Comment thread README.md Outdated
with:
mode: sync
llm: hosted
target_branch: main

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

target_branch, is this the target in tehms of where the sync will happen or in terms of which branch we will sync with, unsure that wording is clear again.

i think that most of these things will be read by ppl or even more by their agents so proly descriptive and somewhat clear names are worth investing in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, it was ambiguous, and under branch the old description ("Branch updated by sync mode") was just wrong. I kept the name since it's an existing v1 input and renaming would break every current workflow, but the description now says it's the code branch sync analyzes: with push / pull_request the analysis is also committed to it, with branch it's only read. Same wording in the README table and the new section.

sync_strategy: branch saves the analysis to an orphan branch in the same
repository, codeboarding/analysis unless analysis_branch names another, one
fast-forward commit per sync, and never writes to the target branch. Each
commit carries CodeBoarding-Source and CodeBoarding-Config trailers, also
recorded in .codeboarding/source.json.

Reviews read their base from the branch after an exact artifact and a
baseline committed at the merge base: an entry for the merge base is reused,
an entry for an ancestor is caught up. Only entries made under this run's
configuration count, so a different engine version or model is never reused
as is. Sync continues from the tip under the same rule, replacing the
generated state wholesale and keeping only the checkout's user config.

Delivery refuses an existing branch that is not an analysis branch (no
trailer, or anything besides .codeboarding/), builds on the tip it fetched
rather than the one ls-remote saw, and the guard rejects an analysis_branch
git cannot use before anything runs. The importable ruleset now restricts
creating and updating the branch to its bypass actor, since sync and review
load a pickle from it.

target_branch's description now says it is the code branch sync analyzes,
written only by push and pull_request. The README carries a prompt for
moving an existing setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@Svilen-Stefanov Svilen-Stefanov changed the title feat: keep the diagram on a baseline branch of its own feat: save the diagram to an analysis branch of its own Oct 9, 2026
…emetry

target_branch becomes synced_branch, sync_strategy becomes save_baseline_to
(synced_branch | pull_request | baseline_branch), and the unreleased
analysis_branch becomes baseline_branch with default codeboarding/baseline.
The released names keep working through old_api_migrator.sh, the only reader
of deprecated inputs.

The base_analysis_method output and the review comment's base line and
timers are removed. Engine runs carry CODEBOARDING_RUN_ID tagged base, head
or sync, so the engine's own telemetry shows how each base was obtained.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 608b37dd48

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +22 to +23
# save_baseline_to has a default, so only a value other than it is a conflict.
[ "$save_baseline_to" = synced_branch ] || fail "Set save_baseline_to only; sync_strategy is its deprecated name."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect conflicts with the explicit default destination

When a migrating workflow explicitly sets save_baseline_to: synced_branch but accidentally retains sync_strategy: pull_request, both inputs are set, yet this condition treats the new value as merely its default and silently lets the deprecated input overwrite it. The run then opens a rolling PR instead of committing to the explicitly requested branch, contrary to the documented guarantee that setting both names fails. Use an empty action-input default and apply synced_branch only after conflict detection, or otherwise preserve whether the new input was explicitly supplied.

Useful? React with 👍 / 👎.

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.

2 participants