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
52 changes: 0 additions & 52 deletions .github/config/disclaimers.yml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Generate formatted work items from security model mitigations and standards gaps

## ADO Work Item Template

> **Note:** This work item was generated by the Security Planner AI agent. Review for accuracy and adjust before assigning to a person.

- [ ] Reviewed by a human security professional before execution

Assign sequential IDs within the security plan using the format `WI-SEC-{NNN}` (for example, WI-SEC-001, WI-SEC-002). Order work items by type hierarchy: Epic, Feature, User Story, Task, Bug.

Required fields per work item:
Expand Down Expand Up @@ -60,6 +64,10 @@ Five MCP tool categories support ADO operations: work item creation, work item u

## GitHub Issue Template

> **Note:** This work item was generated by the Security Planner AI agent. Review for accuracy and adjust before assigning to a person.

- [ ] Reviewed by a human security professional before execution

Assign temporary IDs using the format `{{SEC-TEMP-N}}`, replaced with real issue numbers on creation. Order operations by type: create, update, comment, label, close.

Required fields per issue:
Expand Down
17 changes: 17 additions & 0 deletions .github/instructions/security/identity.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ Core responsibilities:

Voice: clear, methodical, and security-focused. Communicate with professional authority while keeping guidance accessible and actionable.

## Disclaimer and Attribution Protocol

### Session Start Display

On the first turn of any Security Planner session, display the canonical Security Planning disclaimer block defined in [.github/instructions/shared/disclaimer-language.instructions.md](../shared/disclaimer-language.instructions.md) verbatim. Record the display by setting `state.disclaimerShownAt` to an ISO 8601 timestamp. Do not advance to any phase work before the disclaimer is shown for the session.

### Exit Point Reminder

At each of the following exit points, re-surface a brief one-line professional-review reminder. Use the canonical wording in [.github/instructions/shared/disclaimer-language.instructions.md](../shared/disclaimer-language.instructions.md) (Security Planning section) for the reminder text.

1. **Phase 6 completion (handoff success path)** — Display the reminder immediately before presenting the final handoff summary.
2. **Compact handoff** — Display the reminder when the orchestrator hands off to ADO or GitHub backlog workflows.
3. **Error exit** — Display the reminder on any unrecoverable error path before terminating the session.
4. **User-initiated exit** — Display the reminder when the user explicitly stops the session or switches agents.

Each reminder must state that the generated plan is AI-assisted and requires professional security review before execution.

## Six-Phase Definitions

Each phase has entry criteria, activities, exit criteria, artifacts produced, and a defined transition.
Expand Down
14 changes: 14 additions & 0 deletions .github/instructions/shared/disclaimer-language.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ applyTo: '**/.copilot-tracking/rai-plans/**, **/.copilot-tracking/security-plans

Planning agents that generate assessments requiring professional review display a CAUTION block during startup. Each section contains the verbatim disclaimer for the corresponding planner. Prompt files and agents reference the appropriate section via `#file:` to ensure consistent presentation across all entry points.

<!--
Authoring contract (parsed by scripts/linting/Validate-PlannerArtifacts.ps1):

- Each planner gets exactly one H2 section. The first whitespace-delimited word of the heading, lowercased, is the slug. Examples: "RAI Planning" -> "rai"; "Security Planning" -> "security"; "SSSC Planning" -> "sssc".
- The slug derives three downstream identifiers used by ai-artifact footer validation:
- planner key: `{slug}-planner`
- disclaimer id: `{slug}-full-disclaimer`
- disclaimer label: `{heading} Disclaimer` (full heading, not slug)
- Each H2 section must contain exactly one `> [!CAUTION]` blockquote. Only the first CAUTION block in a section is extracted; additional CAUTION blocks within the same H2 are ignored.
- The CAUTION block's prose should begin with `**Disclaimer:**` (the trailing colon is optional). This prefix is stripped before the text is matched against artifact footers; prose without it is retained verbatim.
- Multi-line blockquote prose is joined with single spaces. Keep wrapping natural for source readability.
-->


## RAI Planning

