Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
896197c
🚨 Save a REPL entry's pull-request request, and pair it beside its pu…
taras Oct 20, 2026
b5b441e
🚨 Infer a malformed fixture's type, and drop eight narrating comments…
taras Oct 20, 2026
92b1300
🚨 Open and resume a REPL entry's pull request, and retain its three r…
taras Oct 20, 2026
c3c2fc0
🚨 Restore the semantic layer an entry's evidence read composes throug…
taras Oct 20, 2026
bae40d7
🚨 Complete the frozen evidence a REPL entry's pull request is accepte…
taras Oct 20, 2026
247063d
🚨 Hold one owned attempt's cleanup, and watch publication wait for it…
taras Oct 20, 2026
9321e3c
🚨 Park the earlier publisher above its own publication before the lat…
taras Oct 20, 2026
4bd72e5
🚨 Open a pull request from a real terminal, leave, and come back to i…
taras Oct 20, 2026
b7e4783
🚨 Move the REPL entry's repository policy into the command that owns …
taras Oct 21, 2026
7354a9c
🚨 Say which six operations a REPL entry's selector declares (#967)
taras Oct 21, 2026
d200f47
🚨 Finish the package cut, and prove what the cancelled attempt releas…
taras Oct 21, 2026
fcc1e8d
🚨 Let the scope that asks for a fixture `git` own its child (#967)
taras Oct 21, 2026
8771261
🚨 Read the entry's own journal, and let the question say what it rest…
taras Oct 21, 2026
a0d2f3e
🚨 Load the Deno adapter where the journey that needs it runs (#967)
taras Oct 21, 2026
23bce22
🚨 Let three stale paths follow the files the move relocated (#967)
taras Oct 21, 2026
2b4a2b0
🚨 Refuse a declined entry read where the chain ends, not from the top…
taras Oct 21, 2026
0fc3fe1
🚨 Give one body to each rule the entry's pull request asks twice (#967)
taras Oct 21, 2026
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
2 changes: 1 addition & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"ignorePatterns": [
"**/*.md",
"scripts/tests/fixtures/**",
"packages/git/tests/fixtures/retained-repository-history/**",
"packages/cli/tests/fixtures/retained-repository-history/**",
"**/npm/**",
"packages/workflow/vendor/cloudflare-computer-dofs/**",
"packages/acp/vendor/acpx/**",
Expand Down
54 changes: 34 additions & 20 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -5248,10 +5248,20 @@ A **Plugin** is trusted code installed before an execution imports a root
document. XMD bundles exactly one, `@executablemd/git`, and an operator adds more with a
repeatable `--plugin <specifier>`. The bundled value is statically imported
rather than resolved, so a distribution carries it whole and nothing can
substitute it; it is activated for `run`, `plan`, `syntax` and the workflow
actions that execute a document — `start`, `resume` and `fork` — and for nothing
else. `xmd test`, `upgrade` and a workflow management action carry no Plugin
they did not ask for.
substitute it; it is activated for `run`, `plan`, `syntax`, `repl` and the
workflow actions that execute a document — `start`, `resume` and `fork` — and
for nothing else. `xmd test`, `upgrade` and a workflow management action carry
no Plugin they did not ask for.

`repl` is the one command that assembles the bundled value rather than installing
it directly. Git contributes the vocabulary and its two domain admissions; what
an entry wrote down about the checkout it was standing in is that command's own
record, so the admission that reads it back is contributed by the command, from
inside the same contribution — because an admission is applied within each
execution's own journal read and cannot be attached from outside one. The
assembly keeps the bundled Plugin's name, its identity-based deduplication and
its place in an operator's own Plugin order, and every other command is
delegated untouched.

`git` is a reserved *host selector*, not a package name: writing `--plugin git`
names the value the profile already holds, resolves without loading a module,
Expand Down Expand Up @@ -5397,15 +5407,17 @@ A host that retains nothing named after an invocation runs the same bodies over
the public feature operations, with no identity authenticated, because there is
nothing for one to name. Only an operation a host retains *above* the public
chain, named after an invocation it authenticates, takes its operation as an
argument, and that is `Git.Push` and `Issue`. A workflow run retains
`PullRequest` and its three evidence reads too — the upsert through the Git-host
reconciliation beneath it, each read as policy on `PullRequestReadExecution` —
but it installs that retention under their feature Api instead of owning a
boundary over it, and no REPL entry owns one either, so those four ask their Api
directly as any other component does. A REPL entry retains the four Git reads
the same way — `GitQuery` middleware at its own implementation slot — which is
what lets those four stay ordinary registrations under a host that retains every
page they answer with. That is what makes an ordinary run, a workflow run, a
argument, and that is `Git.Push`, `Issue`, `PullRequest` and its three evidence
reads. A workflow run retains `PullRequest` and those reads too — the upsert
through the Git-host reconciliation beneath it, each read as policy on
`PullRequestReadExecution` — but it installs that retention under their feature
Api instead of owning a boundary over it, so under a workflow run those four ask
their Api directly as any other component does. A REPL entry does own a boundary
over all six, because each of its records is named after the occurrence that
asked for it. A REPL entry retains the four Git reads
differently — `GitQuery` middleware at its own implementation slot — which is
what lets those four stay ordinary registrations under a host that retains
every page they answer with. That is what makes an ordinary run, a workflow run, a
provider-absent runtime and a REPL entry four policies rather than four
components.

Expand Down Expand Up @@ -7298,13 +7310,15 @@ persistence failure with its first cause: no success binding is offered, no
later sibling runs as though the commit succeeded, and no compensating Close is
appended.

A pull request is not here: `<PullRequest>` stays declared where it already is
and refuses through its own unchanged missing-provider default, and a local
result confers no publication evidence. Publication itself is the next
subsection's subject, and `<Issue>` the section's after it — the first because
it belongs to a checkout and to a remote at once, the second because it belongs
to no checkout at all. Nothing inventories the managed root, snapshots a
checkout's contents or restores one.
A pull request is here too, with its own protocol: `<PullRequest>` is retained
as a saved request and a Git-host outcome at two positions, and a local result
confers no publication evidence for it. What admits one is this entry's own last
acknowledged publication of the branch it names, which the subsection after next
is about. Its three evidence reads retain the provider's snapshot and nothing
above it. Publication itself is the next subsection's subject, and `<Issue>` the
section's after it — the first because it belongs to a checkout and to a remote
at once, the second because it belongs to no checkout at all. Nothing
inventories the managed root, snapshots a checkout's contents or restores one.

#### What one entry asked to publish, and what the Git host did about it

Expand Down
12 changes: 9 additions & 3 deletions packages/cli/src/deno-repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import type { Plugin, PluginInstallRequest, PluginInstallation } from "@executab
import type { Operation } from "effection";
import { cwd } from "@executablemd/runtime";
import { gitPlugin } from "@executablemd/git";
import { replRepositoryInstallation, useRunComposition } from "@executablemd/git/deno";
import { useRunComposition } from "@executablemd/git/deno";
import { replRepositoryInstallation } from "./repl/repositories/deno/provider.ts";
import { replRepositoryPlugin } from "./repl/repositories/plugin.ts";
import type { HelperAssembly } from "@executablemd/git/credential-helper";
import { DEFAULT_REPOSITORY_ROOT } from "./run-repositories.ts";
import { ReplEntryHost } from "./repl-entry-installation.ts";
Expand Down Expand Up @@ -62,6 +64,10 @@ export function denoRunRepositories(
// selection the command already made. The caller's own object is neither
// mutated nor frozen — this is a copy, and the copy is what is used.
const configured = snapshot(helper);
// The same CLI assembly the default selection uses, constructed once here, so
// a `repl` request reaches this command's entry-history admission through
// this configured variant as well.
const bundled = replRepositoryPlugin(gitPlugin);
return {
// The bundled name, because this *is* the bundled Plugin: an operator
// selecting `--plugin git` is naming this value, and two values under one
Expand All @@ -72,8 +78,8 @@ export function denoRunRepositories(
// bundled Plugin rather than reimplementing it, so the vocabulary, the
// commands it declares for and the admissions it contributes are the ones
// that package decides.
const install = gitPlugin.install;
const declared = install === undefined ? undefined : yield* install(request);
const install = bundled.install;
const declared = install === undefined ? undefined : yield* install.call(bundled, request);
if (declared === undefined) {
return undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ import type {
ReplLocalOperation,
ReplLocalRequest,
ReplLocalValue,
} from "../../retained-repository/records.ts";
} from "../records.ts";

/**
* One managed checkout this entry selected, as file routing needs it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,14 @@ import type {
GitApi,
GitCommitInvocation,
GitSwitchInvocation,
} from "../../composition/git-api.ts";
import { GitOperationAdmissionError } from "../../composition/errors.ts";
import type {
GitAddResult,
GitCommitResult,
GitSwitchResult,
} from "../../composition/git-records.ts";
import { ADD, admitPathspecs } from "../../composition/components/GitAdd.ts";
import {
admitCommitMessage,
admitMessageSource,
COMMIT,
} from "../../composition/components/GitCommit.ts";
import { SWITCH } from "../../composition/components/GitSwitch.ts";
import { sameRepositoryIdentity } from "../../composition/selection.ts";
import type { RepositorySelection } from "../../composition/selection.ts";
} from "@executablemd/git/api";
import { GitOperationAdmissionError } from "@executablemd/git";
import type { GitAddResult, GitCommitResult, GitSwitchResult } from "@executablemd/git";
import { ADD, admitPathspecs } from "@executablemd/git";
import { admitCommitMessage, admitMessageSource, COMMIT } from "@executablemd/git";
import { SWITCH } from "@executablemd/git";
import { sameRepositoryIdentity } from "@executablemd/git";
import type { RepositorySelection } from "@executablemd/git/api";
import {
parseReplGitRecord,
replGitFingerprint,
Expand All @@ -91,7 +83,7 @@ import {
replGitRefusal,
ReplGitRefusal,
REPL_REPOSITORY_GIT,
} from "../../retained-repository/git-records.ts";
} from "../git-records.ts";
import type {
ReplGitAddRequest,
ReplGitCommitRequest,
Expand All @@ -102,24 +94,16 @@ import type {
ReplGitRequest,
ReplGitSwitchRequest,
ReplGitValue,
} from "../../retained-repository/git-records.ts";
import { gitCommitMessageEvidence } from "../composition/commit.ts";
import { GitRefusal } from "../composition/git.ts";
import type { GitSession } from "../composition/git.ts";
import { CompositionRefusal, refusalReason, restoredGitRefusal } from "../composition/refusals.ts";
import type { GitCommitIdentity } from "../run-composition/identity.ts";
import { UnresolvedGitIdentityError } from "../run-composition/errors.ts";
import {
liveAdd,
liveCheckout,
liveCommit,
liveSwitch,
selectCheckout,
type GitCheckout,
type RegisteredCheckout,
} from "../run-composition/operations.ts";
import type { SlotKind } from "../run-composition/leases.ts";
import type { SelectionRegistry } from "../selections.ts";
} from "../git-records.ts";
import { gitCommitMessageEvidence } from "@executablemd/git/deno";
import type { DenoRepositoryOperations } from "@executablemd/git/deno";
import { GitRefusal } from "@executablemd/git/deno";
import { CompositionRefusal, refusalReason, restoredGitRefusal } from "@executablemd/git/deno";
import type { GitCommitIdentity } from "@executablemd/git/deno";
import { UnresolvedGitIdentityError } from "@executablemd/git/deno";
import { selectCheckout, type RegisteredCheckout } from "@executablemd/git/deno";
import type { SlotKind } from "@executablemd/git/deno";
import type { SelectionRegistry } from "@executablemd/git/deno";
import { holdManagedPlacement } from "./leases.ts";
import { retainedRecordAhead } from "./local.ts";
import { nativeGitTurn, placementSubject } from "./selections.ts";
Expand Down Expand Up @@ -152,8 +136,8 @@ export interface ReplGitCheckout {

/** What recorded local Git needs from the entry that owns it. */
export interface ReplGitEntry {
/** This entry's Git session, acquired once into the entry's own scope. */
git(): Operation<GitSession>;
/** The native repository work this entry asks for, credential-free. */
readonly operations: DenoRepositoryOperations;
/**
* Who an ordinary commit in this entry is by, or `undefined` when this host
* cannot say.
Expand Down Expand Up @@ -216,7 +200,9 @@ function* recordedAdd(entry: ReplGitEntry, invocation: GitAddInvocation): Operat
...place(entry, ADD, invocation),
paths,
});
const value = yield* recorded(entry, ADD, request, (checkout) => liveAdd(checkout, paths));
const value = yield* recorded(entry, ADD, request, (checkout, workingDirectory) =>
entry.operations.add(checkout, workingDirectory, paths),
);
if (!("paths" in value)) {
throw mismatch(ADD);
}
Expand All @@ -240,8 +226,8 @@ function* recordedSwitch(
branch,
base: base ?? null,
});
const value = yield* recorded(entry, SWITCH, request, (checkout) =>
liveSwitch(checkout, branch, base),
const value = yield* recorded(entry, SWITCH, request, (checkout, workingDirectory) =>
entry.operations.switch(checkout, workingDirectory, branch, base),
);
if (!("requestedBranch" in value)) {
throw mismatch(SWITCH);
Expand All @@ -263,11 +249,17 @@ function* recordedCommit(
messageDigest: evidence.digest,
messageLength: evidence.length,
});
const value = yield* recorded(entry, COMMIT, request, function* (checkout) {
const value = yield* recorded(entry, COMMIT, request, function* (checkout, workingDirectory) {
// Read here, because this is the operation that needs it: an entry that
// commits nothing asks the host nothing about who it is, and a completed
// Commit never reaches this callback at all.
return yield* liveCommit(checkout, message, messageSource, yield* entry.identity());
return yield* entry.operations.commit(
checkout,
workingDirectory,
message,
messageSource,
yield* entry.identity(),
);
});
if (!("commit" in value)) {
throw mismatch(COMMIT);
Expand Down Expand Up @@ -320,7 +312,7 @@ function* recorded(
entry: ReplGitEntry,
element: string,
request: ReplGitRequest,
perform: (checkout: GitCheckout) => Operation<ReplGitValue>,
perform: (checkout: RegisteredCheckout, workingDirectory: string) => Operation<ReplGitValue>,
): Operation<ReplGitValue> {
const fingerprint = yield* replGitFingerprint(request);
const name = replGitRecordName({ operation: request.operation, fingerprint });
Expand All @@ -342,9 +334,7 @@ function* recorded(
const target = yield* routing();
yield* own(entry, target);
const outcome = yield* attempted(request.operation, function* () {
return yield* perform(
liveCheckout(yield* entry.git(), target.checkout, request.workingDirectory),
);
return yield* perform(target.checkout, request.workingDirectory);
});
const payload = replGitRecordJson({ fingerprint, request, outcome });
// Read back before the coordinator publishes it. A payload this build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,31 @@ import type { ExecutionInstallation, JournalAdmission } from "@executablemd/core
import type { DurableEvent } from "@executablemd/durable-streams";
import { StaleInputError } from "@executablemd/durable-streams";
import type { Json } from "@executablemd/durable-streams";
import { issueDeclaration } from "../../composition/components/Issue.ts";
import type { BindOperations } from "../../composition/invocation-binding.ts";
import { IssueOperations } from "../../issue/operations.ts";
import { issueDeclaration } from "@executablemd/git";
import type { BindOperations } from "@executablemd/git";
import { IssueOperations } from "@executablemd/git/api";
import type {
IssueOperationsApi,
IssueReadInvocation,
IssueUpsertInvocation,
} from "../../issue/operations.ts";
import { ISSUE_EFFECT } from "../../issue/effect-type.ts";
} from "@executablemd/git/api";
import { ISSUE_EFFECT } from "@executablemd/git";
import {
issueRequestFingerprint,
parseIssueDetails,
parseIssueRecord,
parseIssueRequest,
} from "../../issue/records.ts";
import type { IssueRequest } from "../../issue/records.ts";
} from "@executablemd/git";
import type { IssueRequest } from "@executablemd/git";
import {
liveIssueRead,
liveIssueUpsert,
ownedUpsertKey,
readOwnedIssue,
upsertOwnedIssue,
} from "../../issue/owned.ts";
import { useGitHubIssues } from "../issue/github.ts";
import type { GitHubIssuesOptions } from "../issue/github.ts";
import { denoGitHubSource } from "../composition/github-host.ts";
} from "@executablemd/git";
import { useDenoGitHubIssues } from "@executablemd/git/deno";
import type { GitHubIssuesOptions } from "@executablemd/git/deno";
import type { Around } from "@effectionx/context-api";

/** Why one entry's retained issue history cannot be continued. */
Expand Down Expand Up @@ -101,14 +100,15 @@ export function replIssueInstallation(
}

/**
* The platform's transport, handed to an adapter that knows the protocol and
* not the platform.
* The platform's transport, installed without this entry naming its source.
*
* Naming it is not building one: it is a factory the adapter calls if and when
* a request turns out to be its own.
* Naming it is not building one: the adapter opens a session if and when a
* request turns out to be its own. Which concrete transport that is, and the
* credential behind it, stay inside the domain — this entry states only what a
* caller overrode, and overrides nothing itself.
*/
export function useEntryIssueTransport(options: GitHubIssuesOptions): Operation<void> {
return useGitHubIssues({ host: denoGitHubSource, ...options });
return useDenoGitHubIssues(options);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import type { Operation } from "effection";
import { StaleInputError } from "@executablemd/durable-streams";
import { useAdvisoryLock } from "@executablemd/workflow/deno";
import { commonDirectoryLock } from "../run-composition/checkout-layout.ts";
import { ManagedCheckoutError } from "../run-composition/errors.ts";
import type { ManagedCheckoutReason } from "../run-composition/errors.ts";
import { commonDirectoryLock } from "@executablemd/git/deno";
import { ManagedCheckoutError } from "@executablemd/git/deno";
import type { ManagedCheckoutReason } from "@executablemd/git/deno";

/**
* A checkout another live entry or another process is already working in.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ import {
REPL_REPOSITORY_LOCAL,
REPL_REPOSITORY_SELECTION,
retainableRefusal,
} from "../../retained-repository/records.ts";
} from "../records.ts";
import type {
ReplAmbientCheckout,
ReplLocalOperation,
ReplLocalRequest,
ReplLocalValue,
} from "../../retained-repository/records.ts";
} from "../records.ts";

/**
* A saved record standing where this operation belongs that it cannot read.
Expand Down
Loading
Loading