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
37 changes: 37 additions & 0 deletions .github/workflows/governance-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Governance — Issue

on:
workflow_call:

permissions:
issues: write

jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const body = context.payload.issue.body || '';
const add = new Set();
const f = n => ((body.match(new RegExp(`###\\s*${n}\\s*\\n+([\\s\\S]*?)(?=\\n###\\s|$)`,'i'))||[,''])[1]||'').replace(/_No response_/gi,'').trim();

const key = (f('Severity').match(/S[1-4]/)||[])[0];
const PRIORITY = { S1: 'P0', S2: 'P1', S3: 'P2', S4: 'P3' };
if (key) { add.add(`sev:${key}`); add.add('priority:' + PRIORITY[key]); }

const SECRETS = [
[/\bgh[pousr]_[A-Za-z0-9]{20,}\b/,'GitHub token'],
[/\bAKIA[0-9A-Z]{16}\b/,'AWS key id'],
[/\bsk-[A-Za-z0-9]{20,}\b/,'API secret'],
[/-----BEGIN [A-Z ]*PRIVATE KEY-----/,'private key'],
];
const hits = SECRETS.filter(([re]) => re.test(body)).map(([,n]) => n);
if (hits.length) {
add.add('security:possible-leak');
await github.rest.issues.createComment({ ...context.repo, issue_number: context.payload.issue.number,
body: `> [!CAUTION]\n> Possible **${hits.join(', ')}** in this issue. Edit it out and **rotate the credential** — edit history is public.` });
}
if (add.size) await github.rest.issues.addLabels({ ...context.repo, issue_number: context.payload.issue.number, labels: [...add] });
if (hits.length) core.setFailed('Possible credential: ' + hits.join(', '));
45 changes: 45 additions & 0 deletions .github/workflows/governance-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Governance — PR

on:
workflow_call:
inputs:
strict:
type: boolean
default: true

permissions:
contents: read
pull-requests: write

jobs:
gates:
if: github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const body = context.payload.pull_request.body || '';
const fail = [];
const sec = n => (body.match(new RegExp(`##\\s*${n}([\\s\\S]*?)(?=\\n##\\s|$)`,'i'))||[,''])[1];

const problem = sec('Problem').replace(/<!--[\s\S]*?-->/g,'').replace(/Closes #\d*/i,'').trim();
if (problem.length < 30) fail.push('**Problem** empty or boilerplate.');

const risk = [...sec('Risk').matchAll(/^\s*-\s*\[([ xX])\]/gm)].filter(m => m[1] !== ' ');
if (risk.length !== 1) fail.push(`**Risk**: check exactly one (found ${risk.length}).`);

if (!/```[\s\S]*?```/.test(sec('Evidence')) && !/actions\/runs\/\d+/.test(sec('Evidence')))
fail.push('**Evidence**: paste output or link a CI run.');

for (const line of sec('Gates').split('\n')) {
const m = line.match(/^\s*-\s*\[ \]\s*(.+)$/);
if (m && !/(n\/a|not applicable|—|--|:)\s*\S{4,}/i.test(m[1]))
fail.push(`**Gate** unchecked without reason: _${m[1].slice(0,60)}_`);
}

if (fail.length) {
core.summary.addHeading('Governance gates',2).addList(fail).write();
if (${{ inputs.strict }}) core.setFailed(`${fail.length} gate issue(s).`);
else core.warning(`${fail.length} gate issue(s) — advisory mode.`);
}
106 changes: 106 additions & 0 deletions .github/workflows/seed-governance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Seed non-inheritable governance

# Community health files (SECURITY.md, CONTRIBUTING.md, PR/issue templates) are
# INHERITED automatically — this workflow does not touch them.
#
# It seeds only the two things GitHub cannot inherit:
# 1. .github/CODEOWNERS (not a community health file)
# 2. .github/workflows/governance.yml (a 12-line caller pinned to @v1)
#
# This is a ONE-TIME seed per repo. After it lands, updating governance logic means
# editing governance-pr.yml here and moving the v1 tag — no fan-out, no token use.

on:
workflow_dispatch:
inputs:
mode:
description: Dry run lists targets only; seed opens PRs.
type: choice
options: [dry-run, seed]
default: dry-run
repo_filter:
description: Optional substring filter, e.g. "l9-" to pilot a subset.
type: string
default: ""

permissions:
contents: read

jobs:
seed:
runs-on: ubuntu-latest
environment: governance-distribution # add required reviewers here
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- id: token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0
with:
app-id: ${{ vars.GOVERNANCE_APP_ID }}
private-key: ${{ secrets.GOVERNANCE_APP_PRIVATE_KEY }}
owner: Quantum-L9

- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.token.outputs.token }}
script: |
const fs = require('fs');
const MODE = '${{ inputs.mode }}';
const FILTER = '${{ inputs.repo_filter }}';
const BRANCH = 'chore/seed-governance';

