Skip to content

Commit d7a7a1c

Browse files
feat(scripts): add instruction applyTo scope Pester suite (#1640)
# Pull Request ## Description This PR adds a single Pester regression guard, **`scripts/tests/linting/Test-InstructionApplyToScope.Tests.ps1`**, that enforces phase-narrowed `applyTo` globs on every instruction file under `.github/instructions/security/**` and `.github/instructions/rai-planning/**`. Any file whose frontmatter declares `applyTo: '**'` is rejected unless its repo-relative path is on the in-script allowlist (currently empty, matching the Phase 6 audit-matrix migration map). The suite is data-driven (`It -ForEach`) so each scanned file produces a discrete test case (currently 18 cases, all passing). It uses `powershell-yaml` (already required by `scripts/linting/Validate-MarkdownFrontmatter.ps1` and other existing suites — no new repo-level dependency) and explicitly throws on missing frontmatter so silent skips cannot mask regressions. This PR is a **sibling** of #1497, branched off `main`. It is independent of PRs A/C/B in the post-#1497 stack and can land in any order relative to them. ### Scope reduction The originating plan listed four Pester suites under "planner linter hardening." Three are deferred to **PR B** because they assert content not yet on `main`: * Rule 5 "discover" keyword in `security/identity.instructions.md` and `sssc-identity.instructions.md`. * `## Startup` H2 in six prompts under `.github/prompts/security/` and `.github/prompts/security/sssc/`. * "Informational" bucket in `security-model.instructions.md`. Shipping those tests now would produce a red base. They will land in PR B alongside the content edits they exercise. ## Related Issue(s) Sibling of #1497. No other issue references. ## Type of Change **Code & Documentation:** * [x] New feature (non-breaking change adding functionality) **Infrastructure & Configuration:** * [x] Linting configuration (markdown, PowerShell, etc.) **Other:** * [x] Script/automation (`.ps1`, `.sh`, `.py`) ## Testing Automated validation performed by the agent: * `npm run test:ps -- -TestPath scripts/tests/linting/Test-InstructionApplyToScope.Tests.ps1` — **18/18 passed**. * `npm run lint:ps` — clean for repository code. Security analysis findings: * No secrets, credentials, or customer data in the diff. * No new runtime dependencies. `powershell-yaml` is already required by existing linting and test suites. * No changes that broaden privilege boundaries; the test reads files only. Diff-based assessments: * The new file follows the existing `scripts/tests/linting/Test-*.Tests.ps1` naming and structure. > [!NOTE] > Manual testing was not performed. ## Checklist ### Required Checks * [x] Documentation is updated (if applicable) — *(N/A — passive regression test.)* * [x] Files follow existing naming conventions. * [x] Changes are backwards compatible. * [x] Tests added for new functionality (if applicable). ### Required Automated Checks * [x] PowerShell analysis: `npm run lint:ps` ## Security Considerations * [x] This PR does not contain any sensitive or NDA information * [x] Any new dependencies have been reviewed for security issues — *(N/A — no new dependencies.)* * [x] Security-related scripts follow the principle of least privilege — *(N/A — read-only test.)* ## Additional Notes Companion PRs in the post-#1497 work stream: * **PR A (#1638)** — Security Planner state schema and fixtures. * **PR C (#1639)** — Planner disclaimer SSOT migration. * **PR B** — Security Planner agent and phase-gate parity (depends on PR A); will also carry the three deferred Pester suites and their content edits. * **PR E** — Dev environment alignment (sibling off `main`, optional).
1 parent ec35cec commit d7a7a1c

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

scripts/tests/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ tests/
115115
Test files use the `.Tests.ps1` suffix convention for automatic discovery by
116116
Pester.
117117

118+
Planner-related tests are split intentionally: rule-validator suites (state
119+
schema, cadence ordering, startup blocks, risk-grid grammar) live under
120+
`linting/` alongside other linter tests, while artifact-signing and runtime
121+
concerns (e.g. `Sign-PlannerArtifacts.Tests.ps1`) live under `security/`.
122+
118123
## Related Documentation
119124

120125
* [Testing Architecture](../../docs/architecture/testing.md) for Pester
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#Requires -Modules Pester, powershell-yaml
2+
# Copyright (c) Microsoft Corporation.
3+
# SPDX-License-Identifier: MIT
4+
<#
5+
.SYNOPSIS
6+
Regression guard: every instruction under .github/instructions/security/** and
7+
.github/instructions/rai-planning/** must use a phase-narrowed `applyTo` glob.
8+
No file may declare `applyTo: '**'` unless it appears on an explicit allowlist
9+
matching the Phase 6 audit matrix migration map.
10+
#>
11+
12+
BeforeDiscovery {
13+
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path
14+
15+
$script:scanRoots = @(
16+
(Join-Path $script:repoRoot '.github/instructions/security'),
17+
(Join-Path $script:repoRoot '.github/instructions/rai-planning')
18+
)
19+
20+
$script:cases = foreach ($root in $script:scanRoots) {
21+
if (-not (Test-Path $root)) {
22+
throw "Scan root not found: $root"
23+
}
24+
25+
Get-ChildItem -Path $root -Filter '*.md' -Recurse -File | ForEach-Object {
26+
@{
27+
Path = $_.FullName
28+
Rel = ($_.FullName.Substring($script:repoRoot.Length + 1) -replace '\\','/')
29+
}
30+
}
31+
}
32+
33+
if (-not $script:cases) {
34+
throw 'No instruction files discovered to validate.'
35+
}
36+
}
37+
38+
Describe 'Phase-narrowed applyTo regression guard' -Tag 'Unit' {
39+
BeforeAll {
40+
$script:applyToAllowlist = @()
41+
function Test-WorkspaceWideApplyTo {
42+
param($Value)
43+
if ($null -eq $Value) { return $false }
44+
if ($Value -is [string]) { return $Value.Trim() -eq '**' }
45+
if ($Value -is [System.Collections.IEnumerable]) {
46+
foreach ($v in $Value) {
47+
if (($v -as [string]) -and ($v.Trim() -eq '**')) { return $true }
48+
}
49+
}
50+
return $false
51+
}
52+
}
53+
54+
It 'File <Rel> uses a phase-narrowed applyTo (not workspace-wide)' -ForEach $script:cases {
55+
$raw = Get-Content -Path $Path -Raw
56+
if ($raw -notmatch '(?s)^---\s*\r?\n(.*?)\r?\n---') {
57+
throw "Missing frontmatter in $Rel"
58+
}
59+
$frontmatter = ConvertFrom-Yaml $Matches[1]
60+
$applyTo = $frontmatter.applyTo
61+
if (Test-WorkspaceWideApplyTo -Value $applyTo) {
62+
$script:applyToAllowlist | Should -Contain $Rel -Because "$Rel declares applyTo '**' which is not on the audit-matrix allowlist"
63+
} else {
64+
$true | Should -BeTrue
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)