argus-cortex 0.4.6__tar.gz → 0.5.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.
Files changed (57) hide show
  1. argus_cortex-0.5.0/.cursor/rules/agent-coding-packs.mdc +29 -0
  2. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/.env.example +5 -0
  3. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/.github/workflows/ci.yml +2 -2
  4. argus_cortex-0.5.0/AGENTS.md +24 -0
  5. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/CHANGELOG.md +43 -1
  6. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/CLAUDE.md +17 -3
  7. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/Makefile +4 -1
  8. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/PKG-INFO +21 -7
  9. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/README.md +20 -6
  10. argus_cortex-0.5.0/SUITE.md +102 -0
  11. argus_cortex-0.5.0/consent-schema.json +257 -0
  12. argus_cortex-0.5.0/provenance-schema.json +125 -0
  13. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/__init__.py +38 -1
  14. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/_version.py +2 -2
  15. argus_cortex-0.5.0/src/argus_cortex/consent.py +240 -0
  16. argus_cortex-0.5.0/src/argus_cortex/provenance.py +128 -0
  17. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/taxonomy.py +19 -2
  18. argus_cortex-0.5.0/taxonomy-schema.json +57 -0
  19. argus_cortex-0.5.0/tests/test_consent.py +249 -0
  20. argus_cortex-0.5.0/tests/test_provenance.py +144 -0
  21. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_taxonomy.py +18 -2
  22. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/.copier-answers.yml +0 -0
  23. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/.github/workflows/release.yml +0 -0
  24. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/.gitignore +0 -0
  25. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/LICENSE +0 -0
  26. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/pyproject.toml +0 -0
  27. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/backends.py +0 -0
  28. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/gpu.py +0 -0
  29. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/moderation/__init__.py +0 -0
  30. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/moderation/_http.py +0 -0
  31. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/moderation/nudenet.py +0 -0
  32. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/moderation/policies.py +0 -0
  33. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/moderation/singguard.py +0 -0
  34. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/py.typed +0 -0
  35. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/server.py +0 -0
  36. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/store/__init__.py +0 -0
  37. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/store/blob.py +0 -0
  38. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/store/config.py +0 -0
  39. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/store/errors.py +0 -0
  40. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/store/lineage.py +0 -0
  41. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/store/models.py +0 -0
  42. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/store/schema.py +0 -0
  43. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/store/vector.py +0 -0
  44. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/wire.py +0 -0
  45. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/src/argus_cortex/xmp.py +0 -0
  46. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_backends.py +0 -0
  47. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_blob.py +0 -0
  48. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_gpu.py +0 -0
  49. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_moderation.py +0 -0
  50. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_policies.py +0 -0
  51. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_server.py +0 -0
  52. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_smoke.py +0 -0
  53. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_store.py +0 -0
  54. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_store_pg_integration.py +0 -0
  55. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_vector.py +0 -0
  56. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_wire.py +0 -0
  57. {argus_cortex-0.4.6 → argus_cortex-0.5.0}/tests/test_xmp.py +0 -0
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: Fetch ai-code-auditor coding packs for argus-cortex before changing code
3
+ alwaysApply: true
4
+ ---
5
+
6
+ <!-- ai-audit:agent-packs:start -->
7
+ # Agent coding packs
8
+
9
+ Do not wait for a pasted brief — fetch the audit packs for `argus-cortex` yourself.
10
+
11
+ The audit API base URL is `$AI_AUDIT_PUBLIC_BASE_URL`. The real host/IP is in the local `.env`
12
+ (see `.env.example`, placeholder `http://<AUDIT_HOST>:8765`); read it there, don't guess.
13
+
14
+ 1. **Bundle (one call):** `GET $AI_AUDIT_PUBLIC_BASE_URL/audit/packs/bundle?repo=argus-cortex`
15
+ (world model + ranked specialist packs + token-capped `briefs_markdown`;
16
+ optional `&token_budget=2500&q=<keyword>&limit_specialists=5`).
17
+ CLI equivalent: `ai-audit packs --bundle --repo argus-cortex`.
18
+ 2. **Claim** the specialist pack you pick:
19
+ `POST $AI_AUDIT_PUBLIC_BASE_URL/audit/packs/<id>/status` `{"status":"claimed"}`
20
+ (send `X-API-Token: $AI_AUDIT_API_TOKEN`).
21
+ 3. **Fix:** `ai-audit repair --pack-id <id>` (agent-composer repair loop; fetches the pack for you;
22
+ HTTP: `POST http://127.0.0.1:9150/api/repair/run` `{"pack_id":"<id>","mode":"review"}`),
23
+ or read `GET $AI_AUDIT_PUBLIC_BASE_URL/audit/packs/<id>` / `.../brief` and implement it yourself.
24
+ 4. **Close:** `POST $AI_AUDIT_PUBLIC_BASE_URL/audit/validate_patch` `{"repo":"argus-cortex","patch":"<diff>","pack_id":"<id>"}`
25
+ — a passing run marks the pack `resolved`.
26
+
27
+ World-model constraints (ownership, boundaries, scan branch `dev`) win over guesswork.
28
+ Full flow: ai-code-auditor `AGENTS.md`.
29
+ <!-- ai-audit:agent-packs:end -->
@@ -19,3 +19,8 @@ CORTEX_S3_ACCESS_KEY=
19
19
  CORTEX_S3_SECRET_KEY=
20
20
  CORTEX_S3_BUCKET=argus
21
21
  CORTEX_S3_REGION=us-east-1
22
+
23
+ # ── Agent tooling (not read by this service) ──
24
+ # ai-code-auditor audit API base that AGENTS.md tells coding agents to use.
25
+ # Put the real host/IP in your local .env, never here.
26
+ # AI_AUDIT_PUBLIC_BASE_URL=http://<AUDIT_HOST>:8765
@@ -2,9 +2,9 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [main]
5
+ branches: [dev, main]
6
6
  pull_request:
7
- branches: [main]
7
+ branches: [dev, main]
8
8
 
9
9
  concurrency:
10
10
  group: ci-${{ github.ref }}
