Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ tests/
docs/
*.md

# Documentation build output (MkDocs)
_site/

# Astro / Node.js (landing page)
site/node_modules/
site/dist/
site/.astro/

# Virtual environments
.venv/
venv/
Expand Down
2 changes: 1 addition & 1 deletion .grype.yaml → .github/.grype.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Grype configuration — CVE ignore list
# Kept in sync with .trivyignore.yaml.
# Kept in sync with .github/.trivyignore.yaml.

ignore:
- vulnerability: CVE-2026-22184
Expand Down
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions .github/release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"release-type": "python",
"package-name": "synthorg",
"include-component-in-tag": false,
"changelog-path": ".github/CHANGELOG.md",
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"changelog-sections": [
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ jobs:
# MIT-0: cffi 2.0.0 — permissive (MIT variant, no attribution required)
# LicenseRef-scancode-free-unknown: aiosqlite 0.21.0, aiodocker 0.26.0,
# pycparser 3.0, sse-starlette 3.3.2 — MIT per classifiers, scancode misdetects
# LGPL-*: @img/sharp-libvips-* (Astro image optimization, build-time only)
# BlueOak-1.0.0: lru-cache, sax (Astro transitive deps, permissive)
allow-licenses: >-
MIT, MIT-0, Apache-2.0, BSD-2-Clause, BSD-3-Clause,
ISC, MPL-2.0, PSF-2.0, Unlicense, 0BSD,
CC0-1.0, Python-2.0, Python-2.0.1,
LicenseRef-scancode-free-unknown
LicenseRef-scancode-free-unknown,
LGPL-2.0-only, LGPL-2.1-only, LGPL-3.0-only, LGPL-3.0-or-later,
BlueOak-1.0.0
Comment on lines +30 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For actions/dependency-review-actionv4, does theallow-licenses setting apply globally to all dependency changes in the pull request, or can license allowances be scoped to specific packages or manifests?

💡 Result:

In actions/dependency-review-action@v4, allow-licenses is a single, global allowlist: the action will fail the PR if it introduces any dependency whose detected license doesn’t match the allow-licenses list. There’s no built-in way to scope different license allowlists to specific packages or specific manifests. [1]

If you need exceptions for particular dependencies, the v4 action provides allow-dependencies-licenses, which lets you exclude specific packages (by purl) from license checks—but that’s package-based, not manifest-scoped. [1]

Sources
[1] actions/dependency-review-action README (Configuration options for allow-licenses and allow-dependencies-licenses) (github.com)

Citations:


Don't globally allow LGPL licenses in the dependency review.

The allow-licenses setting applies globally to every dependency change in pull requests—there's no way to scope exceptions to specific packages or only build-time dependencies. Once LGPL licenses are added to this list, any future LGPL dependency will pass the check silently, even if it's a runtime or production dependency outside the intended Astro scope.

Use allow-dependencies-licenses instead to exclude only the specific Astro packages by their package URL (purl), or move LGPL exceptions to a narrower configuration if the action supports alternative scoping mechanisms.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/dependency-review.yml around lines 30 - 38, The workflow
currently adds LGPL entries to the global allow-licenses list (the
allow-licenses key) which permits LGPL for every dependency; remove the
LGPL-2.0-only, LGPL-2.1-only, LGPL-3.0-only and LGPL-3.0-or-later entries from
allow-licenses and instead add an allow-dependencies-licenses mapping that lists
only the specific package URLs (purls) for the Astro/build-time packages (e.g.,
the `@img/sharp-libvips-`* purls) or otherwise scope the exception to build-time
packages; update the configuration so LGPL is not globally permitted while the
specific Astro package purls are explicitly allowed via
allow-dependencies-licenses.

comment-summary-in-pr: always
79 changes: 73 additions & 6 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ jobs:
contents: read
packages: write
id-token: write
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
Expand Down Expand Up @@ -103,7 +105,7 @@ jobs:
format: table
exit-code: "1"
severity: CRITICAL
trivyignores: .trivyignore.yaml
trivyignores: .github/.trivyignore.yaml

- name: Trivy scan (high — warn only)
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
Expand All @@ -113,15 +115,15 @@ jobs:
format: table
exit-code: "0"
severity: HIGH
trivyignores: .trivyignore.yaml
trivyignores: .github/.trivyignore.yaml

- name: Grype scan
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
with:
image: ${{ steps.scan-ref.outputs.ref }}
fail-build: true
severity-cutoff: critical
config: .grype.yaml
config: .github/.grype.yaml

# Push only after scans pass — prevents publishing vulnerable images.
# NOTE: This is a separate build invocation from the scan step. GHA cache
Expand Down Expand Up @@ -162,6 +164,8 @@ jobs:
contents: read
packages: write
id-token: write
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
Expand Down Expand Up @@ -214,7 +218,7 @@ jobs:
format: table
exit-code: "1"
severity: CRITICAL
trivyignores: .trivyignore.yaml
trivyignores: .github/.trivyignore.yaml

- name: Trivy scan (high — warn only)
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
Expand All @@ -224,15 +228,15 @@ jobs:
format: table
exit-code: "0"
severity: HIGH
trivyignores: .trivyignore.yaml
trivyignores: .github/.trivyignore.yaml

- name: Grype scan
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
with:
image: ${{ steps.scan-ref.outputs.ref }}
fail-build: true
severity-cutoff: critical
config: .grype.yaml
config: .github/.grype.yaml

# Push only after scans pass — prevents publishing vulnerable images.
# NOTE: Separate build invocation; GHA cache ensures deterministic layers,
Expand Down Expand Up @@ -263,3 +267,66 @@ jobs:
exit 1
fi
cosign sign --yes ghcr.io/aureliolo/synthorg-web@${DIGEST}

# Append container image references to the GitHub Release (version tags only)
update-release:
name: Update Release Notes
if: startsWith(github.ref, 'refs/tags/v')
needs: [version, build-backend, build-web]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Append container images to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
VERSION: ${{ needs.version.outputs.app_version }}
BACKEND_DIGEST: ${{ needs.build-backend.outputs.digest }}
WEB_DIGEST: ${{ needs.build-web.outputs.digest }}
run: |
set -euo pipefail

# Validate required inputs
if [ -z "$VERSION" ] || [ -z "$BACKEND_DIGEST" ] || [ -z "$WEB_DIGEST" ]; then
echo "::error::Missing required values: VERSION='$VERSION' BACKEND_DIGEST='$BACKEND_DIGEST' WEB_DIGEST='$WEB_DIGEST'"
exit 1
fi

EXISTING=$(gh release view "$TAG" --json body -q '.body // ""' 2>/dev/null || true)

IMAGES=$(cat <<'BLOCK'

## Container Images

| Image | Pull |
|-------|------|
| Backend | `docker pull ghcr.io/aureliolo/synthorg-backend:VERSION_PH` |
| Web | `docker pull ghcr.io/aureliolo/synthorg-web:VERSION_PH` |

**Digests** (for pinning):
- Backend: `ghcr.io/aureliolo/synthorg-backend@BACKEND_DIGEST_PH`
- Web: `ghcr.io/aureliolo/synthorg-web@WEB_DIGEST_PH`

All images are signed with [cosign](https://github.com/sigstore/cosign). Verify with:
```bash
cosign verify ghcr.io/aureliolo/synthorg-backend@BACKEND_DIGEST_PH \
--certificate-identity-regexp='github\.com/Aureliolo/synthorg' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com'

cosign verify ghcr.io/aureliolo/synthorg-web@WEB_DIGEST_PH \
--certificate-identity-regexp='github\.com/Aureliolo/synthorg' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```
Comment thread
greptile-apps[bot] marked this conversation as resolved.
BLOCK
)
# Dedent (remove leading 10-space YAML indentation) and substitute placeholders
IMAGES=$(echo "$IMAGES" | sed 's/^ //')
IMAGES=${IMAGES//VERSION_PH/$VERSION}
IMAGES=${IMAGES//BACKEND_DIGEST_PH/$BACKEND_DIGEST}
IMAGES=${IMAGES//WEB_DIGEST_PH/$WEB_DIGEST}

# Idempotent: strip existing Container Images section before appending
CLEANED=$(echo "$EXISTING" | sed '/^## Container Images$/,$d')
gh release edit "$TAG" --notes "${CLEANED}${IMAGES}"
91 changes: 91 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: GitHub Pages

on:
push:
branches: [main]
paths:
- "docs/**"
- "site/**"
- "mkdocs.yml"
- "src/ai_company/**"
- ".github/workflows/pages.yml"
workflow_dispatch:
Comment on lines +3 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider gating deployment on CI success.

The workflow triggers on src/ai_company/** changes to regenerate API docs from docstrings, but doesn't wait for the main CI workflow to pass. This could deploy documentation for code that fails tests or type-checking.

Consider adding a dependency on the CI workflow:

💡 Suggested workflow modification
 on:
   push:
     branches: [main]
     paths:
       - "docs/**"
       - "site/**"
       - "mkdocs.yml"
       - "src/ai_company/**"
       - ".github/workflows/pages.yml"
   workflow_dispatch:
+  workflow_run:
+    workflows: ["CI"]  # or your main CI workflow name
+    types: [completed]
+    branches: [main]

Then add a condition to skip if CI failed:

jobs:
  build:
    # Only run if triggered manually, by push, or if CI succeeded
    if: >
      github.event_name != 'workflow_run' ||
      github.event.workflow_run.conclusion == 'success'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
push:
branches: [main]
paths:
- "docs/**"
- "site/**"
- "mkdocs.yml"
- "src/ai_company/**"
- ".github/workflows/pages.yml"
workflow_dispatch:
on:
push:
branches: [main]
paths:
- "docs/**"
- "site/**"
- "mkdocs.yml"
- "src/ai_company/**"
- ".github/workflows/pages.yml"
workflow_dispatch:
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [main]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pages.yml around lines 3 - 12, The workflow currently runs
on pushes to src/ai_company/** and may deploy docs even if the main CI failed;
add a workflow_run trigger for the main CI and guard the build job with an if
condition so it only runs when not invoked by a workflow_run or when the
triggering workflow_run concluded successfully: update the top-level on: to
include a workflow_run for the CI workflow name/id, and add an if: condition on
the build job (jobs.build.if) equivalent to "github.event_name != 'workflow_run'
|| github.event.workflow_run.conclusion == 'success'" so docs
regeneration/deploy is skipped when CI failed.


permissions: {}

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
name: Build Site
runs-on: ubuntu-latest
Comment thread
coderabbitai[bot] marked this conversation as resolved.
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

# --- MkDocs (documentation at /docs) ---
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.14"
allow-prereleases: true

- name: Install uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1

- name: Install docs dependencies
run: uv sync --group docs --no-dev

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI reproducibility: this workflow uses uv sync without --frozen, which can allow dependency resolution drift (and differs from the repo’s setup-python-uv action that runs uv sync --frozen). Consider adding --frozen here (and optionally reusing the composite action) so Pages builds fail fast if uv.lock is out of date.

Suggested change
run: uv sync --group docs --no-dev
run: uv sync --group docs --no-dev --frozen

Copilot uses AI. Check for mistakes.

- name: Build MkDocs
run: uv run mkdocs build --strict --site-dir _site/docs

# --- Astro (landing page at /) ---
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"

- name: Install Astro dependencies
working-directory: site
run: npm ci

- name: Build Astro
working-directory: site
run: npm run build

# --- Merge outputs ---
- name: Merge Astro + MkDocs into final output
run: |
# Guard against Astro accidentally producing a docs/ route
if [ -d "site/dist/docs" ]; then
echo "::error::Astro output contains 'docs/' directory which would overwrite MkDocs output"
exit 1
fi
# Astro output goes to root (use /. to avoid glob edge cases)
cp -r site/dist/. _site/
# MkDocs output is already at _site/docs/ from the build step

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
path: _site

deploy:
name: Deploy
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac553fd0d31 # v4
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,13 @@ Thumbs.db
web/node_modules/
web/dist/

# Documentation build output (MkDocs)
_site/

# Astro / Node.js (landing page)
site/node_modules/
site/dist/
site/.astro/
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# uv
.python-version
16 changes: 15 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

```bash
uv sync # install all deps (dev + test)
uv sync --group docs # install MkDocs docs toolchain
uv run ruff check src/ tests/ # lint
uv run ruff check src/ tests/ --fix # lint + auto-fix
uv run ruff format src/ tests/ # format
Expand All @@ -37,8 +38,20 @@ uv run pytest tests/ -m integration -n auto # integration tests only
uv run pytest tests/ -m e2e -n auto # e2e tests only
uv run pytest tests/ -n auto --cov=ai_company --cov-fail-under=80 # full suite + coverage
uv run pre-commit run --all-files # all pre-commit hooks
uv run mkdocs build --strict --site-dir _site/docs # build docs (output: _site/docs/)
uv run mkdocs serve # local docs preview (http://127.0.0.1:8000)
```

## Documentation

- **Docs source**: `docs/` (MkDocs markdown + mkdocstrings auto-generated API reference)
- **Landing page**: `site/` (Astro, Concept C hybrid design)
- **Config**: `mkdocs.yml` at repo root
- **API reference**: auto-generated from docstrings via mkdocstrings + Griffe (AST-based, no imports)
- **CI**: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages
- **Architecture decision**: `docs/decisions/ADR-003-documentation-architecture.md`
- **Dependencies**: `docs` group in `pyproject.toml` (`mkdocs-material`, `mkdocstrings[python]`, `griffe-pydantic`)

## Docker

```bash
Expand Down Expand Up @@ -159,7 +172,8 @@ src/ai_company/
## CI

- **Jobs**: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass (gate)
- **Docker**: `.github/workflows/docker.yml` — builds backend + web images, pushes to GHCR, signs with cosign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype (critical cutoff). CVE triage via `.trivyignore.yaml` and `.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
- **Pages**: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
- **Docker**: `.github/workflows/docker.yml` — builds backend + web images, pushes to GHCR, signs with cosign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype (critical cutoff). CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
- **Matrix**: Python 3.14
- **Dependabot**: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge
- **Secret scanning**: gitleaks workflow on push/PR + weekly schedule
Expand Down
Loading
Loading