argus-darkroom 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- argus_darkroom-0.1.0/.copier-answers.yml +21 -0
- argus_darkroom-0.1.0/.cursor/rules/git-workflow.mdc +20 -0
- argus_darkroom-0.1.0/.dockerignore +11 -0
- argus_darkroom-0.1.0/.env.example +45 -0
- argus_darkroom-0.1.0/.github/workflows/ci.yml +32 -0
- argus_darkroom-0.1.0/.github/workflows/release.yml +163 -0
- argus_darkroom-0.1.0/.gitignore +15 -0
- argus_darkroom-0.1.0/CLAUDE.md +60 -0
- argus_darkroom-0.1.0/Dockerfile +62 -0
- argus_darkroom-0.1.0/LICENSE +21 -0
- argus_darkroom-0.1.0/Makefile +33 -0
- argus_darkroom-0.1.0/PKG-INFO +288 -0
- argus_darkroom-0.1.0/README.md +247 -0
- argus_darkroom-0.1.0/compose.yaml +47 -0
- argus_darkroom-0.1.0/demo/run.py +223 -0
- argus_darkroom-0.1.0/demo/smoke.py +105 -0
- argus_darkroom-0.1.0/eval/preflight_probes.jsonl +17 -0
- argus_darkroom-0.1.0/examples/workflows/sdxl.json +95 -0
- argus_darkroom-0.1.0/examples/workflows/sdxl_lora.json +111 -0
- argus_darkroom-0.1.0/pyproject.toml +72 -0
- argus_darkroom-0.1.0/schema/darkroom-wire.schema.json +680 -0
- argus_darkroom-0.1.0/scripts/preflight_eval.py +95 -0
- argus_darkroom-0.1.0/spec.md +250 -0
- argus_darkroom-0.1.0/src/argus_darkroom/__init__.py +16 -0
- argus_darkroom-0.1.0/src/argus_darkroom/_version.py +24 -0
- argus_darkroom-0.1.0/src/argus_darkroom/age.py +249 -0
- argus_darkroom-0.1.0/src/argus_darkroom/cli.py +231 -0
- argus_darkroom-0.1.0/src/argus_darkroom/comfyui.py +259 -0
- argus_darkroom-0.1.0/src/argus_darkroom/config.py +87 -0
- argus_darkroom-0.1.0/src/argus_darkroom/engine.py +481 -0
- argus_darkroom-0.1.0/src/argus_darkroom/expansion.py +84 -0
- argus_darkroom-0.1.0/src/argus_darkroom/guard.py +107 -0
- argus_darkroom-0.1.0/src/argus_darkroom/hashing.py +36 -0
- argus_darkroom-0.1.0/src/argus_darkroom/images.py +27 -0
- argus_darkroom-0.1.0/src/argus_darkroom/modelfiles.py +56 -0
- argus_darkroom-0.1.0/src/argus_darkroom/models.py +172 -0
- argus_darkroom-0.1.0/src/argus_darkroom/outcome.py +122 -0
- argus_darkroom-0.1.0/src/argus_darkroom/preflight.py +92 -0
- argus_darkroom-0.1.0/src/argus_darkroom/prompt.py +44 -0
- argus_darkroom-0.1.0/src/argus_darkroom/py.typed +1 -0
- argus_darkroom-0.1.0/src/argus_darkroom/request.py +76 -0
- argus_darkroom-0.1.0/src/argus_darkroom/scan.py +83 -0
- argus_darkroom-0.1.0/src/argus_darkroom/server/__init__.py +5 -0
- argus_darkroom-0.1.0/src/argus_darkroom/server/app.py +375 -0
- argus_darkroom-0.1.0/src/argus_darkroom/store.py +95 -0
- argus_darkroom-0.1.0/src/argus_darkroom/workflow.py +193 -0
- argus_darkroom-0.1.0/tests/conftest.py +45 -0
- argus_darkroom-0.1.0/tests/fake_comfyui.py +176 -0
- argus_darkroom-0.1.0/tests/fakes.py +220 -0
- argus_darkroom-0.1.0/tests/rig.py +109 -0
- argus_darkroom-0.1.0/tests/test_age.py +141 -0
- argus_darkroom-0.1.0/tests/test_cli.py +182 -0
- argus_darkroom-0.1.0/tests/test_comfyui.py +130 -0
- argus_darkroom-0.1.0/tests/test_engine_render.py +213 -0
- argus_darkroom-0.1.0/tests/test_expansion.py +212 -0
- argus_darkroom-0.1.0/tests/test_generate.py +470 -0
- argus_darkroom-0.1.0/tests/test_outcome.py +217 -0
- argus_darkroom-0.1.0/tests/test_preflight.py +233 -0
- argus_darkroom-0.1.0/tests/test_server.py +520 -0
- argus_darkroom-0.1.0/tests/test_smoke.py +8 -0
- argus_darkroom-0.1.0/tests/test_store_and_schema.py +69 -0
- argus_darkroom-0.1.0/tests/test_workflow.py +174 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Managed by Copier — do not edit by hand.
|
|
2
|
+
# Run `copier update` in this repo to pull template changes.
|
|
3
|
+
_commit: b53a3a4
|
|
4
|
+
_src_path: /home/smk/argus-pkg-template
|
|
5
|
+
author: smk762
|
|
6
|
+
cli_name: argus-darkroom
|
|
7
|
+
description: 'Guarded image generation for the Argus suite: prompts and outputs are
|
|
8
|
+
checked before anything is released.'
|
|
9
|
+
dockerfile: Dockerfile
|
|
10
|
+
github_owner: smk762
|
|
11
|
+
has_cli: true
|
|
12
|
+
has_server: true
|
|
13
|
+
image_name: argus-darkroom
|
|
14
|
+
package_name: argus_darkroom
|
|
15
|
+
post_test: ''
|
|
16
|
+
project_name: argus-darkroom
|
|
17
|
+
python_min: '3.11'
|
|
18
|
+
ruff_pin: 0.15.16
|
|
19
|
+
test_extras: dev
|
|
20
|
+
version_build_arg: false
|
|
21
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Keep main releasable by doing work on dev in a worktree
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git workflow
|
|
7
|
+
|
|
8
|
+
`main` is the release branch. Do not commit on it, and do not push it.
|
|
9
|
+
|
|
10
|
+
`dev` is `main` plus updates that are not released yet. Before starting work, fetch and merge `origin/main` into `dev`.
|
|
11
|
+
|
|
12
|
+
Do each change in a worktree branched from `dev`. Open a pull request into `dev`. A release is a pull request from `dev` into `main`.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git fetch origin
|
|
16
|
+
git switch dev && git merge origin/main
|
|
17
|
+
git worktree add ../argus-darkroom-<topic> -b <topic> dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Do not force-push `main`. Do not commit secrets.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copy to .env. Compose reads it; nothing here is a secret, but .env is git-ignored all the same.
|
|
2
|
+
|
|
3
|
+
# --- Required ---------------------------------------------------------------
|
|
4
|
+
# SingGuard (agent-composer's /api/guard/check). Darkroom never runs unguarded: without this it refuses to start.
|
|
5
|
+
SINGGUARD_URL=http://host.docker.internal:9150
|
|
6
|
+
# ComfyUI models root (the folder that holds checkpoints/ and loras/). Mounted read-only; hashed for the record.
|
|
7
|
+
# Use the directory ComfyUI actually lists — if it loads models via extra_model_paths.yaml, that folder, not models/.
|
|
8
|
+
MODELS_ROOT=/path/to/ComfyUI/models
|
|
9
|
+
|
|
10
|
+
# --- Backends, reached by URL (they run outside compose) ---------------------
|
|
11
|
+
COMFYUI_URL=http://host.docker.internal:8188
|
|
12
|
+
# NudeNet. Empty means SFW generation is unavailable (every SFW output would be omitted), so darkroom refuses it.
|
|
13
|
+
NUDENET_URL=
|
|
14
|
+
# Curator's POST /faces?body=true (InsightFace + MiVOLO, body crop and unmatched people). Empty falls back to the in-process [age] extra.
|
|
15
|
+
AGE_URL=http://host.docker.internal:8101
|
|
16
|
+
# Prompt expansion. Both must be set to turn it on.
|
|
17
|
+
OLLAMA_URL=
|
|
18
|
+
OLLAMA_MODEL=
|
|
19
|
+
|
|
20
|
+
# --- Policy ------------------------------------------------------------------
|
|
21
|
+
# 1 lets an NSFW policy enforce (and makes adult-granular the default). Anything else: SFW only.
|
|
22
|
+
ALLOW_NSFW=0
|
|
23
|
+
# cascade | fast | fast-slow | slow
|
|
24
|
+
THINKING=cascade
|
|
25
|
+
# 1 refuses every write: generation, pre-flight and record changes. Records stay readable.
|
|
26
|
+
READ_ONLY=0
|
|
27
|
+
# Origin(s) of the UI allowed to write, comma separated. Safelight's address as the browser sees it.
|
|
28
|
+
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
|
|
29
|
+
|
|
30
|
+
# --- Development -------------------------------------------------------------
|
|
31
|
+
# 1 adds POST /purge and the purge button in safelight: it deletes every record and image. Leave off elsewhere.
|
|
32
|
+
DEVMODE=0
|
|
33
|
+
|
|
34
|
+
# --- Publish -------------------------------------------------------------------
|
|
35
|
+
# Host:port published for each service. 127.0.0.1 keeps them off the LAN.
|
|
36
|
+
# Use 0.0.0.0:<port> only when the UI is opened as a LAN address (the browser then
|
|
37
|
+
# calls that same host). Darkroom is not for public hosting.
|
|
38
|
+
DARKROOM_PUBLISH=127.0.0.1:8106
|
|
39
|
+
SAFELIGHT_PUBLISH=127.0.0.1:3000
|
|
40
|
+
|
|
41
|
+
# --- Safelight -----------------------------------------------------------------
|
|
42
|
+
# Where the browser reaches darkroom. Baked into the safelight image when it is built.
|
|
43
|
+
DARKROOM_PUBLIC_URL=http://localhost:8106
|
|
44
|
+
# Safelight's repo, as a build context, until it publishes an image.
|
|
45
|
+
SAFELIGHT_CONTEXT=../argus-safelight
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
ci:
|
|
15
|
+
uses: smk762/argus-ci/.github/workflows/python-ci.yml@v1
|
|
16
|
+
with:
|
|
17
|
+
ruff-version: "0.15.16"
|
|
18
|
+
test-extras: "dev"
|
|
19
|
+
|
|
20
|
+
# The wire schema is a contract other repos generate code from: a model change must come with the regenerated file.
|
|
21
|
+
schema:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0 # hatch-vcs reads the version from git history
|
|
27
|
+
- uses: astral-sh/setup-uv@v6
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
- run: uv venv && uv pip install -e ".[cli]"
|
|
31
|
+
- run: uv run --no-sync argus-darkroom schema --check
|
|
32
|
+
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
name: Release to PyPI
|
|
2
|
+
|
|
3
|
+
# Top-level (non-reusable) on purpose: PyPI trusted publishing cannot run inside
|
|
4
|
+
# a reusable workflow. Keep this file in sync across the suite via `copier update`.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
# Sent by argus-cortex's release workflow. The image installs argus-cortex
|
|
10
|
+
# from the pyproject range at build time, so rebuilding the newest release's
|
|
11
|
+
# image is how a new cortex reaches it. Only the image jobs run: PyPI and the
|
|
12
|
+
# GitHub Release for that tag already exist.
|
|
13
|
+
repository_dispatch:
|
|
14
|
+
types: [cortex-released]
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
id-token: write
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
# Not on a cortex dispatch, so the PyPI and Release jobs that chain off it
|
|
23
|
+
# skip too.
|
|
24
|
+
if: github.event_name == 'push'
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
with:
|
|
29
|
+
fetch-depth: 0
|
|
30
|
+
- uses: astral-sh/setup-uv@v6
|
|
31
|
+
with:
|
|
32
|
+
enable-cache: true
|
|
33
|
+
- run: uv build
|
|
34
|
+
- uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
|
|
39
|
+
test-install:
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
needs: build
|
|
42
|
+
strategy:
|
|
43
|
+
matrix:
|
|
44
|
+
python-version: ["3.11", "3.12"]
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/download-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: dist
|
|
49
|
+
path: dist/
|
|
50
|
+
- uses: astral-sh/setup-uv@v6
|
|
51
|
+
with:
|
|
52
|
+
enable-cache: true
|
|
53
|
+
- name: Install wheel and smoke-test import
|
|
54
|
+
run: |
|
|
55
|
+
uv venv --python ${{ matrix.python-version }}
|
|
56
|
+
uv pip install dist/*.whl
|
|
57
|
+
uv run --no-sync python -c "import argus_darkroom; print(f'argus-darkroom {argus_darkroom.__version__} OK')"
|
|
58
|
+
|
|
59
|
+
publish-testpypi:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
needs: test-install
|
|
62
|
+
environment: testpypi
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/download-artifact@v4
|
|
65
|
+
with:
|
|
66
|
+
name: dist
|
|
67
|
+
path: dist/
|
|
68
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
69
|
+
with:
|
|
70
|
+
repository-url: https://test.pypi.org/legacy/
|
|
71
|
+
|
|
72
|
+
publish-pypi:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
# Ordered *after* the TestPyPI rehearsal so its outcome is visible first, but
|
|
75
|
+
# deliberately not gated on it. A TestPyPI-only fault — a missing trusted
|
|
76
|
+
# publisher, an index outage — must not silently withhold the real release:
|
|
77
|
+
# that failure mode shipped argus-proof images for two tags with no wheel on
|
|
78
|
+
# PyPI, and nothing surfaced it because publish-image is independent.
|
|
79
|
+
# Only the build + install gate is required; a TestPyPI failure still marks
|
|
80
|
+
# the whole run failed, so the problem stays loud.
|
|
81
|
+
needs: [test-install, publish-testpypi]
|
|
82
|
+
if: "!cancelled() && needs.test-install.result == 'success'"
|
|
83
|
+
environment: pypi
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/download-artifact@v4
|
|
86
|
+
with:
|
|
87
|
+
name: dist
|
|
88
|
+
path: dist/
|
|
89
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
90
|
+
|
|
91
|
+
release-tag:
|
|
92
|
+
# The tag the image jobs build: the pushed one, or on a cortex dispatch the
|
|
93
|
+
# newest final release. A pre-release never moved `latest`, so a dispatch
|
|
94
|
+
# does not rebuild one.
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
outputs:
|
|
97
|
+
tag: ${{ steps.tag.outputs.tag }}
|
|
98
|
+
sha: ${{ steps.tag.outputs.sha }}
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
if: github.event_name == 'repository_dispatch'
|
|
102
|
+
- id: tag
|
|
103
|
+
env:
|
|
104
|
+
CORTEX_VERSION: ${{ github.event.client_payload.version }}
|
|
105
|
+
run: |
|
|
106
|
+
if [ "$GITHUB_EVENT_NAME" != repository_dispatch ]; then
|
|
107
|
+
echo "tag=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
|
|
108
|
+
echo "sha=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
|
|
109
|
+
exit 0
|
|
110
|
+
fi
|
|
111
|
+
refs=$(git ls-remote --tags origin 'refs/tags/v*')
|
|
112
|
+
tag=$(echo "$refs" | sed -n 's|.*refs/tags/\(v[0-9]*\.[0-9]*\.[0-9]*\)\(\^{}\)\{0,1\}$|\1|p' | sort -uV | tail -n1)
|
|
113
|
+
if [ -z "$tag" ]; then
|
|
114
|
+
echo "::notice::No release tag yet, so no image to rebuild for cortex $CORTEX_VERSION."
|
|
115
|
+
exit 0
|
|
116
|
+
fi
|
|
117
|
+
# An annotated tag lists its commit on the peeled ^{} line.
|
|
118
|
+
sha=$(echo "$refs" | awk -v t="refs/tags/$tag" '$2 == t {l = $1} $2 == t "^{}" {p = $1} END {print p ? p : l}')
|
|
119
|
+
echo "Rebuilding $tag ($sha) for cortex $CORTEX_VERSION."
|
|
120
|
+
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
121
|
+
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
|
122
|
+
|
|
123
|
+
publish-image:
|
|
124
|
+
runs-on: ubuntu-latest
|
|
125
|
+
needs: [release-tag, test-install]
|
|
126
|
+
# On a cortex dispatch test-install is skipped (nothing was built for PyPI),
|
|
127
|
+
# so the job runs on the tag alone.
|
|
128
|
+
if: "!cancelled() && needs.release-tag.outputs.tag != '' && (needs.test-install.result == 'success' || github.event_name == 'repository_dispatch')"
|
|
129
|
+
permissions:
|
|
130
|
+
contents: read
|
|
131
|
+
packages: write
|
|
132
|
+
steps:
|
|
133
|
+
- uses: actions/checkout@v4
|
|
134
|
+
with:
|
|
135
|
+
ref: ${{ needs.release-tag.outputs.tag }}
|
|
136
|
+
fetch-depth: 0 # hatch-vcs reads the tag from git history
|
|
137
|
+
- uses: docker/setup-buildx-action@v3
|
|
138
|
+
- uses: docker/login-action@v3
|
|
139
|
+
with:
|
|
140
|
+
registry: ghcr.io
|
|
141
|
+
username: ${{ github.actor }}
|
|
142
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
143
|
+
- id: meta
|
|
144
|
+
uses: docker/metadata-action@v5
|
|
145
|
+
with:
|
|
146
|
+
images: ghcr.io/${{ github.repository_owner }}/argus-darkroom
|
|
147
|
+
labels: org.opencontainers.image.revision=${{ needs.release-tag.outputs.sha }}
|
|
148
|
+
tags: |
|
|
149
|
+
type=semver,pattern={{version}},value=${{ needs.release-tag.outputs.tag }}
|
|
150
|
+
type=semver,pattern={{major}}.{{minor}},value=${{ needs.release-tag.outputs.tag }}
|
|
151
|
+
type=raw,value=latest
|
|
152
|
+
- uses: docker/build-push-action@v6
|
|
153
|
+
with:
|
|
154
|
+
context: .
|
|
155
|
+
file: ./Dockerfile
|
|
156
|
+
push: true
|
|
157
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
158
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
159
|
+
# A dispatch rebuilds an unchanged context, so a cached install layer
|
|
160
|
+
# would keep the old argus-cortex.
|
|
161
|
+
no-cache: ${{ github.event_name == 'repository_dispatch' }}
|
|
162
|
+
cache-from: type=gha
|
|
163
|
+
cache-to: type=gha,mode=max
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
# compose settings; .env.example is the committed template
|
|
3
|
+
.env
|
|
4
|
+
compose.override.yaml
|
|
5
|
+
darkroom-output/
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.pyc
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
# hatch-vcs writes this at build time (version derived from git tags)
|
|
14
|
+
src/argus_darkroom/_version.py
|
|
15
|
+
demo-data/
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# argus-darkroom
|
|
2
|
+
|
|
3
|
+
Guarded image generation. Read `spec.md` for the design and `README.md` for running it. This file is the rules that must
|
|
4
|
+
not be broken and where things are.
|
|
5
|
+
|
|
6
|
+
## Rules that must hold
|
|
7
|
+
|
|
8
|
+
- **Nothing is written before the verdict.** No temp file, no cortex blob, no `SaveImage`/`PreviewImage` node. A
|
|
9
|
+
deleted or omitted image is never written, and its buffer is dropped. `tests/test_generate.py` lists the output root,
|
|
10
|
+
the system temp directory and the fake ComfyUI history before and after to prove it.
|
|
11
|
+
- **Never run unguarded.** An empty `SINGGUARD_URL` refuses to start (`Settings.__post_init__`). A failed check is an
|
|
12
|
+
`unchecked` cell, never a pass. An unchecked enforcing cell deletes; an unchecked advisory cell is only shown.
|
|
13
|
+
- **Every check judges the final prompt**, expansion included. The negative prompt is always part of pre-flight. Do not
|
|
14
|
+
add a path that checks only the user's text.
|
|
15
|
+
- **Policies are chosen by name only.** Never accept policy text in a request. Policy texts, the illegal table and
|
|
16
|
+
`mode()` live in `argus_cortex.moderation.policies`; do not copy them here.
|
|
17
|
+
- **Fail closed.** If you are unsure whether an error should refuse, delete or pass, it refuses or deletes.
|
|
18
|
+
- **Never import NudeNet** (AGPL). Reach it over HTTP through cortex's `NudeNetClient`.
|
|
19
|
+
- **The server binds to localhost by default.** Writes need `WriteGuard` and `cross_site_refuse`. `POST /purge` exists
|
|
20
|
+
only with `ARGUS_DARKROOM_DEVMODE` and answers 404 otherwise.
|
|
21
|
+
- **The age is a tool's estimate.** Name it `age_estimate_years`, source `mivolo`. Never call it an age, and never send
|
|
22
|
+
it to SingGuard as context.
|
|
23
|
+
- **No real sexual, violent or illegal content** in the repo or tests. Drive the illegal path with a fake verdict.
|
|
24
|
+
|
|
25
|
+
## Layout
|
|
26
|
+
|
|
27
|
+
| | |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `engine.py` | `Engine`: `begin` validates, `_run` expands, pre-flights, renders, scans, decides, releases or destroys |
|
|
30
|
+
| `outcome.py` | the outcome table (`decide`). Precedence: deleted, omitted (SFW), review, passed. Unit-test any change by cell combination |
|
|
31
|
+
| `preflight.py`, `scan.py`, `guard.py` | the text check, the image scans, and the SingGuard wrapper that turns failures into `unchecked` |
|
|
32
|
+
| `prompt.py` | `assemble_prompt`, and `frame` (folds line breaks so text cannot pose as the frame's labels) |
|
|
33
|
+
| `expansion.py` | `OllamaExpander`. Ollama is never a guard |
|
|
34
|
+
| `comfyui.py`, `workflow.py`, `modelfiles.py`, `hashing.py` | render over the websocket, the `$placeholder` template, model sha256s |
|
|
35
|
+
| `age.py` | curator over `AGE_URL` (`POST /faces?body=true`), or in-process `detect_faces` behind the `[age]` extra |
|
|
36
|
+
| `store.py` | `<id>.json` and `<id>.png` in the output root; atomic image write |
|
|
37
|
+
| `models.py` | the versioned wire models. `schema/darkroom-wire.schema.json` is generated from them |
|
|
38
|
+
| `demo/run.py`, `demo/smoke.py` | the real server and engine over the test fakes; a marker in the prompt picks the verdicts. Demo only: it turns `DEVMODE` on and uses fake models |
|
|
39
|
+
| `server/app.py`, `cli.py`, `config.py`, `request.py` | HTTP, Typer, `ARGUS_DARKROOM_*`, the request model (`extra=forbid`) |
|
|
40
|
+
|
|
41
|
+
## Working
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
make install && make test && make lint
|
|
45
|
+
uv run --no-sync argus-darkroom schema --check # after any change to models.py: run `schema`, commit the file
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- `main` is releases, `dev` is work. Work in a worktree from `dev`, and open PRs into `dev`. CI runs on `main` only, so
|
|
49
|
+
run lint, tests and `schema --check` locally before every PR.
|
|
50
|
+
- Use `docker compose`, not `docker-compose`.
|
|
51
|
+
- The record is a wire contract (`record_version`). Adding a field is fine; renaming or removing one is a new version.
|
|
52
|
+
The blur decision lives only in `guard.nudenet.blur`, which is recorded for every NSFW-policy image (`exposed_score`
|
|
53
|
+
is `null` when NudeNet was unreachable). Each LoRA records the `trigger_words` the request supplied.
|
|
54
|
+
- argus-safelight codes against the HTTP shapes in the README's API table (`/health`, `/policies`, `/models`, `/records`
|
|
55
|
+
paging, SSE event bodies). Change one and `make demo` plus `make smoke` show whether the UI still works; update
|
|
56
|
+
safelight's `types.ts` in step.
|
|
57
|
+
- Tests use fake servers on real sockets (`tests/fakes.py`, `tests/fake_comfyui.py`, `tests/rig.py`). Prefer extending
|
|
58
|
+
them to mocking.
|
|
59
|
+
- `scripts/preflight_eval.py` is run by hand against a real SingGuard that supports text-only checks (agent-composer
|
|
60
|
+
PR #4). It is not a test.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
FROM python:3.11-slim AS base
|
|
3
|
+
|
|
4
|
+
# Pin past the eMudhra/emSign incident (discuss.python.org/t/109576). A floating
|
|
5
|
+
# :latest tag can stay cached on a pre-0.13.4 digest.
|
|
6
|
+
COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /bin/
|
|
7
|
+
|
|
8
|
+
WORKDIR /app
|
|
9
|
+
|
|
10
|
+
# Distrust emSign ECC Root CA - G3 and the Comodo AAA cross-sign root, then
|
|
11
|
+
# replace the base image's pip. pip < 26.2.2 trusts its vendored bundle in
|
|
12
|
+
# addition to the system store.
|
|
13
|
+
RUN apt-get update \
|
|
14
|
+
&& apt-get install -y --no-install-recommends ca-certificates \
|
|
15
|
+
&& sed -i \
|
|
16
|
+
-e 's|^mozilla/emSign_ECC_Root_CA_-_G3|!mozilla/emSign_ECC_Root_CA_-_G3|' \
|
|
17
|
+
-e 's|^mozilla/Comodo_AAA|!mozilla/Comodo_AAA|' \
|
|
18
|
+
/etc/ca-certificates.conf \
|
|
19
|
+
&& update-ca-certificates --fresh >/dev/null \
|
|
20
|
+
&& rm -rf /var/lib/apt/lists/* \
|
|
21
|
+
&& python -m pip install --no-cache-dir \
|
|
22
|
+
"pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# hatch-vcs derives the version from git history, so this stage needs git and a build context that includes .git
|
|
26
|
+
# (keep it out of .dockerignore). Only the wheel leaves this stage: git and the history stay behind.
|
|
27
|
+
FROM base AS build
|
|
28
|
+
RUN apt-get update \
|
|
29
|
+
&& apt-get install -y --no-install-recommends git \
|
|
30
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
31
|
+
COPY . .
|
|
32
|
+
RUN git config --global --add safe.directory /app && uv build --wheel --out-dir /wheel
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
FROM base
|
|
36
|
+
COPY --from=build /wheel /wheel
|
|
37
|
+
RUN uv pip install --system --no-cache "certifi>=2026.10.19" "$(ls /wheel/*.whl)[server,cli]" \
|
|
38
|
+
&& rm -rf /wheel
|
|
39
|
+
|
|
40
|
+
# The age check (the `age` extra) pulls in onnxruntime and InsightFace and needs the MiVOLO model file, so it is left
|
|
41
|
+
# out of the image; /health reports it as unavailable and records get `age: null`.
|
|
42
|
+
|
|
43
|
+
# The example workflows are the default template; mount your own and set ARGUS_DARKROOM_WORKFLOW to use it.
|
|
44
|
+
COPY examples ./examples
|
|
45
|
+
ENV ARGUS_DARKROOM_OUTPUT_ROOT=/data \
|
|
46
|
+
ARGUS_DARKROOM_MODELS_ROOT=/models \
|
|
47
|
+
ARGUS_DARKROOM_WORKFLOW=/app/examples/workflows/sdxl.json
|
|
48
|
+
|
|
49
|
+
# Records and images live in /data, so give the unprivileged user that and nothing else.
|
|
50
|
+
RUN useradd --system --uid 10001 --no-create-home darkroom \
|
|
51
|
+
&& mkdir /data \
|
|
52
|
+
&& chown darkroom /data
|
|
53
|
+
USER darkroom
|
|
54
|
+
VOLUME /data
|
|
55
|
+
|
|
56
|
+
# Bound to all interfaces so the port can be published; publish it on 127.0.0.1 (compose.yaml does). Darkroom is not
|
|
57
|
+
# for public hosting. It refuses to start without ARGUS_DARKROOM_SINGGUARD_URL.
|
|
58
|
+
EXPOSE 8106
|
|
59
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
|
|
60
|
+
CMD ["python", "-c", "import urllib.request as u; u.urlopen('http://127.0.0.1:8106/health', timeout=4)"]
|
|
61
|
+
ENTRYPOINT ["argus-darkroom"]
|
|
62
|
+
CMD ["serve", "--host", "0.0.0.0", "--port", "8106"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 smk762
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
UV ?= uv
|
|
2
|
+
|
|
3
|
+
.PHONY: help install lint format test build demo smoke clean
|
|
4
|
+
|
|
5
|
+
help: ## Show this help
|
|
6
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
|
|
7
|
+
|
|
8
|
+
install: ## Create venv and install with test extras
|
|
9
|
+
$(UV) venv
|
|
10
|
+
$(UV) pip install -e ".[dev]"
|
|
11
|
+
|
|
12
|
+
lint: ## Ruff check + format check
|
|
13
|
+
$(UV)x ruff@0.15.16 check src/ tests/
|
|
14
|
+
$(UV)x ruff@0.15.16 format --check src/ tests/
|
|
15
|
+
|
|
16
|
+
format: ## Ruff autoformat + autofix
|
|
17
|
+
$(UV)x ruff@0.15.16 format src/ tests/
|
|
18
|
+
$(UV)x ruff@0.15.16 check --fix src/ tests/
|
|
19
|
+
|
|
20
|
+
test: ## Run the test suite
|
|
21
|
+
$(UV) run --no-sync pytest --tb=short -q
|
|
22
|
+
|
|
23
|
+
build: ## Build sdist + wheel
|
|
24
|
+
$(UV) build
|
|
25
|
+
|
|
26
|
+
demo: ## Run the real server against fake backends (no GPU), on http://127.0.0.1:8106
|
|
27
|
+
$(UV) run --no-sync python demo/run.py
|
|
28
|
+
|
|
29
|
+
smoke: ## With `make demo` running: send every marker prompt and check the outcome
|
|
30
|
+
$(UV) run --no-sync python demo/smoke.py
|
|
31
|
+
|
|
32
|
+
clean: ## Remove build + cache artifacts
|
|
33
|
+
rm -rf dist build .venv *.egg-info .pytest_cache .ruff_cache src/argus_darkroom/_version.py
|