Skip to content

2.0.5#573

Merged
adamavenir merged 72 commits intomainfrom
2.0.5
Mar 24, 2026
Merged

2.0.5#573
adamavenir merged 72 commits intomainfrom
2.0.5

Conversation

@adamavenir
Copy link
Copy Markdown
Collaborator

@adamavenir adamavenir commented Mar 24, 2026

Added

  • Wildcard index [*] for array element projection: @arr[*].field extracts a field from every element, producing a flat array. Works anywhere arrays appear — variables, guard expressions, template interpolation. Combines naturally with .includes() for membership checks like @mx.tools.history[*].name.includes("verify").
  • Tool provenance rollout: .mlld/sec/audit.jsonl now assigns a stable id to every event, exe/native tool invocations emit toolCall records with args/timing/result summaries, result values carry .mx.tools lineage with audit references, and guards now expose that lineage as @mx.tools.history.
  • Dynamic MCP tool collections: var tools @name = mcp @expr now builds a first-class ToolCollection directly from runtime-discovered MCP server tools, complementing the existing import tools from mcp "..." flow.
  • SDK mcp_servers option on execute() and process(): maps logical names to MCP server commands per-execution. import tools from mcp "name" resolves against the map before treating the spec as a shell command. Enables parallel executions with independent MCP server instances. Supported in TypeScript, Python, and live stdio transport.
  • MCP tool argument coercion: arguments are automatically coerced to match the declared inputSchema types. String-to-array wrapping ("x"["x"]), string-to-number/integer, string-to-boolean, string-to-null, and JSON string parsing for object/array types. Runs at the MCP call boundary using schema type info from tools/list.
  • MCP tool argument name matching: when calling an MCP tool with variable-reference arguments whose names match schema property names, arguments are matched by name instead of position. Exe wrappers can declare params in any order (e.g. control args first) and mlld routes them to the correct MCP schema properties.
  • Policy authorization bundles phase 1: policy.authorizations now enforces task-scoped default-deny envelopes for tool:w operations, supports tolerant literal/eq/oneOf arg pinning, reads trusted controlArgs from var tools metadata, composes across policy layers, and fails closed when invalid fragments are activated through with { policy }. mlld validate and mlld analyze now also report unknown tools/args, missing control-arg coverage, and unconstrained authorizations with context-aware diagnostics.
  • Tolerant comparison operators ~= and !~= for guard/when/expression conditions. The new matcher handles string vs single-item array coercion, order-independent flat arrays, subset matching for array expectations, null/empty equivalence when the expected side is empty, numeric string coercion, and comma-separated string to array matching. This makes LLM-produced guard args much easier to validate without hand-written normalization logic.
  • mlld validate / mlld analyze now surface policy declarations (policies), executable labels, richer guard metadata (filter, privileged, arms), and a new --context option for validating guards against tool modules. Context-aware validation warns on missing exe filters, missing operation labels, and @mx.args.* references that do not match the guarded executable signature.
  • Filesystem integrity rollout across phases 1-3: write-executor outputs are signed, content-loader verifies raw file bytes on read, signer policies assign file trust labels, and filesystem_integrity rules add identity-aware write protection on top of normal filesystem capability checks.
  • Filesystem integrity phase 4: mlld status reports verified/modified/unsigned files with signer labels and taint metadata, runtime reads populate @mx.sig (including @mx.sig.files("glob")), and the Python/live SDK surface now exposes fs:status via client.fs_status().
  • Filesystem integrity phase 5: the Python SDK and live stdio transport now expose sign, verify, and sign_content, and ExecuteHandle.write_file() writes execution-scoped files that are auto-signed as agent:{script} with taint/provenance metadata.
  • Policy built-ins now include destination-aware send rules: no-send-to-unknown requires the first argument to exfil:send operations to carry known, and no-send-to-external requires known:internal for internal-only send policies.
  • Policy built-ins now include no-destroy-unknown, which requires the first argument to destructive:targeted operations to carry known. This gives delete/cancel/remove flows the same positive-check protection model as the send-specific rules and supports pinned-target privileged overrides when policies are not locked.
  • SDK boundary labels: payload field labels and labeled in-flight @state updates are now available across the live transport and all maintained language SDKs (Go, Python, Rust, Ruby, Elixir). payloadLabels / payload_labels attach labels to individual @payload fields, and SDK state updates can now send labels so injected values participate in normal label flow. Python also keeps the trusted(), untrusted(), and labeled() helpers for inline payload construction.
  • Guards now expose named operation inputs through @mx.args, including dot access for identifier-safe names (@mx.args.value), bracket access for arbitrary names (@mx.args["repo-name"]), and the reserved discovery list @mx.args.names. The named-arg snapshot is available in guard bodies, after-guards, and denied handlers for both direct exec calls and pipeline-stage bindings.
  • Guards now support function filters (guard before @send_email = when [...]), matching by exe name. This parallels the existing hook @fnName syntax and enables guards scoped to specific executables without requiring label-based matching.
  • Hooks now match op: filters against operation labels (e.g., hook before op:tool:w), not just built-in operation types. This aligns hook and guard trigger semantics — both now treat custom labels as valid op: targets.
  • Guard and managed policy label-flow denials now surface as structured SDK observability data. Streamed executions emit immediate guard_denial events, structured execute results collect denials, and the live stdio / Python / Ruby / Go / Rust / Elixir SDK layers now preserve that payload without string parsing.