const payload = {
'.github/CODEOWNERS': fs.readFileSync('templates/CODEOWNERS.repo', 'utf8'),
'.github/workflows/governance.yml': fs.readFileSync('templates/governance-caller.yml', 'utf8'),
};

const all = await github.paginate(github.rest.repos.listForOrg, { org: 'Quantum-L9', type: 'all', per_page: 100 });
const targets = all.filter(r => !r.archived && !r.fork && r.name !== '.github' && (!FILTER || r.name.includes(FILTER)));

const rows = [];
for (const repo of targets) {
const o = repo.owner.login, n = repo.name;
try {
// idempotency: skip if both files already exist
const present = await Promise.all(Object.keys(payload).map(p =>
github.rest.repos.getContent({ owner: o, repo: n, path: p }).then(() => true).catch(() => false)));
if (present.every(Boolean)) { rows.push([n, 'already seeded']); continue; }

if (MODE === 'dry-run') { rows.push([n, 'would seed']); continue; }

const base = repo.default_branch;
const ref = await github.rest.git.getRef({ owner: o, repo: n, ref: `heads/${base}` });
await github.rest.git.createRef({ owner: o, repo: n, ref: `refs/heads/${BRANCH}`, sha: ref.data.object.sha })
.catch(e => { if (e.status !== 422) throw e; });

for (const [path, content] of Object.entries(payload)) {
const existing = await github.rest.repos.getContent({ owner: o, repo: n, path, ref: BRANCH }).catch(() => null);
await github.rest.repos.createOrUpdateFileContents({
owner: o, repo: n, path, branch: BRANCH,
message: `chore(governance): seed ${path}`,
content: Buffer.from(content).toString('base64'),
sha: existing?.data?.sha,
});
}

const pr = await github.rest.pulls.create({
owner: o, repo: n, head: BRANCH, base,
title: 'chore(governance): seed CODEOWNERS and governance caller',
body: [
'Seeds the only two governance files GitHub cannot inherit from `Quantum-L9/.github`.',
'',
'- `.github/CODEOWNERS` — CODEOWNERS is not a community health file, so it must be physical.',
'- `.github/workflows/governance.yml` — a caller pinned to `@v1`. Logic lives in `.github`; this file should never need to change again.',
'',
'Everything else (SECURITY.md, CONTRIBUTING.md, PR and issue templates) is already inherited automatically — nothing was copied.',
].join('\n'),
});
rows.push([n, `PR #${pr.data.number}`]);
} catch (e) {
rows.push([n, `failed: ${e.status || ''} ${e.message}`.slice(0, 90)]);
}
}

core.summary.addHeading(`Seed (${MODE}) — ${rows.length} repos`, 2)
.addTable([[{data:'repo',header:true},{data:'result',header:true}], ...rows.map(r => r.map(String))])
.write();
30 changes: 17 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,27 @@ Before opening any pull request, verify each item:

---

## Quick Setup (3 Steps)
## Quick Setup

