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
64 changes: 47 additions & 17 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ The `@executablemd/workflow` package owns `WorkflowRun`, `useWorkflow()`,
`getWorkflowRun()` and the Git capability. It depends on `@executablemd/core`,
`@executablemd/durable-streams` and `@executablemd/runtime`, whose contextual
`exec()` and `cwd()` the Git provider invokes; core never imports workflow or
Git. The CLI lifecycle is `xmd workflow start` and `xmd workflow resume`; the
durable lookup resumption requires is the run storage below. Ordinary `xmd run`
remains unchanged.
Git. The future CLI lifecycle is `xmd workflow start` and `xmd workflow resume`;
there is no workflow CLI execution branch yet. The durable lookup that resume
will require is the run storage below. Ordinary `xmd run` remains unchanged.

## Workflow run storage

Expand Down Expand Up @@ -310,9 +310,10 @@ member value can.

The command selects the environment; the document describes the procedure.
`xmd run` uses the caller's current environment and makes no restoration
promise. `xmd workflow` creates a workflow run with one implicit root Workspace.
The same declarative components use contextual capabilities in both modes; the
workflow host supplies the stronger durability and authority boundary.
promise. The specified future `xmd workflow` command creates a workflow run with
one implicit root Workspace. The same declarative components use contextual
capabilities in both modes; the workflow host supplies the stronger durability
and authority boundary.

Workspace identity belongs to the workflow run. A document path locates an
immutable definition but does not identify a previous run. A host-generated or
Expand Down Expand Up @@ -391,11 +392,11 @@ external branches, pushes, pull requests or provider state.

## Agent authority and generated XMD

An Agent under `xmd workflow` is read-only. The host enforces that ceiling in
the permission bridge, the provider-native sandbox and the filesystem view; a
document cannot raise it. A provider that cannot enforce the boundary fails
before Prompt execution. `xmd run` keeps its caller-selected Agent permission
behavior.
An Agent under the specified future `xmd workflow` command is read-only. The
host enforces that ceiling in the permission bridge, the provider-native sandbox
and the filesystem view; a document cannot raise it. A provider that cannot
enforce the boundary fails before Prompt execution. `xmd run` keeps its
caller-selected Agent permission behavior.

Native Agent processes inspect disposable read-only materializations of the
current logical Workspace root. Those views have no write-back path. An Agent
Expand Down Expand Up @@ -625,7 +626,19 @@ through middleware; waiting itself is never raised.
### 8. Durability failures are outside the model

A durability failure (Β§6.11) says the journal no longer describes the document
execution. No middleware sees it; it is never the document's own outcome.
execution. No middleware sees it; it is never the document's own outcome. A
backing-journal append failure is the same kind of boundary failure: it
preserves the adapter error as its cause, resumes no consumer with an
unpersisted success, and never triggers a compensating `Close`. The first
durability failure is shared by the root and every durable child. From that
point, durable entry fails with that exact error before replay or execution,
and the ordered append boundary rechecks it immediately before storage. Work
already executing may finish. Concurrent work that began earlier cannot be
undone, but its not-yet-started append is fenced. An unmarked backing-stream
rejection is a persistence failure regardless of the error class the adapter
throws. A pre-persistence policy rejection remains the policy's ordinary
document failure; the guarded stream marks that boundary before the backing
append and does not activate the fail-stop state.

## Attempts

Expand Down Expand Up @@ -673,10 +686,27 @@ replaced by a live one.
Service attachment and `ephemeral eval` execute again during partial replay so
the current process and middleware chain are reconstructed. A completed
document replay returns its recorded result without expanding the document and
therefore starts no service. Workflow execution installs a non-delegating
`API.Service` denial provider: a workflow cannot reach an inherited host
adapter, because a run-owned durable service requires stable identity and
reconciliation rather than an execution-owned live process.
therefore starts no service. Ordinary durable eval transforms a block and
validates its declared exports against the live overlay before constructing its
durable effect. A collision therefore has no eval `Yield`; compatible partial
replay remains aligned, while retained history containing a now-incompatible
successful eval is rejected by the existing replay guard or divergence path.
Every root and child termination checks its retained subtree before appending a
`Close`. The replay index tracks which coroutine identities the current
definition claims, so a completed retained child is not mistaken for aligned
history merely because it already has a `Close`. Durability failures take
precedence over ordinary termination even after the last retained `Yield` was
consumed. If collision handling terminates immediately,
`TerminalDivergenceError` retains the collision as its cause and the history
receives no terminal event; restoring the compatible definition can still
replay it.

