Skip to content
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ build.bat --all # Release build for current architecture
| Logging access denied | [`docs/logging-access-denied.md`](docs/logging-access-denied.md) | Diagnose blocked accesses and author policy |
| Telemetry | [`docs/telemetry.md`](docs/telemetry.md) | Consent and administrative controls |
| Backend guides | [`docs/backends/`](docs/backends/) | Platform and backend prerequisites and behavior |
| Policy Store API (proposal) | [`docs/mxc-policy-store-api.md`](docs/mxc-policy-store-api.md) | Types, behavior, and caller examples under API review |
| Policy Store design (proposal) | [`docs/mxc-policy-store.md`](docs/mxc-policy-store.md) | Catalog authoring, validation, and implementation design |

Repository contributors should start with the
[MXC development documentation](docs/development/README.md).
Expand Down
330 changes: 330 additions & 0 deletions docs/mxc-policy-store-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
# Policy Store API

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: Chaz Gordish (@ChazGo) , we now have a formal way of submitting feature and api specs here: https://github.com/microsoft/mxc/blob/main/CONTRIBUTING.md#plan-features-together . We should update this proposal to align with that. That said, I know this has been up for a while so we can do that after review. Another thing is, we'll need an issue associated with this so we can place it in a MXC Milestone when the time comes.


**Status:** Proposed API, under review before implementation.
Not part of MXC 1.0; no later release is committed.

Policy Store suggests container requirements for known tools. Lookup is
command-free and uses the SDK's bundled default catalog. It does not execute
tools, create containers, download data or save caller settings.

## 1. API overview

One operation accepts a tool or collection, plus optional lookup context:

```ts
export declare function resolveToolRequirements(
tool: ToolInput,
ctx?: ResolveContext,
): Promise<ToolRequirementsResolution>;
export declare function resolveToolRequirements(
tools: readonly ToolInput[],
ctx?: ResolveContext,
): Promise<ToolRequirementsResolution>;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Feedback notes I had from a conversation with Alexander Sklar (@asklar):

  • Keep API input parameter that supports an array of tools. Don't push problem to caller because composing multiple tool policies can be viewed as being too tricky.
  • Don't return a container suggestion when different tools have conflicting requirements. Tool A specifying %USERPATH%\Downloads is a deny path can't compose with Tool B specifying it requires read/write access to %USERPATH%\Downloads. The API should have a clear return result telling the caller that the tools can't be composed.
  • The tools requirement result is a superset of what MXC containers support. E.g. Hints about required credentials are not something a MXC ContainerRequest supports, but the caller could handle themselves.

```
Comment on lines +12 to +23

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thought: I guess this works if the consumer does one tool workload at a time, but probably breaks down if they run a powershell script that does multiple things. Another thing to think about is making sure we have the same behavior on linux and macOS as well.


