Skip to content

CRW-965: accept and integrate implementation nodes by commit without a pull request - #886

Merged
thisisjun786 merged 40 commits into
devfrom
codex/crw-965-commit-acceptance-integration
Oct 8, 2026
Merged

thisisjun786 merged 40 commits into
devfrom
codex/crw-965-commit-acceptance-integration

Conversation

@thisisjun786

Copy link
Copy Markdown
Owner

No description provided.

Codex and others added 4 commits October 8, 2026 03:45
dag-accept gains --commit <head> --base <base> --verification <record> --checkout <path>: the
relay proves in the local checkout that the commit is the head the generation's ruling fixed
(dag_verified_heads.head_sha), that it descends from the base, and that the verification record
names this commit's tree with a reusable PASS. Each failure is refused with its own reason
(head_not_receipt_head, merge_base_mismatch, revision_mismatch, disposition_conflict,
malformed_receipt). The pull-request path is untouched.

The zone gains two appended tables: dag_acceptance_verifications (the record the acceptance was
taken on, so a repeat call can prove it is the same output) and dag_integration_batches (one row
per integration batch, used by the next commit).
dag_acceptance_verifications and dag_integration_batches are appended statements, so
testdata/dag_zone_shipped.json is regenerated (CRW_GOLDEN=update) with their text.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@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: a5e39397d4

ℹ️ 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 thread internal/relay/dagsched/accept.go
Comment thread internal/relay/integrate/batch.go Outdated
Comment thread internal/relay/integrate/batch.go Outdated
Comment thread internal/relay/integrate/batch.go Outdated
Comment thread internal/relay/dagsched/accept_commit.go Outdated
Comment thread internal/relay/integrate/batch.go Outdated
Comment thread internal/relay/integrate/batch.go Outdated
Comment thread internal/relay/integrate/batch.go Outdated
Comment thread internal/relay/integrate/batch.go Outdated
Comment thread internal/relay/store/dag_zone.go
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review


P1 Badge Expose commit acceptance through dag-accept

Adding AcceptInput.Commit does not make this path reachable from the product CLI: the dag-accept parser specification still declares only the pull-request options, and runAccept only constructs PullRequest, never Commit. Consequently, invoking the documented commit flow with --commit, --base, --checkout, or --verification is rejected as unknown arguments, while omitting them leaves implementation acceptance requiring a pull request. Wire these options into the parser and construct CommitRef in runAccept.



P1 Badge Create a missing integration ref with an empty old value

When --integration-ref does not exist, this assigns the base tip to OldHead; the later updateRef therefore passes that non-empty commit as Git's expected old value. git update-ref -h documents the third argument as [<old-val>], and a missing ref has the zero old value, so this compare-and-swap always fails and the first batch cannot create the integration branch as intended. Keep the base tip as the merge starting point, but pass an empty expected old value when the ref was absent.


