Skip to content

Commit cf62a6b

Browse files
James3014claude
andauthored
fix(#511): raise completion generation cap to 5 to match Nexus-new loop (#512)
* test(#511): pin completion generation budget to Nexus-new loop maximum (5) RED on nexus/connector-host-v1@528dc50: missing export and the advertised maxGenerations maximum is 3, not 5. Refs James3014/Nexus-new#1384 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(#511): raise completion generation cap to 5 to match Nexus-new loop One host constant MAX_COMPLETION_INTEGRATION_GENERATIONS=5 mirrors Nexus-new MAX_INTEGRATION_GENERATIONS and drives the parser default and max, both port generation guards, the proxy schema .max() and its description, and the bridge fallback. revalidate_affected_dimension is unchanged and still fails closed for every dimension. Refs James3014/Nexus-new#1384 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(#511): completion generation cap is 5 Refs James3014/Nexus-new#1384 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: James3014 <185240413+James3014@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 528dc50 commit cf62a6b

6 files changed

Lines changed: 65 additions & 9 deletions

‎docs/security.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ It never exposes raw DevSpace workspace, edit, write, or shell tools, and
136136
`run_github_completion_loop()` caller through a JSON-lines host bridge. The
137137
Python caller cannot merge `main` directly: its CAS merge port is routed back
138138
into the existing `git_merge_pull_request` core. Integration generations are
139-
same-repository, non-force PR-branch updates and are capped at 3 generations
139+
same-repository, non-force PR-branch updates and are capped at 5 generations
140140
and 2700 seconds. Any affected semantic/authority/test/transport dimension
141141
fails closed for fresh Candidate acceptance.
142142

‎scripts/github_completion_bridge.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def reconcile_post_merge(
199199
initial_evidence=evidence,
200200
request=request,
201201
port=HostPort(),
202-
max_generations=int(start.get("max_generations", 3)),
202+
max_generations=int(start.get("max_generations", 5)),
203203
max_elapsed_seconds=float(start.get("max_elapsed_seconds", 2700.0)),
204204
git_root=nexus_root,
205205
)

‎src/github-completion-host.test.ts‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import assert from "node:assert/strict";
22
import { describe, it } from "node:test";
33
import {
4+
MAX_COMPLETION_INTEGRATION_GENERATIONS,
45
isTransientRequiredChecksDecision,
6+
parseCompletionGeneration,
7+
parseGitHubCompletionToolInput,
58
} from "./github-completion-host.js";
69

710
const missingReason = "no check run or commit status matches the required context for the expected head SHA";
@@ -48,3 +51,39 @@ describe("isTransientRequiredChecksDecision", () => {
4851
);
4952
});
5053
});
54+
55+
describe("completion integration generation cap (#1384)", () => {
56+
const baseInput = {
57+
initialEvidence: {},
58+
standingGrantRequest: {},
59+
mergeMethod: "merge",
60+
ownerConfirmation: true,
61+
};
62+
63+
it("matches the Nexus-new MAX_INTEGRATION_GENERATIONS=5 loop budget", () => {
64+
assert.equal(MAX_COMPLETION_INTEGRATION_GENERATIONS, 5);
65+
});
66+
67+
it("defaults maxGenerations to 5 and accepts 4 and 5", () => {
68+
assert.equal(parseGitHubCompletionToolInput({ ...baseInput }).maxGenerations, 5);
69+
assert.equal(parseGitHubCompletionToolInput({ ...baseInput, maxGenerations: 4 }).maxGenerations, 4);
70+
assert.equal(parseGitHubCompletionToolInput({ ...baseInput, maxGenerations: 5 }).maxGenerations, 5);
71+
});
72+
73+
it("rejects maxGenerations above 5 or below 1", () => {
74+
for (const value of [0, 6, 2.5, "5"]) {
75+
assert.throws(
76+
() => parseGitHubCompletionToolInput({ ...baseInput, maxGenerations: value }),
77+
/maxGenerations_INVALID/,
78+
);
79+
}
80+
});
81+
82+
it("port generation guard admits I4 and I5 and rejects I6", () => {
83+
assert.equal(parseCompletionGeneration(4), 4);
84+
assert.equal(parseCompletionGeneration(5), 5);
85+
for (const value of [0, 6, null, "4"]) {
86+
assert.throws(() => parseCompletionGeneration(value), /generation_INVALID/);
87+
}
88+
});
89+
});