```bash
# Step 1: Clone Cursor-Governance alongside your target repo
git clone https://github.com/Quantum-L9/Cursor-Governance.git

# Step 2: Run workspace symlink wiring
cd Cursor-Governance
bash scripts/setup_workspace_symlinks.sh
Governance defaults (PR/issue templates, `SECURITY.md`, this file) are **inherited
automatically** from `Quantum-L9/.github` — no cloning or copying required. For a
new or existing repo, one idempotent command verifies your setup and reports
anything that still needs local wiring:

# Step 3: Validate symlinks
ls -la .cursor/rules .cursor/skills .cursor/commands
# Expected: all three resolve without error
```bash
# one-time bootstrap for a new or existing repo (idempotent, safe to re-run)
curl -fsSL https://raw.githubusercontent.com/Quantum-L9/.github/main/scripts/bootstrap.sh | bash
```

Per [CANONICAL_LAW.md §2](https://github.com/Quantum-L9/Cursor-Governance/blob/main/CANONICAL_LAW.md#2-symlink-contract):
the workspace root must have `.cursor/` symlinks resolving to `Cursor-Governance/rules/`, `skills/`, and `commands/`.
`bootstrap.sh` reports which files inherit from the org defaults and which have
local overrides. It never duplicates inherited files — duplication is the drift
mechanism this repo exists to eliminate (see `docs/AUDIT.md` finding #4 for the
migration away from the previous "clone Cursor-Governance alongside your repo" step).

For Cursor workspace wiring (rules/skills/commands symlinks), follow
[CANONICAL_LAW.md §2](https://github.com/Quantum-L9/Cursor-Governance/blob/main/CANONICAL_LAW.md#2-symlink-contract):
the workspace root must have `.cursor/` symlinks resolving to `Cursor-Governance/rules/`, `skills/`, and `commands/`,
validated with `ls -la .cursor/rules .cursor/skills .cursor/commands`.

---

Expand Down
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Quantum-L9 — org defaults & governance

Canonical, inheritance-first governance for the Quantum-L9 constellation.

## Status of the five findings

| # | Finding | Fix | Propagation | Token |
| --- | --- | --- | --- | --- |
| 1 | PR template was at repo root, so it never propagated | `.github/pull_request_template.md` | Inherited | No |
| 2 | No canonical CODEOWNERS | `.github/CODEOWNERS` + `templates/CODEOWNERS.repo` | Seeded once | Yes |
| 3 | SECURITY.md duplicated per repo | canonical `SECURITY.md` | Inherited | No |
| 4 | CONTRIBUTING.md was a manual clone step | rewritten + `scripts/bootstrap.sh` | Inherited | No |
| 5 | No cross-repo mechanism | `workflow_call` callees + 12-line caller | By reference | No |

Four of five need **no credentials at all**. See `docs/DISTRIBUTION.md`.

## Layout

```
.github/
├── pull_request_template.md # inherited org-wide ← finding 1
├── CODEOWNERS # governs THIS repo ← finding 2
└── workflows/
├── governance-pr.yml # workflow_call callee ← finding 5
├── governance-issue.yml # workflow_call callee
└── seed-governance.yml # dispatch-only, seeds the 2 non-inheritable files
templates/
├── CODEOWNERS.repo # copied into each repo
└── governance-caller.yml # 12-line caller, pinned @v1
SECURITY.md # inherited org-wide ← finding 3
CONTRIBUTING.md # inherited org-wide ← finding 4
docs/
├── AUDIT.md # findings + evidence
└── DISTRIBUTION.md # what the token does and does not block
scripts/
├── preflight.sh # read-only verification, run first
└── bootstrap.sh
```

## Before you merge

```bash
./scripts/preflight.sh
```

It checks that every `@Quantum-L9/<team>` slug in CODEOWNERS actually exists (an
unresolvable owner makes the rule silently inert), that this repo is public, and
which repos have local overrides that block inheritance.

## Preflight coverage

`./scripts/preflight.sh` is read-only and checks five things:

1. Every `@Quantum-L9/<team>` slug in CODEOWNERS resolves to a real org team — an
unresolvable owner makes the rule silently inert.
2. This repo is public, without which nothing inherits.
3. Which repos already have `CODEOWNERS` / the governance caller (seed idempotency).
4. Which repos hold local overrides that block inheritance.
5. **Actions policy per repo** — a `local_only` or disabled-Actions repo will fail a
cross-repo `workflow_call` before any step runs. See `docs/DISTRIBUTION.md`
Appendix B.

## Placeholders — resolved at integration

The pack shipped with unverified placeholder slugs (`@Quantum-L9/maintainers`,
`governance`, `infra`, `ci-cd`, `security`). At integration time the live org was
queried (`gh api orgs/Quantum-L9/teams`): only **`platform`** exists. All CODEOWNERS
rules in `templates/CODEOWNERS.repo` now use `@Quantum-L9/platform` (+ `@cryptoxdog`
on blast-radius paths), matching this repo's existing `.github/CODEOWNERS`.

## Integration notes (v2.0.1 → live repo)

- `.github/CODEOWNERS` — the repo already had a canonical copy with real teams;
the pack's placeholder version was **not** installed over it.
- `.github/pull_request_template.md` — owned by PR #15 (pr-template-kit), which
ships a superset of the pack's template (adds *Changes by intent* + the
`pr-files.yml` bot). Finding 1's fix (nested path) is satisfied there.
- `ISSUE_TEMPLATE/` — owned by PR #16 (issue-template-kit), per the pack's own
"what not to do yet" guidance.
- `SECURITY.md` / `CONTRIBUTING.md` — merged: existing detail retained, pack's
canonical-single-source clause, disclosure timeline, and bootstrap-first setup added.
13 changes: 12 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@

## Scope

This policy applies to all repositories in the **Quantum-L9** GitHub organization.
This policy applies to all repositories in the **Quantum-L9** GitHub organization,
including internal tooling (`l9-*` cartridges), infrastructure-as-code, and CI/CD
workflows. This file is the **single canonical source**, inherited org-wide from
`Quantum-L9/.github` — individual repos MUST NOT maintain a competing `SECURITY.md`;
link here instead. (One local copy makes that repo ignore this file entirely; there
is no merging.)

## Out of Scope

Vulnerabilities requiring physical access, social engineering of maintainers, or
issues in third-party dependencies without a demonstrated exploit path against
Quantum-L9 systems specifically — report those upstream instead.

## Reporting a Vulnerability

Expand Down
Loading