chore: merge develop into hare/develop

This commit is contained in:
2026-09-24 01:01:35 +09:00
178 changed files with 61787 additions and 7023 deletions
+99 -4
View File
@@ -1,11 +1,18 @@
/// <reference lib="deno.ns" />
import { assert, assertEquals } from "jsr:@std/assert";
import {
assert,
assertEquals,
assertRejects,
assertThrows,
} from "jsr:@std/assert";
import {
commitConfigTree,
ConfigSourceApiError,
fetchConfigEntry,
fetchConfigRevision,
fetchConfigTree,
parseWorkspaceConfigTreeResponse,
} from "../../src/lib/workspace/config-source/api.ts";
function response(body: unknown, status = 200): Response {
@@ -15,11 +22,43 @@ function response(body: unknown, status = 200): Response {
});
}
const entry = {
path: "profiles/main.dcdl",
content_type: "decodal",
content: "profile = {}",
content_digest: "sha256:entry",
};
const snapshot = {
revision: 7,
digest: "sha256:tree",
entries: { "profiles/main.dcdl": entry },
};
const tree = {
snapshot,
contract: {
contract_version: 1,
decodal_version: "0.4.0",
schema_version: 1,
entrypoints: ["main.dcdl"],
import_policy_version: 1,
schema_bundle: {
contributions: [],
source: "builtin",
fingerprint: "sha256:schema",
},
fingerprint: "sha256:toolchain",
},
projection_digest: "sha256:projection",
};
Deno.test("config source API commits directly through the workspace scope", async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
const fetcher = ((input: string | URL | Request, init?: RequestInit) => {
calls.push({ url: String(input), init });
return Promise.resolve(response({ ok: true }));
const url = String(input);
calls.push({ url, init });
if (url.includes("/entries/")) return Promise.resolve(response(entry));
if (url.includes("/revisions/")) return Promise.resolve(response(snapshot));
return Promise.resolve(response(tree));
}) as typeof fetch;
await fetchConfigTree("w/one", fetcher);
@@ -44,7 +83,63 @@ Deno.test("config source API commits directly through the workspace scope", asyn
);
});
Deno.test("config source API surfaces failed evaluation instead of treating it as a successful commit", async () => {
Deno.test("config source API rejects unknown response fields", async () => {
const fetcher =
(() =>
Promise.resolve(response({ ...tree, unexpected: true }))) as typeof fetch;
await assertRejects(
() => fetchConfigTree("w", fetcher),
ConfigSourceApiError,
"invalid response",
);
});
Deno.test("config source API rejects mismatched entry map paths", async () => {
const invalid = {
...tree,
snapshot: {
...snapshot,
entries: { "profiles/other.dcdl": entry },
},
};
const fetcher = (() => Promise.resolve(response(invalid))) as typeof fetch;
await assertRejects(
() => fetchConfigTree("w", fetcher),
ConfigSourceApiError,
"invalid response",
);
});
Deno.test("config source API rejects unsafe revisions and oversized entry content", () => {
assertThrows(
() =>
parseWorkspaceConfigTreeResponse({
...tree,
snapshot: { ...snapshot, revision: Number.MAX_SAFE_INTEGER + 1 },
}),
ConfigSourceApiError,
"invalid response",
);
assertThrows(
() =>
parseWorkspaceConfigTreeResponse({
...tree,
snapshot: {
...snapshot,
entries: {
"profiles/main.dcdl": {
...entry,
content: "x".repeat(256 * 1024 + 1),
},
},
},
}),
ConfigSourceApiError,
"invalid response",
);
});
Deno.test("config source API surfaces bounded failed evaluation details", async () => {
const fetcher = (() =>
Promise.resolve(
new Response("structured diagnostics", { status: 422 }),
+95 -57
View File
@@ -14,11 +14,88 @@ function assertEquals(actual: unknown, expected: unknown): void {
}
}
function event(
kind: string,
fields: Record<string, unknown>,
): MergeRequestThreadEvent {
return { kind, sequence: 1, at: "2026-09-01T00:00:00Z", ...fields };
const worker = { runtime_id: "runtime", worker_id: "worker" };
const createdAt = "2026-09-01T00:00:00Z";
type ReviewRequested = Extract<
MergeRequestThreadEvent,
{ kind: "review_requested" }
>;
type Review = Extract<MergeRequestThreadEvent, { kind: "review" }>;
type ReviewRevoked = Extract<
MergeRequestThreadEvent,
{ kind: "review_revoked" }
>;
type ReviewCancelled = Extract<
MergeRequestThreadEvent,
{ kind: "review_cancelled" }
>;
function reviewRequested(
eventId: string,
subjectRef: string,
): ReviewRequested {
return {
kind: "review_requested",
event_id: eventId,
sequence: 1,
subject_ref: subjectRef,
requested_by: worker,
reviewer: worker,
created_at: createdAt,
};
}
function review(
eventId: string,
requestEventId: string,
subjectRef: string,
): Review {
return {
kind: "review",
event_id: eventId,
sequence: 1,
request_event_id: requestEventId,
subject_ref: subjectRef,
decision: "approve",
body: "approved",
findings: [],
reviewer: worker,
created_at: createdAt,
};
}
function reviewRevoked(
eventId: string,
reviewEventId: string,
subjectRef: string,
): ReviewRevoked {
return {
kind: "review_revoked",
event_id: eventId,
sequence: 1,
review_event_id: reviewEventId,
subject_ref: subjectRef,
reason: "superseded",
revoked_by: worker,
created_at: createdAt,
};
}
function reviewCancelled(
eventId: string,
requestEventId: string,
subjectRef: string,
): ReviewCancelled {
return {
kind: "review_cancelled",
event_id: eventId,
sequence: 1,
request_event_id: requestEventId,
subject_ref: subjectRef,
reason: "cancelled",
created_at: createdAt,
};
}
function detail(thread: MergeRequestThreadEvent[]): MergeRequestDetail {
@@ -30,22 +107,17 @@ function detail(thread: MergeRequestThreadEvent[]): MergeRequestDetail {
selector_from: "work/ticket",
selector_to: "develop",
state: "open",
opened_by: {
runtime_id: "runtime",
worker_id: "worker",
assignment_id: "assignment",
},
created_at: "2026-09-01T00:00:00Z",
updated_at: "2026-09-01T00:00:00Z",
created_at: createdAt,
updated_at: createdAt,
source: {
status: "known",
ref: "source-2",
observed_at: "2026-09-01T00:00:00Z",
observed_at: createdAt,
},
target: {
status: "known",
ref: "target-2",
observed_at: "2026-09-01T00:00:00Z",
observed_at: createdAt,
},
linked_tickets: [{ ticket_id: "T-1", key: "T-1" }],
thread,
@@ -54,20 +126,9 @@ function detail(thread: MergeRequestThreadEvent[]): MergeRequestDetail {
Deno.test("revoked review requires a fresh review instead of appearing pending", () => {
const mergeRequest = detail([
event("review_requested", {
event_id: "request-1",
subject_ref: "source-2",
}),
event("review", {
event_id: "review-1",
request_event_id: "request-1",
subject_ref: "source-2",
decision: "approve",
}),
event("review_revoked", {
event_id: "revoke-1",
review_event_id: "review-1",
}),
reviewRequested("request-1", "source-2"),
review("review-1", "request-1", "source-2"),
reviewRevoked("revoke-1", "review-1", "source-2"),
]);
assertEquals(
@@ -78,10 +139,7 @@ Deno.test("revoked review requires a fresh review instead of appearing pending",
Deno.test("unresolved review request for the current source is pending", () => {
const mergeRequest = detail([
event("review_requested", {
event_id: "request-2",
subject_ref: "source-2",
}),
reviewRequested("request-2", "source-2"),
]);
assertEquals(
@@ -92,16 +150,8 @@ Deno.test("unresolved review request for the current source is pending", () => {
Deno.test("completed or cancelled request is not projected as pending", () => {
const approved = detail([
event("review_requested", {
event_id: "request-3",
subject_ref: "source-2",
}),
event("review", {
event_id: "review-3",
request_event_id: "request-3",
subject_ref: "source-2",
decision: "approve",
}),
reviewRequested("request-3", "source-2"),
review("review-3", "request-3", "source-2"),
]);
assertEquals(
sourceReviewFreshness(approved),
@@ -109,15 +159,8 @@ Deno.test("completed or cancelled request is not projected as pending", () => {
);
const cancelled = detail([
event("review_requested", {
event_id: "request-4",
subject_ref: "source-2",
}),
event("review_cancelled", {
event_id: "cancel-4",
request_event_id: "request-4",
subject_ref: "source-2",
}),
reviewRequested("request-4", "source-2"),
reviewCancelled("cancel-4", "request-4", "source-2"),
]);
assertEquals(
sourceReviewFreshness(cancelled),
@@ -127,12 +170,7 @@ Deno.test("completed or cancelled request is not projected as pending", () => {
Deno.test("source movement explains the exact stale and current refs", () => {
const mergeRequest = detail([
event("review", {
event_id: "review-old",
request_event_id: "request-old",
subject_ref: "source-1",
decision: "approve",
}),
review("review-old", "request-old", "source-1"),
]);
assertEquals(
@@ -1,5 +1,5 @@
import { assert, assertEquals } from "jsr:@std/assert";
import type { WorkspaceRuntimeResource } from "../src/lib/generated/legacy-server-api.ts";
import type { WorkspaceRuntimeResource } from "../src/lib/generated/runtime-api.ts";
import { parseRepositorySshConnectionProbeResponse } from "../src/lib/workspace/api/workspace-model.ts";
import {
changeRepositorySshProbeRuntime,
@@ -55,7 +55,12 @@ Deno.test("sidebar Stop uses the workspace-scoped Worker lifecycle endpoint", as
const requests: Array<{ url: string; init?: RequestInit }> = [];
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
requests.push({ url: input.toString(), init });
return jsonResponse({ state: "accepted", diagnostics: [] });
return jsonResponse({
state: "accepted",
runtime_id: worker.runtime_id,
worker_id: worker.worker_id,
diagnostics: [],
});
}) as typeof fetch;
await stopSidebarWorker("team space", worker, fetchFn);
@@ -76,7 +81,13 @@ Deno.test("sidebar Stop rejects non-accepted lifecycle responses", async () => {
Promise.resolve(
jsonResponse({
state: "rejected",
diagnostics: [{ severity: "error", message: "Worker cannot stop" }],
runtime_id: worker.runtime_id,
worker_id: worker.worker_id,
diagnostics: [{
code: "cannot_stop",
severity: "error",
message: "Worker cannot stop",
}],
}),
)) as typeof fetch;
@@ -92,25 +103,48 @@ Deno.test("sidebar Delete executes the authoritative runtime cleanup plan", asyn
requests.push({ url: input.toString(), init });
if (requests.length === 1) {
return jsonResponse({
revision: 7,
workspace_id: "team",
runtime_id: worker.runtime_id,
generated_at: "2026-01-01T00:00:00Z",
revision: "7",
digest: "digest-7",
candidates: [],
workers: [{
target_id: "worker-target",
action: "worker_delete",
worker_id: worker.worker_id,
runtime_id: worker.runtime_id,
runtime_worker_id: worker.worker_id,
reason: "stopped worker",
blocking_reason: null,
pinned: false,
retention_state: "stopped",
linked_workdir_ids: [],
running_linked: false,
}],
workdirs: [],
diagnostics: [],
});
}
return jsonResponse({
workspace_id: "team",
runtime_id: worker.runtime_id,
executed_at: "2026-01-01T00:00:01Z",
results: [{
target_id: "worker-target",
action: "worker_delete",
status: "deleted",
message: null,
message: "",
}],
plan_after: {
workspace_id: "team",
runtime_id: worker.runtime_id,
generated_at: "2026-01-01T00:00:01Z",
revision: "8",
digest: "digest-8",
workers: [],
workdirs: [],
diagnostics: [],
},
diagnostics: [],
});
}) as typeof fetch;
@@ -123,7 +157,7 @@ Deno.test("sidebar Delete executes the authoritative runtime cleanup plan", asyn
]);
assertEquals(requests[1]?.init?.method, "POST");
assertEquals(JSON.parse(String(requests[1]?.init?.body)), {
expected_plan_revision: 7,
expected_plan_revision: "7",
expected_plan_digest: "digest-7",
worker_target_ids: ["worker-target"],
workdir_target_ids: [],
@@ -135,14 +169,23 @@ Deno.test("sidebar Delete reports cleanup-plan blocking reasons", async () => {
const fetchFn = (() =>
Promise.resolve(
jsonResponse({
revision: 8,
workspace_id: "team",
runtime_id: worker.runtime_id,
generated_at: "2026-01-01T00:00:00Z",
revision: "8",
digest: "digest-8",
candidates: [],
workers: [{
target_id: "worker-target",
action: "worker_delete",
worker_id: worker.worker_id,
runtime_id: worker.runtime_id,
runtime_worker_id: worker.worker_id,
reason: "stopped worker",
blocking_reason: "Worker is pinned",
pinned: true,
retention_state: "pinned",
linked_workdir_ids: [],
running_linked: false,
}],
workdirs: [],
diagnostics: [],