#390 provides and tests the non-delegating `useWorkflowServiceDenial()` provider.
#366 will install it in the future `xmd workflow start` and `xmd workflow resume`
scopes. No workflow CLI execution branch exists yet. The provider prevents a
workflow from reaching an inherited host adapter, because a run-owned durable
service requires stable identity and reconciliation rather than an
execution-owned live process.

## State ownership

Expand Down Expand Up @@ -735,7 +765,7 @@ Status is measured against main.
| `API.Service` / `startService()` | creates an authenticated, supervised loopback service attachment through a provider-neutral operation | built on main |
| `service=<binding>` | publishes the attachment's endpoint into the live binding overlay for its invocation | built on main |
| `ephemeral eval` | reconstructs live middleware and bindings without a journal entry | built on main |
| workflow service denial | prevents workflow documents from inheriting an ordinary host service adapter | built on main |
| `useWorkflowServiceDenial()` | provides and tests a non-delegating workflow service denial provider; #366 will install it in future start and resume scopes | built on main; no workflow CLI execution branch exists yet |
| `xmd workflow start` / `xmd workflow resume` | starts or resumes a workflow run from the CLI | defined in `specs/workflow-workspace-spec.md`, unbuilt; the lookup it resumes through is built |
| implicit workflow Workspace | retains provider-neutral filesystem, repository and attachment state by run ID | defined in `specs/workflow-workspace-spec.md`, unbuilt (#218) |
| Repository / Worktree / transactional Git effects | compose named checkouts and publish local mutations with their journal result | defined in `specs/workflow-workspace-spec.md`, unbuilt |
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import type { Context, Operation } from "effection";
import {
ContinuePastCloseDivergenceError,
DivergenceError,
EarlyReturnDivergenceError,
DurablePersistenceError,
StaleInputError,
TerminalDivergenceError,
} from "@executablemd/durable-streams";
import { InvocationTeardownError } from "./invocation.ts";
import type { ErrorSegment } from "./types.ts";
Expand Down Expand Up @@ -186,7 +187,8 @@ export class ContentError extends Error {
export type DurabilityFailure =
| StaleInputError
| DivergenceError
| EarlyReturnDivergenceError
| DurablePersistenceError
| TerminalDivergenceError
| ContinuePastCloseDivergenceError;

/** A failure that ends the execution rather than becoming a printed error. */
Expand Down Expand Up @@ -287,7 +289,8 @@ function asDurabilityFailure(error: unknown): DurabilityFailure | undefined {
if (
error instanceof StaleInputError ||
error instanceof DivergenceError ||
error instanceof EarlyReturnDivergenceError ||
error instanceof DurablePersistenceError ||
error instanceof TerminalDivergenceError ||
error instanceof ContinuePastCloseDivergenceError
) {
return error;
Expand Down
4 changes: 1 addition & 3 deletions packages/core/src/eval-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export const evalFactory: ModifierFactory = (_params) => (_args, _next) =>
};

const transformed = transformBlock(ctx.content, ctx.blockId, Object.keys(evalEnv.values));
validateDurableExports(transformed.exports, liveEnvironment(evalEnv));

const bindings = serializeExports(evalEnv.values, transformed.imports);
const result = (yield createDurableOperation<Json>(
Expand Down Expand Up @@ -209,9 +210,6 @@ export const evalFactory: ModifierFactory = (_params) => (_args, _next) =>

return { value: exports as unknown as Json } as Json;
},
{
validate: () => validateDurableExports(transformed.exports, liveEnvironment(evalEnv)),
},
)) as unknown as { value: Json };

if (result.value && typeof result.value === "object") {
Expand Down
Loading
Loading