argus-cortex 0.4.4__tar.gz → 0.4.6__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_cortex-0.4.4 → argus_cortex-0.4.6}/.github/workflows/release.yml +51 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/CHANGELOG.md +40 -1
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/PKG-INFO +1 -1
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/_version.py +2 -2
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/moderation/policies.py +95 -20
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_policies.py +55 -3
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/.copier-answers.yml +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/.env.example +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/.github/workflows/ci.yml +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/.gitignore +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/CLAUDE.md +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/LICENSE +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/Makefile +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/README.md +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/pyproject.toml +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/__init__.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/backends.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/gpu.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/moderation/__init__.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/moderation/_http.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/moderation/nudenet.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/moderation/singguard.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/py.typed +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/server.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/store/__init__.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/store/blob.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/store/config.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/store/errors.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/store/lineage.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/store/models.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/store/schema.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/store/vector.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/taxonomy.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/wire.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/src/argus_cortex/xmp.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_backends.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_blob.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_gpu.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_moderation.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_server.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_smoke.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_store.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_store_pg_integration.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_taxonomy.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_vector.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_wire.py +0 -0
- {argus_cortex-0.4.4 → argus_cortex-0.4.6}/tests/test_xmp.py +0 -0
|
@@ -118,3 +118,54 @@ jobs:
|
|
|
118
118
|
else
|
|
119
119
|
gh release create "$tag" dist/* --verify-tag --title "$tag" --notes-file notes.md $prerelease
|
|
120
120
|
fi
|
|
121
|
+
|
|
122
|
+
dispatch-stages:
|
|
123
|
+
# Each stage image installs argus-cortex at build time from its pyproject
|
|
124
|
+
# range, so a new cortex only reaches running services once the images are
|
|
125
|
+
# rebuilt. Tell the stages: their release.yml rebuilds the image of their
|
|
126
|
+
# newest release on `cortex-released`. atlas and survey are left out on
|
|
127
|
+
# purpose: they `uv sync --locked`, so a rebuild reinstalls the cortex their
|
|
128
|
+
# lockfile pins — they need a lock bump, not a rebuild. quarry has no cortex.
|
|
129
|
+
runs-on: ubuntu-latest
|
|
130
|
+
needs: publish-pypi
|
|
131
|
+
if: "!cancelled() && needs.publish-pypi.result == 'success'"
|
|
132
|
+
steps:
|
|
133
|
+
- name: Wait until pip can resolve the new version
|
|
134
|
+
# The upload is accepted before the simple index serves it; a stage
|
|
135
|
+
# build that starts in that window would resolve the previous cortex.
|
|
136
|
+
run: |
|
|
137
|
+
version="${GITHUB_REF_NAME#v}"
|
|
138
|
+
wheel="argus_cortex-${version}-py3-none-any.whl"
|
|
139
|
+
for _ in $(seq 60); do
|
|
140
|
+
if curl -fsS https://pypi.org/simple/argus-cortex/ | grep -q "$wheel"; then
|
|
141
|
+
exit 0
|
|
142
|
+
fi
|
|
143
|
+
sleep 10
|
|
144
|
+
done
|
|
145
|
+
echo "::error::$wheel is not on the PyPI simple index after 10 minutes; no stage was told."
|
|
146
|
+
exit 1
|
|
147
|
+
- name: Dispatch cortex-released to the stages
|
|
148
|
+
# A pre-release is skipped: no stage range admits one, so the rebuild
|
|
149
|
+
# would reinstall the cortex already there.
|
|
150
|
+
# SUITE_DISPATCH_TOKEN: a fine-grained PAT with Contents: read & write
|
|
151
|
+
# on the repos below (GITHUB_TOKEN cannot reach another repo).
|
|
152
|
+
env:
|
|
153
|
+
GH_TOKEN: ${{ secrets.SUITE_DISPATCH_TOKEN }}
|
|
154
|
+
run: |
|
|
155
|
+
tag="${GITHUB_REF_NAME}"
|
|
156
|
+
case "$tag" in *-*|*rc*|*a[0-9]*|*b[0-9]*) echo "Pre-release $tag: not dispatching."; exit 0 ;; esac
|
|
157
|
+
if [ -z "$GH_TOKEN" ]; then
|
|
158
|
+
echo "::error::SUITE_DISPATCH_TOKEN is not set; no stage image was rebuilt for $tag."
|
|
159
|
+
exit 1
|
|
160
|
+
fi
|
|
161
|
+
failed=0
|
|
162
|
+
for repo in argus-curator argus-lens argus-proof argus-forge argus-darkroom; do
|
|
163
|
+
if gh api "repos/${GITHUB_REPOSITORY_OWNER}/${repo}/dispatches" \
|
|
164
|
+
-f event_type=cortex-released -f "client_payload[version]=${tag#v}"; then
|
|
165
|
+
echo "Dispatched cortex-released to $repo"
|
|
166
|
+
else
|
|
167
|
+
echo "::error::Could not dispatch to $repo"
|
|
168
|
+
failed=1
|
|
169
|
+
fi
|
|
170
|
+
done
|
|
171
|
+
exit "$failed"
|
|
@@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.6] - 2026-11-06
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **`adult-cinematic` passes depicted crime without a film cue.** A robbery,
|
|
14
|
+
kidnapping, armed standoff, drugs or war is Safe unless it is sexual, shows
|
|
15
|
+
gore or dismemberment, or teaches how to do it. The crimes heading at B is
|
|
16
|
+
gone, because it was read as any crime shown.
|
|
17
|
+
- **`adult-cinematic` letters changed.** B to F are now Unethical Behavior,
|
|
18
|
+
Cybersecurity, Agent Safety, Politically Sensitive and Animal Abuse, and G is
|
|
19
|
+
Criminal Instructions, so B to G no longer match the other adult policies. A
|
|
20
|
+
filter that names a letter selects a different rule here, including on
|
|
21
|
+
verdicts recorded earlier. Illegal A is unchanged.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **A release now rebuilds the stage images.** Once the version is on the PyPI index,
|
|
25
|
+
`release.yml` sends `cortex-released` to curator, lens, proof, forge and darkroom, whose
|
|
26
|
+
release workflows rebuild their newest release's image against it. Needs the
|
|
27
|
+
`SUITE_DISPATCH_TOKEN` secret (a fine-grained PAT, Contents: read & write on those repos).
|
|
28
|
+
atlas and survey are not sent it: they install from their lockfile.
|
|
29
|
+
|
|
30
|
+
## [0.4.5] - 2026-10-31
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- **`adult-cinematic`**: `adult` with B narrowed to how-to / real targeting, and
|
|
34
|
+
Safe naming fictional crime (heist, fraud, dirty cops, war as setting, weapons
|
|
35
|
+
as a prop, drugs as a plot). Legal adult entertainment stays Safe the same
|
|
36
|
+
way as under `adult` (commercial porn, studio stills, an adult-site watermark).
|
|
37
|
+
Under evaluation against `adult` (#24). Letters and illegal A are unchanged.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- **Adult policies no longer invite a "closest" H/I dump-bin.** Safe now names
|
|
41
|
+
legal adult entertainment, commercial porn, explicit sex, genitalia, a
|
|
42
|
+
suggestive pose, a commercial or studio set, and an adult-site watermark, and
|
|
43
|
+
says that if no risk bullet matches the verdict is Safe. Headings dropped the
|
|
44
|
+
word "Extreme" (`A. Illegal Sexual Content`, granular `I. Sexual Violence`),
|
|
45
|
+
which the model read as a bin for legal porn. Letters are unchanged.
|
|
46
|
+
|
|
10
47
|
## [0.4.4] - 2026-10-31
|
|
11
48
|
|
|
12
49
|
### Fixed
|
|
@@ -178,7 +215,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
178
215
|
(`LocalBackend`, `RemoteBackend`, `RemoteProvider`, `with_retries`
|
|
179
216
|
retrying only transient errors, `resolve_device`).
|
|
180
217
|
|
|
181
|
-
[Unreleased]: https://github.com/smk762/argus-cortex/compare/v0.4.
|
|
218
|
+
[Unreleased]: https://github.com/smk762/argus-cortex/compare/v0.4.6...HEAD
|
|
219
|
+
[0.4.6]: https://github.com/smk762/argus-cortex/compare/v0.4.5...v0.4.6
|
|
220
|
+
[0.4.5]: https://github.com/smk762/argus-cortex/compare/v0.4.4...v0.4.5
|
|
182
221
|
[0.4.4]: https://github.com/smk762/argus-cortex/compare/v0.4.3...v0.4.4
|
|
183
222
|
[0.4.3]: https://github.com/smk762/argus-cortex/compare/v0.4.2...v0.4.3
|
|
184
223
|
[0.4.2]: https://github.com/smk762/argus-cortex/compare/v0.4.1...v0.4.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: argus-cortex
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
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
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.4.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 4,
|
|
21
|
+
__version__ = version = '0.4.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 6)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -23,20 +23,38 @@ from typing import Literal
|
|
|
23
23
|
from argus_cortex.moderation._http import ModerationConfigError
|
|
24
24
|
from argus_cortex.moderation.singguard import rule_letter
|
|
25
25
|
|
|
26
|
-
PolicyName = Literal["default", "adult", "adult-granular", "adult-cautious"]
|
|
26
|
+
PolicyName = Literal["default", "adult", "adult-granular", "adult-cautious", "adult-cinematic"]
|
|
27
27
|
Mode = Literal["sfw", "nsfw"]
|
|
28
28
|
|
|
29
|
-
# "adult-granular" and "adult-
|
|
30
|
-
# one adult policy remains at release, beside "default".
|
|
31
|
-
POLICIES: tuple[PolicyName, ...] = (
|
|
29
|
+
# "adult-granular", "adult-cautious" and "adult-cinematic" are under evaluation
|
|
30
|
+
# against "adult"; one adult policy remains at release, beside "default".
|
|
31
|
+
POLICIES: tuple[PolicyName, ...] = (
|
|
32
|
+
"default",
|
|
33
|
+
"adult",
|
|
34
|
+
"adult-granular",
|
|
35
|
+
"adult-cautious",
|
|
36
|
+
"adult-cinematic",
|
|
37
|
+
)
|
|
32
38
|
SFW_PREFIX = "sfw-"
|
|
33
39
|
|
|
34
|
-
# Rules B to G, shared by every adult policy: the same
|
|
35
|
-
# SingGuard's defaults
|
|
36
|
-
#
|
|
37
|
-
|
|
40
|
+
# Rules B to G, shared by every adult policy except cinematic: the same
|
|
41
|
+
# letters and areas as SingGuard's defaults. They are our wording, so their
|
|
42
|
+
# verdicts can differ from the built-ins.
|
|
43
|
+
_CRIME_B = [
|
|
38
44
|
"### B. Real-World Crimes & Public Safety",
|
|
39
45
|
" - Content involving violent crime, weapons, other crimes, or public-safety threats.",
|
|
46
|
+
]
|
|
47
|
+
# Depiction is not a hit; how-to, a named real target, and operational threats are.
|
|
48
|
+
# Last, not at B: a heading called crimes at B was read as any crime shown,
|
|
49
|
+
# whatever its bullet said.
|
|
50
|
+
_CRIME_CINEMATIC = [
|
|
51
|
+
"### G. Criminal Instructions",
|
|
52
|
+
(
|
|
53
|
+
" - Instructional or operational assistance for a real-world crime: how to commit one, "
|
|
54
|
+
"targeting a named real person or a real institution, or an operational public-safety threat."
|
|
55
|
+
),
|
|
56
|
+
]
|
|
57
|
+
_OTHER_AFTER_B = [
|
|
40
58
|
"### C. Unethical Behavior",
|
|
41
59
|
" - Content involving hate, harassment, manipulation, self-harm, or harmful misinformation.",
|
|
42
60
|
" - Gore, dismemberment, and shock content meant to frighten or disgust.",
|
|
@@ -52,6 +70,14 @@ _OTHER_RULES = [
|
|
|
52
70
|
"### G. Animal Abuse",
|
|
53
71
|
" - Content involving cruelty to animals or the spread of animal abuse.",
|
|
54
72
|
]
|
|
73
|
+
_OTHER_RULES = [*_CRIME_B, *_OTHER_AFTER_B]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _one_letter_up(line: str) -> str:
|
|
77
|
+
"""A ``### C. ...`` heading as ``### B. ...``; a bullet as it is."""
|
|
78
|
+
return f"### {chr(ord(line[4]) - 1)}{line[5:]}" if line.startswith("### ") else line
|
|
79
|
+
|
|
80
|
+
|
|
55
81
|
_MINORS = " - Sexual content involving anyone under 18, or anyone who appears to be a minor."
|
|
56
82
|
_NON_CONSENT = (
|
|
57
83
|
" - Non-consensual sexual content, including hidden recording, sexual images of a real "
|
|
@@ -66,9 +92,13 @@ _EXTREME = " - Sexual violence, bestiality, or necrophilia."
|
|
|
66
92
|
# two people; the posing line covers a solo adult. Its own trained taxonomy
|
|
67
93
|
# files legal adult material under "Adult Content (Non-criminal)", and it cited
|
|
68
94
|
# that as a violation under every adult policy, so Safe names it.
|
|
95
|
+
# Headings must not say "Extreme Content": the model treated explicit adult
|
|
96
|
+
# stills as I (or A) because that word was the closest dump bin, then picked
|
|
97
|
+
# H as "closest" even while saying the frame was consensual.
|
|
69
98
|
_ADULT_CONTENT_SAFE = (
|
|
70
|
-
' - "Adult Content (Non-criminal)" is Safe under this policy: legal
|
|
71
|
-
"alone or together
|
|
99
|
+
' - "Adult Content (Non-criminal)" is Safe under this policy: legal adult entertainment, '
|
|
100
|
+
"legal nudity and sexual content by adults, alone or together, including commercial porn, "
|
|
101
|
+
"explicit sex, genitalia, a suggestive pose, a commercial or studio set, and an adult-site watermark."
|
|
72
102
|
)
|
|
73
103
|
_ADULT_SAFE = [
|
|
74
104
|
" - Content that does not match any risk category, including consensual adult nudity and sexual content.",
|
|
@@ -78,6 +108,10 @@ _ADULT_SAFE = [
|
|
|
78
108
|
" - Consent cannot be read from an image. A posed, camera-aware adult counts as consenting unless "
|
|
79
109
|
"hidden recording, restraint, coercion, or an unconscious person is shown."
|
|
80
110
|
),
|
|
111
|
+
(
|
|
112
|
+
" - If no risk-category bullet matches, the verdict is Safe. Do not assign a risk letter "
|
|
113
|
+
"because it is the closest heading."
|
|
114
|
+
),
|
|
81
115
|
]
|
|
82
116
|
|
|
83
117
|
# Sent in full: SingGuard reads a bare "nsfw" as rule text. One broad rule A
|
|
@@ -85,7 +119,7 @@ _ADULT_SAFE = [
|
|
|
85
119
|
ADULT_POLICY = "\n".join(
|
|
86
120
|
[
|
|
87
121
|
"## Risk Categories",
|
|
88
|
-
"### A. Illegal
|
|
122
|
+
"### A. Illegal Sexual Content",
|
|
89
123
|
_MINORS,
|
|
90
124
|
_NON_CONSENT,
|
|
91
125
|
_EXTREME,
|
|
@@ -96,9 +130,9 @@ ADULT_POLICY = "\n".join(
|
|
|
96
130
|
)
|
|
97
131
|
|
|
98
132
|
# The same coverage with a letter per hard case (A minors, H non-consent,
|
|
99
|
-
# I sexual violence
|
|
100
|
-
#
|
|
101
|
-
#
|
|
133
|
+
# I sexual violence), so the verdict itself says which hard rule fired
|
|
134
|
+
# instead of only the write-up. Narrower rules may also fire less often on
|
|
135
|
+
# doubtful cases; that is what the evaluation measures.
|
|
102
136
|
ADULT_GRANULAR_POLICY = "\n".join(
|
|
103
137
|
[
|
|
104
138
|
"## Risk Categories",
|
|
@@ -108,7 +142,7 @@ ADULT_GRANULAR_POLICY = "\n".join(
|
|
|
108
142
|
"### H. Non-Consensual Sexual Content",
|
|
109
143
|
_NON_CONSENT,
|
|
110
144
|
" - Sexual content showing someone asleep, unconscious, or otherwise unable to consent.",
|
|
111
|
-
"### I. Sexual Violence
|
|
145
|
+
"### I. Sexual Violence",
|
|
112
146
|
_EXTREME,
|
|
113
147
|
"### Safe",
|
|
114
148
|
*_ADULT_SAFE,
|
|
@@ -120,7 +154,7 @@ ADULT_GRANULAR_POLICY = "\n".join(
|
|
|
120
154
|
ADULT_CAUTIOUS_POLICY = "\n".join(
|
|
121
155
|
[
|
|
122
156
|
"## Risk Categories",
|
|
123
|
-
"### A. Illegal
|
|
157
|
+
"### A. Illegal Sexual Content",
|
|
124
158
|
_MINORS,
|
|
125
159
|
" - Nude or sexual content where any person's age is unclear and they could be under 18.",
|
|
126
160
|
_NON_CONSENT,
|
|
@@ -136,9 +170,47 @@ ADULT_CAUTIOUS_POLICY = "\n".join(
|
|
|
136
170
|
"sexual content where every person clearly appears to be an adult."
|
|
137
171
|
),
|
|
138
172
|
(
|
|
139
|
-
' - "Adult Content (Non-criminal)" is Safe under this policy: legal
|
|
140
|
-
"where every person clearly appears to be an adult
|
|
173
|
+
' - "Adult Content (Non-criminal)" is Safe under this policy: legal adult entertainment '
|
|
174
|
+
"and legal nudity and sexual content where every person clearly appears to be an adult, "
|
|
175
|
+
"including commercial porn, explicit sex, genitalia, a suggestive pose, a commercial "
|
|
176
|
+
"or studio set, and an adult-site watermark."
|
|
177
|
+
),
|
|
178
|
+
(
|
|
179
|
+
" - If no risk-category bullet matches, the verdict is Safe. Do not assign a risk letter "
|
|
180
|
+
"because it is the closest heading."
|
|
181
|
+
),
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# ADULT_POLICY with no crimes heading at B: the rules after it move up a
|
|
186
|
+
# letter, and G is how-to / real targeting only. Depicted crime is named under
|
|
187
|
+
# Safe, and adult entertainment stays Safe the same way as under adult.
|
|
188
|
+
# Letters must stay contiguous: with a gap at B the model renumbered the rules
|
|
189
|
+
# in its reasoning, so verdicts named the wrong rule and some changed. Compared
|
|
190
|
+
# against adult; do not fold this into granular (that mix of lettering and
|
|
191
|
+
# crime permissiveness is issue #24).
|
|
192
|
+
ADULT_CINEMATIC_POLICY = "\n".join(
|
|
193
|
+
[
|
|
194
|
+
"## Risk Categories",
|
|
195
|
+
"### A. Illegal Sexual Content",
|
|
196
|
+
_MINORS,
|
|
197
|
+
_NON_CONSENT,
|
|
198
|
+
_EXTREME,
|
|
199
|
+
*(_one_letter_up(line) for line in _OTHER_AFTER_B),
|
|
200
|
+
*_CRIME_CINEMATIC,
|
|
201
|
+
"### Safe",
|
|
202
|
+
*_ADULT_SAFE,
|
|
203
|
+
(
|
|
204
|
+
" - Legal adult entertainment is Safe under this policy, the same as under adult: "
|
|
205
|
+
"commercial porn, studio stills, explicit sex, and an adult-site watermark."
|
|
206
|
+
),
|
|
207
|
+
(
|
|
208
|
+
" - A depiction of crime or violence is Safe, as in film, television, games, and print, and it "
|
|
209
|
+
"needs no sign that it is fiction: a robbery, a heist, a kidnapping, a person bound or held "
|
|
210
|
+
"captive, theft, fraud, corrupt officials, an armed standoff, a police raid, a fight, a battle, "
|
|
211
|
+
"weapons, drugs, and war."
|
|
141
212
|
),
|
|
213
|
+
" - That stops being Safe when it is sexual, shows gore or dismemberment, or teaches how to do it.",
|
|
142
214
|
]
|
|
143
215
|
)
|
|
144
216
|
|
|
@@ -147,16 +219,19 @@ _TEXT: dict[str, str | None] = {
|
|
|
147
219
|
"adult": ADULT_POLICY,
|
|
148
220
|
"adult-granular": ADULT_GRANULAR_POLICY,
|
|
149
221
|
"adult-cautious": ADULT_CAUTIOUS_POLICY,
|
|
222
|
+
"adult-cinematic": ADULT_CINEMATIC_POLICY,
|
|
150
223
|
}
|
|
151
224
|
|
|
152
225
|
# Rule letters that mean illegal content. "default" has none: its A covers
|
|
153
|
-
# consensual adult nudity too, so it cannot tell illegal from adult. Crime
|
|
154
|
-
#
|
|
226
|
+
# consensual adult nudity too, so it cannot tell illegal from adult. Crime and
|
|
227
|
+
# animal abuse depict, they are not illegal content, under every policy, whatever
|
|
228
|
+
# their letters: B and G, or G and F under adult-cinematic.
|
|
155
229
|
_ILLEGAL: dict[str, frozenset[str]] = {
|
|
156
230
|
"default": frozenset(),
|
|
157
231
|
"adult": frozenset({"A"}),
|
|
158
232
|
"adult-granular": frozenset({"A", "H", "I"}),
|
|
159
233
|
"adult-cautious": frozenset({"A"}),
|
|
234
|
+
"adult-cinematic": frozenset({"A"}),
|
|
160
235
|
}
|
|
161
236
|
|
|
162
237
|
|
|
@@ -8,7 +8,13 @@ from argus_cortex.moderation import ModerationConfigError, SingGuardClient, poli
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def test_every_policy_has_a_text_and_an_illegal_table() -> None:
|
|
11
|
-
assert policies.POLICIES == (
|
|
11
|
+
assert policies.POLICIES == (
|
|
12
|
+
"default",
|
|
13
|
+
"adult",
|
|
14
|
+
"adult-granular",
|
|
15
|
+
"adult-cautious",
|
|
16
|
+
"adult-cinematic",
|
|
17
|
+
)
|
|
12
18
|
assert policies.policy_text("default") is None # SingGuard's own rules: nothing is sent
|
|
13
19
|
for name in policies.POLICIES[1:]:
|
|
14
20
|
text = policies.policy_text(name)
|
|
@@ -22,6 +28,7 @@ def test_illegal_rules_are_per_policy() -> None:
|
|
|
22
28
|
assert policies.illegal_rules("default") == frozenset() # its A is all sexual content, adult included
|
|
23
29
|
assert policies.illegal_rules("adult-granular") == {"A", "H", "I"}
|
|
24
30
|
assert policies.illegal_rules("adult") == policies.illegal_rules("adult-cautious") == {"A"}
|
|
31
|
+
assert policies.illegal_rules("adult-cinematic") == {"A"}
|
|
25
32
|
# Each illegal letter is a heading in that policy's own text.
|
|
26
33
|
for name in policies.POLICIES[1:]:
|
|
27
34
|
for letter in policies.illegal_rules(name):
|
|
@@ -31,18 +38,63 @@ def test_illegal_rules_are_per_policy() -> None:
|
|
|
31
38
|
def test_is_illegal_reads_a_rule_as_verdicts_report_it() -> None:
|
|
32
39
|
assert policies.is_illegal("adult-granular", "H. Non-Consensual Sexual Content")
|
|
33
40
|
assert policies.is_illegal("adult-granular", "I") and policies.is_illegal("adult-granular", "a.")
|
|
34
|
-
assert policies.is_illegal("adult", "A. Illegal
|
|
41
|
+
assert policies.is_illegal("adult", "A. Illegal Sexual Content")
|
|
42
|
+
assert policies.is_illegal("adult", "A. Illegal or Extreme Sexual Content") # an older recorded title
|
|
35
43
|
assert not policies.is_illegal("default", "A. Sexual Content Risk")
|
|
36
44
|
for name in policies.POLICIES: # crime and animal abuse go to review, never deletion
|
|
37
45
|
assert not policies.is_illegal(name, "B. Real-World Crimes & Public Safety")
|
|
38
46
|
assert not policies.is_illegal(name, "G. Animal Abuse")
|
|
39
47
|
assert not policies.is_illegal(name, "Safe")
|
|
48
|
+
# adult-cinematic's own letters: crime is G and animal abuse F.
|
|
49
|
+
assert not policies.is_illegal("adult-cinematic", "G. Criminal Instructions")
|
|
50
|
+
assert not policies.is_illegal("adult-cinematic", "F. Animal Abuse")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_adult_safe_names_explicit_commercial_content() -> None:
|
|
54
|
+
for name in ("adult", "adult-granular", "adult-cautious", "adult-cinematic"):
|
|
55
|
+
text = policies.policy_text(name)
|
|
56
|
+
risks, safe = text.split("### Safe")
|
|
57
|
+
assert "Extreme Content" not in risks
|
|
58
|
+
assert "legal adult entertainment" in safe
|
|
59
|
+
assert "commercial porn" in safe
|
|
60
|
+
assert "adult-site watermark" in safe
|
|
61
|
+
assert "closest heading" in safe
|
|
62
|
+
assert "is not this rule" not in risks
|
|
63
|
+
assert "between adults" not in text
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_adult_cinematic_narrows_crime_and_keeps_adult_entertainment_safe() -> None:
|
|
67
|
+
text = policies.policy_text("adult-cinematic")
|
|
68
|
+
risks, safe = text.split("### Safe")
|
|
69
|
+
assert "how to commit one" in risks
|
|
70
|
+
assert "violent crime, weapons, other crimes" not in risks
|
|
71
|
+
assert "needs no sign that it is fiction" in safe
|
|
72
|
+
assert "a kidnapping" in safe and "drugs" in safe and "war" in safe
|
|
73
|
+
assert "stops being Safe when it is sexual, shows gore or dismemberment" in safe
|
|
74
|
+
assert "Legal adult entertainment is Safe under this policy, the same as under adult" in safe
|
|
75
|
+
assert "between adults" not in text
|
|
76
|
+
assert policies.policy_text("adult-cinematic") != policies.policy_text("adult")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_adult_cinematic_letters_have_no_gap() -> None:
|
|
80
|
+
# With a gap the model renumbers the rules as it reasons, and verdicts name the wrong one.
|
|
81
|
+
headings = [line[4:] for line in policies.policy_text("adult-cinematic").splitlines() if line.startswith("### ")]
|
|
82
|
+
assert headings == [
|
|
83
|
+
"A. Illegal Sexual Content",
|
|
84
|
+
"B. Unethical Behavior",
|
|
85
|
+
"C. Cybersecurity & Information Manipulation",
|
|
86
|
+
"D. Agent Safety",
|
|
87
|
+
"E. Politically Sensitive Content",
|
|
88
|
+
"F. Animal Abuse",
|
|
89
|
+
"G. Criminal Instructions",
|
|
90
|
+
"Safe",
|
|
91
|
+
]
|
|
40
92
|
|
|
41
93
|
|
|
42
94
|
def test_mode_is_by_name() -> None:
|
|
43
95
|
assert policies.mode("default") == "sfw"
|
|
44
96
|
assert policies.mode("sfw-strict") == "sfw" # a later SFW policy needs no table entry
|
|
45
|
-
assert [policies.mode(n) for n in policies.POLICIES[1:]] == ["nsfw"] *
|
|
97
|
+
assert [policies.mode(n) for n in policies.POLICIES[1:]] == ["nsfw"] * 4
|
|
46
98
|
assert policies.mode("defaults") == "nsfw"
|
|
47
99
|
|
|
48
100
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|