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
5 changes: 5 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
// the exact indentation the rule tests assert on.
// `packages/*/npm` is generated dnt output, written by a test while the
// battery runs; format-checking it fails on a file nobody wrote by hand.
// The retained-repository history fixtures are journals an older revision
// emitted, held to the SHA-256 their own receipt records: reserializing one
// changes the bytes the evidence is, and the suite that continues them
// verifies those hashes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// verifies those hashes.

"ignorePatterns": [
"**/*.md",
"scripts/tests/fixtures/**",
"packages/git/tests/fixtures/retained-repository-history/**",
"**/npm/**",
"packages/workflow/vendor/cloudflare-computer-dofs/**",
"packages/acp/vendor/acpx/**",
Expand Down
95 changes: 76 additions & 19 deletions architecture.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/cli/src/plugin-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import type {
ExecutionDeclaration,
ExecutionInstallation,
IdentityComponent,

Check warning on line 28 in packages/cli/src/plugin-host.ts

View workflow job for this annotation

GitHub Actions / lint

eslint(no-unused-vars)

Type 'IdentityComponent' is imported but never used.
IncompleteStructural,
} from "@executablemd/core/host";

Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/repl/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,11 @@ function* preflight(
return yield* validateDocument({
...inlineSource(source),
includes,
// The command's own declarations. A Plugin's vocabulary is registered in
// the scope this session was opened in, so the names an entry may write
// are already resolvable here — and metadata alone: validation resolves a
// name from a declaration and calls no factory, so nothing initializes
// policy.
components: declaredComponents(installations),
declarations: declaredMarkdown(installations),
});
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export function* useCommandSyntax(
const inputs = {
includes: [...includes],
registry: new Map(yield* Component.operations.registry),
// The engine's own invocation-named components. A Plugin's vocabulary is
// in the registry this captured above, so describing it reaches no
// identity: inspection reads metadata and calls no factory.
components: agentIdentityComponents(),
// `<Plan>` is part of the run profile, so symbols that left it out would
// describe a vocabulary no run has. Described from the packaged bytes:
Expand Down
13 changes: 9 additions & 4 deletions packages/cli/tests/repl-issues.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ function useReplRepositories(root: string): Operation<void> {
* What a command contributes to an entry's execution.
*
* Taken from the Git Plugin's own contribution for the `repl` command rather
* than written out here, because that contribution is the production path.
* than written out here, because that contribution is the production path — and
* called in the scope the session is opened in, because installing it is what
* registers the vocabulary. `<Issue>` resolves in preflight and in the entry
* through that registry, with no component catalog supplied beside it.
*/
function* replInstallations(): Operation<readonly ExecutionInstallation[]> {
const install = gitPlugin.install;
Expand Down Expand Up @@ -1485,9 +1488,11 @@ describe("how a REPL entry gets its issues", () => {
});
expect(absent.entryInstallation).toBe(undefined);

// Selecting reaches nothing, and what it selected declares both halves of
// an entry's external capability: an installation is built per entry, and
// this command has run none.
// Selecting reaches nothing, and what it selected declares exactly the
// operations an entry retains — publication and filing, and neither
// `<PullRequest>` nor its reads, which stay the defaults the command's
// scope registered. An installation is built per entry, and this command
// has run none.
const built = yield* scoped(function* (): Operation<ExecutionInstallation> {
if (factory === undefined) {
throw new Error("the production selector selected no entry capability");
Expand Down
13 changes: 10 additions & 3 deletions packages/cli/tests/repl-push.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,14 @@ function useWorkingDirectory(directory: string): Operation<void> {
);
}

/** What the Git Plugin contributes for the `repl` command. */
/**
* What the Git Plugin contributes for the `repl` command.
*
* Called in the scope the session is opened in, because installing is what
* registers the vocabulary: `<Git.Push />` resolves in preflight and in the
* entry through this registry, exactly as it does under the real command, and
* this harness supplies no component catalog of its own.
*/
function* replInstallations(): Operation<readonly ExecutionInstallation[]> {
const install = gitPlugin.install;
if (install === undefined) {
Expand Down Expand Up @@ -424,8 +431,8 @@ describe("how a REPL entry gets its publication capability", () => {
}

// Building one reaches nothing — no directory, no `git`, no credential and
// no managed root — and what it declares is both of an entry's external
// capabilities.
// no managed root — and what it declares is the two operations this entry
// retains, under names the command's scope has already registered.
const built = yield* scoped(function* (): Operation<ExecutionInstallation> {
return yield* factory('["executablemd.repl.issue-owner",1,"execution-cli-6","1"]');
});
Expand Down
10 changes: 5 additions & 5 deletions packages/git/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ export { replRepositoryInstallation } from "./src/deno/retained-repository/provi
/**
* One REPL entry's issue capability, as the inert value the CLI selects.
*
* The installation alone. What it builds — the entry's own `<Issue>`
* implementation, the identity behind it, the admission over its history and
* the transport-facing default inside a live attempt — stays inside it: a
* package that could reach one of those could file an issue under an entry it
* does not own.
* The installation alone. What it builds — the common `<Issue>` declaration,
* the policy behind it, the admission over its history and the
* transport-facing default inside a live attempt — stays inside it: a package
* that could reach one of those could file an issue under an entry it does not
* own.
*/
export { replIssueInstallation } from "./src/deno/retained-repository/issues.ts";
8 changes: 8 additions & 0 deletions packages/git/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ export { gitDirectorySelection } from "./src/composition/definitions.ts";
* one this package ships.
*/
export { declaresFor as gitPluginDeclaresFor } from "./src/plugin.ts";
/**
* Whether this command's executions install their own policy under the six
* operations no local transaction encloses, rather than registering them.
*
* Named for the Plugin it classifies, for the same reason as the predicate
* above: a host assembling a command needs this answer and the vocabulary's own
* gating to agree.
*/
export { GitObjectError, GitRepositoryError, GitRevisionError } from "./src/git.ts";
export type { GitObjectFormat } from "./src/git.ts";
export {
Expand Down
94 changes: 58 additions & 36 deletions packages/git/src/composition/components/GitPush.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@
* Publish the contextual checkout's current branch to its Repository origin
* (specs/workflow-workspace-spec.md §7.4).
*
* `<Git.Push />` takes no props or content, renders nothing and binds nothing.
* The enclosing Repository and cwd select the checkout, including a linked
* worktree reached through Dir. Remote, branch, commit, refspec and force are not
* authored inputs; reconciliation evidence stays in the journal.
*
* The remote cannot join the local transaction. Shared Git-host reconciliation
* observes first, adopts this exact commit, publishes a proven absence or advances
* a proven ancestor once, and refuses unproven compatibility. It never force-pushes
* or changes upstream tracking.
*
* Git.Switch's failure policy applies. Detached HEAD is a fixed GitOperationError
* before remote contact; missing Repository is an admission failure that printing
* cannot recover.
* worktree reached through Dir. Remote, branch, commit, refspec and force are
* not authored inputs, and the remote cannot join the local transaction: what
* reconciliation proves before it publishes belongs to the host policy beneath
* this body and stays in the journal.
*/

import { cwd } from "@executablemd/runtime";
import { hasContent } from "@executablemd/core";
import type { FirstPartyDocumentation, PropsSchema } from "@executablemd/core";
import { documented, formDispatcher } from "@executablemd/core";
import type {
ComponentRegistration,
FirstPartyDocumentation,
FunctionComponent,
PropsSchema,
} from "@executablemd/core";
import type { IdentityClaimant, IdentityComponent } from "@executablemd/core/host";
import type { Operation } from "effection";
import type { Json } from "@executablemd/durable-streams";
import { Git } from "../git-api.ts";
import type { GitApi } from "../git-api.ts";
import { COMPOSITION_ORIGIN } from "../definitions.ts";
import { selectedRepository } from "../context.ts";
import { GitOperationAdmissionError, GitOperationError } from "../errors.ts";
import { boundComponent } from "../invocation-binding.ts";
import type { BindOperations, ComponentBody } from "../invocation-binding.ts";

/** The component name, as a document writes it and as a refusal names it. */
export const PUSH = "<Git.Push>";

export const props: PropsSchema = {
Expand All @@ -35,13 +35,6 @@ export const props: PropsSchema = {
additionalProperties: false,
};

/**
* What a reader is told about this component, in one place.
*
* Declared here rather than beside a registration because there are two: the
* bundled vocabulary's, and the one a REPL entry installs with its own identity
* behind it. Inspection describes one `Git.Push`, so both read this.
*/
export const documentation: FirstPartyDocumentation = {
description:
`Publish the current branch. \`<Git.Push />\` pushes its current commit to the same branch ` +
Expand All @@ -50,15 +43,10 @@ export const documentation: FirstPartyDocumentation = {
context: null,
};

export default function* GitPush(_props: Record<string, Json>): Operation<string> {
if (yield* hasContent()) {
throw new GitOperationError(
PUSH,
"invalid-invocation",
`${PUSH} renders nothing, so it takes no content. Write it as <Git.Push />.`,
);
}

export const body: ComponentBody<GitApi["push"]> = function* GitPush(
_props: Record<string, Json>,
push: GitApi["push"],
): Operation<Json> {
const repository = yield* selectedRepository();
if (repository === undefined) {
throw new GitOperationAdmissionError(
Expand All @@ -67,10 +55,44 @@ export default function* GitPush(_props: Record<string, Json>): Operation<string
"origin a branch could be published to",
);
}
yield* push({ repository, workingDirectory: yield* cwd() });
return "";
};

const METADATA = {
name: "Git.Push",
origin: COMPOSITION_ORIGIN,
props,
forms: ["self-closing"] as const,
...documented(documentation),
};

yield* Git.operations.push({
repository,
workingDirectory: yield* cwd(),
/**
* The form comes from the scan, not from a contextual `hasContent()` answer: an
* irreversible publication is not something an installed handler may select.
*/
function dispatched(fn: FunctionComponent): FunctionComponent {
return formDispatcher({
forms: "self-closing",
fn,
refuse: () =>
new GitOperationError(
PUSH,
"invalid-invocation",
`${PUSH} renders nothing, so it takes no content. Write it as <Git.Push />.`,
),
});
return "";
}

/** An ordinary registered default, over whichever provider the host installed. */
export function pushRegistration(push: GitApi["push"]): ComponentRegistration {
return { ...METADATA, fn: dispatched((props) => body(props, push)) };
}

/** A declaration, for an execution that owns this operation's policy. */
export function pushDeclaration(bind: BindOperations<GitApi["push"]>): IdentityComponent {
return {
...METADATA,
factory: (claim: IdentityClaimant) => dispatched(boundComponent(claim, bind, body)),
};
}
Loading
Loading