Every non-error call returns the [two-field result](#3-types-and-fields):

| Field | Meaning |
|---|---|
| `requirements` | Suggested settings, or absence when no requirements can be returned |
| `unresolvedInputs` | Required, sorted, distinct zero-based indexes of original input occurrences not completely included |

### Results, coverage, and attribution

Resolution is best effort across **complete tool requirements and selected
dependencies**, never arbitrary fragments. Duplicate requests retain separate
input indexes even when their shared requirements are included once.

| Outcome | `requirements` | `unresolvedInputs` |
|---|---|---|
| Both inputs resolve | Combined requirements | `[]` |
| Only input 0 resolves | Input 0's requirements | `[1]` |
| Sole input does not resolve | Absent | `[0]` |
| Required symbol prevents output for two inputs | Absent | `[0, 1]` |
| Empty input list | Absent | `[]` |
| Complete input needs no additional fields | Defined empty aggregate | `[]` |

For nonempty input, output exists exactly when at least one index is not listed.
A failed sole input leaves no dependency-only output. The SDK rejects malformed
summaries, including missing, non-integer, duplicate, unsorted or out-of-range
indexes and inconsistent output presence.

A single-tool caller checks output presence. A multi-tool caller can use partial
output and report omitted inputs; there is no mandatory all-tools-covered gate.
Whole-call failures remain errors. **Neither completeness nor suggested access
is authorization or a guarantee of execution success.**

## 2. Zava Agent example

Zava Agent prepares a Git push on Windows using saved settings first, then the catalog if
enabled. Current client restrictions apply to either source. The omitted
application helpers decide acceptance and any prompting; no automatic saving or
review UI is prescribed. Paths and tool versions are illustrative.

```ts
// Proposed lookup API and its input/result types.
import { resolveToolRequirements } from "@microsoft/mxc-sdk/v1";
import type {
ContainerRequirements, ResolveContext, ToolCandidate,
} from "@microsoft/mxc-sdk/v1";
// Existing SDK request type, used only after client acceptance.
import type { ContainerRequest } from "@microsoft/mxc-sdk/v1";

// Zava-owned helpers; implementations omitted.
declare function loadApplicableToolRequirements(
tool: ToolCandidate, context: ResolveContext,
): Promise<ContainerRequirements | undefined>;
declare function isMxcFloorCatalogEnabled(): boolean;
declare function applyClientSettings(
tool: ToolCandidate, requirements: ContainerRequirements,
): Promise<ContainerRequirements | undefined>;
declare function reportUnresolvedTool(tool: ToolCandidate): void;

async function createRequestForTool(
tool: ToolCandidate,
command: string,
workingDirectory: string,
context: ResolveContext,
): Promise<ContainerRequest | undefined> {
let requirements = await loadApplicableToolRequirements(tool, context);

if (requirements === undefined) {
if (!isMxcFloorCatalogEnabled()) {
return undefined;
}

const result = await resolveToolRequirements(tool, context);
if (result.requirements === undefined) {
reportUnresolvedTool(tool);
return undefined;
}
requirements = result.requirements;
}

// Saved requirements must also respect today's client restrictions.
const selected = await applyClientSettings(tool, requirements);
return selected === undefined
? undefined
: { ...selected, command, workingDirectory };
}

const workspace = String.raw`D:\work\repo`;
const request = await createRequestForTool(
{ invocationName: "git", detectedVersion: "2.45", intent: "push" },
"git push",
workspace,
{
projectRoot: workspace,
allowWeakIdentityFallback: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: I don't see allowWeakIdentityFallback defined anywhere. What's it for exactly?

symbols: {
git_prefix: String.raw`D:\tools\git`,
ssh_prefix: String.raw`D:\tools\ssh`,
},
},
);
// Only a defined request proceeds to Zava's normal MXC run/spawn path.
```

The helpers are scoped to the user, workspace and target; saved-setting reuse
checks tool/version/intent and context. Lookup/helper exceptions propagate to
Zava's application error boundary, not an empty or uncontained fallback.
`projectRoot` binds symbols; the explicit `workingDirectory` sets the process cwd.

## 3. Types and fields

These proposed declarations are exported from `@microsoft/mxc-sdk/v1`.
Rust/.NET use equivalent versioned types and reuse their existing SDK policy
sections rather than defining a parallel policy model.
Comment on lines +135 to +137

```ts
import type { ContainerRequest } from "@microsoft/mxc-sdk/v1";

export type ContainerRequirements = Pick<
ContainerRequest,
"filesystem" | "network" | "ui" | "timeoutMs"
>;

export interface ToolCandidate {
invocationName: string;
packageUrl?: string;
detectedVersion?: string;
intent?: string;
}

export type ToolInput = string | ToolCandidate;
export type CatalogPlatform = "windows" | "linux" | "macos";
export type CatalogArchitecture = "x64" | "arm64";

export interface ResolveContext {
projectRoot?: string;
symbols?: Record<string, string>;
platform?: CatalogPlatform;
architecture?: CatalogArchitecture;
allowWeakIdentityFallback?: boolean;
}

export interface ToolRequirementsResolution {
requirements: ContainerRequirements | undefined;
unresolvedInputs: readonly number[];
}
```

| Output field | Supported content |
|---|---|
| `filesystem` | `readonlyPaths`, `readwritePaths`, `deniedPaths` |
| `network` | Directional `egress` and `ingress` policy |
| `ui` | Required `disable`; optional `clipboard` and `allowInputInjection` |
| `timeoutMs` | Optional uint32 execution-timeout suggestion, not a minimum runtime |

The reused types are broader than resolver output. `filesystem.clearPolicyOnExit`
and `network.runtimeConfig` remain unset (`undefined`/`None`/`null`); `Pick` is
not runtime validation. The caller can omit/override the timeout and owns the
command, cwd, containment, environment, cleanup, proxy runtime values and other
execution settings.

## 4. Matching and returned requirements

### Context defaults and symbol resolution

| Input omitted | Default |
|---|---|
| `ctx`, `projectRoot`, `symbols` | No caller overrides or invented project root |
| `platform` | Current host |
| `architecture` | Native host architecture, not an emulated process architecture |
| `allowWeakIdentityFallback` | `false` |
| Package identity/version | No fabricated evidence |
| `intent` | All intents in the selected effective policy |

`projectRoot` and `symbols.project_root` are aliases. If both are supplied,
different strings fail the whole context before lookup; no path normalization
or object comparison chooses a winner.

For needed symbols, use caller values, then supported local discovery, then
documented platform defaults. An unresolved required symbol suppresses all
requirements; a configuration-read failure is an error. Path overrides do not
provide remote/guest filesystem identity or execute discovery commands.
See the [symbol contract](mxc-policy-store.md#32-entry-shape) for implementation details.

### Identity and match precedence

A string input supplies only `invocationName`; weak-name matching still requires
opt-in. Callers supply identity/version evidence and map commands to intent
names; the catalog neither parses commands nor verifies the installed tool.

Rank matching entries by package identity, then requested-intent specificity,
then exact architecture. One highest-ranked entry wins; distinct ties fail.
Multiple matching predicates in one entry count once. Version/intent failure
does not retry a weaker entry or wildcard.
Comment on lines +214 to +217

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: we mention "package identity". For the non packaged apps what do we do? executable name? I'm also thinking about linux and macOS too, only thing I can think of is the executable name/path.


| Match input | Comparison |
|---|---|
| `packageUrl` | Validate the whole PURL, then compare decoded type/namespace/name using type-specific rules; ignore candidate version/qualifiers/subpath |
| `detectedVersion` | The only version evidence; parse using the entry's supported scheme |
| `invocationName` | Locale-independent, case-insensitive on Windows/macOS; exact on Linux |
| Invalid candidate PURL | Exclude that input; no repair or weak-name retry |

The [precise PURL/VERS rules](mxc-policy-store.md#33-identity) are shared by
authoring and lookup. Host filesystem casing is never package-identity casing.

### Version, intent, and platform selection

| Input | Selected requirements |
|---|---|
| Version omitted | Common default plus applicable platform additions |
| Version in one range | Default, platform and that version's additions |
| Valid version outside every range | Default plus platform additions, never nearest/highest variant |
| Unparseable version | Exclude input |
| Supported named intent | Effective base plus that intent's additions |
| Intent omitted | Effective base plus all effective intents, or base alone if none exist |
| Unsupported named intent | Exclude input, never a base/all-intents fallback |

Select at most one add-only overlay per dimension; ranges do not overlap or
cascade. A newer-only intent with an out-of-range version remains unavailable.
Exact architecture takes precedence over platform-neutral additions, otherwise
retain the common default; never select another architecture. A tool running
under emulation may need explicit architecture context. Detection failure is
a library error.

### Dependencies and composition

Return one combined sandbox suggestion, not separate permissions per tool.
Each included input brings its complete selected dependency closure; shared
layers are included once. Unknown necessary filesystem identity excludes all
affected inputs, not just the restrictive side.

Required catalog grants can supersede read-only access or remove an entire
conflicting catalog deny, including egress denies. Caller restrictions are never
removed. Undefined merges fail rather than approximate broader access.
The [composition rules](mxc-policy-store.md#35-dependencies-and-composition)
define dependency selection, alias handling, egress exclusions and scalar conflicts.

## 5. Errors and caller responsibilities

| Whole-call failure | MXC code | Optional reason |
|---|---|---|
| Invalid input/context | `malformed_request` | `invalid_context` |
| Invalid catalog, dependencies or cycles | `policy_validation` | `invalid_catalog` |
| Tied highest-ranked entries | `policy_validation` | `ambiguous_match` |
| Incompatible SDK target or undefined composition | `policy_validation` | `composition_conflict` |
| Unsupported/undetectable host | `unsupported_containment` | `unsupported_host` |
| Bundled catalog cannot be read | `backend_error` | `integrity` |

Handle primary codes; optional reasons, where exposed, do not require message
parsing. Unmatched tools, unsupported intents and other defined per-input
exclusions are different from these whole-call errors.

The caller decides acceptance and preserves its user/OS/backend ceilings.
No result authorizes uncontained fallback. The summary reports input coverage,
not per-entry provenance. [Internal troubleshooting](mxc-policy-store.md#42-results-and-troubleshooting)
does not provide a stable caller audit or log-parsing API.

## 6. Language bindings

All bindings preserve the same inputs, two-field result and partial outcomes.
TypeScript uses the [Promise-returning overloads above](#1-api-overview).
Rust is synchronous; .NET provides synchronous and Task-returning forms.

### Rust

Signature in `mxc_sdk::v1`:

```rust
pub fn resolve_tool_requirements(
tools: &[ToolCandidate],
context: Option<&ResolveContext>,
) -> Result<ToolRequirementsResolution, Error>;
```

One tool uses a one-element slice; `None` selects default context.
The result holds `requirements: Option<ContainerRequirements>` and
`unresolved_inputs: Vec<usize>`. `ToolCandidate::from(&str)` copies only the
invocation name into the candidate; package URL, version and intent remain `None`.

### .NET

Member signatures in `Microsoft.Mxc.Sdk.V1.MxcContainer`:

```csharp
public static ToolRequirementsResolution ResolveToolRequirements(
ToolCandidate tool, ResolveContext? context = null);
public static ToolRequirementsResolution ResolveToolRequirements(
IReadOnlyList<ToolCandidate> tools, ResolveContext? context = null);
public static Task<ToolRequirementsResolution> ResolveToolRequirementsAsync(
ToolCandidate tool, ResolveContext? context = null,
CancellationToken cancellationToken = default);
public static Task<ToolRequirementsResolution> ResolveToolRequirementsAsync(
IReadOnlyList<ToolCandidate> tools, ResolveContext? context = null,
CancellationToken cancellationToken = default);
```

The result has `ContainerRequirements? Requirements` and required
`IReadOnlyList<int> UnresolvedInputs`. Cancellation stops awaiting, not a native
call already in progress; the SDK owns late-result cleanup.
`ToolCandidate(string invocationName)` and implicit string conversion preserve
the name and leave other candidate fields null; a null name throws
`ArgumentNullException`. Use a typed `ToolCandidate[]` or target-typed collection:
a `string[]` does not convert element-by-element.

Neither binding's conversion trims names, parses commands/PURLs, invents identity
or enables weak matching. Converted and directly constructed candidates use
the same resolver validation.
Loading
Loading