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
14 changes: 9 additions & 5 deletions packages/effect-e2e-cli/src/init/init-failure.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ const LOGGED_BY_RUNTIME: Runtime.errorReported = '~effect/Runtime/errorReported'
* `init` refused and wrote nothing (requirements AC-1.4): the files it names already exist, or the suite root's
* `package.json` cannot be merged into. The CLI has printed why, so the process exits 2 without a second report.
*/
export class InitRefused extends Schema.TaggedError<InitRefused>()('InitRefused', { message: Schema.String }) {
export class InitRefused extends Schema.TaggedError<InitRefused, { readonly brand: unique symbol }>()('InitRefused', {
message: Schema.String,
}) {
override readonly [EXIT_CODE] = 2
override readonly [LOGGED_BY_RUNTIME] = false
}

export class InstallFailed extends Schema.TaggedError<InstallFailed>()('InstallFailed', {
message: Schema.String,
exitCode: Schema.Int,
}) {}
export class InstallFailed
extends Schema.TaggedError<InstallFailed, { readonly brand: unique symbol }>()('InstallFailed', {
message: Schema.String,
exitCode: Schema.Int,
})
{}
52 changes: 30 additions & 22 deletions packages/effect-e2e-cli/src/init/plan-scaffold.workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,47 @@ export type SuiteWrite = typeof SuiteWrite.Type

const Dependencies = Schema.Record(Schema.String, Schema.String)

export class PlanScaffold extends Schema.TaggedClass<PlanScaffold>()('PlanScaffold', {
force: Schema.Boolean,
files: Schema.Array(TargetFile),
/** The suite root's `package.json` text, when it has one. */
manifest: Schema.Option(Schema.String),
/** The suite root's `.gitignore` text, when it has one. */
ignore: Schema.Option(Schema.String),
/** The name a `package.json` that `init` creates carries. */
name: Schema.String,
script: Schema.String,
devDependencies: Dependencies,
ignoredOutput: Schema.String,
}) {
export class PlanScaffold
extends Schema.TaggedClass<PlanScaffold, { readonly brand: unique symbol }>()('PlanScaffold', {
force: Schema.Boolean,
files: Schema.Array(TargetFile),
/** The suite root's `package.json` text, when it has one. */
manifest: Schema.Option(Schema.String),
/** The suite root's `.gitignore` text, when it has one. */
ignore: Schema.Option(Schema.String),
/** The name a `package.json` that `init` creates carries. */
name: Schema.String,
script: Schema.String,
devDependencies: Dependencies,
ignoredOutput: Schema.String,
})
{
static readonly [Workflow.InstrumentationBrand] = { force: 'app.e2e.init.force' } as const
}

export class ScaffoldPlanned extends Schema.TaggedClass<ScaffoldPlanned>()('ScaffoldPlanned', {
writes: Schema.Array(SuiteWrite),
}) {
export class ScaffoldPlanned
extends Schema.TaggedClass<ScaffoldPlanned, { readonly brand: unique symbol }>()('ScaffoldPlanned', {
writes: Schema.Array(SuiteWrite),
})
{
readonly [PlanScaffoldTypeId] = PlanScaffoldTypeId
}

/** Files `init` would replace; without `--force` it writes nothing (requirements AC-1.4). */
export class ScaffoldRefused extends Schema.TaggedClass<ScaffoldRefused>()('ScaffoldRefused', {
conflicts: Schema.NonEmptyArray(Schema.String),
}) {
export class ScaffoldRefused
extends Schema.TaggedClass<ScaffoldRefused, { readonly brand: unique symbol }>()('ScaffoldRefused', {
conflicts: Schema.NonEmptyArray(Schema.String),
})
{
readonly [PlanScaffoldTypeId] = PlanScaffoldTypeId
}

/** The suite root's `package.json` cannot be merged into, so `init` writes nothing. */
export class ManifestUnreadable extends Schema.TaggedClass<ManifestUnreadable>()('ManifestUnreadable', {
expected: Schema.String,
}) {
export class ManifestUnreadable
extends Schema.TaggedClass<ManifestUnreadable, { readonly brand: unique symbol }>()('ManifestUnreadable', {
expected: Schema.String,
})
{
readonly [PlanScaffoldTypeId] = PlanScaffoldTypeId
}

Expand Down
14 changes: 8 additions & 6 deletions packages/effect-e2e-cli/src/run/run-failure.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,31 @@ const LOGGED_BY_RUNTIME: Runtime.errorReported = '~effect/Runtime/errorReported'
* `run` refused before it started Vitest: no suite root at or above the working directory, or flags that cannot
* hold together. The CLI has printed why, so the process exits 2 without a second report.
*/
export class RunRefused extends Schema.TaggedError<RunRefused>()('RunRefused', {
export class RunRefused extends Schema.TaggedError<RunRefused, { readonly brand: unique symbol }>()('RunRefused', {
message: Schema.String,
}) {
override readonly [EXIT_CODE] = 2
override readonly [LOGGED_BY_RUNTIME] = false
}

export class RunConfigRefused extends Schema.TaggedError<RunConfigRefused>()('RunConfigRefused', {
message: Schema.String,
}) {
export class RunConfigRefused
extends Schema.TaggedError<RunConfigRefused, { readonly brand: unique symbol }>()('RunConfigRefused', {
message: Schema.String,
})
{
override readonly [EXIT_CODE] = 2
override readonly [LOGGED_BY_RUNTIME] = false
}

/** The run could not start or produced no report: a Vitest config or collection error (requirements FR-5). */
export class RunFailed extends Schema.TaggedError<RunFailed>()('RunFailed', {
export class RunFailed extends Schema.TaggedError<RunFailed, { readonly brand: unique symbol }>()('RunFailed', {
message: Schema.String,
}) {
override readonly [EXIT_CODE] = 2
override readonly [LOGGED_BY_RUNTIME] = false
}

export class RunEnded extends Schema.TaggedError<RunEnded>()('RunEnded', {
export class RunEnded extends Schema.TaggedError<RunEnded, { readonly brand: unique symbol }>()('RunEnded', {
status: Schema.String,
exitCode: Schema.Int,
}) {
Expand Down
16 changes: 15 additions & 1 deletion packages/effect-e2e-cli/tests/__fixtures__/cli.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,29 @@ const stdinOf = (input: string | undefined) =>
onSome: (keys) => Stream.make(new TextEncoder().encode(keys)),
})

const shellQuoted = (word: string) => `'${word.replaceAll("'", "'\\''")}'`

/**
* Keystrokes only reach a prompt on a terminal (effect 4.1 skips fallback prompts off a TTY), so a request with
* `input` runs the bin under a pseudo-terminal from util-linux `script`.
*/
const invocationOf = (request: BinRequest): readonly [command: string, args: ReadonlyArray<string>] => {
const command = request.command ?? BIN
return request.input === undefined
? [command, request.args]
: ['script', ['-qec', [command, ...request.args].map(shellQuoted).join(' '), '/dev/null']]
}

/** Runs a binary as a child process with only PATH and HOME from the environment, and collects its output. */
export const runBin = (request: BinRequest) =>
Effect.scoped(
Effect.gen(function*() {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
const inherited = yield* Config.String('PATH')
const home = yield* Config.String('HOME')
const [command, args] = invocationOf(request)
const handle = yield* spawner.spawn(
ChildProcess.make(request.command ?? BIN, [...request.args], {
ChildProcess.make(command, [...args], {
cwd: request.cwd,
env: {
PATH: Option.match(Option.fromNullishOr(request.pathPrefix), {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ export const copiedRunnerSuite = (config: SuiteConfig) =>
yield* fs.makeDirectory(SCRATCH, { recursive: true })
const root = yield* fs.makeTempDirectoryScoped({ directory: SCRATCH, prefix: 'cli-run-' })
const suite = `${root}/suite`
yield* fs.copy(RUNNER_SUITE, suite)
yield* fs.makeDirectory(suite, { recursive: true })
// Other tests run this suite in place, so Vite keeps transient files under its node_modules while the copy reads.
const entries = (yield* fs.readDirectory(RUNNER_SUITE)).filter((entry) => entry !== 'node_modules')
yield* Effect.forEach(entries, (entry) => fs.copy(`${RUNNER_SUITE}/${entry}`, `${suite}/${entry}`))
yield* fs.remove(`${suite}/.e2e`, { recursive: true, force: true })
if (config === 'refused') {
yield* fs.copyFile(`${suite}/vitest.refused.config.ts`, `${suite}/vitest.config.ts`)
Expand Down
8 changes: 4 additions & 4 deletions packages/effect-e2e/etc/effect-e2e-agent.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,10 @@ export interface WaitForResult {

// Warnings were encountered during analysis:
//
// dist/wait-for-DVc9_Cj7.d.ts:783:5 - (ae-forgotten-export) The symbol "CacheKeyContext" needs to be exported by the entry point agent.d.ts
// dist/wait-for-DVc9_Cj7.d.ts:784:5 - (ae-forgotten-export) The symbol "AgentConfig" needs to be exported by the entry point agent.d.ts
// dist/wait-for-DVc9_Cj7.d.ts:787:5 - (ae-forgotten-export) The symbol "CacheMode" needs to be exported by the entry point agent.d.ts
// dist/wait-for-DVc9_Cj7.d.ts:791:5 - (ae-forgotten-export) The symbol "EngineIdentity" needs to be exported by the entry point agent.d.ts
// dist/wait-for-Brb6_pdg.d.ts:783:5 - (ae-forgotten-export) The symbol "CacheKeyContext" needs to be exported by the entry point agent.d.ts
// dist/wait-for-Brb6_pdg.d.ts:784:5 - (ae-forgotten-export) The symbol "AgentConfig" needs to be exported by the entry point agent.d.ts
// dist/wait-for-Brb6_pdg.d.ts:787:5 - (ae-forgotten-export) The symbol "CacheMode" needs to be exported by the entry point agent.d.ts
// dist/wait-for-Brb6_pdg.d.ts:791:5 - (ae-forgotten-export) The symbol "EngineIdentity" needs to be exported by the entry point agent.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
4 changes: 2 additions & 2 deletions packages/effect-e2e/etc/effect-e2e-cache.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ export type Written = typeof Written.Type;

// Warnings were encountered during analysis:
//
// dist/cache-store.service-CdfpLQnG.d.ts:271:9 - (ae-forgotten-export) The symbol "LocatorExpression" needs to be exported by the entry point cache.d.ts
// dist/cache-store.service-CdfpLQnG.d.ts:398:3 - (ae-forgotten-export) The symbol "Check" needs to be exported by the entry point cache.d.ts
// dist/cache-store.service-CTBPFCe5.d.ts:279:9 - (ae-forgotten-export) The symbol "LocatorExpression" needs to be exported by the entry point cache.d.ts
// dist/cache-store.service-CTBPFCe5.d.ts:406:3 - (ae-forgotten-export) The symbol "Check" needs to be exported by the entry point cache.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
4 changes: 2 additions & 2 deletions packages/effect-e2e/etc/effect-e2e-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3775,8 +3775,8 @@ export type TestStatus = typeof TestStatus.Type;

// Warnings were encountered during analysis:
//
// dist/run-verdict.schema-Ca6kURxN.d.ts:739:11 - (ae-forgotten-export) The symbol "LocatorExpression" needs to be exported by the entry point report.d.ts
// dist/run-verdict.schema-Ca6kURxN.d.ts:913:7 - (ae-forgotten-export) The symbol "Check" needs to be exported by the entry point report.d.ts
// dist/run-verdict.schema-BT-4NBmC.d.ts:745:11 - (ae-forgotten-export) The symbol "LocatorExpression" needs to be exported by the entry point report.d.ts
// dist/run-verdict.schema-BT-4NBmC.d.ts:919:7 - (ae-forgotten-export) The symbol "Check" needs to be exported by the entry point report.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
10 changes: 6 additions & 4 deletions packages/effect-e2e/src/Config/config-invalid.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ const keyPathText = (path: ReadonlyArray<PathSegment>): string => path.map(stepT
const pathText = (path: ReadonlyArray<PathSegment>): string => (path.length === 0 ? '(root)' : keyPathText(path))

/** A config that does not decode: the key path of the first refusal and what that key expected. */
export class ConfigInvalid extends Schema.TaggedError<ConfigInvalid>()('ConfigInvalid', {
path: Schema.Array(PathSegment),
expected: Schema.String,
}) {
export class ConfigInvalid
extends Schema.TaggedError<ConfigInvalid, { readonly brand: unique symbol }>()('ConfigInvalid', {
path: Schema.Array(PathSegment),
expected: Schema.String,
})
{
override get message(): string {
return `config ${pathText(this.path)}: ${this.expected}`
}
Expand Down
30 changes: 18 additions & 12 deletions packages/effect-e2e/src/agent/admit-check-answer.workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,27 +92,33 @@ const AdmitCheckAnswerDecisionTypeId: unique symbol = Symbol.for(
)
type AdmitCheckAnswerDecisionTypeId = typeof AdmitCheckAnswerDecisionTypeId

export class CheckAdmitted extends Schema.TaggedClass<CheckAdmitted>()('CheckAdmitted', {
check: Check,
callId: Schema.String,
}) {
export class CheckAdmitted
extends Schema.TaggedClass<CheckAdmitted, { readonly brand: unique symbol }>()('CheckAdmitted', {
check: Check,
callId: Schema.String,
})
{
readonly [AdmitCheckAnswerDecisionTypeId] = AdmitCheckAnswerDecisionTypeId
}

export class CheckRefused extends Schema.TaggedClass<CheckRefused>()('CheckRefused', {
replies: Schema.NonEmptyArray(Message),
}) {
export class CheckRefused
extends Schema.TaggedClass<CheckRefused, { readonly brand: unique symbol }>()('CheckRefused', {
replies: Schema.NonEmptyArray(Message),
})
{
readonly [AdmitCheckAnswerDecisionTypeId] = AdmitCheckAnswerDecisionTypeId
}

export const AdmitCheckAnswerDecision = Schema.Union([CheckAdmitted, CheckRefused])
export type AdmitCheckAnswerDecision = typeof AdmitCheckAnswerDecision.Type

export class AdmitCheckAnswer extends Schema.TaggedClass<AdmitCheckAnswer>()('AdmitCheckAnswer', {
answer: ModelAnswer,
observation: Observation,
refsOffered: Schema.Boolean,
}) {
export class AdmitCheckAnswer
extends Schema.TaggedClass<AdmitCheckAnswer, { readonly brand: unique symbol }>()('AdmitCheckAnswer', {
answer: ModelAnswer,
observation: Observation,
refsOffered: Schema.Boolean,
})
{
static readonly [Workflow.InstrumentationBrand] = {} as const
}

Expand Down
24 changes: 15 additions & 9 deletions packages/effect-e2e/src/agent/check-evaluate.workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,19 @@ export const UnevaluableReason = Schema.Literals([
])
export type UnevaluableReason = typeof UnevaluableReason.Type

export class Holds extends Schema.TaggedClass<Holds>()('Holds', { evidence: Evidence }) {
export class Holds
extends Schema.TaggedClass<Holds, { readonly brand: unique symbol }>()('Holds', { evidence: Evidence })
{
readonly [CheckEvaluateDecisionTypeId] = CheckEvaluateDecisionTypeId
}

export class DoesNotHold extends Schema.TaggedClass<DoesNotHold>()('DoesNotHold', { evidence: Evidence }) {
export class DoesNotHold
extends Schema.TaggedClass<DoesNotHold, { readonly brand: unique symbol }>()('DoesNotHold', { evidence: Evidence })
{
readonly [CheckEvaluateDecisionTypeId] = CheckEvaluateDecisionTypeId
}

export class Unevaluable extends Schema.TaggedClass<Unevaluable>()('Unevaluable', {
export class Unevaluable extends Schema.TaggedClass<Unevaluable, { readonly brand: unique symbol }>()('Unevaluable', {
reason: UnevaluableReason,
evidence: Evidence,
}) {
Expand All @@ -59,12 +63,14 @@ export class Unevaluable extends Schema.TaggedClass<Unevaluable>()('Unevaluable'
export const CheckEvaluateDecision = Schema.Union([Holds, DoesNotHold, Unevaluable])
export type CheckEvaluateDecision = typeof CheckEvaluateDecision.Type

export class CheckEvaluate extends Schema.TaggedClass<CheckEvaluate>()('CheckEvaluate', {
check: Check,
route: Route,
resolvedOn: Route,
located: Schema.Array(LocatorMatches),
}) {
export class CheckEvaluate
extends Schema.TaggedClass<CheckEvaluate, { readonly brand: unique symbol }>()('CheckEvaluate', {
check: Check,
route: Route,
resolvedOn: Route,
located: Schema.Array(LocatorMatches),
})
{
static readonly [Workflow.InstrumentationBrand] = {} as const
}

Expand Down
18 changes: 11 additions & 7 deletions packages/effect-e2e/src/agent/next-act.workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export type ActTool = typeof ActTool.Type
const NextActDecisionTypeId: unique symbol = Symbol.for('@systemfsoftware/effect-e2e/NextActDecision')
type NextActDecisionTypeId = typeof NextActDecisionTypeId

export class Execute extends Schema.TaggedClass<Execute>()('Execute', {
export class Execute extends Schema.TaggedClass<Execute, { readonly brand: unique symbol }>()('Execute', {
callId: Schema.String,
action: Action,
located: Schema.Array(NodeRef),
Expand All @@ -133,28 +133,32 @@ export class Execute extends Schema.TaggedClass<Execute>()('Execute', {
readonly [NextActDecisionTypeId] = NextActDecisionTypeId
}

export class Capture extends Schema.TaggedClass<Capture>()('Capture', { callId: Schema.String }) {
export class Capture
extends Schema.TaggedClass<Capture, { readonly brand: unique symbol }>()('Capture', { callId: Schema.String })
{
readonly [NextActDecisionTypeId] = NextActDecisionTypeId
}

export class Done extends Schema.TaggedClass<Done>()('Done', {
export class Done extends Schema.TaggedClass<Done, { readonly brand: unique symbol }>()('Done', {
callId: Schema.String,
summary: Schema.String,
}) {
readonly [NextActDecisionTypeId] = NextActDecisionTypeId
}

export class Correct extends Schema.TaggedClass<Correct>()('Correct', { reply: Message }) {
export class Correct
extends Schema.TaggedClass<Correct, { readonly brand: unique symbol }>()('Correct', { reply: Message })
{
readonly [NextActDecisionTypeId] = NextActDecisionTypeId
}

export class Exhausted extends Schema.TaggedClass<Exhausted>()('Exhausted', {
export class Exhausted extends Schema.TaggedClass<Exhausted, { readonly brand: unique symbol }>()('Exhausted', {
budget: Schema.Literals(['steps', 'model-calls']),
}) {
readonly [NextActDecisionTypeId] = NextActDecisionTypeId
}

export class Unsupported extends Schema.TaggedClass<Unsupported>()('Unsupported', {
export class Unsupported extends Schema.TaggedClass<Unsupported, { readonly brand: unique symbol }>()('Unsupported', {
callId: Schema.String,
capability: EngineCapability,
}) {
Expand All @@ -164,7 +168,7 @@ export class Unsupported extends Schema.TaggedClass<Unsupported>()('Unsupported'
export const NextActDecision = Schema.Union([Execute, Capture, Done, Correct, Exhausted, Unsupported])
export type NextActDecision = typeof NextActDecision.Type

export class NextAct extends Schema.TaggedClass<NextAct>()('NextAct', {
export class NextAct extends Schema.TaggedClass<NextAct, { readonly brand: unique symbol }>()('NextAct', {
answer: ModelAnswer,
observation: Observation,
capabilities: Schema.Array(EngineCapability),
Expand Down
Loading
Loading