if err := runner.update(ctx, in.Checkout, in.IntegrationRef, out.NewHead, out.OldHead); err != nil {

P1 Badge Fence the coordinator epoch before moving the branch

When coordinator ownership has changed, or the caller supplies a stale --expect-epoch, this ref update still executes because Batch never calls the scheduler's epoch fence; the subsequent MarkMerged delegates to AssignmentView.Mark, which also does not validate ExpectedEpoch. A stale coordinator can therefore move the integration branch and record merged marks despite the CLI contract saying stale epochs are refused. Validate the epoch before any Git or database write.


recordPath := filepath.Join(os.TempDir(), "crw-965-verification-"+shaOf([]byte(tree))+".json")

P1 Badge Prevent stale verification records from passing a batch

The record path is deterministic for a tree and is neither newly created nor removed before the verifier runs. If that tree was verified previously and a later verifier invocation exits before writing its record, os.ReadFile reads the old PASS file and the batch treats the failed invocation as successful, moves the branch, and records merged marks. Use a unique fresh path per invocation or remove/create the destination before starting the verifier.


if !strings.EqualFold(record.Head, in.Head) {
return out, refuse(contract.RefusalHeadNotReceiptHead, "the verification record names head %s and the call names %s", record.Head, in.Head)
}
// the commit descends from the base
if ancestor, err := commitIsAncestor(ctx, in.Checkout, in.Base, in.Head); err != nil {

P2 Badge Require the verification record to name the supplied base

When a head has multiple ancestor commits, a caller can provide a verification record whose base_commit is A while passing B as --base; this code validates only that B is an ancestor and never compares record.Base with in.Base. The resulting row stores B in base_commit while retaining raw verification JSON that says A, so the acceptance attributes verification to a base the record did not cover. Refuse mismatched record and input bases before the ancestry check.


local, found, err := refTip(ctx, checkout, integrationRef)

P1 Badge Pin pushes to the verified batch head

If the local integration ref moves after dag-integrate verifies it but before dag-integrate-push runs, this reads and pushes the new tip without checking it against any recorded batch or expected SHA. As long as that unverified tip is a fast-forward of the remote, it is published even though no batch verified its tree. Require the verified new_head or batch identity as input and refuse when the local ref no longer equals it.


acc, found, err := loadActiveAcceptance(ctx, q, plan, node)
if err != nil {
return "", err
}
if !found || acc.HeadSHA == "" {

P1 Badge Mark the acceptance that was actually merged

If an acceptance is superseded or its stand changes while the batch verification is running, MarkMerged reloads whichever acceptance is active now instead of checking the acceptance ID, event, and head captured in the merged candidate. The branch can therefore contain the old head while the merged mark is written for the replacement revision that was never merged. Pass the captured candidate identity into this operation and refuse any change before updating the ref or marking it.


for _, c := range candidates {
if !hasCommit(ctx, in.Checkout, c.HeadSHA) {
return out, refuse(contract.RefusalMergeTargetUnreadable, "%s (%s) is not in %s: fetch it there first", c.HeadSHA, c.NodeID, in.Checkout)

P1 Badge Reject candidates accepted for another repository

For commit-based acceptances, Candidate.Repository records the exact checkout in which the commit was accepted, but the batch never compares it with in.Checkout; it only checks whether that checkout happens to contain the commit object. A checkout containing fetched or shared history from another repository can therefore merge and mark a candidate accepted for a different target. Bind every candidate to the batch checkout—or otherwise prove the checkout represents its recorded repository—before merging.


recordJSON, err := json.Marshal(out.Verification)

P2 Badge Preserve the complete verification record in the batch

Marshalling the reduced VerificationRecord struct discards fields from the verifier's original document, including fields such as tool versions and dependency digests used by the supplied records. Only the digest of the original bytes survives in assignment marks, so the stored batch cannot reproduce or audit the evidence that produced that digest. Carry the raw record bytes through settlement and store them unchanged, as commit acceptance already does.


if head, found, err := refTip(ctx, in.Checkout, in.IntegrationRef); err != nil {
return out, refuse(contract.RefusalMergeTargetUnreadable, "git could not read %s in %s: %v", in.IntegrationRef, in.Checkout, err)
} else if found {
out.OldHead = head

P1 Badge Require the integration branch to include the base tip

When the integration branch already exists, its head replaces baseTip without any ancestry check, so an integration branch left at B0 is used even if --base has advanced to B1. The resulting candidate is verified and marked from a tree that omits B1, and it can still be pushed when the remote is likewise behind. Before settling candidates, require the current base tip to be an ancestor of the integration head or incorporate the new base into the candidate.


for _, id := range in.Nodes {
c, ok := byNode[id]
if !ok {
return nil, refuse(contract.RefusalDispositionConflict, "node %s is not a ready accepted candidate of plan %s", id, in.Plan)
}
kept = append(kept, c)

P2 Badge Reject duplicate node selections before merging

If the repeatable CLI option is supplied as --node A --node A, this loop appends the same candidate twice. The first pass creates its merge commit; the second pass then creates another merge commit whose second parent is already an ancestor, producing redundant history and duplicate result entries and marks. Track selected IDs and refuse or deduplicate repeats before calling mergeCandidates.


`CREATE TABLE IF NOT EXISTS dag_acceptance_verifications (
acceptance_id TEXT PRIMARY KEY REFERENCES dag_acceptances (acceptance_id),

P2 Badge Declare the new text primary keys NOT NULL

SQLite rowid tables do not make a TEXT PRIMARY KEY implicitly non-null, and CHECK (id <> '') also permits NULL; the repository's preceding merge-train guard documents this exact SQLite behavior. Both new tables therefore accept unreachable rows with a NULL acceptance_id or batch_id, bypassing the intended identity and, for the verification table, the foreign key. Add NOT NULL or equivalent insert guards for both new primary keys.

ℹ️ 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".

@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-07T19:30:50.412005Z a5e3939 PR opened
ℹ️ 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.

…er candidate

A code-pinned artifact_verified edge is satisfied by a commit acceptance that
carries its verification record, as well as by a pull-request acceptance.
The batch freezes each candidate's criteria set on its intent row, and an
explicit request for a node that already integrated is refused as
disposition_conflict with the reason. Tests cover the edge, the judge's
reuse keys and the criteria reading through the production commit path.
…rify script

The commit path judges a verification record by its reuse keys, reads a
criteria change through revalidation, and launches the verify script through
its shebang line.
…e a dash remote

A candidate whose head the integration branch already contains is not merged
again by a later batch. A merge that fails for a host reason is an error, not
a conflict. A remote name that starts with a dash is refused before git runs.
The plugin version is re-recorded from the revised payload.
…it again

A candidate whose head the integration branch already contains is no longer
dropped. It is marked from its frozen row when the branch head is a head this
relay verified (recorded as a batch intent before the branch moves); otherwise
it is reported as contained_unverified and stays ready. A batch that merges
nothing moves no ref and runs no verifier. A merge host failure now carries
git's own message.
…integrate

The integration command prints the candidates the branch already contained,
with their marks, and the contained candidates left ready because no verified
head covers the branch. The commit-acceptance section documents the rule.
…ntainment at the read head

When the candidates left after a removal no longer merge cleanly, the batch
defers the conflicting candidate and retries it on the verified set instead of
aborting the whole batch. Containment is judged against the branch head the
batch read once, the same head its coverage check reads.
…pt currency and push and checkout addresses

A pull-request edge reads the verification table only when its acceptance has
no forge row, so a store without the table still reads its pull requests. A
verify command that moves HEAD fails the verification. A deferred candidate
that depends on a removed candidate is left out. A candidate whose receipt is
no longer the generation head is not a candidate. The push reads and writes
the same address (the remote's push URL). A linked worktree's git directory is
read from its .git file.
…es removed

The gitfile branch of the git directory helper ran git with the inherited
environment, so a poisoned GIT_DIR could name another repository. It now runs
with every GIT_ variable removed, as the other git reads of the target reader do.
…defer HTTP 5xx pushes

A batch integrates only the candidates accepted for its checkout. A retried
batch reports a mark it already recorded instead of inserting the stage row
again. A push that GitHub answers with a 5xx is an outage and is deferred. The
observation reader resolves a linked worktree's git directory; the lane
movement reader is restored to its original line.
…on the integration branch

A record whose tool value contradicts its own pin is refused by the one judge.
A commit-accepted node completes on the integration branch its batches moved,
and its integrated edges are judged there, not on the base ref the edge names.
A push answered with a 5xx status is deferred; a test pins the path where
ls-remote succeeds and the push is rejected.
…d and tree

The replay and revalidation check compares the verified head and tree the
verification row stores, not the bytes of the record file. A fresh record of
the same output is judged by the one judge and is replayed; the row keeps the
first evidence.
…arking contained candidates

A verified-head row records the head's tree, ci.yml digest and dependency
digests. A contained candidate is marked only while one of those rows still
matches the branch head. Otherwise the head is verified again without a merge,
and only its PASS covers the candidates. An all-contained batch runs the
verifier only in that case.
…concile a planned move

The compare-and-swap and the moved record now share one transaction that checks
the coordinator epoch, so an epoch claimed before the swap refuses the batch.
A planned intent the branch holds and no moved record covers is reconciled once
by the next run; one the branch does not hold is reported as abandoned. Retried
contained batches do not insert their mark twice, a batch does not take a
candidate accepted for another checkout, and the refusal names that case.
…letion target and the fenced move

The reuse paragraph states what the relay judges (tool values against their own
pins) and what it leaves to the writer (Go flags and environment). A covering
verification counts only while its keys hold. Commit-accepted nodes complete on the
integration branch the batch moved. A batch moves the branch in one fenced
transaction and reconciles a planned move after a crash; an HTTP 5xx push is an
outage.
… the relay's records

- Each merged candidate is proved current again inside the fenced transaction
  that moves the integration branch, by the predicate candidate selection uses.
  A candidate that changed refuses the batch with merge_candidate_moved and the
  branch does not move.
- A stored verification of a head also records os and arch and is reused only
  on the same platform.
- A commit-accepted node completes on the integration refs that a batch with a
  moved record has moved; planned, abandoned and failed intents add no target.
- A candidate the bisection leaves out is reported as not_proven_failing unless
  it fails when verified alone.
- dag-integrate-push reads the relay store and pushes only a tip the relay moved
  the integration branch onto; any other tip is refused before the remote is read.
…mmit path

- contracttest replays dag-integrate, dag-integrate-push, dag-integration-observe
  and dag-accept with --commit through the built crw (usage and absent checkout
  refusals). The goldens were written with CRW_GOLDEN=update.
- docs/relay/dag-scheduler.md states the record judgement boundary, the reading of
  criterion c2, the currency check at the swap, the bisection limitation, the
  completion refs and the push rule.
- merge-readiness.md has the same push and bisection rules for the integration
  section.
- plugin.json is re-recorded for the changed reference.
The guard that proves a frozen candidate current inside the fenced transaction
compared the acceptance, event, revision, generation and head but not the
criteria set digest. A revalidation during verification kept those identities,
so the branch could move on a verification of an older criteria set (finding d2).
Candidates also carry the acceptance-time criteria digest, which the covering
check uses (finding d1).
The completion refs of an acceptance were collected from every batch that
carried an intent for it, including attempts that failed before their move, and
were then matched against a branch name moved by some other batch. A failed
attempt on one ref could therefore make an unrelated branch a mandatory target
(finding d4). The refs now come only from the acceptance's own batches that have
a ref_moved row.
A ref moved by the reconciliation after a crash was recorded as a ref_moved stage
row without a batch row, and the push check read only batch rows, so the
recovered head could never be pushed (finding d3). The check now reads one source
for both paths: a ref_moved row naming the tip, with the planned intent of its
batch carrying the verified head and a verification digest for the same ref.
…nged

A stored verification covered a contained candidate without naming its criteria
set, so a revalidated candidate was published on the verification of its earlier
criteria set (finding d1). The covering check now binds each candidate to the
criteria set its verification named, and an unnamed candidate only while its set
is still the accepted one. The batch verifies its head again, with no merge, when
a candidate is not covered. The swap also refuses a candidate revalidated during
the verification (finding d2 test).

Red tests added in integration_findings_test.go: a contained candidate whose
criteria changed is verified again, and an unchanged one is not; a candidate
revalidated during the verification refuses the swap; a reconciled move pushes;
an attempt that moved nothing on one ref adds no completion target on another.
The red tests were written with the fixes in the same change, so their failing
output before the fix was not captured separately.
The commit-path section said that every batch runs the verify command afresh,
which the covering verification does not do. It now says that a batch reuses only
its own covering PASS while the tree, the ci.yml and dependency digests, the
platform and the candidate's criteria set still match, and runs the verifier
again on the head otherwise.
@thisisjun786 thisisjun786 added the crw-lane Merge lane holds this pull request: CI runs every test leg (temporary light PR CI mode, CRW-790) label Oct 8, 2026
@thisisjun786 thisisjun786 added crw-lane Merge lane holds this pull request: CI runs every test leg (temporary light PR CI mode, CRW-790) and removed crw-lane Merge lane holds this pull request: CI runs every test leg (temporary light PR CI mode, CRW-790) labels Oct 8, 2026
@thisisjun786
thisisjun786 merged commit 5459d59 into dev Oct 8, 2026
34 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crw-lane Merge lane holds this pull request: CI runs every test leg (temporary light PR CI mode, CRW-790)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant