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
7 changes: 7 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# CODEOWNERS — interim owners for governance and workflows
# Replace with your org/team handles when available (e.g., @libis/security @libis/privacy).

/governance/* @ErykKul
/governance/** @ErykKul
/policies/* @ErykKul
/.github/workflows/* @ErykKul
7 changes: 7 additions & 0 deletions .github/ai-transition-sync.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"source_repo": "libis/ai-transition",
"source_ref": "main",
"synced_at": "2025-09-16T00:00:00Z",
"files_copied": [],
"upstream_commit": "unknown"
}
67 changes: 67 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!-- @ai-generated: true -->

# @ai-tool: Copilot

## Summary

<!-- Provide 1–3 bullets: what changed and why; link issues like #123 or full URLs. Do not leave this placeholder. -->

## AI Provenance (required for AI-assisted changes)

- Prompt: <!-- paste exact prompt or link -->
- Model: <!-- e.g., GitHub Copilot gpt-5 -->
- Date: <!-- UTC ISO-8601, e.g., 2025-09-12T14:23:45Z; NEVER ${...} -->
- Author: <!-- @handle only (no names/emails) -->
- Role: provider|deployer

## Compliance checklist

- [ ] No secrets/PII
- [ ] Transparency notice updated (if user-facing)
- [ ] Agent logging enabled (actions/decisions logged)
- [ ] Kill-switch / feature flag present for AI features
- [ ] No prohibited practices under EU AI Act
- [ ] Human oversight retained (required if high-risk or agent mode)
- Risk classification: limited|high
- Personal data: yes|no
- DPIA: <link or N/A>
- Automated decision-making: yes|no
- Agent mode used: yes|no
- GPAI obligations: <link or N/A> (if Role: provider)
- Vendor GPAI compliance reviewed: <link or N/A> (if Role: deployer)
- [ ] License/IP attestation
- Attribution: <link or N/A>
- Oversight plan: <link> (required if high-risk/ADM)

<!-- PLACEHOLDER-LINKS: Automation (/gov links, /gov autofill apply) will replace <link or N/A> values. -->
> Tip: comment '/gov' to run checks + Copilot review, and '/gov links' to preview suggested links.

### Change-type specifics

- Security review: <link> or check: [ ] Security review requested (required if auth/permissions/etc.)
- Media assets changed:
- [ ] AI content labeled
- C2PA: <link or N/A>
- UI changed:
- [ ] Accessibility review (EN 301 549/WCAG)
- Accessibility statement: <link or N/A>
- Deploy/infra changed:
- Privacy notice: <link>
- Lawful basis: <e.g., public task/consent/contract or N/A>
- Retention schedule: <link or N/A>
- NIS2 applicability: yes|no|N/A
- Incident response plan: <link if NIS2=yes>
- Backend/API changed:
- ASVS: <link> or check [ ] OWASP ASVS review
- Log retention policy: <link or N/A>
- Data paths changed:
- TDM: yes|no|N/A
- TDM compliance: <link if yes>

## Tests & Risk

- [ ] Unit/integration tests added/updated
- [ ] Security scan passed
- Rollback plan: <summary>
- Smoke test: <link>
- [ ] Docs updated (if needed)
137 changes: 137 additions & 0 deletions .github/workflows/ai-agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# @ai-generated: true
# @ai-tool: Copilot
name: AI Governance Agent (ChatOps)

on:
issue_comment:
types: [created]

permissions:
contents: read
issues: write
pull-requests: write

jobs:
respond:
name: Respond to /gov commands on PRs
if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/gov') }}
runs-on: ubuntu-latest
steps:
- name: Handle /gov command
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const body = context.payload.comment.body.trim();
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = context.payload.issue.number;

const helpText =
'AI Governance Agent commands:\\n\\n\n' +
`- /gov help — show this help\n` +
`- /gov check — scan this PR for missing governance checklist items and summarize changes\n` +
`- /gov copilot — ask GitHub Copilot to review this PR\n` +
`- /gov links — preview suggested links (governance/test runs) for the PR template\n` +
`- /gov autofill apply — auto-fill safe N/A defaults and add run links into the PR body\n` +
`- /gov — run default check, trigger Copilot review, preview links, and auto-apply autofill\n`;

const isHelp = body.match(/^\/gov\s+help\b/i);
const isCheck = body.match(/^\/gov\s+check\b/i);
const isBare = body.match(/^\/gov\s*$/i);

if (!isHelp && !isCheck && !isBare) {
return; // Ignore other /gov variants for now
}

if (isHelp) {
await github.rest.issues.createComment({ owner, repo, issue_number, body: helpText });
return;
}

// Fetch PR details and changed files (treat bare /gov as /gov check)
const doCheck = isCheck || isBare;
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: issue_number });
const prBody = (pr.body || '').toString();
const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number: issue_number, per_page: 100 });

// Heuristics for change types
const changedPaths = files.map(f => f.filename);
const rx = {
userUI: /(^|\/)(ui|web|frontend|public|templates)(\/|$)|(^|\/)src\/.*\.(html|tsx?|vue)$/i,
sensitive: /(^|\/)(auth|authn|authz|login|acl|permissions?|access[_-]?control|secrets?|tokens?|jwt|oauth)(\/|$)|\.(policy|rego)$/i,
infra: /(^|\/)(k8s|kubernetes|helm|charts|deploy|ops|infra|infrastructure|manifests|terraform|ansible)(\/|$)|(^|\/)dockerfile$|docker-compose\.ya?ml$|Chart\.ya?ml$/i,
backend: /(^|\/)(src|api|server|backend|app)(\/|\/.*)([^\/]+)\.(js|ts|py|rb|go|java|cs)$/i,
media: /\.(png|jpe?g|gif|webp|svg|mp4|mp3|wav|pdf)$/i,
data: /(^|\/)(data|datasets|training|notebooks|scripts)(\/|$)/i
};
const has = (re) => changedPaths.some(p => re.test(p));
const flags = {
userUI: has(rx.userUI),
sensitive: has(rx.sensitive),
infra: has(rx.infra),
backend: has(rx.backend),
media: has(rx.media),
data: has(rx.data)
};

// Simple PR body checks mirroring the reusable workflow
const missing = [];
const need = (label, ok) => { if (!ok) missing.push(label); };

need('Prompt', /Prompt/i.test(prBody));
need('Model', /Model/i.test(prBody));
need('Date', /Date/i.test(prBody));
need('Author', /Author/i.test(prBody));
need('[x] No secrets/PII', /\[x\].*no\s+secrets\/?pii|no\s+pii\/?secrets/i.test(prBody));
need('Risk classification: limited|high', /Risk\s*classification:\s*(limited|high)/i.test(prBody));
need('Personal data: yes|no', /Personal\s*data:\s*(yes|no)/i.test(prBody));
need('Automated decision-making: yes|no', /Automated\s*decision-?making:\s*(yes|no)/i.test(prBody));
need('Agent mode used: yes|no', /Agent\s*mode\s*used:\s*(yes|no)/i.test(prBody));
need('Role: provider|deployer', /Role:\s*(provider|deployer)/i.test(prBody));

if (flags.userUI) {
need('[x] Transparency notice updated', /\[x\].*transparency\s+notice/i.test(prBody));
need('Accessibility statement: <link or N/A>', /Accessibility\s*statement:\s*(https?:\/\/|N\/?A)/i.test(prBody));
}
if (flags.media) {
need('[x] AI content labeled', /\[x\].*ai\s*content\s*labeled/i.test(prBody));
need('C2PA: <link or N/A>', /C2PA:\s*(https?:\/\/|N\/?A)/i.test(prBody));
}
if (flags.infra) {
need('Privacy notice: <link>', /Privacy\s*notice:\s*(https?:\/\/)/i.test(prBody));
need('Lawful basis: <text or N/A>', /Lawful\s*basis:\s*([A-Za-z]+|N\/?A)/i.test(prBody));
need('Retention schedule: <link or N/A>', /Retention\s*schedule:\s*(https?:\/\/|N\/?A)/i.test(prBody));
}
if (flags.backend) {
need('[x] OWASP ASVS review or ASVS: <link>', /\[x\].*owasp\s*asvs|ASVS:\s*(https?:\/\/)/i.test(prBody));
}

// Build a concise response
const bullet = (b) => `- ${b}`;
const filesList = changedPaths.slice(0, 50).map(bullet).join('\n');
const missingList = missing.length ? missing.map(bullet).join('\n') : '- None (looks good)';
const flagsList = Object.entries(flags).filter(([,v]) => v).map(([k]) => `
- ${k}`).join('') || '\n - none detected';

const reply =
'### Governance Agent Report\\n\\n\n' +
`PR: #${issue_number} by @${pr.user.login}\n\n` +
`Changed files (${changedPaths.length}):\n${filesList}\n\n` +
`Detected change types:${flagsList}\n\n` +
`Missing or incomplete items:\n${missingList}\n\n` +
`Tip: Use the PR template fields to satisfy these checks.\n\n` +
`Run /gov help for commands. Also try: /gov links and /gov autofill apply.`;

if (doCheck) {
await github.rest.issues.createComment({ owner, repo, issue_number, body: reply });
}

// If the command was bare /gov, also trigger Copilot review, links preview, and auto-apply autofill
if (isBare) {
await github.rest.issues.createComment({ owner, repo, issue_number, body: '/gov copilot' });
// And trigger auto-links preview so contributors can quickly fill PR fields
await github.rest.issues.createComment({ owner, repo, issue_number, body: '/gov links' });
// Finally, auto-apply link autofill (safe defaults + run links)
await github.rest.issues.createComment({ owner, repo, issue_number, body: '/gov autofill apply' });
}
Loading