‎src/github-completion-host.ts‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ const FULL_SHA = /^[0-9a-f]{40}$/;
2020
const SAFE_PATH = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))(?!.*\\)(?!.*[\u0000-\u001f\u007f]).+$/;
2121
const REPOSITORY = "James3014/Nexus-new";
2222
const DEFAULT_BRANCH = "main";
23+
/**
24+
* Mirrors Nexus-new `MAX_INTEGRATION_GENERATIONS` /
25+
* `DEFAULT_MAX_INTEGRATION_GENERATIONS` in
26+
* `nexus/orchestrator/github_completion_loop.py` (#1384). The host must not
27+
* advertise or enforce a tighter budget than the loop it drives.
28+
*/
29+
export const MAX_COMPLETION_INTEGRATION_GENERATIONS = 5;
2330
const COMPLETION_TARGET = {
2431
remoteName: "origin",
2532
repository: REPOSITORY,
@@ -75,6 +82,10 @@ function positiveInt(value: unknown, name: string, maximum: number): number {
7582
return value;
7683
}
7784

85+
export function parseCompletionGeneration(value: unknown): number {
86+
return positiveInt(value, "generation", MAX_COMPLETION_INTEGRATION_GENERATIONS);
87+
}
88+
7889
export function isTransientRequiredChecksDecision(
7990
decision: Awaited<ReturnType<typeof evaluateRequiredChecks>>,
8091
): boolean {
@@ -111,8 +122,8 @@ export function parseGitHubCompletionToolInput(raw: Record<string, unknown>): Gi
111122
throw new Error("ownerConfirmation must be exactly true");
112123
}
113124
const maxGenerations = raw.maxGenerations === undefined
114-
? 3
115-
: positiveInt(raw.maxGenerations, "maxGenerations", 3);
125+
? MAX_COMPLETION_INTEGRATION_GENERATIONS
126+
: positiveInt(raw.maxGenerations, "maxGenerations", MAX_COMPLETION_INTEGRATION_GENERATIONS);
116127
const maxElapsedSeconds = raw.maxElapsedSeconds === undefined
117128
? 2700
118129
: positiveInt(raw.maxElapsedSeconds, "maxElapsedSeconds", 2700);
@@ -272,7 +283,7 @@ class CompletionHostPort {
272283
const baseSha = normalizeSha(params.base_sha, "base_sha");
273284
const baseTreeSha = normalizeSha(params.base_tree_sha, "base_tree_sha");
274285
const expectedHead = normalizeSha(params.expected_pr_head_sha, "expected_pr_head_sha");
275-
const generation = positiveInt(params.generation, "generation", 3);
286+
const generation = parseCompletionGeneration(params.generation);
276287
const target = await this.target();
277288
const pr = await this.transport.getPullRequest(REPOSITORY, this.prNumber);
278289
if (pr.state !== "OPEN" || pr.isDraft) {
@@ -368,7 +379,7 @@ class CompletionHostPort {
368379

369380
private async readRequiredChecks(params: Record<string, unknown>): Promise<Record<string, unknown>> {
370381
const headSha = normalizeSha(params.head_sha, "checks_head_sha");
371-
const generation = positiveInt(params.generation, "generation", 3);
382+
const generation = parseCompletionGeneration(params.generation);
372383
const timeoutSeconds = typeof params.timeout_seconds === "number"
373384
? Math.max(0, Math.min(params.timeout_seconds, 2700))
374385
: 2700;

‎src/nexus-gateway-proxy.test.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,12 @@ try {
396396
for (const forbiddenKey of ["workspaceId", "cwd", "repo", "repository", "remote", "branch", "execution_lane", "executionLane", "internal", "completion"]) {
397397
assert.ok(!properties.includes(forbiddenKey), `public schema must NOT contain ${forbiddenKey}`);
398398
}
399+
// #1384: the completion action's generation budget matches the Nexus-new loop maximum (5).
400+
const completionTool = schemaPayload.result.tools.find((tool: { name: string }) => tool.name === "github_complete_pull_request");
401+
assert.ok(completionTool, "github_complete_pull_request must be advertised");
402+
assert.equal(completionTool.inputSchema.properties?.maxGenerations?.maximum, 5);
403+
assert.equal(completionTool.inputSchema.properties?.maxGenerations?.minimum, 1);
404+
assert.match(completionTool.description, /at most 5 non-force PR integration generations/);
399405
await schemaBoundary.close();
400406
}
401407

‎src/nexus-gateway-proxy.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
type MergeEffectGuard,
2020
type TrustedIntegrationTarget,
2121
} from "./git-pr-merge.js";
22-
import { runGitHubCompletionTool } from "./github-completion-host.js";
22+
import { MAX_COMPLETION_INTEGRATION_GENERATIONS, runGitHubCompletionTool } from "./github-completion-host.js";
2323

2424
type JsonSchema = {
2525
type?: string | string[];
@@ -620,13 +620,13 @@ export async function createNexusGatewayProxyServer(
620620
name,
621621
{
622622
description:
623-
"Run Nexus run_github_completion_loop() against the server-bound James3014/Nexus-new checkout. Ordinary main drift may create at most 3 non-force PR integration generations and wait at most 2700 seconds total. Affected semantic/authority/test/transport dimensions fail closed for fresh acceptance. Protected main integration delegates to the existing git_merge_pull_request merge core; this action does not implement a second merge authority.",
623+
`Run Nexus run_github_completion_loop() against the server-bound James3014/Nexus-new checkout. Ordinary main drift may create at most ${MAX_COMPLETION_INTEGRATION_GENERATIONS} non-force PR integration generations and wait at most 2700 seconds total. Affected semantic/authority/test/transport dimensions fail closed for fresh acceptance. Protected main integration delegates to the existing git_merge_pull_request merge core; this action does not implement a second merge authority.`,
624624
inputSchema: {
625625
initialEvidence: z.record(z.string(), z.unknown()).describe("Exact accepted nexus.github_orchestration_evidence payload."),
626626
standingGrantRequest: z.record(z.string(), z.unknown()).describe("Exact nexus.standing_grant_request.v1 payload bound to the durable standing grant."),
627627
mergeMethod: z.enum(["merge", "squash", "rebase"]).default("merge"),
628628
ownerConfirmation: z.boolean().describe("Must be exactly true for the host protected merge authority."),
629-
maxGenerations: z.number().int().min(1).max(3).optional(),
629+
maxGenerations: z.number().int().min(1).max(MAX_COMPLETION_INTEGRATION_GENERATIONS).optional(),
630630
maxElapsedSeconds: z.number().int().min(1).max(2700).optional(),
631631
},
632632
annotations: {

0 commit comments

Comments
 (0)