argus-cortex 0.4.5__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.
Files changed (47) hide show
  1. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/.github/workflows/release.yml +51 -0
  2. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/CHANGELOG.md +22 -1
  3. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/PKG-INFO +1 -1
  4. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/_version.py +2 -2
  5. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/moderation/policies.py +29 -14
  6. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_policies.py +21 -1
  7. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/.copier-answers.yml +0 -0
  8. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/.env.example +0 -0
  9. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/.github/workflows/ci.yml +0 -0
  10. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/.gitignore +0 -0
  11. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/CLAUDE.md +0 -0
  12. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/LICENSE +0 -0
  13. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/Makefile +0 -0
  14. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/README.md +0 -0
  15. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/pyproject.toml +0 -0
  16. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/__init__.py +0 -0
  17. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/backends.py +0 -0
  18. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/gpu.py +0 -0
  19. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/moderation/__init__.py +0 -0
  20. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/moderation/_http.py +0 -0
  21. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/moderation/nudenet.py +0 -0
  22. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/moderation/singguard.py +0 -0
  23. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/py.typed +0 -0
  24. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/server.py +0 -0
  25. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/store/__init__.py +0 -0
  26. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/store/blob.py +0 -0
  27. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/store/config.py +0 -0
  28. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/store/errors.py +0 -0
  29. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/store/lineage.py +0 -0
  30. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/store/models.py +0 -0
  31. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/store/schema.py +0 -0
  32. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/store/vector.py +0 -0
  33. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/taxonomy.py +0 -0
  34. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/wire.py +0 -0
  35. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/src/argus_cortex/xmp.py +0 -0
  36. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_backends.py +0 -0
  37. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_blob.py +0 -0
  38. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_gpu.py +0 -0
  39. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_moderation.py +0 -0
  40. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_server.py +0 -0
  41. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_smoke.py +0 -0
  42. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_store.py +0 -0
  43. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_store_pg_integration.py +0 -0
  44. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_taxonomy.py +0 -0
  45. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_vector.py +0 -0
  46. {argus_cortex-0.4.5 → argus_cortex-0.4.6}/tests/test_wire.py +0 -0
  47. {argus_cortex-0.4.5 → 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,26 @@ 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
+
10
30
  ## [0.4.5] - 2026-10-31
11
31
 
12
32
  ### Added
@@ -195,7 +215,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
195
215
  (`LocalBackend`, `RemoteBackend`, `RemoteProvider`, `with_retries`
196
216
  retrying only transient errors, `resolve_device`).
197
217
 
198
- [Unreleased]: https://github.com/smk762/argus-cortex/compare/v0.4.5...HEAD
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
199
220
  [0.4.5]: https://github.com/smk762/argus-cortex/compare/v0.4.4...v0.4.5
200
221
  [0.4.4]: https://github.com/smk762/argus-cortex/compare/v0.4.3...v0.4.4
201
222
  [0.4.3]: https://github.com/smk762/argus-cortex/compare/v0.4.2...v0.4.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: argus-cortex
3
- Version: 0.4.5
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.5'
22
- __version_tuple__ = version_tuple = (0, 4, 5)
21
+ __version__ = version = '0.4.6'
22
+ __version_tuple__ = version_tuple = (0, 4, 6)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -37,7 +37,7 @@ POLICIES: tuple[PolicyName, ...] = (
37
37
  )
38
38
  SFW_PREFIX = "sfw-"
39
39
 
40
- # Rules B to G, shared by every adult policy except cinematic B: the same
40
+ # Rules B to G, shared by every adult policy except cinematic: the same
41
41
  # letters and areas as SingGuard's defaults. They are our wording, so their
42
42
  # verdicts can differ from the built-ins.
43
43
  _CRIME_B = [
@@ -45,8 +45,10 @@ _CRIME_B = [
45
45
  " - Content involving violent crime, weapons, other crimes, or public-safety threats.",
46
46
  ]
47
47
  # Depiction is not a hit; how-to, a named real target, and operational threats are.
48
- _CRIME_B_CINEMATIC = [
49
- "### B. Real-World Crimes & Public Safety",
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",
50
52
  (
51
53
  " - Instructional or operational assistance for a real-world crime: how to commit one, "
52
54
  "targeting a named real person or a real institution, or an operational public-safety threat."
@@ -69,6 +71,13 @@ _OTHER_AFTER_B = [
69
71
  " - Content involving cruelty to animals or the spread of animal abuse.",
70
72
  ]
71
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
+
72
81
  _MINORS = " - Sexual content involving anyone under 18, or anyone who appears to be a minor."
73
82
  _NON_CONSENT = (
74
83
  " - Non-consensual sexual content, including hidden recording, sexual images of a real "
@@ -173,10 +182,13 @@ ADULT_CAUTIOUS_POLICY = "\n".join(
173
182
  ]
174
183
  )
175
184
 
176
- # ADULT_POLICY with B narrowed to how-to / real targeting, and cinematic
177
- # crime named under Safe. Adult entertainment stays Safe the same way as
178
- # under adult. Compared against adult; do not fold this into granular
179
- # (that mix of lettering and B permissiveness is issue #24).
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).
180
192
  ADULT_CINEMATIC_POLICY = "\n".join(
181
193
  [
182
194
  "## Risk Categories",
@@ -184,8 +196,8 @@ ADULT_CINEMATIC_POLICY = "\n".join(
184
196
  _MINORS,
185
197
  _NON_CONSENT,
186
198
  _EXTREME,
187
- *_CRIME_B_CINEMATIC,
188
- *_OTHER_AFTER_B,
199
+ *(_one_letter_up(line) for line in _OTHER_AFTER_B),
200
+ *_CRIME_CINEMATIC,
189
201
  "### Safe",
190
202
  *_ADULT_SAFE,
191
203
  (
@@ -193,10 +205,12 @@ ADULT_CINEMATIC_POLICY = "\n".join(
193
205
  "commercial porn, studio stills, explicit sex, and an adult-site watermark."
194
206
  ),
195
207
  (
196
- " - Fictional or cinematic crime as a story or still is Safe: a bank robbery, a heist, "
197
- "fraud such as stealing pensions, institutional corruption such as dirty cops, "
198
- "non-sexual violence, weapons shown as a prop, drugs as a plot, and war as a setting."
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."
199
212
  ),
213
+ " - That stops being Safe when it is sexual, shows gore or dismemberment, or teaches how to do it.",
200
214
  ]
201
215
  )
202
216
 
@@ -209,8 +223,9 @@ _TEXT: dict[str, str | None] = {
209
223
  }
210
224
 
211
225
  # Rule letters that mean illegal content. "default" has none: its A covers
212
- # consensual adult nudity too, so it cannot tell illegal from adult. Crime (B)
213
- # and animal abuse (G) depict, they are not illegal content, under every policy.
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.
214
229
  _ILLEGAL: dict[str, frozenset[str]] = {
215
230
  "default": frozenset(),
216
231
  "adult": frozenset({"A"}),
@@ -45,6 +45,9 @@ def test_is_illegal_reads_a_rule_as_verdicts_report_it() -> None:
45
45
  assert not policies.is_illegal(name, "B. Real-World Crimes & Public Safety")
46
46
  assert not policies.is_illegal(name, "G. Animal Abuse")
47
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")
48
51
 
49
52
 
50
53
  def test_adult_safe_names_explicit_commercial_content() -> None:
@@ -65,12 +68,29 @@ def test_adult_cinematic_narrows_crime_and_keeps_adult_entertainment_safe() -> N
65
68
  risks, safe = text.split("### Safe")
66
69
  assert "how to commit one" in risks
67
70
  assert "violent crime, weapons, other crimes" not in risks
68
- assert "bank robbery" in safe and "stealing pensions" in safe and "dirty cops" in safe
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
69
74
  assert "Legal adult entertainment is Safe under this policy, the same as under adult" in safe
70
75
  assert "between adults" not in text
71
76
  assert policies.policy_text("adult-cinematic") != policies.policy_text("adult")
72
77
 
73
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
+ ]
92
+
93
+
74
94
  def test_mode_is_by_name() -> None:
75
95
  assert policies.mode("default") == "sfw"
76
96
  assert policies.mode("sfw-strict") == "sfw" # a later SFW policy needs no table entry
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes