Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ jobs:
- name: The <Plan> Component the compiled binary carries
run: deno test --allow-all --frozen scripts/tests/plan-component-compiled.test.ts

# The ordinary repository provider is assembled at a runtime-named
# entrypoint and holds managed checkouts with a kernel-released advisory
# lock, so only the binary shows both surviving `deno compile`. The script
# runs two of them at once against a managed root of its own.
- name: Smoke test repository composition with the compiled binary
run: deno run --allow-all --frozen scripts/smoke-run-composition.ts

# `<Fetch>` resolves from core's registry, requests through the contextual
# Fetch adapter, and detaches the response before binding it. All three
# live in the module graph, so only the binary shows they survived
Expand Down
35 changes: 29 additions & 6 deletions architecture.md

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion packages/cli/src/bun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { compileTempFile } from "@executablemd/core";
import { runXmd } from "./cli.ts";
import { unassembledMachineSessions } from "./session-coordinator.ts";
import { unsupportedWorkflowHost } from "./workflow.ts";
import { unsupportedRepositories } from "./run-repositories.ts";
import { useBunService } from "./bun-service.ts";

const ENTRYPOINT = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -44,5 +45,16 @@ await main(function* (args) {
// build either. Advertising the same names is what makes the refusal say so:
// every advertised operation stops before provider work, while ordinary ACP
// work is unaffected.
yield* runXmd(args, useBunService, unsupportedWorkflowHost, unassembledMachineSessions());
// The same thirteen repository components, and no provider that operates
// any of them. This runtime has no kernel-released advisory lock to hold a
// managed checkout with, so a Repository, Worktree, Git, Issue or PullRequest
// operation reports an absent provider before a local or remote change could
// happen. `xmd syntax` still describes one language everywhere.
yield* runXmd(
args,
useBunService,
unsupportedRepositories,
unsupportedWorkflowHost,
unassembledMachineSessions(),
);
});
76 changes: 71 additions & 5 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import {
inspectDocument,
agentIdentityComponents,
installAgentComponents,
registerComponents,
retainedSource,
rootSourcePath,
useNormalizedOutput,
Expand Down Expand Up @@ -126,6 +127,8 @@ import type { PlanExecution } from "./plan.ts";
import { componentSearchPath, resolveTestTarget } from "./test-target.ts";
import { renderSyntaxJson, renderSyntaxMarkdown, syntaxCatalog } from "./syntax.ts";
import { testingExecutionHost } from "./testing-host.ts";
import { unsupportedRepositories } from "./run-repositories.ts";
import type { RepositoryInstaller } from "./run-repositories.ts";
import { EVAL_ALIAS, EVAL_OPTION, evalGrammarError, readEvalFlags } from "./eval-source.ts";
import type { EvalFlags } from "./eval-source.ts";
import {
Expand All @@ -139,7 +142,7 @@ import type { HostWorkflowInstaller, WorkflowHost, WorkflowStart } from "./workf
import { runWorkflowManagement } from "./workflow-management.ts";
import { establishDefinition } from "./workflow-definition.ts";
import type { EstablishedDefinition } from "./workflow-definition.ts";
import { useWorkflowServiceDenial } from "@executablemd/workflow";
import { COMPOSITION_REGISTRATIONS, useWorkflowServiceDenial } from "@executablemd/workflow";
import denoJson from "../deno.json" with { type: "json" };

const SECRET_DETECTION_OPTION = "--secret-detection";
Expand Down Expand Up @@ -682,6 +685,13 @@ export type HostServiceInstaller = () => Operation<void>;
* value root's stdout — stays with the command that owns those streams.
*/
export function* installDocumentComponents(mode: DocumentMode, verbose: boolean): Operation<void> {
// The repository-composition vocabulary, as ordinary shadowable defaults.
// Registering it installs no provider, discovers no repository, acquires no
// lock and reaches no network: what a name *does* is decided by whichever
// provider the command installed, and a runtime that installs none still
// resolves every one of these.
yield* registerComponents(COMPOSITION_REGISTRATIONS);

// Compose testing around the single core execution entrypoint: both
// commands register the components (assertions work in regular documents,
// explicit <Testing> boundaries affect the outcome), while `xmd test`
Expand Down Expand Up @@ -732,6 +742,8 @@ function* runDocument(
config: DocumentConfig,
mode: DocumentMode,
installService: HostServiceInstaller,
installRepositories: RepositoryInstaller,
childRepositories: RepositoryInstaller,
): Operation<Result<void>> {
const { root, include, verbose, journal, raw, secretDetection, retainProcessOutput } = config;

Expand Down Expand Up @@ -840,6 +852,15 @@ function* runDocument(
// the provider for a service.
yield* installService();

// Repository authority belongs to document execution too, and it is this
// execution's own: the provider it installs holds an invocation identity, the
// leases on the checkouts this document selects, and the evidence of what it
// published. `xmd run` and an approved `xmd plan --run` supply the live one;
// `xmd test` and every runtime without an operational provider supply the one
// that installs nothing, and every repository operation then reports an
// absent provider before touching anything.
yield* installRepositories();

// What a `<Test>` in this document runs a nested execution under. Captured
// before document code begins, so a child is offered exactly what this
// command assembled — and never a second description of it.
Expand All @@ -852,6 +873,13 @@ function* runDocument(
includes: include,
secretDetection,
installService,
// The *entrypoint's* installer, not this command's. A `host="run"` child is
// an ordinary run whatever command is hosting it, so `xmd test` — which
// installs no repository provider for its own document — still gives one to
// a child that asked to be a run. Passed rather than inherited because a
// child runs in an isolated scope and needs a fresh instance: its own
// invocation identity, its own leases and its own Push evidence.
installRepositories: childRepositories,
testAgentWorker: yield* readWorkerCommand(),
plan,
});
Expand Down Expand Up @@ -953,9 +981,13 @@ function* runScopedDocument(
config: DocumentConfig,
mode: DocumentMode,
installService: HostServiceInstaller,
installRepositories: RepositoryInstaller,
childRepositories: RepositoryInstaller = installRepositories,
): Operation<Result<void>> {
try {
return yield* scoped(() => runDocument(config, mode, installService));
return yield* scoped(() =>
runDocument(config, mode, installService, installRepositories, childRepositories),
);
} catch (error) {
return Err(error instanceof Error ? error : new Error(String(error)));
}
Expand Down Expand Up @@ -985,6 +1017,7 @@ export function planExecutor(
stack: AgentStack,
sessions: MachineSessionAssembly | undefined,
installService: HostServiceInstaller,
installRepositories: RepositoryInstaller,
): (approved: PlanExecution) => Operation<Result<void>> {
return (approved) =>
scoped(function* (): Operation<Result<void>> {
Expand All @@ -1011,6 +1044,10 @@ export function planExecutor(
agent: stack,
},
installService,
// An approved plan's second execution is an ordinary run, so it gets
// the ordinary provider — a fresh one, since the authorship profile's
// scope is already gone.
installRepositories,
);
});
}
Expand Down Expand Up @@ -1065,6 +1102,8 @@ function* test(
config: TestConfig,
args: string[],
installService: HostServiceInstaller,
/** What a `<Execution host="run">` child installs. This command installs none. */
installRepositories: RepositoryInstaller,
): Operation<void> {
const patterns = readPatternFlags(args);
if (patterns.missingValue) {
Expand Down Expand Up @@ -1098,6 +1137,12 @@ function* test(
{ ...config, root: { path } },
{ testing: true },
installService,
// The outer `xmd test` command installs no operational repository
// provider. A test that needs the production behavior exercises an
// explicit `<Execution host="run">` child, which is an ordinary run and
// is handed the entrypoint's own installer below.
unsupportedRepositories,
installRepositories,
);
if (!result.ok) {
reportFailure(result.error);
Expand Down Expand Up @@ -1138,6 +1183,8 @@ function* test(
},
{ testing: true },
installService,
unsupportedRepositories,
installRepositories,
);
if (!result.ok) {
reportFailure(result.error, document.relativePath);
Expand Down Expand Up @@ -1864,6 +1911,7 @@ function* dispatch(
evalFlags: EvalFlags,
helpRequest: { requested: boolean; args: string[] },
installService: HostServiceInstaller,
installRepositories: RepositoryInstaller,
workflowHost: WorkflowHost | undefined,
sessions: MachineSessionAssembly | undefined,
): Operation<void> {
Expand Down Expand Up @@ -1965,6 +2013,7 @@ function* dispatch(
agent: runStack,
},
installService,
installRepositories,
);
});
if (!result.ok) {
Expand Down Expand Up @@ -2022,7 +2071,7 @@ function* dispatch(
// A host that answers installs a provider; one that does not installs
// none, and nothing downstream reads a profile to find out which.
installElicitation: installWebElicitation,
execute: planExecutor(config, planStack, sessions, installService),
execute: planExecutor(config, planStack, sessions, installService, installRepositories),
},
);
if (exitCode !== 0) {
Expand Down Expand Up @@ -2062,6 +2111,7 @@ function* dispatch(
{ ...command.config, retainProcessOutput: keepsProcessOutput(command.config.journal) },
evalFlags.rest,
installService,
installRepositories,
);
break;
}
Expand Down Expand Up @@ -2167,6 +2217,9 @@ function* dispatch(
// service adapter would: installed inside the execution scope,
// before the root document is imported.
useWorkflowServiceDenial,
// A workflow run's repositories are the retained ones its Workspace
// attachment installs, so this path installs none of its own.
unsupportedRepositories,
),
),
);
Expand All @@ -2179,6 +2232,12 @@ function* dispatch(
export function* runXmd(
args: string[],
installService: HostServiceInstaller,
// What an ordinary document execution installs for `<Repository>`,
// `<Worktree>`, the Git operations, `<Issue>` and `<PullRequest>`. Deno and
// the compiled binary supply the live provider; Node and Bun supply the one
// that installs nothing, so those runtimes describe the same vocabulary and
// operate none of it.
installRepositories: RepositoryInstaller,
// Defaults to the host that refuses. A caller driving this without naming a
// workflow host has no run store, and inheriting one by omission is the
// failure mode the whole boundary exists to prevent — so the default is the
Expand Down Expand Up @@ -2249,7 +2308,14 @@ export function* runXmd(
(selected.name === "run" || selected.name === "plan");

if (!executes) {
return yield* dispatch(evalFlags, helpRequest, installService, workflowHost, sessions);
return yield* dispatch(
evalFlags,
helpRequest,
installService,
installRepositories,
workflowHost,
sessions,
);
}

const timeouts = resolveRunTimeouts(evalFlags.rest);
Expand All @@ -2260,6 +2326,6 @@ export function* runXmd(
}

yield* underRunDeadline(timeouts, () =>
dispatch(evalFlags, helpRequest, installService, workflowHost, sessions),
dispatch(evalFlags, helpRequest, installService, installRepositories, workflowHost, sessions),
);
}
5 changes: 5 additions & 0 deletions packages/cli/src/compiled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { compileDataUri } from "@executablemd/core";
import { runXmd } from "./cli.ts";
import { useMachineSessions } from "./session-coordinator.ts";
import { useDenoWorkflowHost } from "./deno-workflow.ts";
import { denoRunRepositories } from "./deno-repositories.ts";
import {
isCredentialHelperMode,
runCredentialHelper,
Expand Down Expand Up @@ -64,9 +65,13 @@ if (isCredentialHelperMode(process.argv.slice(2))) {
// two owners of one conversation.
// Helper mode receives neither this nor the workflow host: it is not the
// public CLI and assembles none of it.
// The ordinary repository provider, on the same terms the Deno entrypoint
// installs it: the binary is Deno, and the helper assembly it hands over is
// the one that names this executable rather than a module path.
yield* runXmd(
args,
useCompiledService,
denoRunRepositories(HELPER),
() => useDenoWorkflowHost(HELPER),
useMachineSessions(),
);
Expand Down
52 changes: 52 additions & 0 deletions packages/cli/src/deno-repositories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* The live repository provider, assembled where it can be.
*
* Kept apart from `run-repositories.ts` because that module is on the shared
* command path and this one names the Deno adapter, whose module graph reaches
* `node:sqlite`. Bun has no such built-in, so a static import of this from
* shared code would stop `xmd` loading there — not refuse a repository
* operation, but fail to start at all. Only `deno.ts` and `compiled.ts` import
* this file, and both of them are Deno.
*
* Managed checkouts live beneath `~/.xmd/repositories` and survive every
* execution: what is in one is somebody's work, and nothing deletes one. There
* is no environment variable naming a different root, because the only caller
* that needs one is a test, and a test is handed the root directly.
*/

import type { Operation } from "effection";
import { cwd } from "@executablemd/runtime";
import { useRunComposition } from "@executablemd/workflow/deno";
import type { HelperAssembly } from "@executablemd/workflow/credential-helper";
import { gitHubIssuesConfiguration } from "./github-issues-config.ts";
import { gitHubPullRequestsConfiguration } from "./github-pull-requests-config.ts";
import { DEFAULT_REPOSITORY_ROOT } from "./run-repositories.ts";
import type { RepositoryInstaller } from "./run-repositories.ts";

/**
* The live provider Deno and the compiled binary install.
*
* The two GitHub configurations are read once, when the installer runs, so an
* operator who wrote something this host cannot use learns it before a document
* expands rather than in the middle of one.
*/
export function denoRunRepositories(
helper: HelperAssembly,
root: string = DEFAULT_REPOSITORY_ROOT,
): RepositoryInstaller {
return function* (): Operation<void> {
const gitHubIssues = yield* gitHubIssuesConfiguration();
const gitHubPullRequests = yield* gitHubPullRequestsConfiguration();
yield* useRunComposition({
root,
// The directory this execution starts in, which is where the ambient
// repository is discovered from. Read through the contextual Api rather
// than from the process, so a nested execution that composed its own
// working directory is discovered from that one.
cwd: yield* cwd(),
helper,
...(gitHubIssues === undefined ? {} : { gitHubIssues }),
...(gitHubPullRequests === undefined ? {} : { gitHubPullRequests }),
});
};
}
15 changes: 14 additions & 1 deletion packages/cli/src/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { compileDataUri } from "@executablemd/core";
import { runXmd } from "./cli.ts";
import { useMachineSessions } from "./session-coordinator.ts";
import { useDenoWorkflowHost } from "./deno-workflow.ts";
import { denoRunRepositories } from "./deno-repositories.ts";
import {
isCredentialHelperMode,
runCredentialHelper,
Expand Down Expand Up @@ -78,6 +79,18 @@ if (isCredentialHelperMode(process.argv.slice(2))) {
// two owners of one conversation.
// Helper mode receives neither this nor the workflow host: it is not the
// public CLI and assembles none of it.
yield* runXmd(args, useDenoService, () => useDenoWorkflowHost(HELPER), useMachineSessions());
// The ordinary repository provider: managed checkouts under
// `~/.xmd/repositories`, the ambient repository this command was run in,
// and the two GitHub configurations this deployment authorizes. It is
// parameterized by the same credential-helper assembly the workflow host
// uses, because the program that is running is what knows how to re-invoke
// itself as one.
yield* runXmd(
args,
useDenoService,
denoRunRepositories(HELPER),
() => useDenoWorkflowHost(HELPER),
useMachineSessions(),
);
});
}
14 changes: 13 additions & 1 deletion packages/cli/src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { compileTempFile } from "@executablemd/core";
import { runXmd } from "./cli.ts";
import { unassembledMachineSessions } from "./session-coordinator.ts";
import { unsupportedWorkflowHost } from "./workflow.ts";
import { unsupportedRepositories } from "./run-repositories.ts";
import { useNodeService } from "./node-service.ts";

const ENTRYPOINT = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -51,5 +52,16 @@ await main(function* (args) {
// build either. Advertising the same names is what makes the refusal say so:
// every advertised operation stops before provider work, while ordinary ACP
// work is unaffected.
yield* runXmd(args, useNodeService, unsupportedWorkflowHost, unassembledMachineSessions());
// The same thirteen repository components, and no provider that operates
// any of them. This runtime has no kernel-released advisory lock to hold a
// managed checkout with, so a Repository, Worktree, Git, Issue or PullRequest
// operation reports an absent provider before a local or remote change could
// happen. `xmd syntax` still describes one language everywhere.
yield* runXmd(
args,
useNodeService,
unsupportedRepositories,
unsupportedWorkflowHost,
unassembledMachineSessions(),
);
});
Loading
Loading