diff --git a/test/gh-aw-quality.test.ts b/test/gh-aw-quality.test.ts index ecf70cf15..8ce4e8744 100644 --- a/test/gh-aw-quality.test.ts +++ b/test/gh-aw-quality.test.ts @@ -326,7 +326,7 @@ describe('gh-aw: safe-output configuration', () => { it('each safe-output has a max value that is a positive integer ≤ 1000', () => { for (const [name, config] of Object.entries(safeOutputs)) { - if (name === 'data' || name === 'messages' || name === 'jobs') continue; + if (name === 'data' || name === 'messages' || name === 'jobs' || name === 'allowed-domains') continue; expect(config.max, `${name} should have a max field`).toBeDefined(); const max = config.max as number; expect(max, `${name}.max should be > 0`).toBeGreaterThan(0); diff --git a/test/gh-aw-research-online.test.ts b/test/gh-aw-research-online.test.ts index 4dff755a3..c845ead97 100644 --- a/test/gh-aw-research-online.test.ts +++ b/test/gh-aw-research-online.test.ts @@ -117,6 +117,28 @@ describe('gh-aw: /squad research online-documentation capability', () => { expect(researchSkill).toContain('masquerade as one that consulted a source'); }); + it('preserves full public documentation URLs without weakening general URL sanitization', () => { + const safeOutputsMatch = frontmatter.match( + /^safe-outputs:\n((?:[ \t].*\n?)*)/m + ); + expect(safeOutputsMatch, 'safe-outputs: block should exist in frontmatter').not.toBeNull(); + const allowedDomainsMatch = safeOutputsMatch![1].match( + /^ allowed-domains:\n((?: - .+\n?)*)/m + ); + expect(allowedDomainsMatch, 'safe-outputs.allowed-domains should exist').not.toBeNull(); + const allowedDomains = allowedDomainsMatch![1] + .split('\n') + .map(line => line.trim()) + .filter(line => line.startsWith('- ')) + .map(line => line.slice(2)); + expect(allowedDomains).toEqual(['learn.microsoft.com', 'aspire.dev']); + expect(researchSkill).toMatch(/Preserve\s+each public documentation URL in full/); + expect(researchSkill).toMatch(/do not replace the\s+path with `\/redacted`/); + expect(researchSkill).toMatch( + /Never include URL userinfo, credentials, access tokens,[\s\S]*secret-bearing query parameters/ + ); + }); + it('lists Online sources as a required labeled section of the artifact', () => { // The structural contract MUST-contain list includes Online sources. Bound // the match to the enumeration SENTENCE (up to its terminating period) so it diff --git a/workflows/squad.md b/workflows/squad.md index 44427433e..8bd4ec277 100644 --- a/workflows/squad.md +++ b/workflows/squad.md @@ -169,6 +169,9 @@ pre-agent-steps: SQUAD_CAST_VALIDATOR_RUNNER chmod 500 "$validator_runner" safe-outputs: + allowed-domains: + - learn.microsoft.com + - aspire.dev messages: append-only-comments: true run-success: "🤖 [{workflow_name}]({run_url}) finished processing. This completion message does not indicate Cast success. For Cast, only a linked Cast pull request indicates success." @@ -1464,7 +1467,10 @@ section MUST state exactly one status so a later reader or test can assert on it instead of trusting silence: - `Online sources: consulted` — followed by the list of URLs actually fetched - this run (each URL also appears as a citation in the evidence table); or + this run (each URL also appears as a citation in the evidence table). Preserve + each public documentation URL in full, including its path; do not replace the + path with `/redacted`. Never include URL userinfo, credentials, access tokens, + or secret-bearing query parameters; omit those sensitive parts instead; or - `Online sources: unavailable — ` — when no external documentation was fetched, e.g. the network policy disallowed it, no external source was needed, or a requested source-of-truth site was unreachable.