Skip to content
Merged
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
28 changes: 15 additions & 13 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ priorities.
Promotion also reconciled the unquoted wildcard
redirect story with the fail-closed completeness contract, publishes
sparse exact/unknown effective-value overlays, and pins quoted, escaped,
and continued tilde-prefix behavior against Bash. The PowerShell promotion
half keeps OpenSpec task 1.10 open.
and continued tilde-prefix behavior against Bash. The later PowerShell
execution-region and loop slices now complete OpenSpec task 1.10.
- [x] Promote the first 22 stable PowerShell design cases covering value,
path, and redirect provenance. Exact effective values are published only
when parser-owned fragments prove the post-lexical value; runtime
Expand Down Expand Up @@ -331,8 +331,8 @@ priorities.
the explicit constrained command-resolution baseline, including after
decoded-host boundaries. The v0.3 authored-command approval correction
above supersedes that behavior and is implemented in this slice.
Execution-region and loop/state design promotions remain, so OpenSpec
task 1.10 stays open.
Execution-region and loop/state design promotions remained for later
slices; the final promotion below now closes OpenSpec task 1.10.
- [x] Deliver the first Bash `$()` substitution slice for supported
simple-command arguments and redirect targets. Direct tests and corpus
entries pin multiple and nested ordering, exact ancestry/spans, isolated
Expand Down Expand Up @@ -503,10 +503,11 @@ priorities.
parameters, positional slots, parameter sets, `ScriptBlock[]`, authored
ForEach-Object multi-block coordinates, and semantic Begin/Process/End
phases. The optional Microsoft.PowerShell.ThreadJob entry remains an
unknown incomplete receiver and no longer gates stable v0.3. Local
`Invoke-Command -AsJob`, ambiguous prefixes, malformed value binding,
unproved identities, and unknown receivers retain unknown/incomplete
facts. Supported catalog-owned module qualifications now pass structural
unknown incomplete receiver and no longer gates stable v0.3. A proved
local `Invoke-Command -AsJob` combination now fails atomically because
PowerShell has no compatible in-process parameter set. Ambiguous prefixes,
malformed value binding, unproved identities, and unknown receivers retain
unknown/incomplete facts. Supported catalog-owned module qualifications now pass structural
admission because every possible body remains visible; the occurrence
analyzer still withholds typed receiver facts after an observed command-
resolution mutation unless bounded mutation provenance proves the exact
Expand Down Expand Up @@ -574,12 +575,13 @@ priorities.
authored `Set-Alias Env:...` invocation.
The generator-owned executable corpus now supports per-entry initial-
state mode and includes the promoted Parallel and remote/session cases.
It now contains exact generated expectations for 84 of the 90 PowerShell
It now contains exact generated expectations for 86 of the 90 PowerShell
design cases. Four intentionally deferred condition/deferred-action cases
remain parked. Two stable cases remain implementation work rather than
being mislabeled as corpus-complete: bounded-loop dynamic invocation
still fails atomically, and local `Invoke-Command -AsJob` still needs to
reject its invalid parameter-set combination. Direct generated cases for
remain parked. Bounded-loop dynamic invocation is visible and incomplete,
retains finite loop-variable arguments for the occurrence, and invalidates
following state. A proved local `Invoke-Command -AsJob` combination fails
atomically as an invalid parameter set. Generated entries 539-540 pin
both corrections. Direct generated cases for
`Measure-Command`, `Trace-Command`, and `ForEach-Object
-RemainingScripts` fill the remaining stable receiver-catalog evidence
without renumbering the existing corpus.
Expand Down
30 changes: 18 additions & 12 deletions openspec/changes/v0-3-structured-shell-analysis/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
- [x] 1.7 Synchronize PowerShell grammar and analysis deltas into `SPEC.POWERSHELL.md`.
- [x] 1.8 Update `PROJECT_CONTEXT.md` and `IMPLEMENTATION_PLAN.md` with the accepted v0.3 scope and delivery slices.
- [x] 1.9 Add a paired Bash and PowerShell design corpus that records current behavior, desired structure, command occurrences, bounded values, redirect facts, compatibility projections, and security invariants.
- [ ] 1.10 Promote every design case for a stable-v0.3 construct into the
- [x] 1.10 Promote every design case for a stable-v0.3 construct into the
executable corpus as its production parser slice lands. Retain future-scope
design cases as non-gating evidence rather than release work.
- PowerShell now has exact generated expectations for 84 of 90 design cases.
Four future-scope cases remain non-gating. The stable bounded-loop dynamic
invocation and invalid local `Invoke-Command -AsJob` cases remain pending
production corrections and are not marked as promoted.
- Bash has exact executable expectations for 46 of 49 design cases; its
condition-loop, branch, and process-substitution cases remain future scope.
PowerShell has exact generated expectations for 86 of 90 design cases; its
condition-loop, branch, and deferred-action cases remain future scope.
- [x] 1.11 Correct the PowerShell script-block boundary and lock the additive
execution-region node, origin/phase/timing/cardinality facts, authored-versus-semantic
ordering, command projection, and independent shell-state analysis contract
Expand Down Expand Up @@ -185,8 +185,10 @@
occurrences until tasks 7.3 and 7.4 added binding and runspace analysis.
Alpha.3 still leaves default-mode occurrences incomplete for ambient
resolution; task 7.2c corrects that behavior. Explicit iterator/body state
mutation and dynamic invocation remain strict, and isolated child-host
loops do not taint their outer continuation.
mutation remains strict. Dynamic command identities are visible and
incomplete, preserve bounded loop-variable values for that occurrence,
and invalidate following state proofs. Isolated child-host loops do not
taint their outer continuation.
- [x] 7.2a Add the explicit `PwshInitialStateMode` contract and safe default
before value analysis. Lock the constrained noninteractive no-profile host
and module baseline, current-runspace sharing, child-host noninheritance,
Expand Down Expand Up @@ -271,13 +273,17 @@
- [x] 7.5g Retain explicit atomic-failure behavior for direct-block arguments
and leading `param(...)` declarations. Declaration and argument-binding
grammar is not required for stable v0.3.
- [ ] 7.6 Add adversarial cases for object-valued iterables, mutation, dynamic invocation, splatting, and cap overflow.
- [x] 7.6 Add adversarial cases for object-valued iterables, mutation, dynamic invocation, splatting, and cap overflow.
- Direct and generated corpus cases pin unknown pipeline-object values,
provider and variable mutation, incomplete dynamic identities, opaque
splats, the 32-candidate boundary, and overflow-to-Unknown behavior.
- [ ] 7.7 Add PowerShell corpus entries, live `pwsh` oracle coverage, and Netclaw integration cases.
- `PwshCorpusTool` now supports case-specific `PwshInitialStateMode`; keep
promoting the two remaining stable cases into its generated manifest after
their production corrections, then complete the Netclaw PowerShell policy
matrix. Exact generated cases now cover `Measure-Command`, `Trace-Command`,
and `ForEach-Object -RemainingScripts` directly.
the generated 540-case manifest and live-oracle matrix aligned, then
complete the Netclaw PowerShell policy matrix. Exact generated cases now
cover the final stable dynamic-loop and invalid local `Invoke-Command
-AsJob` cases, plus `Measure-Command`, `Trace-Command`, and
`ForEach-Object -RemainingScripts` directly.
- [x] 7.8 Implement the additive `PwshDialect` API, PowerShell 7 compatibility
default, unknown-value safe-fail, Windows PowerShell 5.1 pipeline-chain
rejection, dialect-specific alias and execution-region metadata, and static
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private bool TryParseForEach(
if (ContainsUnsupportedForEachStateTransfer(iteratorCommands) ||
ContainsUnsupportedForEachStateTransfer(body))
{
error = "PowerShell foreach state mutation or dynamic invocation is not supported in this structural slice";
error = "PowerShell foreach state mutation is not supported in this structural slice";
return false;
}

Expand Down Expand Up @@ -416,11 +416,6 @@ private static bool ContainsUnsupportedForEachStateTransfer(ShellSyntaxNode node
{
foreach (var clause in EnumerateClauses(node))
{
if (clause.Verb.IsDynamic)
{
return true;
}

var verb = clause.Verb.CanonicalVerb ??
(clause.Verb.Tokens.Count == 0 ? null : clause.Verb.Tokens[0]);
if (verb is not null &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2271,6 +2271,12 @@ private PwshFlowResult ApplyExecutionRegionEffect(
simple.Clause,
receiverIdentityProven,
_options.Dialect);
if (binding.Status == PwshExecutionRegionBindingStatus.InvalidParameterSet)
{
_isComplete = false;
return new PwshFlowResult(null, null);
}

if (binding.Status == PwshExecutionRegionBindingStatus.ProvedData)
{
RecordExecutionRegions(
Expand Down Expand Up @@ -3645,7 +3651,10 @@ private PwshFlowResult AnalyzeForEach(ForEachSyntax forEach, AnalysisContext inp

var body = AnalyzeBlock(
forEach.Body,
iterationInput.WithBinding(plan.BindingName, candidate));
iterationInput.WithBinding(
plan.BindingName,
candidate,
loopInput.CanPromote));
if (body.JoinedState is not AnalysisContext bodyExit)
{
return new PwshFlowResult(null, null);
Expand Down Expand Up @@ -3679,7 +3688,10 @@ private PwshFlowResult AnalyzeForEachFixedPoint(

var body = AnalyzeBlock(
forEach.Body,
head.WithBinding(plan.BindingName, plan.Summary));
head.WithBinding(
plan.BindingName,
plan.Summary,
loopInput.CanPromote));
if (body.JoinedState is not AnalysisContext bodyExit)
{
return new PwshFlowResult(null, null);
Expand All @@ -3706,7 +3718,10 @@ private PwshFlowResult AnalyzeForEachFixedPoint(
var widened = AnalysisContext.Widen(wideningBase, nextHead);
var widenedBody = AnalyzeBlock(
forEach.Body,
widened.WithBinding(plan.BindingName, plan.Summary));
widened.WithBinding(
plan.BindingName,
plan.Summary,
loopInput.CanPromote));
exits = AnalysisContext.JoinNullable(exits, widenedBody.JoinedState);
return exits is AnalysisContext widenedExit
? PwshFlowResult.Both(widenedExit)
Expand Down Expand Up @@ -5625,7 +5640,8 @@ internal AnalysisContext WithCwd(string? workingDirectory) =>

internal AnalysisContext WithBinding(
string name,
ShellValueDomain domain)
ShellValueDomain domain,
bool canPromote)
{
var bindings = new List<BindingFrame>(_bindings.Count + 1);
foreach (var binding in _bindings)
Expand All @@ -5638,7 +5654,7 @@ internal AnalysisContext WithBinding(

bindings.Add(new BindingFrame(
name,
CanPromote ? domain : ShellValueDomain.Unknown));
canPromote ? domain : ShellValueDomain.Unknown));
return new AnalysisContext(
WorkingDirectory,
CanPromote,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal enum PwshExecutionRegionBindingStatus
NotApplicable,
ProvedData,
ProvedExecution,
InvalidParameterSet,
Ambiguous,
}

Expand Down Expand Up @@ -366,12 +367,23 @@ private static PwshExecutionRegionBindingResult BindReceiver(
if (arguments.HasAmbiguousScriptBlockBinding
|| arguments.HasDuplicateParameter
|| arguments.HasInvalidScalarScriptBlockArray
|| HasReceiverValidationConflict(receiver, arguments)
|| compatibleSets.Count == 0)
|| HasReceiverValidationConflict(receiver, arguments))
{
return Ambiguous(canonicalName, receiver, scriptBlocks);
}

if (compatibleSets.Count == 0)
{
var result = Ambiguous(canonicalName, receiver, scriptBlocks);
return receiver == PwshExecutionRegionReceiver.InvokeCommand &&
arguments.HasNamed("AsJob")
? result with
{
Status = PwshExecutionRegionBindingStatus.InvalidParameterSet,
}
: result;
}

var bindings = new List<PwshExecutionRegionBinding>();
var parameterSet = PwshExecutionRegionParameterSet.Unknown;
switch (receiver)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"input": "foreach ($x in 1) { Set-Variable x 2 }",
"expected": {
"isUnparseable": true,
"unparseableReasonContains": "PowerShell foreach state mutation or dynamic invocation is not supported in this structural slice"
"unparseableReasonContains": "PowerShell foreach state mutation is not supported in this structural slice"
},
"notes": "Body mutation fails closed until PowerShell abstract state is modeled.",
"oracleExpectation": "OutOfScope"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"input": "foreach ($x in 1) { Set-Alias wipe Remove-Item }; wipe file.txt",
"expected": {
"isUnparseable": true,
"unparseableReasonContains": "PowerShell foreach state mutation or dynamic invocation is not supported in this structural slice"
"unparseableReasonContains": "PowerShell foreach state mutation is not supported in this structural slice"
},
"notes": "Command-resolution mutation cannot leave a post-loop command marked complete.",
"oracleExpectation": "OutOfScope"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"input": "foreach ($x in Import-Module ./commands.psm1) { Write-Output $x }; Invoke-Thing",
"expected": {
"isUnparseable": true,
"unparseableReasonContains": "PowerShell foreach state mutation or dynamic invocation is not supported in this structural slice"
"unparseableReasonContains": "PowerShell foreach state mutation is not supported in this structural slice"
},
"notes": "Iterator module mutation fails closed before later command resolution can be trusted.",
"oracleExpectation": "OutOfScope"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"input": "foreach ($x in 1) { Set-Item Alias:wipe Remove-Item }; wipe file.txt",
"expected": {
"isUnparseable": true,
"unparseableReasonContains": "PowerShell foreach state mutation or dynamic invocation is not supported in this structural slice"
"unparseableReasonContains": "PowerShell foreach state mutation is not supported in this structural slice"
},
"notes": "Alias, function, variable, and environment provider writes are runspace mutation.",
"oracleExpectation": "OutOfScope"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"input": "foreach ($x in 1) { Set-Item \u0027Alias:wipe\u0027 Remove-Item }; wipe file.txt",
"expected": {
"isUnparseable": true,
"unparseableReasonContains": "PowerShell foreach state mutation or dynamic invocation is not supported in this structural slice"
"unparseableReasonContains": "PowerShell foreach state mutation is not supported in this structural slice"
},
"notes": "Quoted provider paths use decoded provenance and cannot bypass mutation gating.",
"oracleExpectation": "OutOfScope"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"input": "foreach ($x in 1) { Set-Item \u0022Env:PATH\u0022 C:\\tools }; tool",
"expected": {
"isUnparseable": true,
"unparseableReasonContains": "PowerShell foreach state mutation or dynamic invocation is not supported in this structural slice"
"unparseableReasonContains": "PowerShell foreach state mutation is not supported in this structural slice"
},
"notes": "A quoted environment provider write can change later command resolution.",
"oracleExpectation": "OutOfScope"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"input": "foreach ($x in $(Set-Item \u0027Microsoft.PowerShell.Core\\Alias::wipe\u0027 Remove-Item; wipe victim)) { }",
"expected": {
"isUnparseable": true,
"unparseableReasonContains": "PowerShell foreach state mutation or dynamic invocation is not supported in this structural slice"
"unparseableReasonContains": "PowerShell foreach state mutation is not supported in this structural slice"
},
"notes": "Provider-qualified iterator mutation cannot publish a later alias invocation as complete.",
"oracleExpectation": "OutOfScope"
Expand Down
Loading