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
9 changes: 6 additions & 3 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
# 🧪 CI · Test
# ----------------------------------------------------------------------------
# Purpose : Run unit + Playwright e2e tests across Linux & Windows
# Trigger : Push to `main`/`dev`, PRs targeting `main`, manual dispatch
# Trigger : Push to `main`/`dev`, PRs targeting `main` and `dev`, manual dispatch
# Jobs : unit — `bun turbo test` + config_assistant Go tests on linux
# only (windows dropped — see
# unit-tests matrix comment; free windows-latest runners
# can't fit the suite in a reasonable CI budget)
# e2e — Playwright chromium on linux + windows (matrix)
# Gate : Required status check on the `main` ruleset — full suite gates
# dev → main PRs. Pushes to `dev` also get a full run (dev is the
# integration/testing branch), but feat/fix → dev PRs are gated by
# typecheck only (see ci-typecheck.yml) to keep CI budget sane.
# integration/testing branch). feat/fix → dev PRs run the unit
# matrix as a required check (#370: the Typecheck-only gate let an
# assertion-level regression merge and keep dev red for 75min);
# E2E stays push-on-dev + dev→main only to keep CI budget sane.
# Notes : `cancel-in-progress: false` — every main/dev push gets a full run
# No trigger on feat/* or fix/* (frequent changes).
# ============================================================================
Expand All @@ -26,6 +28,7 @@ on:
pull_request:
branches:
- main
- dev
workflow_dispatch:

concurrency:
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/specgit-accept.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ jobs:
specgit-acceptance:
name: SpecGit Acceptance
runs-on: ubuntu-latest
timeout-minutes: 15
# Must exceed the slowest required sibling (Unit Tests (linux) runs
# ~28min on PRs): the verdict waits for every policy check to reach a
# terminal state before evaluating.
timeout-minutes: 45
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -68,7 +71,9 @@ jobs:
const retried = [...byName.keys()].find((k) => k.startsWith(name + ' ('));
return retried !== undefined && terminal.has(byName.get(retried));
};
const deadline = Date.now() + 15 * 60 * 1000;
// Must outlast the slowest required sibling (Unit Tests (linux)
// runs ~28min on PRs); the job timeout above bounds this too.
const deadline = Date.now() + 40 * 60 * 1000;
while (Date.now() < deadline) {
const res = await fetch(url, { headers });
if (!res.ok) throw new Error('check-runs API ' + res.status);
Expand Down
8 changes: 4 additions & 4 deletions .specgit.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version: 1
delivery: issue368
delivery: issue370
context:
kind: branch
branch: feat/368-issue368
branch: feat/370-issue370
issues:
- 368
pr: 369
- 370
pr: 371
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
import { InvalidRequestError, ConflictError, notFound } from "../errors"
import { Dag } from "@/dag/dag"
import { DagValidation } from "@/dag/validation"
import { WorkflowAuthoring } from "@/dag/authoring"
import { DagEnvironmentCatalogs } from "@/dag/environment-catalogs"
import { createAdmissionRecord } from "@/dag/admission"
Expand Down Expand Up @@ -176,8 +177,18 @@ export const dagHandlers = HttpApiBuilder.group(InstanceHttpApi, "dag", (handler
profile: "environment",
environment: { directory: session.directory, parent: session.model ?? undefined },
})
if (result.prepared?.action !== "start" || result.errors.length > 0) {
const diagnostics = result.errors
// Parity with the workflow tool's start action: model resolution is
// advisory over HTTP — the tool asks a question (no model configured
// yet), an API caller has no such interaction; the spawn path fails
// loudly (failWithoutFiber) at execution time if a model never
// resolves. Every other diagnostic class stays blocking, and a graph
// that did not COMPILE (prepared === undefined) is always blocking
// regardless of diagnostic classes.
const blocking = result.errors.filter(
(diagnostic) => diagnostic.code !== DagValidation.DIAGNOSTIC_CODES.modelUnavailable,
)
if (result.prepared?.action !== "start" || blocking.length > 0) {
const diagnostics = blocking
.map((diagnostic) => `- [${diagnostic.code}] ${diagnostic.path}: ${diagnostic.message}${diagnostic.hint ? ` (${diagnostic.hint})` : ""}`)
.join("\n")
return yield* Effect.fail(
Expand Down
9 changes: 8 additions & 1 deletion packages/opencode/test/server/httpapi-exercise/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1913,7 +1913,14 @@ const scenarios: Scenario[] = [
http.protected
.post("/dag", "dag.start")
.mutating()
.seeded((ctx) => ctx.session({ title: "DAG start owner" }))
.withLlm()
.seeded((ctx) =>
// environment-profile authoring resolves each node's model through
// node -> tier -> agent -> parent(session.model); the exerciser's fake
// provider only exists under withLlm, and the parent chain needs the
// session to carry the fake model explicitly.
ctx.session({ title: "DAG start owner", model: { providerID: "test", id: "test-model" } }),
)
.at((ctx) => ({
path: "/dag",
headers: ctx.headers(),
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/test/server/httpapi-exercise/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function withContext<A, E>(
return Bun.write(`${directory()}/${name}`, content)
}).pipe(Effect.asVoid),
session: (input) =>
run(modules.Session.Service.use((svc) => svc.create({ title: input?.title, parentID: input?.parentID }))),
run(modules.Session.Service.use((svc) => svc.create({ title: input?.title, parentID: input?.parentID, model: input?.model as never }))),
sessionGet: (sessionID) =>
run(modules.Session.Service.use((svc) => svc.get(sessionID))).pipe(
Effect.catchCause(() => Effect.succeed(undefined)),
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/test/server/httpapi-exercise/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type ScenarioContext = {
directory: string | undefined
headers: (extra?: Record<string, string>) => Record<string, string>
file: (name: string, content: string) => Effect.Effect<void>
session: (input?: { title?: string; parentID?: SessionID }) => Effect.Effect<SessionInfo>
session: (input?: { title?: string; parentID?: SessionID; model?: { id: string; providerID: string } }) => Effect.Effect<SessionInfo>
sessionGet: (sessionID: SessionID) => Effect.Effect<SessionInfo | undefined>
project: () => Effect.Effect<Project.Info>
message: (sessionID: SessionID, input?: { text?: string }) => Effect.Effect<MessageSeed>
Expand Down
1 change: 1 addition & 0 deletions spec_git/policy.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
version: 1
required_checks:
- Typecheck
- Unit Tests (linux)
Loading