Skip to content

Commit e4dbb84

Browse files
authored
Merge pull request #954 from taras/agent/require-justified-mechanisms
Define the security model and hook API contract
2 parents dd74be5 + b5f56b1 commit e4dbb84

5 files changed

Lines changed: 203 additions & 0 deletions

File tree

‎.agents/architect.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ Tests and fake consumers created only to exercise proposed machinery do not
153153
establish its necessity. Distinguish the accepted requirement from the proposed
154154
mechanism before settling the design.
155155

156+
Apply the [security model](../architecture.md#security-model) when judging
157+
defensive mechanisms. Identify reliability and compatibility needs separately.
158+
156159
When the Product Owner says they do not understand, stop the current design
157160
discussion and run this interview before continuing:
158161

‎.agents/architecture-rules.md‎

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

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

57+
Rule 26 sets the [extension trust boundary](../architecture.md#security-model).
58+
Defending against deliberately malicious installed extension code does not
59+
justify a security mechanism or an adversarial test. Trusted code can still
60+
contain bugs; supported API behavior remains part of each feature's contract.
61+
62+
Rule 27 applies at generated-evaluation entry points such as `<Evaluate>`.
63+
A read-only evaluation refuses a generated file write or shell command before
64+
any effect. The admitted component implementations remain trusted under rule 26.
65+
66+
Rule 28 applies to documents a caller chooses to run, such as
67+
`xmd run document.md`, including their `exec` and `eval` blocks. Choosing a
68+
document requires trusting its contents. Text passed to generated evaluation
69+
remains subject to rule 27.
70+
71+
Rule 29 covers service responses, file contents and saved records. Security
72+
tests exercise inputs the boundary can receive: missing fields and invalid
73+
values in a JSON response, for example. Deliberately malicious JavaScript
74+
getters require executable code and fall under rule 26.
75+
76+
Rule 30 protects continuation from corrupted or mismatched records. XMD makes
77+
no authenticity guarantee against someone who can rewrite the underlying local
78+
database and construct a consistent replacement history.
79+
80+
Rule 31 allows useful paths and ordinary error details in diagnostics. An
81+
absolute path is not sensitive merely because it is absolute; a path explicitly
82+
identified as sensitive still receives protection. Relative-path formats can
83+
also serve portability and compatibility independently of secrecy.
84+
85+
Rule 32 includes reliability requirements such as correct replay, preventing
86+
duplicate external writes and completing cancellation cleanup. Additional
87+
adversarial tests cover distinct failure paths that matter under the security
88+
model or a supported reliability contract.
89+
90+
Rule 33 keeps invocation control with the operation's owner. A hook does not
91+
receive `next` and cannot omit or repeat a delegation. It can refuse execution
92+
by throwing before the underlying operation begins.
93+
94+
Rules 34–35 keep the operation's outcome separate from a hook's failure. An
95+
after hook receives success or failure and returns no replacement value. If
96+
the operation succeeded and an after hook fails, the call reports the hook
97+
failure while the completed effect and its saved result remain unchanged.
98+
99+
Rule 36 orders each independently registered set of callbacks around the
100+
operation, matching middleware's nesting order.
101+
102+
Rules 37–38 cover success, failure, cancellation and refusal by any before
103+
hook. After callbacks observe a before-hook rejection even though the underlying
104+
operation never starts. A before hook interrupted partway through uses its own
105+
Effection cleanup for anything it acquired. Independent registrations have no
106+
matching-pair requirement.
107+
108+
Rule 39 gives `createHooks<"expand", TArgs, TResult>(...)` the returned members
109+
`expand`, `beforeExpand` and `afterExpand`. The registration functions accept
110+
handlers returning `Operation<void>` and give them neither `next` nor a
111+
replacement return channel. The adapter keeps its middleware private;
112+
operations offering continuation control retain their existing `.around(...)`.
113+
114+
Rule 40 uses `Ok(value)` for success and `Err(error)` for a before-hook
115+
rejection or implementation failure. Cancellation uses the same `Err` channel
116+
with a dedicated error type. This is the after-hook notification; it does not
117+
turn cancellation into an ordinary returned failure. All after hooks receive
118+
the outcome selected before they run, independent of other after-hook failures.
119+
42120
The Architect applies these rules without another approval when they settle a
43121
design. A new concept, ambiguous fit, conflict or proposed exception returns to
44122
the Product Owner through the interview in

‎.agents/planner.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ Distinguish accepted behavioral and structural requirements from proposed
9696
mechanics. Do not turn an unsupported mechanism into an acceptance criterion
9797
and then use tests of that criterion to justify the mechanism.
9898

99+
Apply the [security model](../architecture.md#security-model) when justifying
100+
defensive mechanisms. Identify reliability and compatibility needs separately.
101+
99102
## Plan contents
100103

101104
The plan records:

‎.agents/product-verification.md‎

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

1516
## Feature review
1617

‎architecture.md‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,124 @@ prove that the earlier effect happened. These distinctions govern
5757

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

60+
### Security model
61+
62+
Installed plugins, providers, middleware and TypeScript components are trusted
63+
application code. XMD does not contain malicious behavior from these extensions.
64+
Their code runs with whatever access the surrounding runtime permits; selecting
65+
an extension requires trusting that code.
66+
67+
Documents explicitly selected for execution, such as `xmd run document.md`, are
68+
trusted programs. Their `exec` and `eval` blocks run with the access their host
69+
and runtime permit. Choosing to run a document requires trusting its contents.
70+
71+
Text passed to generated evaluation, including `<Evaluate>`, is untrusted
72+
input. The evaluator enforces the host's allowed components and effects before
73+
performing any effects. For example, a read-only evaluation refuses a generated
74+
file write or shell command before any effect. The admitted component
75+
implementations remain trusted application code.
76+
77+
Service responses, file contents and saved records are external data. The API
78+
that owns their interpretation validates them at entry; internal code may rely
79+
on the validated contract. Security tests exercise inputs that boundary can
80+
receive. Missing fields and invalid values in a JSON response are relevant;
81+
deliberately malicious JavaScript getters require executable code covered by
82+
the trusted-extension assumption.
83+
84+
Saved history is checked for format, supported version and consistency with
85+
the intended run and effects. These checks protect continuation from corrupted
86+
or mismatched records. XMD makes no authenticity guarantee against someone who
87+
can deliberately rewrite the underlying local database and construct a
88+
consistent replacement history.
89+
90+
Credentials and explicitly identified secrets receive protection against
91+
accidental disclosure. Paths and ordinary error details are not inherently
92+
secret. An absolute project path can appear in a diagnostic; a path explicitly
93+
identified as sensitive still receives protection. Relative-path formats can
94+
serve portability and compatibility independently of secrecy.
95+
96+
A deliberately malicious installed extension is outside the project's threat
97+
model. A security mechanism or adversarial test needs a threat within the model
98+
to justify its complexity; malicious extension behavior alone supplies no such
99+
justification.
100+
101+
Trusted code can still contain bugs. Supported API behavior remains part of
102+
each feature's contract.
103+
104+
Defensive complexity needs a concrete in-scope threat or consequential failure
105+
to justify it, with the simplest sufficient implementation and evidence.
106+
Correct replay, preventing duplicate external writes and completing
107+
cancellation cleanup can justify defenses as reliability requirements.
108+
Additional adversarial tests cover distinct failure paths that matter under
109+
this model or a supported reliability contract.
110+
111+
### Middleware and hooks
112+
113+
The hook adapter below is an approved design contract. The current packages
114+
do not implement `createHooks`.
115+
116+
An API's extension contract distinguishes control over execution from control
117+
over values. Middleware can omit or repeat delegation and transform inputs or
118+
results where that API permits it. Operations whose handlers may only observe
119+
use before/after hooks, with invocation controlled by the operation's owner.
120+
121+
The hook primitive composes the existing `createApi` without modifying it.
122+
`createHooks<"expand", TArgs, TResult>(...)` returns the callable `expand` and
123+
the independent registration functions `beforeExpand` and `afterExpand`:
124+
125+
```ts
126+
yield* beforeExpand(recordExpansionStart);
127+
yield* afterExpand(recordExpansionEnd);
128+
const result = yield* expand(args);
129+
```
130+
131+
Both handlers return `Operation<void>`. Before observes the arguments; after
132+
observes those arguments and the outcome reached before after hooks run,
133+
including cancellation. Neither receives `next` or returns replacement data.
134+
The adapter keeps its middleware private. Operations offering continuation
135+
control retain their existing `.around(...)`.
136+
137+
The after-hook outcome is Effection's `Result<TResult>`. A successful
138+
implementation supplies `Ok(value)`; a before-hook rejection or implementation
139+
failure supplies `Err(error)`. Cancellation supplies `Err` containing a dedicated
140+
cancellation error. This representation is a notification to after hooks; the
141+
call continues to unwind as cancellation. All after hooks receive the same
142+
outcome selected before they run.
143+
144+
A before hook receives no continuation and returns no replacement input.
145+
Returning normally lets the owner proceed automatically; throwing prevents the
146+
underlying operation from starting. A hook cannot omit or repeat a delegation
147+
because it receives no `next`.
148+
149+
An after hook receives the operation's success or failure and returns no
150+
replacement value. If the operation succeeded and an after hook fails, the
151+
call reports the hook failure; the completed operation and its saved result
152+
remain unchanged. If both fail, the operation's failure stays primary and the
153+
hook failure is reported alongside it. The hook primitive never repeats the
154+
operation to recover from a hook failure.
155+
156+
Hooks nest in the same order as middleware: before hooks run from outer to
157+
inner, and after hooks run from inner to outer.
158+
159+
```text
160+
A.before
161+
B.before
162+
operation
163+
B.after
164+
A.after
165+
```
166+
167+
Registered after hooks run when the call unwinds on success, failure or
168+
cancellation, including when a before hook rejects execution. They observe that
169+
rejection while the underlying operation remains unexecuted. Before and after
170+
registrations have no matching-pair requirement. One failing after hook does not
171+
prevent the remaining after hooks from running. They observe cancellation
172+
explicitly, and cancellation remains cancellation.
173+
174+
The call owns all hook work and waits for its cleanup. A before hook
175+
interrupted partway through uses its own Effection cleanup for anything it
176+
acquired.
177+
60178
### Three axes
61179

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

0 commit comments

Comments
 (0)