@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
344 lines • 13.9 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { z } from 'zod';
import { units } from "../rs/document.js";
import { StreamRefusal } from "../rs/errors.js";
import { openStreamSync } from "../rs/stream.js";
import { Limit, OnExceed } from "../types.js";
import { askRule, sameRule } from "./decisions.js";
import { PolicyError } from "./errors.js";
export const PACKET_BYTES = 8 * 1024 * 1024;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
let sequence = 0;
const resultSchema = z.object({
answers: z.array(z.number().int().nonnegative()),
action: z.number().int().nonnegative().nullable(),
ask_rules: z.array(z.number().int().nonnegative()).nullable(),
route: z.number().int().nonnegative().nullable(),
conflict: z.array(z.number().int().nonnegative()),
limit: z
.object({
max_bytes: z
.union([z.number(), z.string().transform(Number)])
.nullable()
.optional(),
max_lines: z
.union([z.number(), z.string().transform(Number)])
.nullable()
.optional(),
timeout_seconds: z.number().nullable().optional(),
walk_limit: z
.union([z.number(), z.string().transform(Number)])
.nullable()
.optional(),
on_exceed: z.enum(OnExceed).nullable().optional(),
})
.nullable(),
invalid: z
.object({ action: z.number(), slot: z.number(), legal: z.array(z.string()) })
.nullable(),
});
const effectSchema = (() => {
const identity = { evaluation: z.string(), effect: z.number().int() };
return z.discriminatedUnion('op', [
z.object({
...identity,
op: z.enum(['run_policy', 'read_action', 'read_settlement_action']),
slot: z.number().int(),
}),
z.object({
...identity,
op: z.literal('read_ask_rules'),
first: z.number().int(),
next: z.number().int(),
}),
z.object({
...identity,
op: z.literal('replace_ask_rules'),
action: z.number().int(),
rules: z.array(z.number().int()),
}),
z.object({
...identity,
op: z.literal('read_final_values'),
routes: z.array(z.number().int()),
limits: z.array(z.number().int()),
}),
]);
})();
const limitFields = {
maxBytes: 'max_bytes',
maxLines: 'max_lines',
timeoutSeconds: 'timeout_seconds',
walkLimit: 'walk_limit',
onExceed: 'on_exceed',
};
function isAnswer(value) {
return (typeof value === 'object' &&
value !== null &&
'kind' in value &&
(value.kind === 'deny' || value.kind === 'ask' || value.kind === 'route'));
}
export class Composition {
settlement;
stream = openStreamSync('policy_compose', {});
evaluation = `evaluation-${String(++sequence)}`;
actions = [];
originals = [];
rules = [];
names = new Map();
constructor(settlement = null) {
this.settlement = settlement;
if (settlement !== null)
this.actions.push(...settlement);
}
static packet(value) {
const data = encoder.encode(JSON.stringify(value));
if (data.byteLength > PACKET_BYTES)
throw new StreamRefusal('policy composition packet limit exceeded', new Uint8Array());
return data;
}
feed(value) {
this.stream.feed(Composition.packet(value));
}
ruleId(rule) {
const index = this.rules.findIndex((entry) => sameRule(entry, rule));
if (index >= 0)
return index;
this.rules.push(rule);
return this.rules.length - 1;
}
descriptors(answered, name) {
const descriptors = [];
for (const value of Array.isArray(answered) ? answered : [answered]) {
if (value === null)
continue;
const index = this.actions.length;
this.actions.push(value);
this.originals.push(value);
let descriptor = { kind: 'invalid', action: index };
if (value instanceof Limit) {
descriptor = { kind: 'limit', action: index };
}
else if (isAnswer(value)) {
const action = name !== '' && (value.policy === undefined || value.policy === '')
? { ...value, policy: name }
: value;
this.actions[index] = action;
descriptor = { kind: action.kind, action: index };
}
descriptors.push(descriptor);
}
return descriptors;
}
request() {
const requested = this.stream.request();
return requested === null ? null : effectSchema.parse(requested[0]);
}
respond(effect, answered, name, more) {
const value = {
evaluation: this.evaluation,
effect: effect.effect,
actions: this.descriptors([answered], name),
more,
};
Composition.packet(value);
this.stream.respond(value, new Uint8Array());
}
respondEffect(effect, ctx) {
const identity = { evaluation: this.evaluation, effect: effect.effect };
let response;
switch (effect.op) {
case 'read_settlement_action': {
const action = this.actions[effect.slot];
const kind = typeof action === 'object' && action !== null && 'kind' in action
? action.kind
: 'invalid';
response = {
...identity,
actions: [{ kind: typeof kind === 'string' ? kind : 'invalid', action: effect.slot }],
};
break;
}
case 'read_ask_rules': {
const first = this.answer(effect.first);
const next = this.originals[effect.next];
if (!isAnswer(next))
throw new Error('invalid policy ask reference');
if (first.kind !== 'ask' || next.kind !== 'ask' || ctx === undefined)
throw new Error('invalid policy ask reference');
this.rules.length = 0;
const firstRules = first.rules ?? [askRule(ctx, first)];
const nextRules = next.rules ?? [askRule(ctx, next)];
response = {
...identity,
first_rules: firstRules.map((rule) => this.ruleId(rule)),
next_rules: nextRules.map((rule) => this.ruleId(rule)),
same_ask: false,
};
break;
}
case 'replace_ask_rules': {
const action = this.answer(effect.action);
if (action.kind !== 'ask')
throw new Error('invalid policy ask reference');
const rules = effect.rules.map((index) => {
const rule = this.rules[index];
if (rule === undefined)
throw new Error('invalid policy rule reference');
return rule;
});
this.actions[effect.action] = { ...action, rules };
response = identity;
break;
}
case 'read_final_values': {
const routes = effect.routes.map((index) => {
const action = this.settlement?.[index] ?? this.answer(index);
if (!('runtime' in action))
throw new Error('invalid policy route reference');
return { action: index, runtime: units(action.runtime) };
});
const sources = effect.limits.map((index) => {
const limit = this.actions[index];
if (!(limit instanceof Limit))
throw new Error('invalid policy limit reference');
return limit;
});
const fieldsSet = new Set(sources.flatMap((limit) => [...limit.fieldsSet]));
const limits = sources.map((action, position) => {
const index = effect.limits[position];
if (index === undefined)
throw new Error('invalid policy limit reference');
const fields = {};
for (const key of fieldsSet) {
const value = action[key];
fields[limitFields[key]] =
typeof value === 'number' && key !== 'timeoutSeconds'
? BigInt(value).toString()
: value;
}
return { action: index, value: fields };
});
response = { ...identity, routes, limits };
break;
}
default:
return false;
}
Composition.packet(response);
this.stream.respond(response, new Uint8Array());
return true;
}
answer(index) {
const value = this.actions[index];
if (!isAnswer(value))
throw new Error('invalid policy action reference');
return value;
}
settlementResult() {
if (this.settlement === null)
throw new Error('missing settlement inputs');
const result = resultSchema.parse(JSON.parse(decoder.decode(this.stream.finish())));
if (result.conflict.length > 0) {
const routes = result.conflict.map((index) => {
const route = this.settlement?.[index];
if (route === undefined || !('runtime' in route))
throw new Error('invalid policy route reference');
return route;
});
return {
kind: 'deny',
reason: `policies place the line on different runtimes: ${routes.map((r) => `${r.policy ?? ''} on ${r.runtime}`).join(', ')}`,
policy: [...new Set(routes.map((r) => r.policy ?? ''))].join(', '),
};
}
const index = result.action ?? result.route;
if (index === null)
return null;
const action = this.settlement[index];
if (action === undefined)
throw new Error('invalid policy action reference');
return action;
}
result(method = '') {
const result = resultSchema.parse(JSON.parse(decoder.decode(this.stream.finish())));
if (result.invalid !== null) {
const { action, slot, legal } = result.invalid;
throw new PolicyError(`${method} of ${this.names.get(slot) ?? ''} returned ${JSON.stringify(this.actions[action])}; ` +
`legal kinds here: ${[...legal].sort((a, b) => (a === 'deny' ? -1 : b === 'deny' ? 1 : 0)).join(', ')}`);
}
const answers = result.answers.map((index) => this.answer(index));
const selected = result.action === null ? null : this.answer(result.action);
let action = selected?.kind === 'route' ? null : selected;
const route = selected?.kind === 'route'
? selected
: result.route === null
? null
: this.answer(result.route);
if (route !== null && route.kind !== 'route')
throw new Error('invalid policy route reference');
if (result.conflict.length > 0) {
const routes = result.conflict.map((index) => {
const answer = this.answer(index);
if (answer.kind !== 'route')
throw new Error('invalid policy route reference');
return answer;
});
action = {
kind: 'deny',
reason: `policies place the line on different runtimes: ${routes.map((r) => `${r.policy ?? ''} on ${r.runtime}`).join(', ')}`,
policy: [...new Set(routes.map((r) => r.policy ?? ''))].join(', '),
};
}
let limit = null;
if (result.limit !== null) {
const init = {};
for (const [key, field] of Object.entries(limitFields)) {
if (field in result.limit)
Object.assign(init, { [key]: result.limit[field] });
}
limit = new Limit(init);
}
return { answers, action, route, limit };
}
close() {
this.stream.cancel();
this.stream.close();
}
}
export function composeSettled(answers, mode) {
if (answers.length === 0)
return null;
const composition = new Composition(answers);
try {
composition.feed({
op: 'settle',
mode,
evaluation: composition.evaluation,
count: answers.length,
});
composition.stream.finish();
for (let effect = composition.request(); effect !== null; effect = composition.request()) {
if (!composition.respondEffect(effect))
throw new Error('invalid settlement effect');
}
return composition.settlementResult();
}
finally {
composition.close();
}
}
//# sourceMappingURL=composition.js.map