diff --git a/.agents/architect.md b/.agents/architect.md index b779f818b..84c5e59f7 100644 --- a/.agents/architect.md +++ b/.agents/architect.md @@ -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: diff --git a/.agents/architecture-rules.md b/.agents/architecture-rules.md index e2c2908d1..caa4963d3 100644 --- a/.agents/architecture-rules.md +++ b/.agents/architecture-rules.md @@ -32,6 +32,21 @@ 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; 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 @@ -39,6 +54,69 @@ 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 ``. +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` 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 diff --git a/.agents/planner.md b/.agents/planner.md index cbccb7cc4..d6e7803f1 100644 --- a/.agents/planner.md +++ b/.agents/planner.md @@ -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: diff --git a/.agents/product-verification.md b/.agents/product-verification.md index fa96d86b2..60e019a62 100644 --- a/.agents/product-verification.md +++ b/.agents/product-verification.md @@ -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 diff --git a/architecture.md b/architecture.md index e0c9e5261..5b6b36670 100644 --- a/architecture.md +++ b/architecture.md @@ -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 ``, 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`. 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`. 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