> [!CAUTION]
Expand Down
24 changes: 12 additions & 12 deletions scripts/agents/activation-harness/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"CleanWorkspace": {
"ScenarioName": "CleanWorkspace",
"AgentBytes": 10096,
"ColdStartBytes": 38409,
"ColdStartBytes": 39456,
"LoadedFiles": [
{
"Path": ".github/agents/project-planning/adr-creation.agent.md",
Expand All @@ -14,15 +14,15 @@
},
{
"Path": ".github/instructions/shared/disclaimer-language.instructions.md",
"Bytes": 4181
"Bytes": 5228
}
],
"Hash": "9fd83b5a9113bfa50d1e8bd4cc24808331bfc038194468c96d8bdf5e11e12ad8"
"Hash": "040ed800dd6635ef75de95bb8a201441d8c59194f8b9a547ba64719ee836376a"
},
"SteadyState": {
"ScenarioName": "SteadyState",
"AgentBytes": 10096,
"ColdStartBytes": 78751,
"ColdStartBytes": 79798,
"LoadedFiles": [
{
"Path": ".github/agents/project-planning/adr-creation.agent.md",
Expand All @@ -46,15 +46,15 @@
},
{
"Path": ".github/instructions/shared/disclaimer-language.instructions.md",
"Bytes": 4181
"Bytes": 5228
}
],
"Hash": "8efde478be60b8ea9ce7c4d7f380f7f6a3d2d623170cfa0ed66ec15b003ce4fb"
"Hash": "296bb1a41437c68ea6e656b0c84cc6a6cd452054a0776d37004a8a2029df4e9e"
},
"GovernEntry": {
"ScenarioName": "GovernEntry",
"AgentBytes": 10096,
"ColdStartBytes": 92147,
"ColdStartBytes": 93194,
"LoadedFiles": [
{
"Path": ".github/agents/project-planning/adr-creation.agent.md",
Expand All @@ -78,19 +78,19 @@
},
{
"Path": ".github/instructions/shared/disclaimer-language.instructions.md",
"Bytes": 4181
"Bytes": 5228
},
{
"Path": ".github/skills/project-planning/adr-author/SKILL.md",
"Bytes": 13396
}
],
"Hash": "24970f60a8fa20ed7f3f97d6e51464420f8ee9ee1972d638c1304fa1ef30f84e"
"Hash": "4f18403391012bd6b84c2b6a832afb146b7ac139fc28370eec72d7d543eae6e8"
},
"AdoptTemplate": {
"ScenarioName": "AdoptTemplate",
"AgentBytes": 10096,
"ColdStartBytes": 99141,
"ColdStartBytes": 100188,
"LoadedFiles": [
{
"Path": ".github/agents/project-planning/adr-creation.agent.md",
Expand All @@ -114,7 +114,7 @@
},
{
"Path": ".github/instructions/shared/disclaimer-language.instructions.md",
"Bytes": 4181
"Bytes": 5228
},
{
"Path": ".github/skills/project-planning/adr-author/scripts/normalize_template.py",
Expand All @@ -125,6 +125,6 @@
"Bytes": 13396
}
],
"Hash": "2293d95cd4862e6d8fabaa31ecaf5b303b2fc9826c8ea2faf04e205c7bc17d1c"
"Hash": "c3eefe020ece450eaea370fcdfd492fa36d75ba7d8d5078210ed647af4ccce2b"
}
}
92 changes: 66 additions & 26 deletions scripts/linting/Validate-PlannerArtifacts.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
Validates AI artifact footer and disclaimer presence in instruction templates.

.DESCRIPTION
Reads footer-with-review.yml and disclaimers.yml config files as the single source
of truth, then scans instruction files for required footer text based on artifact
Reads footer-with-review.yml for footer text and artifact-classification rules, and
parses shared/disclaimer-language.instructions.md as the canonical disclaimer source.
Scans instruction files for required footer and disclaimer text based on artifact
classification rules. Outputs results as JSON and sets CI environment variables on
failure.

Expand All @@ -23,8 +24,9 @@
.PARAMETER FooterConfigPath
Path to the footer-with-review.yml config file.

.PARAMETER DisclaimerConfigPath
Path to the disclaimers.yml config file.
.PARAMETER DisclaimerSourcePath
Path to the shared disclaimer-language instructions markdown file. The validator
parses H2 sections and their CAUTION blockquote bodies to derive disclaimer text.

