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
2 changes: 1 addition & 1 deletion test/gh-aw-quality.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
22 changes: 22 additions & 0 deletions test/gh-aw-research-online.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion workflows/squad.md
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -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 — <reason>` — 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.
Expand Down