chore: import upstream snapshot with attribution
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
# ========= 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. =========
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from functools import partial
|
||||
from typing import Any, Callable
|
||||
|
||||
from mirage.provision import Precision, ProvisionResult
|
||||
from mirage.shell.node_kind import NodeKind, node_kind
|
||||
from mirage.shell.types import NodeType as NT
|
||||
from mirage.shell.types import RedirectKind
|
||||
from mirage.shell.types import ShellBuiltin as SB
|
||||
from mirage.types import PathSpec
|
||||
from mirage.workspace.expand import (classify_parts, expand_and_classify,
|
||||
expand_parts, expand_redirects)
|
||||
from mirage.workspace.mount import MountRegistry
|
||||
from mirage.workspace.mount.namespace import Namespace
|
||||
from mirage.workspace.provision.builtins import handle_builtin_provision
|
||||
from mirage.workspace.provision.command import handle_command_provision
|
||||
from mirage.workspace.provision.control import (handle_for_provision,
|
||||
handle_function_provision,
|
||||
handle_if_provision,
|
||||
handle_while_provision)
|
||||
from mirage.workspace.provision.pipes import (handle_connection_provision,
|
||||
handle_pipe_provision)
|
||||
from mirage.workspace.provision.redirect import handle_redirect_provision
|
||||
from mirage.workspace.provision.rollup import rollup_list, rollup_pipe
|
||||
from mirage.workspace.session import Session
|
||||
|
||||
from mirage.shell.helpers import ( # isort: skip
|
||||
get_case_items, get_command_name, get_for_parts, get_function_body,
|
||||
get_function_name, get_if_branches, get_list_parts, get_negated_command,
|
||||
get_parts, get_pipeline_commands, get_redirects, get_subshell_body,
|
||||
get_text, get_while_parts, has_command_substitution, split_env_prefix)
|
||||
|
||||
# eval / source execute their payload, so they are NOT free builtins:
|
||||
# leaving them out lets them fall through to command resolution, which
|
||||
# honestly reports UNKNOWN instead of a zero-cost EXACT.
|
||||
_BUILTIN_NAMES = frozenset({
|
||||
SB.CD,
|
||||
SB.TRUE,
|
||||
SB.FALSE,
|
||||
SB.EXPORT,
|
||||
SB.UNSET,
|
||||
SB.LOCAL,
|
||||
SB.PRINTENV,
|
||||
SB.READ,
|
||||
SB.SET,
|
||||
SB.SHIFT,
|
||||
SB.TRAP,
|
||||
SB.TEST,
|
||||
SB.BRACKET,
|
||||
SB.DOUBLE_BRACKET,
|
||||
SB.WAIT,
|
||||
SB.FG,
|
||||
SB.KILL,
|
||||
SB.JOBS,
|
||||
SB.PS,
|
||||
SB.ECHO,
|
||||
SB.PRINTF,
|
||||
SB.SLEEP,
|
||||
"return",
|
||||
"break",
|
||||
"continue",
|
||||
})
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlanScope:
|
||||
"""Walk-local planner state.
|
||||
|
||||
Function definitions seen during this plan are recorded here (not
|
||||
on the session: planning must not mutate shell state), and
|
||||
`planning` guards recursive functions from looping the planner.
|
||||
"""
|
||||
functions: dict[str, list] = field(default_factory=dict)
|
||||
planning: set[str] = field(default_factory=set)
|
||||
|
||||
|
||||
async def _provision_redirected(
|
||||
recurse: Callable,
|
||||
registry: MountRegistry,
|
||||
namespace: Namespace | None,
|
||||
execute_fn: Callable,
|
||||
command: Any,
|
||||
redirects: list,
|
||||
session: Session,
|
||||
) -> ProvisionResult:
|
||||
"""Plan one redirected command: expand targets, cost, degrade.
|
||||
|
||||
Args:
|
||||
recurse (Callable): the provision recursion.
|
||||
registry (MountRegistry): mount registry.
|
||||
namespace (Namespace | None): addressing authority.
|
||||
execute_fn (Callable): recursive execute (for expansions).
|
||||
command (Any): the redirected command node.
|
||||
redirects (list): parsed redirects.
|
||||
session (Session): shell session state.
|
||||
"""
|
||||
expanded, pipe_node = await expand_redirects(redirects, session,
|
||||
execute_fn, registry)
|
||||
# A cmdsub target expands empty under provision, so its
|
||||
# classification is garbage; the precision degrade below keeps
|
||||
# the plan honest without costing a phantom write (mirrors TS).
|
||||
targets = [
|
||||
(r.kind, r.target) for r in expanded
|
||||
if r.kind in (RedirectKind.STDIN, RedirectKind.STDOUT) and isinstance(
|
||||
r.target, PathSpec) and not r.target.virtual.startswith("/dev/")
|
||||
and not (r.target_node is not None
|
||||
and has_command_substitution(r.target_node))
|
||||
]
|
||||
result = await handle_redirect_provision(recurse, registry, command,
|
||||
targets, session, namespace)
|
||||
if any(r.target_node is not None
|
||||
and has_command_substitution(r.target_node) for r in redirects):
|
||||
# A suppressed substitution hid the real redirect target.
|
||||
result.precision = Precision.UNKNOWN
|
||||
if pipe_node is not None:
|
||||
return rollup_pipe([result, await recurse(pipe_node, session)])
|
||||
return result
|
||||
|
||||
|
||||
async def _provision_reassociated(
|
||||
recurse: Callable,
|
||||
registry: MountRegistry,
|
||||
namespace: Namespace | None,
|
||||
execute_fn: Callable,
|
||||
redirects: list,
|
||||
right: Any,
|
||||
node: Any,
|
||||
session: Session,
|
||||
) -> ProvisionResult:
|
||||
"""Provision recurse wrapper for a re-associated trailing redirect.
|
||||
|
||||
Mirrors the executor: the list's last command carries the hoisted
|
||||
redirects; every other node provisions normally.
|
||||
|
||||
Args:
|
||||
recurse (Callable): the provision recursion.
|
||||
registry (MountRegistry): mount registry.
|
||||
namespace (Namespace | None): addressing authority.
|
||||
execute_fn (Callable): recursive execute (for expansions).
|
||||
redirects (list): parsed redirects hoisted off the list.
|
||||
right (Any): the list's last command node.
|
||||
node (Any): node being provisioned by the connection handler.
|
||||
session (Session): shell session state.
|
||||
"""
|
||||
if node is not right:
|
||||
return await recurse(node, session)
|
||||
return await _provision_redirected(recurse, registry, namespace,
|
||||
execute_fn, right, redirects, session)
|
||||
|
||||
|
||||
async def provision_node(
|
||||
registry: MountRegistry,
|
||||
dispatch: Callable,
|
||||
execute_fn: Callable,
|
||||
namespace: Namespace | None,
|
||||
node: Any,
|
||||
session: Session,
|
||||
scope: PlanScope | None = None,
|
||||
) -> ProvisionResult:
|
||||
"""Walk tree-sitter AST and estimate execution cost.
|
||||
|
||||
Dispatches on the same NodeKind classification as the executor
|
||||
(`mirage.shell.classify`), so every construct the executor runs
|
||||
has a planner branch; kinds neither walker supports fall through
|
||||
to an honest UNKNOWN.
|
||||
|
||||
Args:
|
||||
registry (MountRegistry): mount registry for path resolution.
|
||||
dispatch (Callable): VFS op dispatcher (op, path, **kw).
|
||||
execute_fn (Callable): recursive execute (for expansions).
|
||||
node (Any): tree-sitter node to plan.
|
||||
session (Session): shell session state.
|
||||
scope (PlanScope | None): walk-local planner state; created at
|
||||
the root and threaded through recursion.
|
||||
"""
|
||||
plan_scope = scope if scope is not None else PlanScope()
|
||||
recurse = partial(provision_node,
|
||||
registry,
|
||||
dispatch,
|
||||
execute_fn,
|
||||
namespace,
|
||||
scope=plan_scope)
|
||||
kind = node_kind(node)
|
||||
|
||||
if kind == NodeKind.COMMENT:
|
||||
return ProvisionResult(precision=Precision.EXACT)
|
||||
|
||||
if kind in (NodeKind.PROGRAM, NodeKind.SUBSHELL, NodeKind.COMPOUND):
|
||||
if kind == NodeKind.SUBSHELL:
|
||||
body = get_subshell_body(node)
|
||||
else:
|
||||
body = [c for c in node.named_children if c.type != NT.COMMENT]
|
||||
children = []
|
||||
for child in body:
|
||||
children.append(await recurse(child, session))
|
||||
if not children:
|
||||
return ProvisionResult(precision=Precision.EXACT)
|
||||
return rollup_list(";", children)
|
||||
|
||||
if kind == NodeKind.COMMAND:
|
||||
name = get_command_name(node)
|
||||
func_body = plan_scope.functions.get(name)
|
||||
if func_body is None:
|
||||
func_body = session.functions.get(name)
|
||||
if func_body is not None:
|
||||
return await handle_function_provision(recurse, name, func_body,
|
||||
plan_scope.planning,
|
||||
session)
|
||||
if name in _BUILTIN_NAMES:
|
||||
return await handle_builtin_provision()
|
||||
_, parts = split_env_prefix(get_parts(node))
|
||||
if not parts:
|
||||
return ProvisionResult(precision=Precision.EXACT)
|
||||
expanded = await expand_parts(parts, session, execute_fn)
|
||||
classified = classify_parts(expanded, registry, session.cwd)
|
||||
result = await handle_command_provision(registry, classified, session,
|
||||
namespace)
|
||||
if any(has_command_substitution(p) for p in parts):
|
||||
# The plan walk suppressed the substitution, so the operand
|
||||
# list is incomplete: the totals are floors, not answers.
|
||||
result.precision = Precision.UNKNOWN
|
||||
return result
|
||||
|
||||
if kind == NodeKind.PIPELINE:
|
||||
commands, _ = get_pipeline_commands(node)
|
||||
return await handle_pipe_provision(recurse, commands, session)
|
||||
|
||||
if kind == NodeKind.LIST:
|
||||
left, op, right = get_list_parts(node)
|
||||
return await handle_connection_provision(recurse, left, op, right,
|
||||
session)
|
||||
|
||||
if kind == NodeKind.REDIRECT:
|
||||
command, redirects = get_redirects(node)
|
||||
if command.type == NT.LIST:
|
||||
# Mirror the executor: a trailing redirect hoisted over an
|
||||
# &&/|| list binds to the last command.
|
||||
left, op, right = get_list_parts(command)
|
||||
wrapped = partial(_provision_reassociated, recurse, registry,
|
||||
namespace, execute_fn, redirects, right)
|
||||
return await handle_connection_provision(wrapped, left, op, right,
|
||||
session)
|
||||
return await _provision_redirected(recurse, registry, namespace,
|
||||
execute_fn, command, redirects,
|
||||
session)
|
||||
|
||||
if kind == NodeKind.IF:
|
||||
branches, else_body = get_if_branches(node)
|
||||
return await handle_if_provision(recurse, branches, else_body, session)
|
||||
|
||||
if kind == NodeKind.FOR:
|
||||
_, values, body = get_for_parts(node)
|
||||
if any(has_command_substitution(v) for v in values):
|
||||
# The iteration count comes from a suppressed substitution:
|
||||
# plan one pass as a floor and degrade.
|
||||
result = await handle_for_provision(recurse, body, 1, session)
|
||||
result.precision = Precision.UNKNOWN
|
||||
return result
|
||||
classified = await expand_and_classify(values, session, execute_fn,
|
||||
registry, session.cwd)
|
||||
n = len(classified) or 1
|
||||
return await handle_for_provision(recurse, body, n, session)
|
||||
|
||||
if kind == NodeKind.SELECT:
|
||||
# select re-prompts until break: unbounded like while.
|
||||
_, _, body = get_for_parts(node)
|
||||
return await handle_while_provision(recurse, body, session)
|
||||
|
||||
if kind in (NodeKind.WHILE, NodeKind.UNTIL):
|
||||
_, body = get_while_parts(node)
|
||||
return await handle_while_provision(recurse, body, session)
|
||||
|
||||
if kind == NodeKind.CASE:
|
||||
items = get_case_items(node)
|
||||
children = []
|
||||
for _, body in items:
|
||||
if not body:
|
||||
continue
|
||||
stmts = [await recurse(stmt, session) for stmt in body]
|
||||
children.append(stmts[0] if len(stmts) ==
|
||||
1 else rollup_list(";", stmts))
|
||||
if children:
|
||||
return rollup_list("||", children)
|
||||
return ProvisionResult(precision=Precision.EXACT)
|
||||
|
||||
if kind == NodeKind.FUNCTION_DEF:
|
||||
name = get_function_name(node)
|
||||
body = get_function_body(node)
|
||||
if name and body is not None:
|
||||
plan_scope.functions[name] = body
|
||||
return await handle_builtin_provision()
|
||||
|
||||
if kind in (NodeKind.DECLARATION, NodeKind.UNSET, NodeKind.TEST,
|
||||
NodeKind.VAR_ASSIGN):
|
||||
return await handle_builtin_provision()
|
||||
|
||||
if kind == NodeKind.NEGATED:
|
||||
inner = get_negated_command(node)
|
||||
return await recurse(inner, session)
|
||||
|
||||
return ProvisionResult(command=get_text(node), precision=Precision.UNKNOWN)
|
||||
Reference in New Issue
Block a user