.PARAMETER FailOnMissing
When specified, treats missing footers and disclaimers as validation failures.
Expand All @@ -51,7 +53,7 @@ param(
[string]$FooterConfigPath = '.github/config/footer-with-review.yml',

[Parameter(Mandatory = $false)]
[string]$DisclaimerConfigPath = '.github/config/disclaimers.yml',
[string]$DisclaimerSourcePath = '.github/instructions/shared/disclaimer-language.instructions.md',

[Parameter(Mandatory = $false)]
[switch]$FailOnMissing,
Expand Down Expand Up @@ -107,40 +109,78 @@ function Import-FooterConfig {
return $config
}

function Import-DisclaimerConfig {
function Import-DisclaimerSource {
<#
.SYNOPSIS
Loads and validates disclaimers.yml.
Parses the shared disclaimer-language instructions markdown as the canonical disclaimer source.

.PARAMETER ConfigPath
Absolute path to the disclaimer config YAML file.
.DESCRIPTION
Reads the markdown file, splits on H2 headings to identify planner sections,
and extracts the verbatim disclaimer prose from each section's CAUTION blockquote.
The first word of each heading (lowercased) maps to the planner key and disclaimer id
convention: 'RAI Planning' -> 'rai-planner' / 'rai-full-disclaimer'.

.PARAMETER SourcePath
Absolute path to the disclaimer-language.instructions.md markdown file.

.OUTPUTS
[hashtable] Parsed disclaimer config.
[hashtable] Parsed config shaped as @{ version; source; disclaimers = @{ key = @{ id; label; text } } }.
#>
[CmdletBinding()]
[OutputType([hashtable])]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$ConfigPath
[string]$SourcePath
)

if (-not (Test-Path $ConfigPath)) {
throw "Disclaimer config not found: $ConfigPath"
if (-not (Test-Path $SourcePath)) {
throw "Disclaimer source not found: $SourcePath"
}

$content = Get-Content -Path $ConfigPath -Raw -Encoding utf8
$config = ConvertFrom-Yaml -Yaml $content
$raw = Get-Content -Path $SourcePath -Raw -Encoding utf8
# Strip YAML frontmatter
$body = $raw -replace '(?s)\A---.*?\r?\n---\s*\r?\n', ''

if (-not $config.version) {
throw "Disclaimer config missing 'version' field: $ConfigPath"
$disclaimers = @{}
$sectionRegex = [regex]'(?ms)^##[ \t]+(?<heading>[^\r\n]+?)[ \t]*\r?\n(?<body>.*?)(?=^##[ \t]|\z)'
$cautionRegex = [regex]'(?m)^>[ \t]*\[!CAUTION\][ \t]*\r?\n(?<block>(?:^>.*\r?\n?)+)'
$prefixRegex = [regex]'^\*\*Disclaimer:?\*\*[\s:\-\u2014]*'

foreach ($match in $sectionRegex.Matches($body)) {
$heading = $match.Groups['heading'].Value.Trim()
$sectionBody = $match.Groups['body'].Value

$cautionMatch = $cautionRegex.Match($sectionBody)
if (-not $cautionMatch.Success) { continue }

$blockLines = $cautionMatch.Groups['block'].Value -split '\r?\n'
$proseParts = foreach ($line in $blockLines) {
$stripped = $line -replace '^>[ \t]?', ''
if ($stripped.Trim().Length -gt 0) { $stripped.Trim() }
}
$prose = ($proseParts -join ' ').Trim()
$prose = $prefixRegex.Replace($prose, '', 1)
if ([string]::IsNullOrWhiteSpace($prose)) { continue }

$slug = ($heading -split '\s+' | Select-Object -First 1).ToLowerInvariant()
$key = "$slug-planner"
$disclaimers[$key] = @{
id = "$slug-full-disclaimer"
label = "$heading Disclaimer"
text = $prose
}
}
if (-not $config.disclaimers) {
throw "Disclaimer config missing 'disclaimers' section: $ConfigPath"

if ($disclaimers.Count -eq 0) {
throw "No disclaimer sections found in source: $SourcePath"
}

return $config
return @{
version = 'markdown-source'
source = $SourcePath
disclaimers = $disclaimers
}
}

function Get-FooterSearchText {
Expand Down Expand Up @@ -318,7 +358,7 @@ function Test-AIArtifactCompliance {
Parsed footer-with-review.yml config.

.PARAMETER DisclaimerConfig
Parsed disclaimers.yml config.
Parsed disclaimer source (see Import-DisclaimerSource).

.PARAMETER RepoRoot
Repository root for relative path display.
Expand Down Expand Up @@ -422,8 +462,8 @@ function Test-AIArtifactValidation {
.PARAMETER FooterConfigPath
Path to footer-with-review.yml relative to repo root.

.PARAMETER DisclaimerConfigPath
Path to disclaimers.yml relative to repo root.
.PARAMETER DisclaimerSourcePath
Path to the shared disclaimer-language instructions markdown file, relative to repo root.

.PARAMETER FailOnMissing
When set, missing footers cause a non-zero exit code.
Expand All @@ -447,7 +487,7 @@ function Test-AIArtifactValidation {
[string]$FooterConfigPath,

[Parameter(Mandatory = $true)]
[string]$DisclaimerConfigPath,
[string]$DisclaimerSourcePath,

[Parameter(Mandatory = $false)]
[switch]$FailOnMissing,
Expand All @@ -464,7 +504,7 @@ function Test-AIArtifactValidation {

# Load configs
$footerConfig = Import-FooterConfig -ConfigPath (Join-Path $repoRoot $FooterConfigPath)
$disclaimerConfig = Import-DisclaimerConfig -ConfigPath (Join-Path $repoRoot $DisclaimerConfigPath)
$disclaimerConfig = Import-DisclaimerSource -SourcePath (Join-Path $repoRoot $DisclaimerSourcePath)

# Collect instruction files
$allFiles = @()
Expand Down Expand Up @@ -616,7 +656,7 @@ if ($MyInvocation.InvocationName -ne '.') {
-Paths $Paths `
-ExcludePaths $ExcludePaths `
-FooterConfigPath $FooterConfigPath `
-DisclaimerConfigPath $DisclaimerConfigPath `
-DisclaimerSourcePath $DisclaimerSourcePath `
-FailOnMissing:$FailOnMissing `
-OutputPath $OutputPath

Expand Down
Loading
Loading