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
3 changes: 3 additions & 0 deletions .agents/architect.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ Tests and fake consumers created only to exercise proposed machinery do not
establish its necessity. Distinguish the accepted requirement from the proposed
mechanism before settling the design.

Apply the [security model](../architecture.md#security-model) when judging
defensive mechanisms. Identify reliability and compatibility needs separately.

When the Product Owner says they do not understand, stop the current design
discussion and run this interview before continuing:

Expand Down
78 changes: 78 additions & 0 deletions .agents/architecture-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,91 @@ the system itself; this file governs how new architecture is designed.
23. Use methods on an owned handle for operations tied to that instance.
24. Use a Context to select a scoped service; capture it for any lifetime that requires a fixed provider.
25. Use middleware when per-call interception or transformation is an intended extension point.
26. Installed plugins, providers, middleware and TypeScript components are trusted application code; XMD does not contain malicious extensions.
27. Generated evaluation treats its input as untrusted and enforces the host's allowed operations before performing effects.
28. Documents explicitly selected for execution are trusted programs that run with the access their host and runtime permit.
29. Validate external data where its owning API interprets it; internal code may rely on the validated contract.
30. Validate saved history for format, supported version and run/effect consistency; deliberate rewriting of local storage is outside the threat model.
31. Protect credentials and explicitly identified secrets from accidental disclosure; paths and ordinary error details are not inherently secret.
32. Justify defensive complexity with an in-scope threat or consequential failure, and use the simplest sufficient implementation and evidence.
33. Before hooks receive no continuation; returning normally proceeds automatically, while throwing prevents the operation.
34. After hooks observe outcomes without replacing them; hook failures leave completed effects unchanged and never trigger an automatic retry.
35. When an operation and its after hook both fail, keep the operation's failure primary and report the hook failure alongside it.
36. Before hooks run from outer to inner; after hooks run from inner to outer.
37. Registered after hooks run on unwind, including before-hook rejection; one failing after hook does not prevent the remaining hooks from running.
38. A call owns its hooks and waits for their cleanup; after hooks observe cancellation explicitly and cancellation remains cancellation.
39. createHooks uses unchanged createApi and returns the named operation plus independent before/after registration functions.
40. After hooks receive Effection Result<T>; cancellation carries a dedicated cancellation error while the call continues to unwind as cancelled.

Rules 23–25 choose the API by ownership and intended composition. A captured
service keeps provider selection fixed for its consumer's lifetime; its mutable
configuration still follows that service's own update contract. A Context
selects a trusted service, while authorization, durable identity,
reconciliation and canonical outcomes stay with their owners.

Rule 26 sets the [extension trust boundary](../architecture.md#security-model).
Defending against deliberately malicious installed extension code does not
justify a security mechanism or an adversarial test. Trusted code can still
contain bugs; supported API behavior remains part of each feature's contract.

Rule 27 applies at generated-evaluation entry points such as `<Evaluate>`.
A read-only evaluation refuses a generated file write or shell command before
any effect. The admitted component implementations remain trusted under rule 26.

Rule 28 applies to documents a caller chooses to run, such as
`xmd run document.md`, including their `exec` and `eval` blocks. Choosing a
document requires trusting its contents. Text passed to generated evaluation
remains subject to rule 27.

Rule 29 covers service responses, file contents and saved records. Security
tests exercise inputs the boundary can receive: missing fields and invalid
values in a JSON response, for example. Deliberately malicious JavaScript
getters require executable code and fall under rule 26.

Rule 30 protects continuation from corrupted or mismatched records. XMD makes
no authenticity guarantee against someone who can rewrite the underlying local
database and construct a consistent replacement history.

Rule 31 allows useful paths and ordinary error details in diagnostics. An
absolute path is not sensitive merely because it is absolute; a path explicitly
identified as sensitive still receives protection. Relative-path formats can
also serve portability and compatibility independently of secrecy.

Rule 32 includes reliability requirements such as correct replay, preventing
duplicate external writes and completing cancellation cleanup. Additional
adversarial tests cover distinct failure paths that matter under the security
model or a supported reliability contract.

Rule 33 keeps invocation control with the operation's owner. A hook does not
receive `next` and cannot omit or repeat a delegation. It can refuse execution
by throwing before the underlying operation begins.

Rules 34–35 keep the operation's outcome separate from a hook's failure. An
after hook receives success or failure and returns no replacement value. If
the operation succeeded and an after hook fails, the call reports the hook
failure while the completed effect and its saved result remain unchanged.

Rule 36 orders each independently registered set of callbacks around the
operation, matching middleware's nesting order.

Rules 37–38 cover success, failure, cancellation and refusal by any before
hook. After callbacks observe a before-hook rejection even though the underlying
operation never starts. A before hook interrupted partway through uses its own
Effection cleanup for anything it acquired. Independent registrations have no
matching-pair requirement.

Rule 39 gives `createHooks<"expand", TArgs, TResult>(...)` the returned members
`expand`, `beforeExpand` and `afterExpand`. The registration functions accept
handlers returning `Operation<void>` and give them neither `next` nor a
replacement return channel. The adapter keeps its middleware private;
operations offering continuation control retain their existing `.around(...)`.

Rule 40 uses `Ok(value)` for success and `Err(error)` for a before-hook
rejection or implementation failure. Cancellation uses the same `Err` channel
with a dedicated error type. This is the after-hook notification; it does not
turn cancellation into an ordinary returned failure. All after hooks receive
the outcome selected before they run, independent of other after-hook failures.

The Architect applies these rules without another approval when they settle a
design. A new concept, ambiguous fit, conflict or proposed exception returns to
the Product Owner through the interview in
Expand Down
3 changes: 3 additions & 0 deletions .agents/planner.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ Distinguish accepted behavioral and structural requirements from proposed
mechanics. Do not turn an unsupported mechanism into an acceptance criterion
and then use tests of that criterion to justify the mechanism.

Apply the [security model](../architecture.md#security-model) when justifying
defensive mechanisms. Identify reliability and compatibility needs separately.

## Plan contents

The plan records:
Expand Down
1 change: 1 addition & 0 deletions .agents/product-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ after the product behavior is approved.
2. Product verification covers user journeys, observable behavior, important failures, recovery and incomplete implementations.
3. Interface and product verification approval precede finalized architecture or implementation handoffs.
4. The Architect and Planner derive lower-level tests from approved product verification without further approval.
5. Add adversarial tests for distinct failure paths that matter under the security model or a supported reliability contract.

## Feature review

Expand Down
118 changes: 118 additions & 0 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,124 @@ prove that the earlier effect happened. These distinctions govern

Start with the properties every execution shares: predictable behavior, scoped ownership and composition across independently loaded package copies.

### Security model

Installed plugins, providers, middleware and TypeScript components are trusted
application code. XMD does not contain malicious behavior from these extensions.
Their code runs with whatever access the surrounding runtime permits; selecting
an extension requires trusting that code.

Documents explicitly selected for execution, such as `xmd run document.md`, are
trusted programs. Their `exec` and `eval` blocks run with the access their host
and runtime permit. Choosing to run a document requires trusting its contents.

Text passed to generated evaluation, including `<Evaluate>`, is untrusted
input. The evaluator enforces the host's allowed components and effects before
performing any effects. For example, a read-only evaluation refuses a generated
file write or shell command before any effect. The admitted component
implementations remain trusted application code.

Service responses, file contents and saved records are external data. The API
that owns their interpretation validates them at entry; internal code may rely
on the validated contract. Security tests exercise inputs that boundary can
receive. Missing fields and invalid values in a JSON response are relevant;
deliberately malicious JavaScript getters require executable code covered by
the trusted-extension assumption.

Saved history is checked for format, supported version and consistency with
the intended run and effects. These checks protect continuation from corrupted
or mismatched records. XMD makes no authenticity guarantee against someone who
can deliberately rewrite the underlying local database and construct a
consistent replacement history.

Credentials and explicitly identified secrets receive protection against
accidental disclosure. Paths and ordinary error details are not inherently
secret. An absolute project path can appear in a diagnostic; a path explicitly
identified as sensitive still receives protection. Relative-path formats can
serve portability and compatibility independently of secrecy.

A deliberately malicious installed extension is outside the project's threat
model. A security mechanism or adversarial test needs a threat within the model
to justify its complexity; malicious extension behavior alone supplies no such
justification.

Trusted code can still contain bugs. Supported API behavior remains part of
each feature's contract.

Defensive complexity needs a concrete in-scope threat or consequential failure
to justify it, with the simplest sufficient implementation and evidence.
Correct replay, preventing duplicate external writes and completing
cancellation cleanup can justify defenses as reliability requirements.
Additional adversarial tests cover distinct failure paths that matter under
this model or a supported reliability contract.

### Middleware and hooks

The hook adapter below is an approved design contract. The current packages
do not implement `createHooks`.

An API's extension contract distinguishes control over execution from control
over values. Middleware can omit or repeat delegation and transform inputs or
results where that API permits it. Operations whose handlers may only observe
use before/after hooks, with invocation controlled by the operation's owner.

The hook primitive composes the existing `createApi` without modifying it.
`createHooks<"expand", TArgs, TResult>(...)` returns the callable `expand` and
the independent registration functions `beforeExpand` and `afterExpand`:

```ts
yield* beforeExpand(recordExpansionStart);
yield* afterExpand(recordExpansionEnd);
const result = yield* expand(args);
```

Both handlers return `Operation<void>`. Before observes the arguments; after
observes those arguments and the outcome reached before after hooks run,
including cancellation. Neither receives `next` or returns replacement data.
The adapter keeps its middleware private. Operations offering continuation
control retain their existing `.around(...)`.

The after-hook outcome is Effection's `Result<TResult>`. A successful
implementation supplies `Ok(value)`; a before-hook rejection or implementation
failure supplies `Err(error)`. Cancellation supplies `Err` containing a dedicated
cancellation error. This representation is a notification to after hooks; the
call continues to unwind as cancellation. All after hooks receive the same
outcome selected before they run.

A before hook receives no continuation and returns no replacement input.
Returning normally lets the owner proceed automatically; throwing prevents the
underlying operation from starting. A hook cannot omit or repeat a delegation
because it receives no `next`.

An after hook receives the operation's success or failure and returns no
replacement value. If the operation succeeded and an after hook fails, the
call reports the hook failure; the completed operation and its saved result
remain unchanged. If both fail, the operation's failure stays primary and the
hook failure is reported alongside it. The hook primitive never repeats the
operation to recover from a hook failure.

Hooks nest in the same order as middleware: before hooks run from outer to
inner, and after hooks run from inner to outer.

```text
A.before
B.before
operation
B.after
A.after
```

Registered after hooks run when the call unwinds on success, failure or
cancellation, including when a before hook rejects execution. They observe that
rejection while the underlying operation remains unexecuted. Before and after
registrations have no matching-pair requirement. One failing after hook does not
prevent the remaining after hooks from running. They observe cancellation
explicitly, and cancellation remains cancellation.

The call owns all hook work and waits for its cleanup. A before hook
interrupted partway through uses its own Effection cleanup for anything it
acquired.

### Three axes

The UX has 3 axes: determinism, resilience and debuggability. If we pull too
Expand Down
Loading