Video evidence records only the application journey and remains local under repository-owned Playwright artifact directories. Screenshots are still required for lightweight review evidence.
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.
-
+
Secrets, external access and publication still need review.
Access scope
diff --git a/docs/commands.md b/docs/commands.md
index 266c0db..0df2f4c 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -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
diff --git a/e2e/dashboard-readiness.spec.js b/e2e/dashboard-readiness.spec.js
index 665c8e0..924c479 100644
--- a/e2e/dashboard-readiness.spec.js
+++ b/e2e/dashboard-readiness.spec.js
@@ -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 });
@@ -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");
diff --git a/test/dashboard-server.test.js b/test/dashboard-server.test.js
index d771403..471706b 100644
--- a/test/dashboard-server.test.js
+++ b/test/dashboard-server.test.js
@@ -2316,17 +2316,22 @@ 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(/]*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);
@@ -2334,6 +2339,13 @@ test("reviewed file trust is exact-job bound, persists on resume/restart and is
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 });