@@ -0,0 +1,24 @@
1
+ <!-- ai-audit:agent-packs:start -->
2
+ ## Agent coding packs (ai-code-auditor)
3
+
4
+ Do not wait for a pasted brief — fetch the audit packs for `argus-cortex` yourself.
5
+
6
+ The audit API base URL is `$AI_AUDIT_PUBLIC_BASE_URL`. The real host/IP is in the local `.env`
7
+ (see `.env.example`, placeholder `http://<AUDIT_HOST>:8765`); read it there, don't guess.
8
+
9
+ 1. **Bundle (one call):** `GET $AI_AUDIT_PUBLIC_BASE_URL/audit/packs/bundle?repo=argus-cortex`
10
+ (world model + ranked specialist packs + token-capped `briefs_markdown`;
11
+ optional `&token_budget=2500&q=<keyword>&limit_specialists=5`).
12
+ CLI equivalent: `ai-audit packs --bundle --repo argus-cortex`.
13
+ 2. **Claim** the specialist pack you pick:
14
+ `POST $AI_AUDIT_PUBLIC_BASE_URL/audit/packs/<id>/status` `{"status":"claimed"}`
15
+ (send `X-API-Token: $AI_AUDIT_API_TOKEN`).
16
+ 3. **Fix:** `ai-audit repair --pack-id <id>` (agent-composer repair loop; fetches the pack for you;
17
+ HTTP: `POST http://127.0.0.1:9150/api/repair/run` `{"pack_id":"<id>","mode":"review"}`),
18
+ or read `GET $AI_AUDIT_PUBLIC_BASE_URL/audit/packs/<id>` / `.../brief` and implement it yourself.
19
+ 4. **Close:** `POST $AI_AUDIT_PUBLIC_BASE_URL/audit/validate_patch` `{"repo":"argus-cortex","patch":"<diff>","pack_id":"<id>"}`
20
+ — a passing run marks the pack `resolved`.
21
+
22
+ World-model constraints (ownership, boundaries, scan branch `dev`) win over guesswork.
23
+ Full flow: ai-code-auditor `AGENTS.md`.
24
+ <!-- ai-audit:agent-packs:end -->
@@ -7,6 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-11-14
11
+
12
+ ### Added
13
+ - **`argus_cortex.provenance`.** `Provenance`, `ProvenanceKind` and `CLEARED_KINDS`, hoisted from
14
+ argus-mask so every package that stores where a face, voice or image came from reads one shape,
15
+ with a committed `provenance-schema.json` (#35).
16
+ - **`argus_cortex.consent`.** `ConsentRecord`, a real person's likeness release as argus-mask#26
17
+ defined it, keyed by the `release_sha256` a `consented` `Provenance` names. `consent_valid()`
18
+ returns a `ConsentVerdict` with one of a closed set of refusal codes, checked in a fixed order
19
+ (`revoked`, `not-yet-signed`, `expired`, `likeness-not-covered`, `use-not-covered`,
20
+ `delivery-not-covered`), so every stage refuses for the same reasons. `consent_matches()` checks
21
+ that a record is the release and subject a provenance names. Committed `consent-schema.json` (#53).
22
+ A record is frozen and its sequences are tuples, so it cannot be changed after its checks pass.
23
+ It is refused when the age check names nobody or comes after signing, when a revocation is
24
+ requested before signing, or when a listed voice delivery is blank (#56).
25
+ - **`Provenance.generator`.** An optional `model`, `model_sha256`, `seed` and `prompt` for how a
26
+ generated source was made, allowed only on `synthetic` and `personal` sources (#53).
27
+
28
+ ### Changed
29
+ - **`Provenance` is strict, as argus-mask's is (argus-mask#64).** Unknown fields are refused instead
30
+ of dropped (`additionalProperties: false` in the schema). `licence` is read as `license`; both given
31
+ and different is refused. A `source` of only whitespace is refused. The derived `cleared` in a dumped
32
+ record is dropped on read and recomputed.
33
+ - **README:** lists the `[images]` and `[gpu]` extras, `ARGUS_GPU_DEVICE`, and the moderation policy
34
+ names with their illegal-rule letters (#42).
35
+
36
+ ## [0.4.7] - 2026-11-13
37
+
38
+ ### Added
39
+ - **`concept` target category.** `TargetCategory` gains `concept`, a style or
40
+ visual-concept LoRA, matching argus-quarry's `concept` subjects. It is
41
+ additive, but a stage that keys tables by category (forge's heuristics, curator's
42
+ scanner) must add an entry before it accepts it.
43
+ - **Committed `taxonomy-schema.json`.** Generated from `TargetProfile` by the new
44
+ `taxonomy_schema()` (`make schema`), with a test that fails when it drifts from
45
+ `taxonomy.py`, so non-Python clients such as studio generate their types instead
46
+ of copying the literals (#29).
47
+ - **`SUITE.md`, the suite map for agents.** Every Argus repo, its port and purpose,
48
+ and a "Need it? Use it" index. Each repo's `CLAUDE.md` imports it (#33).
49
+
10
50
  ## [0.4.6] - 2026-11-06
11
51
 
12
52
  ### Changed
@@ -215,7 +255,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
215
255
  (`LocalBackend`, `RemoteBackend`, `RemoteProvider`, `with_retries`
216
256
  retrying only transient errors, `resolve_device`).
217
257
 
218
- [Unreleased]: https://github.com/smk762/argus-cortex/compare/v0.4.6...HEAD
258
+ [Unreleased]: https://github.com/smk762/argus-cortex/compare/v0.5.0...HEAD
259
+ [0.5.0]: https://github.com/smk762/argus-cortex/compare/v0.4.7...v0.5.0
260
+ [0.4.7]: https://github.com/smk762/argus-cortex/compare/v0.4.6...v0.4.7
219
261
  [0.4.6]: https://github.com/smk762/argus-cortex/compare/v0.4.5...v0.4.6
220
262
  [0.4.5]: https://github.com/smk762/argus-cortex/compare/v0.4.4...v0.4.5
221
263
  [0.4.4]: https://github.com/smk762/argus-cortex/compare/v0.4.3...v0.4.4
@@ -4,17 +4,27 @@ Guidance for AI agents working in this repo. Human-facing usage lives in [README
4
4
 
5
5
  ## What this is
6
6
 
7
- The **shared foundation library** of the Argus suite (quarry → curator → lens → forge → proof). It holds the code the stages *share* so it lives in one place instead of copy-pasted per repo: the target taxonomy, the wire-schema versioning discipline, the local/remote AI-backend contract, and the optional stateful-services layer.
7
+ The **shared foundation library** of the Argus suite. It holds the code the stages *share* so it lives in one place instead of copy-pasted per repo: the target taxonomy, the wire-schema versioning discipline, the local/remote AI-backend contract, and the optional stateful-services layer.
8
8
 
9
9
  Naming: the working directory is `argus-core`, but the GitHub repo, the PyPI package, and the import name are all **`argus-cortex`** (`src/argus_cortex/`). Don't "fix" the mismatch.
10
10
 
11
11
  Because everything here is imported by the downstream stages, **a change ripples across the whole suite** — treat this repo as an API, not a leaf. Read [README.md](README.md) for the *why* and worked examples; it is the source of truth for behaviour.
12
12
 
13
+ ## Argus suite
14
+ <!-- argus-suite: keep this block; argus-ci checks for it -->
15
+ Cortex owns the suite map, [SUITE.md](SUITE.md), which every repo's CLAUDE.md imports. **Keep it accurate:** a cortex
16
+ release that adds or changes a shared module updates its "Need it? Use it" row, and PRs from other repos that update
17
+ their own rows land here. Keep it short, because it is loaded into every session in every repo.
18
+
19
+ @SUITE.md
20
+
13
21
  ## Layout
14
22
 
15
23
  `src/argus_cortex/` (top level is pydantic-only; everything heavier is behind an extra and lazily imported):
16
24
 
17
- - `taxonomy.py` — the "moat" types every stage inherits verbatim: `TargetProfile`, `TargetStyle`/`TargetCategory` literals. Keep it dependency-light; a change here retrains everyone's expectations.
25
+ - `taxonomy.py` — the "moat" types every stage inherits verbatim: `TargetProfile`, `TargetStyle`/`TargetCategory` literals, and `taxonomy_schema()` (the source of the committed `taxonomy-schema.json`). Keep it dependency-light; a change here retrains everyone's expectations.
26
+ - `provenance.py` — `Provenance`/`ProvenanceKind`/`CLEARED_KINDS` for face, voice and image sources, hoisted from argus-mask (#19/#57): `personal` (never cleared) vs cleared `stock`/`synthetic`/`consented` (licence required; `consented` needs `subject` + `release_sha256`); `curated` reserved. **argus-mask owns the meaning of each field — change it only in step with mask.** Optional `generator` (model, sha256, seed, prompt) on `synthetic`/`personal` only. `provenance_schema()` renders the committed `provenance-schema.json` (`make schema`; drift-tested).
27
+ - `consent.py` — `ConsentRecord` (keyed by `release_sha256`, the join to a `consented` `Provenance`), `consent_valid()` → `ConsentVerdict` with a closed `ConsentRefusal` reason (checks in a fixed order; the first failure wins), `consent_matches()`, and `consent_schema()` → the committed `consent-schema.json`. Defined in argus-mask#26; **mask owns the meaning — change it only in step with mask.** `consent_valid` takes `at` from the caller (tz-aware, compared as a UTC day) and never reads the clock; an unknown use is refused, not raised. A record names a person: services return verdicts, never records.
18
28
  - `wire.py` — the versioned wire-schema toolkit consumers build their contracts on: `make_versioned_base(field_name, current, majors)` (a Pydantic base that stamps + version-checks a field like `proof_version`/`manifest_version`), `check_version()` (the major-compatibility gate), `render_schema()`/`wire_schema()` (the canonical committed-schema string behind each consumer's `schema --check` CLI), and `VersionError`.
19
29
  - `backends.py` — the AI-backend contract: `Backend`/`LocalBackend`/`RemoteBackend` (point at a hosted service via `from_host(host, port)` or `from_provider(RemoteProvider.…)`), plus `with_retries()` and `resolve_device()`. httpx is lazy, behind `[remote]`.
20
30
  - `server.py` — the shared FastAPI/ASGI scaffolding other repos' micro-servers mount: `WriteGuard` (pure-ASGI method gate), the `constant_refuse()` (read-only/replay) and `cross_site_refuse()` (cross-origin write) predicates, `env_flag()`, and the `UNSAFE_METHODS`/`TRUTHY`/`FALSY` constants. Starlette is behind `[server]`.
@@ -23,7 +33,7 @@ Because everything here is imported by the downstream stages, **a change ripples
23
33
  - `moderation/` — content-moderation clients over HTTP: `nudenet.py` (`NudeNetClient`, `NsfwResult`, `DEFAULT_EXPOSED_LABELS` — **the suite's one definition of nudity; don't widen it casually**, every stage's NSFW numbers move with it), `singguard.py` (`SingGuardClient` incl. text-only `check_text()`, `GuardResult`, `rule_matches`), `policies.py` (the named policy texts, the per-policy illegal-rule table and `mode()`: `default`/`sfw-*` are SFW — **a policy's text and its illegal letters change together**), `_http.py` (shared POST, proxy-free opener, `CheckFailed`/`ModerationConfigError`). Stdlib + pydantic; Pillow lazily, behind `[images]`. Never import NudeNet (AGPL) — HTTP only. URLs are passed in; stages own their env config.
24
34
  - `store/` — the optional stateful-services layer, opt-in per `CORTEX_*` env var: `lineage.py` (Phase 1 Postgres DAG, `open_lineage_store()`), `vector.py` (Phase 2 Qdrant, `open_vector_store()`), `blob.py` (Phase 3 MinIO/S3, `open_blob_store()`), over `config.py`/`models.py`/`errors.py`. Each `open_*()` returns a `Null*` no-op when its URL is unset. Drivers are behind `[postgres]`/`[qdrant]`/`[s3]`.
25
35
 
26
- There is **no CLI and no committed schema in this repo** — the `schema`/`schema --check` pattern is something `wire.py` *enables* for downstream repos, not something cortex runs on itself.
36
+ There is **no CLI in this repo**. The committed schemas are `taxonomy-schema.json` (from `taxonomy_schema()`), `provenance-schema.json` (from `provenance_schema()`) and `consent-schema.json` (from `consent_schema()`); `make schema` regenerates all three and `tests/test_taxonomy.py` / `tests/test_provenance.py` / `tests/test_consent.py` fail when one is stale. Studio generates its taxonomy types from the first, so a `taxonomy.py`, `provenance.py` or `consent.py` change needs `make schema` in the same commit.
27
37
 
28
38
  ## Commands
29
39
 
@@ -45,6 +55,10 @@ Run one test: `uv run --no-sync pytest tests/test_wire.py::test_name -q`.
45
55
  - **Versioning is git-tag-derived** (`hatch-vcs`). Never hand-edit a version; `src/argus_cortex/_version.py` is generated (gitignored). Tag `vX.Y.Z` to release.
46
56
  - Pydantic v2 everywhere; async tests via `asyncio_mode = auto`. Ruff pinned **0.15.16**, line-length 120, target py311.
47
57
 
58
+ ## Branches
59
+
60
+ `dev` is the default branch and the base for every PR. Cut feature branches from `origin/dev` and bring them up to date by merging `origin/dev`, never `main`. Never push work to `main` or open a PR against it, except the release PR from `dev`: `main` must never be ahead of `dev`, with one exception. A release fast-forwards `main` to a `dev` commit that passed CI (`git push origin origin/dev:main`), then tags it. If `main` is protected, the release is a reviewed PR from `dev` into `main`, and merging it leaves a merge commit on `main` alone. That merge commit is the one exception to the rule: it leaves `main` one commit ahead of `dev` with an identical tree. Leave it there and don't merge `main` back into `dev`. Anything else on `main` that `dev` lacks is a mistake (`git diff origin/dev origin/main` must be empty). Once `main` carries such a commit it can't be fast-forwarded, so later releases are PRs too.
61
+
48
62
  ## CI / release
49
63
 
50
64
  CI runs via the shared [`argus-ci`](https://github.com/smk762/argus-ci) reusable workflow (ruff + pytest with the `dev` extra — which installs the real psycopg/qdrant/minio/starlette clients so each store's live driver path is exercised, not just fakes). Release publishes to PyPI (OIDC trusted publishing) on `v*` tags — **no container image, this is a library**. Scaffolded from [`argus-pkg-template`](https://github.com/smk762/argus-pkg-template); run `copier update` to pull tooling changes.
@@ -1,6 +1,6 @@
1
1
  UV ?= uv
2
2
 
3
- .PHONY: help install lint format test build clean
3
+ .PHONY: help install lint format test schema build clean
4
4
 
5
5
  help: ## Show this help
6
6
  @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
@@ -20,6 +20,9 @@ format: ## Ruff autoformat + autofix
20
20
  test: ## Run the test suite
21
21
  $(UV) run --no-sync pytest --tb=short -q
22
22
 
23
+ schema: ## Regenerate the committed taxonomy-schema.json, provenance-schema.json and consent-schema.json
24
+ $(UV) run --no-sync python -c "from pathlib import Path; from argus_cortex.taxonomy import taxonomy_schema; from argus_cortex.provenance import provenance_schema; from argus_cortex.consent import consent_schema; [Path(f).write_text(r(), encoding='utf-8') for f, r in (('taxonomy-schema.json', taxonomy_schema), ('provenance-schema.json', provenance_schema), ('consent-schema.json', consent_schema))]"
25
+
23
26
  build: ## Build sdist + wheel
24
27
  $(UV) build
25
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: argus-cortex
3
- Version: 0.4.6
3
+ Version: 0.5.0
4
4
  Summary: Shared foundation for the Argus suite: taxonomy, versioned wire-schema tooling, and the local/remote AI backend contract
5
5
  Project-URL: Repository, https://github.com/smk762/argus-cortex
6
6
  Author: smk762
@@ -51,17 +51,19 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  Shared foundation for the Argus suite: taxonomy, versioned wire-schema tooling, and the local/remote AI backend contract.
53
53
 
54
- Part of the [Argus suite](https://github.com/smk762?tab=repositories&q=argus) (quarry → curator → lens → forge → proof). This is the library the stages import so the code they *share* — the target taxonomy, the wire-schema versioning discipline, and the way they call local or hosted models — lives in one place instead of being copy-pasted per repo.
54
+ Part of the Argus suite; [SUITE.md](https://github.com/smk762/argus-cortex/blob/dev/SUITE.md) lists every repo, what it owns, and how they fit together. This is the library the stages import so the code they *share* — the target taxonomy, the wire-schema versioning discipline, and the way they call local or hosted models — lives in one place instead of being copy-pasted per repo.
55
55
 
56
56
  ## What's here
57
57
 
58
- - **`argus_cortex.taxonomy`** — `TargetProfile` / `TargetStyle` / `TargetCategory`, the "moat" types every stage inherits verbatim.
58
+ - **`argus_cortex.taxonomy`** — `TargetProfile` / `TargetStyle` / `TargetCategory`, the "moat" types every stage inherits verbatim. `taxonomy_schema()` renders them into the committed `taxonomy-schema.json` (`make schema` regenerates it; a test fails when it is stale), so non-Python clients generate their types instead of copying the literals.
59
+ - **`argus_cortex.provenance`**: `Provenance` / `ProvenanceKind`, where a face, voice or image came from (the shape argus-mask#19 defined). `personal` needs only a `source` and is never `cleared`; `stock`, `synthetic` and `consented` are cleared, each states its licence, and `consented` also names its `subject` and the `release_sha256` of the signed release (never the document). `curated` is reserved and not cleared. Strict: unknown fields and a blank `source` are refused, `licence` is read as `license`, and a stored `cleared` is recomputed. A `synthetic` or `personal` source may name its `generator` (`model`, `model_sha256`, `seed`, `prompt`; optional, refused on other kinds). `provenance_schema()` renders the committed `provenance-schema.json` (`make schema`; a test fails when it is stale).
60
+ - **`argus_cortex.consent`**: a real person's likeness release, as argus-mask#26 defined it. `ConsentRecord` is keyed by the `release_sha256` a `consented` `Provenance` names, and holds the subject, who collected it, signing and expiry dates (expiry must be stated, `null` for none), the age check (who and when, never the ID), the `face`/`voice` opt-ins, voice `deliveries`, `uses` (`avatar`, `video`, `speech`, `training`; training is off unless listed), sha256s of the reference images and recordings, revocation terms and any `revocation`. `consent_valid(record, use=, likeness=, at=, delivery=)` returns a `ConsentVerdict` (`ok`, `reason`, `detail`), checking in order `revoked`, `not-yet-signed`, `expired`, `likeness-not-covered`, `use-not-covered`, `delivery-not-covered`; `at` must be timezone-aware and dates compare as UTC days, so the check never reads the clock. `consent_matches(record, provenance)` checks the record is the release and subject a provenance names. A record names a person, so services return only verdicts, never records. `consent_schema()` renders the committed `consent-schema.json`.
59
61
  - **`argus_cortex.wire`** — the versioned wire-schema toolkit: `check_version()` (major-compatibility gate), `make_versioned_base()` (a Pydantic base that stamps + checks a version field like `proof_version` / `manifest_version`), and `wire_schema()` / `render_schema()` for the committed-schema `schema --check` CLI pattern.
60
62
  - **`argus_cortex.backends`** — the AI-backend contract generalised from argus-lens: `Backend` / `LocalBackend` / `RemoteBackend` (point at a hosted service by `base_url`, `host`/`port`, or a known `RemoteProvider`), plus `with_retries()` and `resolve_device()`. httpx lives behind the `[remote]` extra.
61
63
  - **`argus_cortex.server`** — shared write-guard + env-flag scaffolding for the suite's FastAPI micro-servers: `WriteGuard` (a pure-ASGI method-gate that never buffers streaming responses), the `constant_refuse()` / `cross_site_refuse()` predicates (read-only/replay mode and cross-site-write protection are two configs of the one guard), the `UNSAFE_METHODS` / `TRUTHY` / `FALSY` constants, and `env_flag()` (warns on a mistyped protection flag rather than silently disabling it). Starlette lives behind the `[server]` extra.
62
- - **`argus_cortex.xmp`** — the suite's one XMP sidecar implementation (hoisted from argus-lens): `write_xmp()` / `read_xmp()` / `merge_xmp()` / `parse_xmp()` over an `XmpData` model covering `dc` (keywords, description), `xmp` (Rating, Label), `lr` (hierarchical keywords), `Iptc4xmpExt` (PersonInImage), `mwg-rs` face/pet regions, and `argus:ContentRating`. Writes **merge**: each property you set replaces that property (latest write wins), everything else in the sidecar — Lightroom develop settings, GPS, another stage's fields — is preserved. `None` leaves a property alone, an empty value removes it. Writes are atomic; an unparseable existing sidecar raises `XmpError` instead of being clobbered. Stdlib only.
64
+ - **`argus_cortex.xmp`** — the suite's one XMP sidecar implementation (hoisted from argus-lens): `write_xmp()` / `read_xmp()` / `merge_xmp()` / `parse_xmp()` over an `XmpData` model covering `dc` (keywords, description), `xmp` (Rating, Label), `lr` (hierarchical keywords), `Iptc4xmpExt` (PersonInImage), `mwg-rs` face/pet regions, and `argus:ContentRating`. Writes **merge**: each property you set replaces that property (latest write wins), everything else in the sidecar — Lightroom develop settings, GPS, another stage's fields — is preserved. `None` leaves a property alone, an empty value removes it. Writes are atomic; an unparseable existing sidecar raises `XmpError` instead of being clobbered. Stdlib + pydantic only.
63
65
  - **`argus_cortex.moderation`** — the suite's content-moderation clients, over HTTP to servers the deployer runs (nothing here imports a detector): `NudeNetClient` (a NudeNet server's `/infer` → `NsfwResult`: detections, per-class maxima, and an **exposed score**) and `SingGuardClient` (a SingGuard server's `/api/guard/check` → `GuardResult`: verdict, the policy rules that fired, explanation; optional custom policy text). `DEFAULT_EXPOSED_LABELS` is the suite's **one definition of nudity**: the five exposed buttocks/breast/genitalia/anus classes — faces, covered parts, and bare feet, belly, armpits or male chest never count. Stdlib + pydantic; Pillow (for SingGuard's downscaling of large images, on by default) is behind `[images]`: without it `SingGuardClient(...)` raises `ModerationConfigError` at construction unless given `downscale=False`. A `user:password@` in a URL is sent as HTTP Basic auth. URLs are passed in, never read from the environment here: each stage owns its config. NudeNet itself is AGPL-3.0, which is why it stays behind an HTTP boundary.
64
- - **`argus_cortex.gpu`**: GPU tenancy, generalised from argus-lens. `coordinator_from_env()` returns a coordinator whose `lease(caller=, min_vram_mb=)` holds a GPU slot for a block of work. `none` (the default) grants every lease at once. `lease` takes an exclusive `flock` that every argus process on the host shares (one lock for the host, not per GPU), then waits until `free_vram_mb()` (NVML, behind `[gpu]`) reports `min_vram_mb` free across *every* process on the card, ComfyUI and Ollama included, which never take the lock. It raises `GpuLeaseTimeout` after `ARGUS_GPU_LEASE_TIMEOUT_S` (300 s). The VRAM check is a snapshot, not a reservation, and a probe that cannot run lets the lease through. Containers each have their own `/tmp`, so set `ARGUS_GPU_LEASE_PATH` to a file on a volume every GPU service mounts, or the lock coordinates nothing.
66
+ - **`argus_cortex.gpu`**: GPU tenancy, generalised from argus-lens. `coordinator_from_env()` returns a coordinator whose `lease(caller=, min_vram_mb=)` holds a GPU slot for a block of work. `none` (the default) grants every lease at once. `lease` takes an exclusive `flock` that every argus process on the host shares (one lock for the host, not per GPU), then waits until `free_vram_mb()` (NVML, behind `[gpu]`) reports `min_vram_mb` free across *every* process on the card, ComfyUI and Ollama included, which never take the lock. It raises `GpuLeaseTimeout` after `ARGUS_GPU_LEASE_TIMEOUT_S` (300 s). The VRAM check is a snapshot, not a reservation, and a probe that cannot run lets the lease through. Containers each have their own `/tmp`, so set `ARGUS_GPU_LEASE_PATH` to a file on a volume every GPU service mounts, or the lock coordinates nothing. `ARGUS_GPU_DEVICE` (default `0`; an invalid value warns and falls back to `0`) picks which card NVML probes for free VRAM; the lock stays host-wide.
65
67
  - **`argus_cortex.store`** — the optional **stateful services layer** (the suite's persistent "memory"). External and opt-in — every store is configured by `CORTEX_*` env vars and no-ops when its URL is unset.
66
68
  - **Phase 1 — Postgres lineage store.** Persists the `source_asset → caption → human_edit → dataset_membership → training_run` DAG for LoRA reproducibility, edit capture, and the feedback loop. Backed by a psycopg connection **pool** (concurrent `asyncio.to_thread` calls are safe; a dropped connection is replaced), with a `transaction()` boundary for atomic multi-step writes. `open_lineage_store()` returns a no-op store when `CORTEX_PG_URL` is unset; psycopg lives behind the `[postgres]` extra.
67
69
  - **Phase 2 — Qdrant vector store.** Stores image + tag-set embeddings for retrieval-augmented few-shot and near-duplicate curation; put a `caption_id`/`asset_id` in a point's payload and a search hit joins straight back to the lineage. `open_vector_store()` no-ops when `CORTEX_QDRANT_URL` is unset; qdrant-client lives behind the `[qdrant]` extra. cortex stores vectors it's *handed* — computing embeddings is the caller's job.
@@ -75,7 +77,7 @@ from argus_cortex.backends import RemoteBackend, RemoteProvider
75
77
  _Versioned = make_versioned_base("proof_version", "1.0", ("1",))
76
78
 
77
79
  # reach a hosted scorer by IP/port, or a known provider
78
- scorer = RemoteBackend.from_host("192.168.1.20", 9000, api_key="…")
80
+ scorer = RemoteBackend.from_host("<LAN_HOST>", 9000, api_key="…") # real host/IP: local .env
79
81
  nim = RemoteBackend.from_provider(RemoteProvider.NVIDIA_NIM, api_key="…")
80
82
  ```
81
83
 
@@ -121,7 +123,17 @@ illegal = [r for r in verdict.rules if policies.is_illegal("adult-granular", r)]
121
123
  policies.mode("adult-granular"), policies.mode("default") # "nsfw", "sfw"
122
124
  ```
123
125
 
124
- Stages ask for a policy by name; `policies` holds each one's text, the rules that mean illegal content under it, and its SFW/NSFW mode (`default` and `sfw-*` are SFW).
126
+ Stages ask for a policy by name; `policies` holds each one's text, the rules that mean illegal content under it, and its SFW/NSFW mode (`default` and `sfw-*` are SFW). The known names are `policies.POLICIES`; any other name raises `ModerationConfigError` (an `sfw-*` name gets mode `sfw` but has no text):
127
+
128
+ | Policy | Mode | Illegal rules |
129
+ |---|---|---|
130
+ | `default` (SingGuard's own rules, no text sent) | sfw | none |
131
+ | `adult` | nsfw | A |
132
+ | `adult-granular` | nsfw | A, H, I |
133
+ | `adult-cautious` | nsfw | A |
134
+ | `adult-cinematic` | nsfw | A |
135
+
136
+ `adult-granular`, `adult-cautious` and `adult-cinematic` are under evaluation against `adult`; one adult policy remains at release, beside `default`.
125
137
 
126
138
  ```python
127
139
  from argus_cortex.gpu import GpuLeaseTimeout, coordinator_from_env
@@ -191,6 +203,8 @@ uv pip install "argus-cortex[server]" # + starlette for argus_cortex.server (
191
203
  uv pip install "argus-cortex[postgres]" # + psycopg for the lineage store
192
204
  uv pip install "argus-cortex[qdrant]" # + qdrant-client for the vector store
193
205
  uv pip install "argus-cortex[s3]" # + minio for the blob store
206
+ uv pip install "argus-cortex[images]" # + Pillow for SingGuardClient downscaling (on by default)
207
+ uv pip install "argus-cortex[gpu]" # + nvidia-ml-py for the GPU lease's free-VRAM probe
194
208
  ```
195
209
 
196
210
  The stateful services are configured via `CORTEX_*` env vars — see [`.env.example`](.env.example). Each is optional: leave a URL unset and that feature degrades to a no-op.
@@ -2,17 +2,19 @@
2
2
 
3
3
  Shared foundation for the Argus suite: taxonomy, versioned wire-schema tooling, and the local/remote AI backend contract.
4
4
 
5
- Part of the [Argus suite](https://github.com/smk762?tab=repositories&q=argus) (quarry → curator → lens → forge → proof). This is the library the stages import so the code they *share* — the target taxonomy, the wire-schema versioning discipline, and the way they call local or hosted models — lives in one place instead of being copy-pasted per repo.
5
+ Part of the Argus suite; [SUITE.md](https://github.com/smk762/argus-cortex/blob/dev/SUITE.md) lists every repo, what it owns, and how they fit together. This is the library the stages import so the code they *share* — the target taxonomy, the wire-schema versioning discipline, and the way they call local or hosted models — lives in one place instead of being copy-pasted per repo.
6
6
 
7
7
  ## What's here
8
8
 
9
- - **`argus_cortex.taxonomy`** — `TargetProfile` / `TargetStyle` / `TargetCategory`, the "moat" types every stage inherits verbatim.
9
+ - **`argus_cortex.taxonomy`** — `TargetProfile` / `TargetStyle` / `TargetCategory`, the "moat" types every stage inherits verbatim. `taxonomy_schema()` renders them into the committed `taxonomy-schema.json` (`make schema` regenerates it; a test fails when it is stale), so non-Python clients generate their types instead of copying the literals.
10
+ - **`argus_cortex.provenance`**: `Provenance` / `ProvenanceKind`, where a face, voice or image came from (the shape argus-mask#19 defined). `personal` needs only a `source` and is never `cleared`; `stock`, `synthetic` and `consented` are cleared, each states its licence, and `consented` also names its `subject` and the `release_sha256` of the signed release (never the document). `curated` is reserved and not cleared. Strict: unknown fields and a blank `source` are refused, `licence` is read as `license`, and a stored `cleared` is recomputed. A `synthetic` or `personal` source may name its `generator` (`model`, `model_sha256`, `seed`, `prompt`; optional, refused on other kinds). `provenance_schema()` renders the committed `provenance-schema.json` (`make schema`; a test fails when it is stale).
11
+ - **`argus_cortex.consent`**: a real person's likeness release, as argus-mask#26 defined it. `ConsentRecord` is keyed by the `release_sha256` a `consented` `Provenance` names, and holds the subject, who collected it, signing and expiry dates (expiry must be stated, `null` for none), the age check (who and when, never the ID), the `face`/`voice` opt-ins, voice `deliveries`, `uses` (`avatar`, `video`, `speech`, `training`; training is off unless listed), sha256s of the reference images and recordings, revocation terms and any `revocation`. `consent_valid(record, use=, likeness=, at=, delivery=)` returns a `ConsentVerdict` (`ok`, `reason`, `detail`), checking in order `revoked`, `not-yet-signed`, `expired`, `likeness-not-covered`, `use-not-covered`, `delivery-not-covered`; `at` must be timezone-aware and dates compare as UTC days, so the check never reads the clock. `consent_matches(record, provenance)` checks the record is the release and subject a provenance names. A record names a person, so services return only verdicts, never records. `consent_schema()` renders the committed `consent-schema.json`.
10
12
  - **`argus_cortex.wire`** — the versioned wire-schema toolkit: `check_version()` (major-compatibility gate), `make_versioned_base()` (a Pydantic base that stamps + checks a version field like `proof_version` / `manifest_version`), and `wire_schema()` / `render_schema()` for the committed-schema `schema --check` CLI pattern.
11
13
  - **`argus_cortex.backends`** — the AI-backend contract generalised from argus-lens: `Backend` / `LocalBackend` / `RemoteBackend` (point at a hosted service by `base_url`, `host`/`port`, or a known `RemoteProvider`), plus `with_retries()` and `resolve_device()`. httpx lives behind the `[remote]` extra.
12
14
  - **`argus_cortex.server`** — shared write-guard + env-flag scaffolding for the suite's FastAPI micro-servers: `WriteGuard` (a pure-ASGI method-gate that never buffers streaming responses), the `constant_refuse()` / `cross_site_refuse()` predicates (read-only/replay mode and cross-site-write protection are two configs of the one guard), the `UNSAFE_METHODS` / `TRUTHY` / `FALSY` constants, and `env_flag()` (warns on a mistyped protection flag rather than silently disabling it). Starlette lives behind the `[server]` extra.
13
- - **`argus_cortex.xmp`** — the suite's one XMP sidecar implementation (hoisted from argus-lens): `write_xmp()` / `read_xmp()` / `merge_xmp()` / `parse_xmp()` over an `XmpData` model covering `dc` (keywords, description), `xmp` (Rating, Label), `lr` (hierarchical keywords), `Iptc4xmpExt` (PersonInImage), `mwg-rs` face/pet regions, and `argus:ContentRating`. Writes **merge**: each property you set replaces that property (latest write wins), everything else in the sidecar — Lightroom develop settings, GPS, another stage's fields — is preserved. `None` leaves a property alone, an empty value removes it. Writes are atomic; an unparseable existing sidecar raises `XmpError` instead of being clobbered. Stdlib only.
15
+ - **`argus_cortex.xmp`** — the suite's one XMP sidecar implementation (hoisted from argus-lens): `write_xmp()` / `read_xmp()` / `merge_xmp()` / `parse_xmp()` over an `XmpData` model covering `dc` (keywords, description), `xmp` (Rating, Label), `lr` (hierarchical keywords), `Iptc4xmpExt` (PersonInImage), `mwg-rs` face/pet regions, and `argus:ContentRating`. Writes **merge**: each property you set replaces that property (latest write wins), everything else in the sidecar — Lightroom develop settings, GPS, another stage's fields — is preserved. `None` leaves a property alone, an empty value removes it. Writes are atomic; an unparseable existing sidecar raises `XmpError` instead of being clobbered. Stdlib + pydantic only.
14
16
  - **`argus_cortex.moderation`** — the suite's content-moderation clients, over HTTP to servers the deployer runs (nothing here imports a detector): `NudeNetClient` (a NudeNet server's `/infer` → `NsfwResult`: detections, per-class maxima, and an **exposed score**) and `SingGuardClient` (a SingGuard server's `/api/guard/check` → `GuardResult`: verdict, the policy rules that fired, explanation; optional custom policy text). `DEFAULT_EXPOSED_LABELS` is the suite's **one definition of nudity**: the five exposed buttocks/breast/genitalia/anus classes — faces, covered parts, and bare feet, belly, armpits or male chest never count. Stdlib + pydantic; Pillow (for SingGuard's downscaling of large images, on by default) is behind `[images]`: without it `SingGuardClient(...)` raises `ModerationConfigError` at construction unless given `downscale=False`. A `user:password@` in a URL is sent as HTTP Basic auth. URLs are passed in, never read from the environment here: each stage owns its config. NudeNet itself is AGPL-3.0, which is why it stays behind an HTTP boundary.
15
- - **`argus_cortex.gpu`**: GPU tenancy, generalised from argus-lens. `coordinator_from_env()` returns a coordinator whose `lease(caller=, min_vram_mb=)` holds a GPU slot for a block of work. `none` (the default) grants every lease at once. `lease` takes an exclusive `flock` that every argus process on the host shares (one lock for the host, not per GPU), then waits until `free_vram_mb()` (NVML, behind `[gpu]`) reports `min_vram_mb` free across *every* process on the card, ComfyUI and Ollama included, which never take the lock. It raises `GpuLeaseTimeout` after `ARGUS_GPU_LEASE_TIMEOUT_S` (300 s). The VRAM check is a snapshot, not a reservation, and a probe that cannot run lets the lease through. Containers each have their own `/tmp`, so set `ARGUS_GPU_LEASE_PATH` to a file on a volume every GPU service mounts, or the lock coordinates nothing.
17
+ - **`argus_cortex.gpu`**: GPU tenancy, generalised from argus-lens. `coordinator_from_env()` returns a coordinator whose `lease(caller=, min_vram_mb=)` holds a GPU slot for a block of work. `none` (the default) grants every lease at once. `lease` takes an exclusive `flock` that every argus process on the host shares (one lock for the host, not per GPU), then waits until `free_vram_mb()` (NVML, behind `[gpu]`) reports `min_vram_mb` free across *every* process on the card, ComfyUI and Ollama included, which never take the lock. It raises `GpuLeaseTimeout` after `ARGUS_GPU_LEASE_TIMEOUT_S` (300 s). The VRAM check is a snapshot, not a reservation, and a probe that cannot run lets the lease through. Containers each have their own `/tmp`, so set `ARGUS_GPU_LEASE_PATH` to a file on a volume every GPU service mounts, or the lock coordinates nothing. `ARGUS_GPU_DEVICE` (default `0`; an invalid value warns and falls back to `0`) picks which card NVML probes for free VRAM; the lock stays host-wide.
16
18
  - **`argus_cortex.store`** — the optional **stateful services layer** (the suite's persistent "memory"). External and opt-in — every store is configured by `CORTEX_*` env vars and no-ops when its URL is unset.
17
19
  - **Phase 1 — Postgres lineage store.** Persists the `source_asset → caption → human_edit → dataset_membership → training_run` DAG for LoRA reproducibility, edit capture, and the feedback loop. Backed by a psycopg connection **pool** (concurrent `asyncio.to_thread` calls are safe; a dropped connection is replaced), with a `transaction()` boundary for atomic multi-step writes. `open_lineage_store()` returns a no-op store when `CORTEX_PG_URL` is unset; psycopg lives behind the `[postgres]` extra.
18
20
  - **Phase 2 — Qdrant vector store.** Stores image + tag-set embeddings for retrieval-augmented few-shot and near-duplicate curation; put a `caption_id`/`asset_id` in a point's payload and a search hit joins straight back to the lineage. `open_vector_store()` no-ops when `CORTEX_QDRANT_URL` is unset; qdrant-client lives behind the `[qdrant]` extra. cortex stores vectors it's *handed* — computing embeddings is the caller's job.
@@ -26,7 +28,7 @@ from argus_cortex.backends import RemoteBackend, RemoteProvider
26
28
  _Versioned = make_versioned_base("proof_version", "1.0", ("1",))
27
29
 
28
30
  # reach a hosted scorer by IP/port, or a known provider
29
- scorer = RemoteBackend.from_host("192.168.1.20", 9000, api_key="…")
31
+ scorer = RemoteBackend.from_host("<LAN_HOST>", 9000, api_key="…") # real host/IP: local .env
30
32
  nim = RemoteBackend.from_provider(RemoteProvider.NVIDIA_NIM, api_key="…")
31
33
  ```
32
34
 
@@ -72,7 +74,17 @@ illegal = [r for r in verdict.rules if policies.is_illegal("adult-granular", r)]
72
74
  policies.mode("adult-granular"), policies.mode("default") # "nsfw", "sfw"
73
75
  ```
74
76
 
75
- Stages ask for a policy by name; `policies` holds each one's text, the rules that mean illegal content under it, and its SFW/NSFW mode (`default` and `sfw-*` are SFW).
77
+ Stages ask for a policy by name; `policies` holds each one's text, the rules that mean illegal content under it, and its SFW/NSFW mode (`default` and `sfw-*` are SFW). The known names are `policies.POLICIES`; any other name raises `ModerationConfigError` (an `sfw-*` name gets mode `sfw` but has no text):
78
+
79
+ | Policy | Mode | Illegal rules |
80
+ |---|---|---|
81
+ | `default` (SingGuard's own rules, no text sent) | sfw | none |
82
+ | `adult` | nsfw | A |
83
+ | `adult-granular` | nsfw | A, H, I |
84
+ | `adult-cautious` | nsfw | A |
85
+ | `adult-cinematic` | nsfw | A |
86
+
87
+ `adult-granular`, `adult-cautious` and `adult-cinematic` are under evaluation against `adult`; one adult policy remains at release, beside `default`.
76
88
 
77
89
  ```python
78
90
  from argus_cortex.gpu import GpuLeaseTimeout, coordinator_from_env
@@ -142,6 +154,8 @@ uv pip install "argus-cortex[server]" # + starlette for argus_cortex.server (
142
154
  uv pip install "argus-cortex[postgres]" # + psycopg for the lineage store
143
155
  uv pip install "argus-cortex[qdrant]" # + qdrant-client for the vector store
144
156
  uv pip install "argus-cortex[s3]" # + minio for the blob store
157
+ uv pip install "argus-cortex[images]" # + Pillow for SingGuardClient downscaling (on by default)
158
+ uv pip install "argus-cortex[gpu]" # + nvidia-ml-py for the GPU lease's free-VRAM probe
145
159
  ```
146
160
 
147
161
  The stateful services are configured via `CORTEX_*` env vars — see [`.env.example`](.env.example). Each is optional: leave a URL unset and that feature degrades to a no-op.
@@ -0,0 +1,102 @@
1
+ # The Argus suite: map for agents
2
+
3
+ The canonical list of every Argus repo: what each one is for, and which one already does the thing you are about to build.
4
+ Every repo's `CLAUDE.md` imports this file. **Before designing a feature, check the "Need it? Use it" index.** If a
5
+ sibling already does it, call that sibling (HTTP, or its library) instead of rebuilding it.
6
+
7
+ **Keep it current. Every release of any Argus repo checks its row here.** If a release adds, removes or renames a
8
+ capability, endpoint, port, UI pairing or dependency, open a PR to `argus-cortex` that updates this file in the same
9
+ release. A new repo adds a row and the import block (see the end of this file) before its first release.
10
+
11
+ ## Repos
12
+
13
+ GitHub: `smk762/<repo>`. Ports are each service's default.
14
+
15
+ | Repo | Kind | Port | What it is for |
16
+ |---|---|---|---|
17
+ | **argus-cortex** | library | n/a | Shared foundation: taxonomy, wire-schema versioning, AI-backend contract, server guards, GPU lease, XMP, moderation clients, likeness provenance and consent records, optional lineage/vector/blob stores. |
18
+ | **argus-quarry** | service | 8102 | Acquire public-domain / CC0 images with provenance and licence into a category-sorted raw pool. Searches local metadata archives of PD12M and Megalith-10m (filters, plus CLIP semantic and "more like this" search over PD12M) and lands chosen slices, with Megalith's Drawthings mirror as a fallback for photos gone from Flickr. Acquisition only. |
19
+ | **argus-curator** | service | 8101 | Decide which images belong in a LoRA training set (quality, near-dup, faces, NSFW, diversity) and export them with a `manifest.jsonl`. |
20
+ | **argus-lens** | service | 8100 (8080 standalone) | Structured, category-bucketed captions, `.txt` sidecars and XMP keywords. Records live captions to the cortex lineage store (the tape lightbox and replay read); runs read-only with `LENS_READ_ONLY`. |
21
+ | **argus-forge** | service | 8103 | Turn a curated, captioned export into runnable kohya_ss / OneTrainer / diffusers configs. |
22
+ | **argus-proof** | service | 8104 | Evaluate a trained LoRA against its dataset: sample grid, scores, pass/fail. |
23
+ | **argus-atlas** | service | 8105 | Bake georeferenced fantasy-map overlays from real terrain (GDAL controls plus a cartography LoRA), to GeoTIFF / PMTiles. |
24
+ | **argus-darkroom** | service | 8106 | Guarded image generation: checks the prompt, generates via ComfyUI, checks each output, and keeps a record of every attempt. |
25
+ | **argus-survey** | service | 8107 | Point-cloud deliverables for land surveyors (LAS/LAZ/COPC/E57 in): QA, ground, DTM/DSM/CHM, contours, stockpile volumes and cut/fill (grid and ground-scaled), change between epochs, profiles, ASPRS Edition 2 (v2, 2024) accuracy, LAS 1.4 / E57 export for ReCap and Civil 3D. Explicit datum operations (MGA2020, AHD via AUSGeoid2020), every command recorded in `job.json`. PyPI `argus-survey`, image `ghcr.io/smk762/argus-survey`. |
26
+ | **argus-mint** | service | 8108 | Provenance tokens for game assets: renders reviewed templates (fixed variable lists, no free prompts) through darkroom, checks each template against darkroom's checkpoints and workflows before approval, frames cards and writes their bios, and has the moderator and owner (SIWE) approve every image before it is pinned to IPFS. Mints ERC-721 cards and soulbound deeds whose metadata records the render's template, variables, checkpoint, LoRAs and seed, and anchors Merkle roots and commit/reveal secrets (Base). PyPI `argus-mint`, image `ghcr.io/smk762/argus-mint`. |
27
+ | **argus-mask** | service | 8109 | A talking face: audio in (or text through a TTS service), ARKit facial-animation frames out (an energy driver on CPU, or NVIDIA Audio2Face-3D as a separate image), rendered in a browser on any LAN screen. Mesh (`.glb`) and LAM Gaussian-splat heads, each with provenance (`personal` or cleared); an optional face editor builds MakeHuman heads with Blender. PyPI `argus-mask`, image `ghcr.io/smk762/argus-mask`. |
28
+ | **argus-studio** | UI + compose | 3000 | The suite web UI, and the compose stack that runs the services from GHCR images. No backend code. |
29
+ | **argus-safelight** | UI | 3097 | Demo UI for darkroom: prompt and pre-flight, text-to-image, image-to-image and inpainting over SSE, the per-policy shield grid, history, and darkroom's docs in `/docs` with in-context help. Follows darkroom's `dev` at a recorded commit (`web/schema/pins.json`: docs, wire types and fixture move together), not a release; built from source by darkroom's compose, no image. |
30
+ | **argus-cull** | UI + compose | 3000 | Demo UI for curator: which images make the set and why, plus the paths to act on. Its compose runs curator with NudeNet, EraX-NSFW and watermark sidecars (NudeNet-shaped `POST /infer`); SingGuard is darkroom's agent-composer. Follows curator's `dev` at a recorded commit, not a release; web image `ghcr.io/smk762/argus-cull`. |
31
+ | **argus-lightbox** | UI (static) + compose | 3000 | Demo gallery navigated by lens captions. Bakes the recorded tape from cortex stores into a static site (the hosted demo), or captions a photo folder through lens in its compose stack, where a small server also lets you pick each photo's final caption. `/docs` links lens's docs at the baked version. |
32
+ | **argus-sightline** | UI | 3107 | Workspace UI for survey: every step over SSE, a map of the derived views with drawing tools, a COPC point view, datums always on screen, a guided demo measured against analytic truth, and a "Made with" line per step from `job.json`. Follows survey's `dev` at a recorded commit (`schema/pins.json`: wire types, demo numbers and docs links move together), not a release; built from source by its compose, no published image yet. |
33
+ | **argus-assay** | UI | 3108 | Review UI for mint. The moderator (token) approves or rejects every card; the owner signs for theirs (EIP-4361 via the wallet) and can change its text and style with a live preview, which both then review again. Templates, frames and bio templates are approved in its catalogue, with sample runs. New drafts render through darkroom on a reviewed checkpoint and LoRAs, or come from mint's pre-rendered portrait library with no GPU, shown as a checkpoint comparison grid. A mock serves mint's recorded fixture for demo mode (`make mock`). Pins a mint release (`web/schema/pins.json`, now v0.2.0: wire types and fixture move together); built from source by its compose, no image. |
34
+ | **argus-halo** | infra | n/a | Terraform for the public demo (`argus.dragonhound.dev`): read-only replay services plus a private data host. |
35
+ | **argus-ci** | infra | n/a | Reusable GitHub Actions workflows (uv, ruff, pytest, build, release). |
36
+ | **argus-pkg-template** | infra | n/a | Copier template for new Argus Python packages; `copier update` pulls tooling changes. |
37
+
38
+ **How they fit.** The LoRA pipeline is `quarry → curator → lens → forge → (trainer) → proof → atlas`; curator, lens,
39
+ forge and proof carry the same `TargetProfile` from cortex. Darkroom is the suite's way to generate an image: mint
40
+ renders through it, and anything new that needs a picture (mask backgrounds, game art) should too. Proof's sample grids
41
+ and atlas's bakes predate it and still drive their own diffusion backend. Survey stands alone; its GeoTIFFs can feed
42
+ atlas. Every Python service except quarry depends on cortex. UIs are thin clients: the service decides and the UI only
43
+ shows.
44
+
45
+ **API and UI pairs.** darkroom ↔ safelight · curator ↔ cull · lens ↔ lightbox · survey ↔ sightline · mint ↔ assay.
46
+ Studio has a page for lens, curator, quarry, forge, proof, atlas and darkroom, and links out to sightline.
47
+
48
+ ## Need it? Use it
49
+
50
+ | Need | Use | Don't |
51
+ |---|---|---|
52
+ | Generate an image (a plate, a background, art, a sample) | **darkroom** `POST /generate/stream` (SSE), `GET /image/{id}`. Its record id, model and seed are your provenance. Example client: mint's `DarkroomClient`. | Call ComfyUI directly or generate without checks. |
53
+ | Check an image for nudity, or text/an image against a policy | `argus_cortex.moderation` (`NudeNetClient`, `SingGuardClient`, `policies`) | Write your own nudity definition or policy text. |
54
+ | Share the GPU with other services | `argus_cortex.gpu` (`coordinator_from_env()`, `lease()`) | Assume the GPU is free. |
55
+ | Call a local or hosted model | `argus_cortex.backends` (`LocalBackend`, `RemoteBackend`, `with_retries`) | Hand-roll an HTTP client per model. |
56
+ | Version a wire payload, commit its schema | `argus_cortex.wire` (`make_versioned_base`, `render_schema`) | Ship an unversioned contract. |
57
+ | Guard a FastAPI server's writes, or run read-only | `argus_cortex.server` (`WriteGuard`, `env_flag`) | Use `BaseHTTPMiddleware`, which buffers streams. |
58
+ | Read or write XMP sidecars | `argus_cortex.xmp` | Overwrite another stage's properties. |
59
+ | Record lineage, embeddings or blobs | `argus_cortex.store` (`open_lineage_store` / `open_vector_store` / `open_blob_store`) | Add a database to a single stage. |
60
+ | Record where a face, voice or image came from, or whether it may be shown publicly | `argus_cortex.provenance` (`Provenance`, `.cleared`, `generator`); non-Python clients use `provenance-schema.json` | Define your own provenance kinds or consent fields. |
61
+ | Check a real person's consent for a use of their face or voice | `argus_cortex.consent` (`ConsentRecord`, `consent_valid` → `ConsentVerdict`, `consent_matches`); non-Python clients use `consent-schema.json`. Records are written by **mask** | Invent refusal reasons, or serve a record (it names a person). |
62
+ | The training target (style, category) | `argus_cortex.taxonomy`; non-Python clients use `taxonomy-schema.json` | Copy the literals. |
63
+ | Acquire source photos with licence and provenance | **quarry** | Scrape inside another stage. |
64
+ | Find public-domain photos by caption, filters or meaning, then land a slice | **quarry** `GET /archives/{name}/search` (`semantic=`, `similar_to=` on PD12M), `POST /archives/{name}/fetch` (opt-in, `QUARRY_ALLOW_FETCH=1`) | Download PD12M or Megalith wholesale to search it. |
65
+ | Score, dedupe or select images; faces; content rating | **curator** | Re-score in a later stage. |
66
+ | Caption or keyword images | **lens** | Write ad-hoc captions. |
67
+ | Training configs, or evaluating a LoRA | **forge**, **proof** | |
68
+ | Terrain, georeferencing, map tiles | **atlas** (stylised), **survey** (survey-grade) | |
69
+ | Point clouds: ground classification, DTM/DSM, volumes, datum transformations (geoid heights, GDA94→GDA2020) | **survey** (CLI, or `POST /jobs/{id}/{step}` over SSE); its GeoTIFFs feed `argus-atlas controls --dem` | Let PROJ pick a transformation, or reproject heights without a geoid grid. |
70
+ | A face that speaks | **mask** `POST /speak?face=<id>` (text), `POST /push?face=<id>` (a WAV or PCM you already have) or the `/session/<id>/source` socket; screens open `/?face=<id>` | Drive a face's animation yourself, or render it per stage. |
71
+ | On-chain provenance, IPFS; game-asset cards from reviewed templates | **mint** (`argus-mint serve`, or the CLI); a consumer supplies template variables, display text and 32-byte leaf hashes | Mint tokens a game reads stats from, or send free text to a model. |
72
+ | Run the whole stack locally | **studio** `compose.yaml` | |
73
+ | A new Python package, or CI | **argus-pkg-template**, **argus-ci** | Copy another repo's tooling by hand. |
74
+
75
+ ## Shared rules
76
+
77
+ - Shared code lives in cortex: import it, don't copy it. A cortex change ripples into every stage on its next bump.
78
+ - Provenance first. Anything the suite serves or trains on carries its source and licence; unlicensed input is refused.
79
+ - Optional heavy dependencies are lazy imports behind extras. A missing service config fails open to a no-op, never a crash.
80
+ - Talk across repos over HTTP or a versioned wire schema, never by reading another repo's files.
81
+
82
+ ## Local checkouts
83
+
84
+ The maintainer's siblings live under `~/`, so the paths are `../argus-<name>`, with two exceptions:
85
+ - cortex is `../argus-core`;
86
+ - studio is `../argus-vision-demo`.
87
+
88
+ Directories named `argus-<repo>-<topic>` are worktrees of `<repo>`, not separate repos.
89
+
90
+ ## The import block (every repo's CLAUDE.md)
91
+
92
+ ```markdown
93
+ ## Argus suite
94
+ <!-- argus-suite: keep this block; argus-ci checks for it -->
95
+ This repo is part of the Argus suite. The suite map below lists every sibling and what it owns; check it before
96
+ building something a sibling may already do. **On every release, update this repo's row and capabilities in
97
+ argus-cortex's SUITE.md** (a PR to argus-cortex in the same release).
98
+
99
+ @../argus-core/SUITE.md
100
+
101
+ Not checked out? Read https://github.com/smk762/argus-cortex/blob/dev/SUITE.md
102
+ ```