Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dashboard/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ <h2>Azure DevOps intake (optional)</h2>
<p class="guidance">Video evidence records only the application journey and remains local under repository-owned Playwright artifact directories. Screenshots are still required for lightweight review evidence.</p>
<p class="guidance">Playwright validation only skips source diagnosis, implementation, review, and pull-request work. It performs required profile setup, reuses browser authentication, starts required applications, and runs the focused browser journey.</p>
</details>
<label><input type="checkbox" name="routineFileAccess"> Allow routine source access and checks for this job</label>
<label><input type="checkbox" name="routineFileAccess" checked> Allow routine source access and checks for this job</label>
<p class="guidance consent-summary">Secrets, external access and publication still need review.</p>
<details class="help-note">
<summary>Access scope</summary>
Expand Down
8 changes: 8 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,14 @@ approval, warning and sandbox-escalation checks.

Eligible native requests also offer **Allow repository-file reads for this job**
or **Allow this test command for this job**, each with a separate confirmation.
Dashboard intake enables **Allow routine source access and checks for this job**
by default. Submitting a fix-and-validate job grants non-sensitive source/test
editing and dashboard-owned validation gates for that job and checkout.
Validation-only modes receive read access, never source/test editing.
Uncheck the option before submission to retain individual approvals. API callers
must still explicitly send `routineFileAccess: true`; omitted or false values
grant no routine access. This default does not enable Allow all tool commands,
override earlier denials, or change existing jobs.
Separately, a stopped native job's **Repository file trust** editor can review
source/test reads or source/test reads and edits for that exact job and canonical
checkout, including resumes and dashboard restart. This setting is not inherited
Expand Down
35 changes: 35 additions & 0 deletions e2e/dashboard-readiness.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,40 @@ test("native Copilot prompts allow once, deny and answer choices in the same liv
}
});

test("Copilot intake defaults to source/test editing and trusted validation gates", async ({ page }) => {
const directory = mkdtempSync(join(tmpdir(), "fixlab-default-file-trust-ui-"));
mkdirSync(join(directory, ".github", "fixlab"), { recursive: true });
writeFileSync(join(directory, ".github", "fixlab", "repository-profile.json"),
readFileSync(join(repository, ".github", "fixlab", "repository-profile.json")));
let finish;
let execution;
const instance = createDashboardServer({
repository: directory, packageRoot, runtime: "copilot", browserOpener() {},
executor(input) {
execution = input;
return {
completion: new Promise((resolve) => { finish = resolve; }),
terminate() { finish({ code: 130 }); }, hasStopped: () => true
};
}
});
try {
const address = await instance.listen({ port: await availablePort() });
await openWorkflowControls(page, address.url);
await expect(page.locator('[name="routineFileAccess"]')).toBeChecked();
await page.locator("#request").fill("Synthetic default backend test editing.");
await page.locator("#start-button").click();
await expect(page.locator("#job-file-access-mode")).toHaveValue("source-read-write");
await expect(page.locator("#job-file-access-gates")).toBeChecked();
expect(execution.fileAccess.mode).toBe("source-read-write");
expect(execution.fileAccess.allowValidationGates).toBe(true);
expect(execution.toolApprovalMode).toBe("ask");
} finally {
await instance.close();
rmSync(directory, { recursive: true, force: true });
}
});

test("Copilot file trust requires a stopped exact job and a separate confirmation without resuming", async ({ page }) => {
const directory = mkdtempSync(join(tmpdir(), "fixlab-file-trust-ui-"));
mkdirSync(join(directory, ".github", "fixlab"), { recursive: true });
Expand All @@ -773,6 +807,7 @@ test("Copilot file trust requires a stopped exact job and a separate confirmatio
try {
const address = await instance.listen({ port: await availablePort() });
await openWorkflowControls(page, address.url);
await page.locator('[name="routineFileAccess"]').uncheck();
await page.locator("#request").fill("Synthetic reviewed source file trust.");
await page.locator("#start-button").click();
await expect(page.locator("#job-file-access-mode")).toHaveValue("manual");
Expand Down
16 changes: 14 additions & 2 deletions test/dashboard-server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2316,24 +2316,36 @@ test("reviewed file trust is exact-job bound, persists on resume/restart and is
await new Promise((resolve) => setTimeout(resolve, 50));
const fresh = await jsonRequest(running.url, "/api/jobs", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request: "A different untrusted job.", mode: "validate-only", fileAccess: expected })
body: JSON.stringify({ request: "A different untrusted job.", mode: "validate-only",
routineFileAccess: false, fileAccess: expected })
});
assert.equal(fresh.response.status, 202, fresh.body.error);
const queued = fresh.body.queue.at(-1);
assert.notEqual(queued.id, created.body.job.id);
assert.equal(queued.fileAccess, null);
assert.equal(launches.length, 2, "creating a queued job does not inherit or execute trusted access");
const intakeHtml = await (await fetch(running.url)).text();
const routineAccessInput = intakeHtml.match(/<input\b[^>]*name="routineFileAccess"[^>]*>/)?.[0];
assert.ok(routineAccessInput, "dashboard intake must expose the routine access choice");
const routineFileAccess = /\bchecked\b/.test(routineAccessInput);
const submission = await jsonRequest(running.url, "/api/jobs", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request: "Explicit routine source consent.", mode: "fix-and-validate",
routineFileAccess: true })
routineFileAccess })
});
assert.equal(submission.response.status, 202, submission.body.error);
const consented = submission.body.queue.at(-1);
assert.equal(consented.fileAccess.mode, "source-read-write");
assert.equal(consented.fileAccess.jobId, consented.id);
assert.equal(consented.fileAccess.repository, realpathSync.native(repository));
assert.equal(consented.fileAccess.allowValidationGates, true);
const validation = await jsonRequest(running.url, "/api/jobs", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request: "Default access in read-only validation.", mode: "validate-only",
routineFileAccess })
});
assert.equal(validation.response.status, 202, validation.body.error);
assert.equal(validation.body.queue.at(-1).fileAccess.mode, "source-read");
} finally {
await running.dashboard.close();
rmSync(repository, { recursive: true, force: true });
Expand Down
Loading