Fixed

  • toolCall audit events are now emitted only when an executable/tool body actually runs. Early returns from pre-guards or parameter label-flow checks no longer create false tool-call records, and duration now measures body execution only.
  • After-guards now preserve taint-only provenance on both @mx.taint and @output.mx.taint, so source markers such as src:mcp and src:js remain visible in after-guard checks.
  • TypeScript AST extractor is now lazy-loaded, so npm install -g mlld no longer requires a globally installed typescript package. The typescript module is only imported when extracting definitions from .ts files.
  • Python package resolver no longer eagerly detects pip/uv during environment bootstrap. Detection is deferred until a @py/ or @python/ import is actually resolved, so scripts that don't use Python imports work without Python installed.
  • py { }, bash { }, and node { } blocks now show actionable error messages when the required binary is missing (e.g. "Python 3 is not installed. Install it to use py { } blocks") instead of the raw spawn ENOENT error.
  • Logical || now preserves normal boolean semantics for ordinary exec/method-call expressions such as @model.includes(...) || ... instead of misrouting them through parallel exec handling. Explicit streamed chains (stream @a() || stream @b()) still run in parallel, and longer streamed chains now include every operand.
  • Truthiness evaluation now fails closed when it receives error-like payloads from runtime evaluation or parallel-loop error markers, preventing when, &&, ||, !, and ternary conditions from accidentally treating errors as truthy values.
  • Standalone executable invocation (@fn(...) / /run @fn(...)) now preserves per-argument security descriptors through the runExec dispatcher, so callee parameters keep labels for both bare variable arguments and inline object/array literals instead of dropping mx.labels at the exe boundary.
  • Control-flow label propagation now covers conditional when [...] expressions and /if branches. Selected when results inherit labels from evaluated conditions, including fallback arms, and /if branch results plus branch-local updates now retain the condition's security context.
  • Policy extraction in mlld analyze / mlld validate now includes operations mappings and locked state consistently, privileged-guard validation now catches missing policy operation mappings, and the MCP built-ins mlld_validate / mlld_analyze now route through the same analyzer surface as the CLI so inline validation sees the same semantic/context warnings.
  • Array equality now compares list values structurally in shared expression/when/guard matching, so conditions like @mx.args.recipients == ["john@gmail.com"] work for privileged policy exceptions and other pinned-argument checks. Added regression coverage for nested array coercion and guard-level array arg matching.
  • Privileged guard allow decisions can now override policy label-flow denials and built-in label-flow rules by default, which enables policy-plus-guard exception envelopes for destructive/exfil/privileged flows. Policies can opt back into absolute denial behavior with locked: true, and managed label-flow checks now run through the guard pipeline so denied handlers and when expressions preserve the correct policy denial semantics.
  • Executable argument evaluation now falls back to object/array literal descriptors when a wrapper variable loses its aggregate mx labels, so nested untrusted values in config objects still trigger untrusted-llms-get-influenced and no-untrusted-destructive. Added regression coverage for parsed messages config objects, nested object field label access, and destructive-policy enforcement through object wrappers.
  • Quoted template interpolation inside nested when/denied-handler blocks now reparses split bracket/dot tails after variable references, so expressions like @mx.args["names"] and chained forms such as @mx.args["names"].mx.labels.join(",") resolve correctly instead of rendering the tail as literal text.
  • Namespace-qualified MCP calls (@mcp.sendEmail()) from an exe with the same unqualified name (@sendEmail) no longer falsely trigger the circular reference guard. The recursion detector was comparing unqualified method names, ignoring the namespace prefix, so every exe wrapper that delegated to its MCP counterpart was rejected as self-recursive.
  • Policy object keys that interpolate path-like values such as @base/docs/*.txt now materialize to their string form instead of degrading to "[object Object]", which fixes config-imported filesystem_integrity globs in mlld status.
  • when expressions no longer misclassify plain object results that happen to include a type key as internal AST nodes. Inline object literals like { type: "response" } now return correctly instead of collapsing to undefined.
  • Live transport (mlld live --stdio): stdout effects no longer write raw text to stdout when streaming is disabled, which was corrupting the NDJSON protocol. Content is now captured in the document buffer instead. Fixes SDK execute()/process() calls failing with invalid live response when scripts produce multiline output (e.g. via claude -p).
  • Python SDK: _reader_loop now buffers incomplete JSON lines instead of failing all pending requests. Provides defense-in-depth against any stdout contamination reaching the transport.
  • SDK/runtime error serialization now strips internal manager/environment state from wrapped causes and live-transport event payloads, so Python and JS callers see the real runtime failure without multi-kilobyte environment dumps.
  • Registry/module resolution now invalidates stale lockfile cache entries when a versioned import requests a different version, and refreshed lock entries persist the resolved registryVersion so subsequent versioned imports reuse the correct cache entry.
  • Pipe inside ternary branches (var @x = @val ? @val | @filter : null) now produces an actionable parse error explaining the limitation and showing two workarounds (exe block wrapper or split into separate steps) instead of a generic "Text content not allowed in strict mode" message.
  • CLI: added --state so scripts can populate @state from @file.json, inline JSON objects, or KEY=VALUE flags without routing through --inject.
  • mlld info now builds source URLs correctly for directory-backed registry modules, fixing broken # tldr lookups that previously produced undefined paths.
  • state:// writes now preserve structured objects and booleans as native values instead of flattening them through the text surface first. This also fixes live @state snapshots and SDK payloads receiving "[object Object]" or stringified booleans in affected flows, and adds first-class support for inline object/array literals in output ... to "state://...".
  • Structured-value text fallbacks no longer silently degrade plain or circular objects to "[object Object]". Objects now prefer JSON serialization, and genuinely unserializable values surface as [unserializable object] instead.
  • Python SDK state-write decoding now normalizes composite JSON payloads from both streamed state:write events and final stateWrites results, avoiding manual json.loads(...) for object values and preventing mixed-type duplicate entries during merge.
  • when expressions no longer swallow hard policy denials (MlldDenialError). Previously, a policy-denied action inside a when arm was silently caught and mishandled as a soft guard denial, allowing execution to continue. Hard denials now propagate correctly.
  • Field access on @payload and other object variables now preserves security metadata (labels, taint) when resolving nested fields via method calls and builtins. Previously, extracting the raw value before field traversal dropped per-field descriptors.
  • Parsed JSON nested objects now expose direct .mx.labels access consistently, instead of only preserving labels after reassignment or on primitive leaves.
  • Before-guards on exe invocations now fire for field-access arguments such as @args.data, matching bare-variable behavior and policy label-flow enforcement.
  • URL alligator results now expose .mx.text, .mx.html, and .mx.md correctly. Previously, the content-type-derived accessors on LoadContentResultURLImpl were not propagated through the StructuredValue metadata path, so .mx.html.isDefined() and .mx.md.isDefined() returned false for HTML pages.
  • Guard op: prefix now accepts colon-compound labels (e.g., op:tool:w, op:net:r). Previously GuardOpIdentifier only allowed dot-separated segments, so op:tool:w failed to parse. Guards also now warn when a data filter like guard before tool:w matches a known operation label, suggesting op:tool:w instead.
  • Live SDK state updates now preserve their labels on the updated @state path/top-level export instead of collapsing those labels onto the entire reserved @state object, and mlld live --stdio now forwards state:update.labels through to the runtime.
  • Bundled language-server semantic highlighting no longer crashes at startup, and agent-style when scripts now get full token coverage instead of leaving semantic highlight gaps.
  • Imported executable arrays now work in exe llm config.tools, so tool lists exported from helper modules preserve their function references instead of re-resolving in the caller scope.
  • Native exe llm tool calling via config.tools now preserves label-flow provenance across the internal function-tool bridge. Tool-call args inherit the enclosing LLM/input descriptor plus prior tool-result taint, so label-based defenses like untrusted-llms-get-influenced and no-untrusted-destructive apply to subsequent native function tool calls instead of resetting at the JSON bridge boundary.
  • exe llm calls with only exe-ref tools (no string tools) no longer leak the CLI's native built-in tools to the model. The runtime now exposes @mx.llm.native (the native tool names CSV), and @mlld/claude passes --tools "" when native is empty to suppress built-in tools. Previously, only the inBox path suppressed native tools; exe-ref-only calls outside a box left all 25+ built-in tools visible alongside the intended MCP-bridged tools.
  • Markdown section extraction (<file # section>) no longer strips inline backtick content. The llmxml library's getSection() was parsing markdown into an AST and re-serializing it, which dropped inline code spans. Replaced with a text-based extractor that preserves content verbatim.

Documentation

  • Added/updated docs for tolerant comparison (~= / !~=), privileged-guard validation guidance, and the expanded mlld validate JSON/context workflow.
  • Python SDK README now documents editable installs for local development (uv pip install -e ./sdk/python) so SDK changes apply immediately in downstream projects.

adamavenir and others added 30 commits March 11, 2026 18:06
Detects `var @x = @Val ? @Val | @filter : null` and provides
actionable guidance (exe block wrapper or split into steps)
instead of generic "Text content not allowed in strict mode".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Users expect done to halt everything, but it's a loop break — execution
continues after the loop block. Add examples showing var capture pattern
and bail as the full-halt alternative.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate from @mlld/claude-poll (positional args) to @mlld/claude 3.0
(config object pattern):
- import { @claudePoll } from @mlld/claude-poll → @mlld/claude
- @claudePoll(@p, "sonnet", "@root", @tools, @out) → @claudePoll(@p, { model: "sonnet", tools: [...], poll: @out })
- Tool permission strings → arrays
- Wrapper exes simplified to pass-through config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds HandleEvent dataclass and next_event() method to _BaseHandle,
enabling consumers to receive state:write events one at a time during
execution rather than buffering until completion. This is the foundation
for the AgentDojo tool-call protocol where mlld writes tool requests to
state:// and Python injects results via update_state().

Also includes the _decode_state_write_value fix for structured values
and three new integration tests covering the roundtrip, simple completion,
and structured value preservation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Callers can now pass labels (e.g., ["untrusted"]) through state:update
requests. Labels propagate into mlld's taint tracking via stateLabels
on the root environment. Python SDK exposes the labels parameter on
update_state() and surfaces the security dict on StateWrite events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…preserve field access metadata

Hard policy denials inside when arms were silently caught as soft guard
denials. Per-field payloadLabels option added across TS/Python SDK and
live transport. Field access on object variables now carries security
descriptors through nested traversal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract resolveObjectMethod helper that properly handles AST entry-based
objects, legacy properties-based objects, and plain objects without
false-matching user data. Unwrap Variable/StructuredValue after
accessFields in namespace method resolution path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When resolving methods on namespace objects, resolveObjectMethod could
return native JS functions (e.g., Array.prototype.includes) from the
prototype chain. These were incorrectly treated as namespace-exported
executables, bypassing the builtin handler. Now defers to the builtin
handler when the resolved value is a native function and the method
name collides with a builtin, while still allowing namespace-exported
mlld executables with the same names to take priority.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update guard, policy, and security docs to cover two new features:
- @mx.args for named operation input access in guards and denied handlers
- Privileged guard override of managed label-flow policy denials with locked: true opt-out

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GuardOpIdentifier only supported dot-separated segments, so
`op:tool:w` failed to parse. Added GuardOpSegment rule to accept
optional colon suffixes (matching DataLabelIdentifier convention).

Also added a validator warning when a guard uses a bare data filter
(e.g., `guard before tool:w`) that matches a known operation label,
suggesting the `op:` prefix to avoid the silent scope mismatch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Guards now support function filters: `guard before @send_email`
targets a specific exe by name, paralleling hook `@fnName` syntax.

Hooks now match `op:` filters against operation labels (not just
built-in types like exe/var/run), so `hook before op:tool:w` works
the same way as `guard before op:tool:w`. Removed the "unknown
operation type" warning since custom labels are valid targets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adamavenir and others added 22 commits March 19, 2026 11:21
Lazy-load the TypeScript AST extractor so npm install -g mlld works
without a global typescript package. Defer Python package manager
detection to first @py/ resolve. Show actionable ENOENT messages
in py/bash/node executors instead of raw spawn errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds mcpServers (map of logical name → shell command) to execute()
and process() across TypeScript SDK, Python SDK, and live stdio
transport. import tools from mcp "name" resolves against the map
before treating the spec as a command, enabling parallel executions
with independent MCP server instances.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Coerce MCP tool arguments to match inputSchema types at the call
boundary: string→array wrapping, string→number/boolean/null parsing,
JSON string→object/array. Uses schema type info already available
from tools/list.

When calling MCP tools with variable-reference arguments whose names
match schema property names, match by name instead of position. Exe
wrappers can declare params in any order and mlld routes them to the
correct MCP properties.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends fake-server.cjs with typed tools (create_event, type_mirror)
that echo back arg types and values. Tests verify coercion (string→
array/integer/boolean) and named-arg matching through the full stack:
McpImportManager → McpImportService → coerceMcpArgs → MCP server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exe wrappers that delegate to their MCP counterpart (e.g. @sendEmail →
@mcp.sendEmail) were rejected as self-recursive because the recursion
guard compared unqualified method names, ignoring the namespace prefix.
Skip recursion tracking when the call resolves through a namespace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Namespace variables (`@mcp` with `isNamespace: true`) lost their flag
when serialized into a module's capturedModuleEnv. Imported exe wrappers
that delegate to `@mcp.toolName()` then hit false circular-reference
detection because the recursion guard couldn't identify the namespace-
qualified call. Add `__namespace` serialization marker (paralleling
`__executable`/`__template`) so the flag survives round-trip.

Propagate action errors from matched when-conditions instead of
silently collecting them and falling through to `*`. Previously a
matched branch whose action threw (e.g. CircularReferenceError) was
caught, pushed to an errors array, and the next condition was tried —
making real failures look like "no case matched."

Harden MCP arg coercion:
- Handle `anyOf`/`oneOf` nullable schemas (`anyOf: [{type: array},
  {type: null}]`) by extracting the non-null type
- Strip null, undefined, and string "null" from MCP payloads so
  servers use schema defaults for optional params
- Coerce empty strings to `[]` for array-typed params

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expose @mx.llm.native so exe llm modules can suppress built-in CLI
tools when config.tools contains only exe refs. Previously only the
inBox path passed --tools "", leaving 25+ native tools visible to the
model alongside the intended MCP-bridged tools.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llmxml's getSection() parsed markdown into an AST and re-serialized
it, which dropped inline code spans. Replaced all four call sites
with a text-based extractor that preserves content verbatim.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
formatBlockingAnalysisErrors only checked result.errors but
hasBlockingAnalysisErrors also flags reserved-conflict redefinitions
as blocking. The redefinition details were silently dropped, producing
"Pre-flight validation failed:" with no explanation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract coercion logic to shared core/mcp/coerce.ts and apply it in all
three MCP dispatch paths: imported tools, config-spawned tools, and the
function bridge (LLM native tool calls). Adds two new coercions: empty
string → omit for nullable/optional params, and comma-separated string
→ array split.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consolidates syntax highlighting, EXAMPLES.md, and llms docs into a
single workflow that rebuilds all generated files on push to main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove docs/llm/*.txt, llms-combined.txt, and tests/cases/EXAMPLES.md
from tracking. These are rebuilt by the build-generated workflow on
push to main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enables @arr[*].field to extract a field from every array element,
producing a flat array. Combines with .includes() for position-independent
membership checks like @mx.tools.history[*].name.includes("verify").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 24, 2026 03:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates mlld to v2.0.5 by expanding security provenance (tool lineage + audit IDs), adding filesystem integrity (sign-on-write / verify-on-read) with new mlld sign / mlld status commands, and improving guard/policy ergonomics (named guard args, new built-in policy rules, richer validation/analyze surfaces).

Changes:

  • Add filesystem integrity policy fields (signers, filesystem_integrity), file signing helpers, and CLI/RPC surfaces (sign, status, fs:status).
  • Add tool provenance tracking (@mx.tools.calls vs @mx.tools.history), toolCall audit events with stable IDs, and propagate provenance via variable metadata.
  • Improve authoring/validation: named guard args (@mx.args.*), tolerant comparison (~=), wildcard projection ([*]), and context-aware mlld_validate / mlld_analyze.

Reviewed changes

Copilot reviewed 134 out of 502 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
docs/user/security.md Document filesystem integrity behavior, policy fields, and @mx.sig inspection.
docs/user/sdk.md Document Python fs_status() for integrity inspection.
docs/user/reference.md Clarify done loop semantics in reference docs.
docs/user/flow-control.md Clarify done vs bail semantics with examples.
docs/user/cli.md Add mlld status docs and list fs:status RPC method.
docs/src/atoms/security/13-patterns--airlock.md Clarify execution-level vs value-level tool history in enforcement guard.
docs/src/atoms/security/12-patterns--audit-guard.md Update dates and document calls vs history rationale.
docs/src/atoms/security/11-audit-log--tool-call-tracking.md Document @mx.tools.calls vs @mx.tools.history and wildcard projection usage.
docs/src/atoms/security/10-audit-log--basics.md Document stable audit IDs and toolCall ledger fields and provenance pointers.
docs/src/atoms/security/07-mcp-security--guards.md Document @mx.args, @mx.tools.history, and after-guard output taint behavior.
docs/src/atoms/security/06-mcp-security--policy.md Clarify hard capability denials vs managed label-flow denials + locked.
docs/src/atoms/security/05-mcp-security--basics.md Document .mx.tools provenance on MCP outputs and request/response taint semantics.
docs/src/atoms/security/04-signing--autosign-autoverify.md Explain calls vs history distinction.
docs/src/atoms/security/01-security-getting-started.md Add task-scoped authorization docs and clarify denial handling + influenced sources.
docs/src/atoms/sdk/07-sdk--language-sdks.md Document boundary labels across SDKs.
docs/src/atoms/sdk/06-sdk--payload.md Document per-field payload labels and Python helpers.
docs/src/atoms/sdk/03-sdk--state.md Document labeled state updates across SDKs.
docs/src/atoms/sdk/02-sdk--execute.md Add payload label + MCP server injection docs.
docs/src/atoms/modules/22-llm-modules.md Add @mx.llm.native and guidance for suppressing built-in tools.
docs/src/atoms/mcp/05-mcp--import.md Document arg coercion, name-based arg matching, and SDK MCP server injection.
docs/src/atoms/mcp/03-mcp--tool-collections.md Document var tools @t = mcp @expr runtime discovery.
docs/src/atoms/mcp/02-mcp--export.md Clarify src:mcp applies to returned values, not inputs.
docs/src/atoms/flow-control/18-loop.md Clarify done vs bail semantics with examples.
docs/src/atoms/flow-control/07-when--operators.md Document tolerant comparison operators ~= / !~=.
docs/src/atoms/effects/17-guards--denied-handlers.md Document denied handlers catching managed label-flow denials and @mx.args usage.
docs/src/atoms/effects/15-guards--privileged.md Document privileged allow overriding managed label-flow denials and locked.
docs/src/atoms/effects/14-guards--composition.md Clarify precedence/override rules for privileged allow vs managed denials.
docs/src/atoms/effects/13-guards--basics.md Document @mx.args semantics and per-arg label inspection examples.
docs/src/atoms/effects/11-labels--tracking.md Clarify union labels on arrays/objects with examples.
docs/src/atoms/effects/08-labels--influenced.md Expand influenced label semantics beyond prompt text to config/messages/system.
docs/src/atoms/effects/07-labels--trust.md Document override pattern + locked interaction.
docs/src/atoms/core/26-builtins--methods.md Document wildcard projection syntax [*].
docs/src/atoms/config/13-box--blocks.md Document @mx.llm.native.
docs/src/atoms/config/08-policy--composition.md Document merge semantics for authorizations.
docs/src/atoms/config/07-policy--label-flow.md Document privileged overrides for managed denials and link to authorizations.
docs/src/atoms/config/06-policy--operations.md Document hierarchical risk labels and link to authorizations.
docs/src/atoms/config/04-policy--basics.md Document built-in positional rules, locked, and authorizations.
docs/src/atoms/cli/06-mcp-dev.md Expand MCP dev tools schemas/outputs and add context-aware validation/analyze.
docs/src/atoms/cli/05-live-stdio.md Document mcpServers injection for live stdio.
docs/src/atoms/cli/03-validate.md Document --deep, --context, richer JSON output, and new warnings.
docs/llm/llms-reference.txt Remove older per-topic LLM reference file (generated docs workflow change).
docs/llm/llms-overview.txt Remove older overview LLM reference file (generated docs workflow change).
docs/llm/llms-output.txt Remove older output LLM reference file (generated docs workflow change).
docs/llm/llms-mcp.txt Remove older MCP LLM reference file (generated docs workflow change).
docs/llm/llms-core-rules.txt Remove older “core rules” LLM reference file (generated docs workflow change).
docs/dev/SECURITY.md Document signers / filesystem_integrity and policy merge semantics.
docs/dev/GUARD-ARGS.md Add developer spec for @mx.args guard arg access and reserved behavior.
core/types/when.ts Add normalizeWhenCondition() helper for evaluator consistency.
core/types/variable/VariableTypes.ts Add tools provenance to variable context type.
core/types/variable/VariableMetadata.ts Preserve .tools when generating metadata from descriptors.
core/types/variable/VarMxHelpers.ts Round-trip .tools between mx and security descriptors + serialization.
core/types/var.ts Add McpToolSourceValue to AST value unions.
core/types/tools.ts Add controlArgs metadata to tool definitions.
core/types/security.ts Add ToolProvenance and descriptor merge/serialize/normalize support.
core/types/primitives.ts Add wildcard field access node and expand binary operators set.
core/types/output.ts Add OutputSourceData variant to output sources union.
core/types/guard.ts Extend guard filter kind union (adds 'function').
core/security/sig-service.test.ts Add tests for signing/verifying and caching behavior with injected FS.
core/security/sig-adapter.ts Add createSigContextWithFS() for non-Environment callers.
core/security/index.ts Re-export sig service, identity resolution helpers, and new context creator.
core/security/identity.ts Add signer identity resolution (user/agent/system) with config/git/env fallbacks.
core/security/identity.test.ts Add tests for identity resolution precedence and derivation.
core/security/AuditLogger.ts Add stable audit IDs + toolCall fields; return generated ID from append.
core/security/AuditLogger.test.ts Test audit ID generation and toolCall event payload fields.
core/resolvers/ResolverManager.ts Improve lockfile/cache behavior for versioned module refs.
core/resolvers/ResolverManager.cache.test.ts Add tests for versioned cache bypass/refresh behavior.
core/resolvers/PythonPackageResolver.ts Lazy-init package manager to avoid eager default selection.
core/resolvers/DynamicModuleResolver.ts Add per-field boundary labels for object dynamic modules + setter API.
core/resolvers/DynamicModuleResolver.test.ts Add tests for per-field labels serialization and updates.
core/policy/union.test.ts Add tests for sticky locked and normalization/merge of integrity fields.
core/policy/label-flow.ts Add positional “known” rules (no-send-*, no-destroy-*) + helpers.
core/policy/label-flow.test.ts Add tests for new positional “known” rules.
core/policy/guards-defaults.test.ts Expand defaults-rule guard generation tests for new built-ins.
core/policy/builtin-rules.ts Add new built-in rule names for positional checks + influenced advice.
core/policy/authorizations.test.ts Add tests for authorization normalization/merge/validation/runtime decisions.
core/mcp/coerce.ts Add MCP inputSchema-based arg coercion helper.
core/mcp/coerce.test.ts Add tests covering coercion and schema parsing.
core/highlighting/rules.ts Update syntax highlighting tokens for operators/directives/keywords.
core/errors/enhanced-error-display.test.ts Add test verifying heavy internal state is sanitized in JSON errors.
core/errors/MlldParseError.ts Sanitize error details in parse error JSON.
core/errors/MlldFileSystemError.ts Sanitize error details in filesystem error JSON.
core/errors/MlldError.ts Sanitize error details in base error JSON.
core/errors/MlldError.test.ts Add tests for error detail sanitization.
core/errors/GuardError.ts Distinguish capability vs label-flow policy denial codes.
cli/utils/state-parser.ts Add --state parsing to build @state object from file/JSON/KEY=VALUE.
cli/utils/state-parser.test.ts Add tests for file + merge behavior for state parsing.
cli/parsers/ArgumentParser.ts Add sign/status commands and parse --state flag.
cli/parsers/ArgumentParser.test.ts Add tests for --state parsing variants.
cli/mcp/FunctionRouter.ts Propagate conversation security descriptor and tool provenance into tool calls.
cli/mcp/FunctionRouter.test.ts Add tests for provenance propagation and toolCall audit logging.
cli/mcp/BuiltinTools.ts Add context-aware mlld_validate / mlld_analyze support for inline code.
cli/mcp/BuiltinTools.test.ts Add tests for new schemas and context-aware validation/analyze output.
cli/interaction/HelpSystem.ts Update help text for validate and add --state documentation.
cli/index.ts Set default signing context tier to user for CLI executions.
cli/execution/FileProcessor.ts Merge --state into @state, add signing context, and surface denials in JSON.
cli/execution/FileProcessor.integration.test.ts Validate CLI interpret passes signing context.
cli/execution/CommandDispatcher.ts Register new sign and status commands.
cli/execution/CommandDispatcher.test.ts Add test verifying status command registration.
cli/commands/status.ts Implement mlld status to report signature state and signer-derived labels.
cli/commands/status.test.ts Add tests for status outputs, glob filtering, JSON, and taint printing.
cli/commands/sign.ts Implement mlld sign for files/globs with resolved user identity.
cli/commands/sign.test.ts Add tests for signing and re-sign reporting.
cli/commands/run.ts Improve blocking validation output (include hard redefinitions) and add signing context.
cli/commands/live.ts Document guard_denial events in live stdio help header.
cli/commands/live-stdio-security.ts Add live signing/verification helpers + execution file writer with provenance.
cli/commands/live-stdio-security.test.ts Add tests for live signing, sign-content, and execution file writes/provenance.
cli/commands/info.ts Add docs URL handling for directory modules and lazy-load heavy deps in fetchSection.
cli/commands/info.test.ts Add tests for source/docs URL construction across module source kinds.
cli/commands/docs.ts Prefer docs URL when fetching docs/tldr sections.
cli/commands/docs.test.ts Add test ensuring docsUrl is used for directory modules.
.github/workflows/build-syntax.yml Remove old syntax-only generator workflow.
.github/workflows/build-generated.yml Add unified workflow to rebuild/commit generated files (syntax/fixtures/docs).
Comments suppressed due to low confidence (1)

cli/index.ts:1

  • The new signingContext option is being passed via interpretOptions as any. This weakens type safety and makes it easier for option regressions to slip in unnoticed. It would be better to extend the interpret() options type (and execute() where applicable) to include signingContext so callers don’t need unsafe casts.
import * as path from 'path';

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/policy/label-flow.ts Outdated
'no-send-to-external',
'no-destroy-unknown',
'no-untrusted-destructive',
'no-untrusted-privileged'
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

hasManagedPolicyLabelFlow() uses LABEL_FLOW_BUILTIN_RULES to detect whether managed label-flow can occur, but no-influenced-advice (added in core/policy/builtin-rules.ts and implemented in checkBuiltinPolicyRules) is missing from this set. This can cause the system to misclassify a policy as not having managed label-flow when only no-influenced-advice is enabled. Add 'no-influenced-advice' to LABEL_FLOW_BUILTIN_RULES (or derive the set from BUILTIN_POLICY_RULES) to keep detection consistent.

Suggested change
'no-untrusted-privileged'
'no-untrusted-privileged',
'no-influenced-advice'

Copilot uses AI. Check for mistakes.
Comment thread core/mcp/coerce.ts
Comment on lines +44 to +60
if (value === null || value === undefined) {
continue;
}
if (typeof value === 'string' && value.trim() === 'null') {
continue;
}
if (typeof value === 'string' && value.trim() === '') {
const schemaType = paramTypes[key];
if (schemaType === 'array') {
result[key] = [];
continue;
}
if (paramNullable[key] || !requiredSet.has(key)) {
continue;
}
}
const schemaType = paramTypes[key];
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

coerceMcpArgs() currently drops the literal string "null" for all params, including those whose schema type is string. This can unintentionally erase valid user/tool input (e.g., a subject/body that is literally "null"). Consider restricting this sentinel handling to non-string schema types and/or to params that are nullable/optional in the schema, while preserving "null" when the declared type is string.

Suggested change
if (value === null || value === undefined) {
continue;
}
if (typeof value === 'string' && value.trim() === 'null') {
continue;
}
if (typeof value === 'string' && value.trim() === '') {
const schemaType = paramTypes[key];
if (schemaType === 'array') {
result[key] = [];
continue;
}
if (paramNullable[key] || !requiredSet.has(key)) {
continue;
}
}
const schemaType = paramTypes[key];
const schemaType = paramTypes[key];
if (value === null || value === undefined) {
continue;
}
if (typeof value === 'string') {
const trimmed = value.trim();
// Treat the string "null" as an omitted value only for non-string params
// that are nullable or optional. Preserve "null" for string-typed params.
if (
trimmed === 'null' &&
schemaType &&
schemaType !== 'string' &&
(paramNullable[key] || !requiredSet.has(key))
) {
continue;
}
if (trimmed === '') {
if (schemaType === 'array') {
result[key] = [];
continue;
}
if (paramNullable[key] || !requiredSet.has(key)) {
continue;
}
}
}

Copilot uses AI. Check for mistakes.
Comment on lines 32 to +37
export async function appendAuditEvent(
fileSystem: IFileSystemService,
projectRoot: string,
event: AuditEvent
): Promise<void> {
): Promise<string> {
const id = event.id ?? randomUUID();
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

appendAuditEvent() now returns Promise<string> (generated ID) instead of Promise<void>, which is a behavioral/typing change for a widely reusable utility. If external/internal callers relied on the previous void return type, this becomes a breaking change. Consider either (1) keeping appendAuditEvent returning Promise<void> and adding a separate helper to generate IDs, or (2) providing an overload / new function name (e.g., appendAuditEventWithId) while keeping the original signature for compatibility.

Copilot uses AI. Check for mistakes.
Comment thread cli/utils/state-parser.ts Outdated

if (rawArg.startsWith('@')) {
const filePath = path.resolve(basePath, rawArg.slice(1));
const content = await fileSystem.readFile(filePath, 'utf8');
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

parseStateArg() calls IFileSystemService.readFile(filePath, 'utf8'). Elsewhere in the repo, IFileSystemService.readFile is used without an encoding argument, and many filesystem abstractions intentionally diverge from Node's signature. If IFileSystemService doesn't accept an encoding parameter, this will be a compile-time error (or a runtime error for non-Node implementations). Prefer calling readFile(filePath) and ensuring it returns a string, or update IFileSystemService to formally support the encoding argument across implementations.

Suggested change
const content = await fileSystem.readFile(filePath, 'utf8');
const rawContent = await fileSystem.readFile(filePath);
const content = typeof rawContent === 'string' ? rawContent : String(rawContent);

Copilot uses AI. Check for mistakes.
Comment thread core/types/security.ts
Comment on lines +199 to +214
const deduped: ToolProvenance[] = [];
const seenAuditRefs = new Set<string>();

for (const value of values) {
const normalized = freezeToolProvenanceEntry(value);
if (!normalized) {
continue;
}
if (normalized.auditRef) {
if (seenAuditRefs.has(normalized.auditRef)) {
continue;
}
seenAuditRefs.add(normalized.auditRef);
}
deduped.push(normalized);
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

Tool provenance deduplication currently happens only when auditRef is present. If some tool provenance entries omit auditRef (e.g., synthetic steps, older data, or partial ingestion), repeated merges can accumulate duplicates and increase descriptor size. Consider also deduping no-auditRef entries by a stable key such as name + JSON.stringify(args) (or ensuring auditRef is always populated at the source).

Copilot uses AI. Check for mistakes.
Comment thread cli/mcp/FunctionRouter.ts
Comment on lines +286 to +297
private buildToolCallSecurityDescriptor(): SecurityDescriptor | undefined {
if (!this.conversationDescriptor) {
return undefined;
}
const policyEnforcer = new PolicyEnforcer(this.environment.getPolicySummary());
return (
policyEnforcer.applyOutputPolicyLabels(this.conversationDescriptor, {
inputTaint: descriptorToInputTaint(this.conversationDescriptor),
exeLabels: ['llm']
}) ?? this.conversationDescriptor
);
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

buildToolCallSecurityDescriptor() constructs a new PolicyEnforcer on every tool call. If MCP routing is hot (many tool calls per execution), this repeated instantiation can become avoidable overhead. Consider caching a PolicyEnforcer instance on the router (and refreshing only if policy summary changes) or passing an already-constructed enforcer via options.

Copilot uses AI. Check for mistakes.
adamavenir and others added 5 commits March 23, 2026 21:08
…rg, and null-string coercion for string params

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@adamavenir adamavenir merged commit 0b67012 into main Mar 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants