Skip to content

Add jl4-service NixOS module with Thailand cosmetics orchestrator#849

Merged
mengwong merged 12 commits intomainfrom
mengwong/nix-service
Mar 16, 2026
Merged

Add jl4-service NixOS module with Thailand cosmetics orchestrator#849
mengwong merged 12 commits intomainfrom
mengwong/nix-service

Conversation

@mengwong
Copy link
Copy Markdown
Contributor

Summary

  • jl4-service NixOS module: Multi-tenant L4 deployment service with bundle store architecture
  • Thailand cosmetics orchestrator: Claude API integration for evaluating cosmetic advertising claims against Thai regulations (Cosmetics Act BE 2558, Manual BE 2567)
  • Classic examples bundle: Extracted hardcoded examples (britishcitizen5, parking, vermin_and_rodent) to .l4 files
  • AWS networking fix: Switch from dhcpcd to systemd-networkd for more reliable DNS management

Key Features

jl4-service

  • Pre-seeds bundles from jl4/experiments/ at startup
  • CBOR caching for fast restarts
  • Security hardening with DynamicUser, ProtectSystem, etc.
  • Optional EnvironmentFile for secrets (ANTHROPIC_API_KEY)

Thailand Cosmetics Orchestrator

  • 51 regulatory test prompts across 3 tiers
  • Category detection (23 cosmetic categories)
  • Calls Claude API for claim evaluation
  • Returns structured compliance decisions with violations and confidence scores

Test plan

  • Deployed to staging (dev.jl4.legalese.com)
  • Verified /service/deployments returns classic and thailand-cosmetics
  • Tested evaluateClaim with violating claim - correctly returns PROHIBITED with 9 violations
  • Verified Claude API integration works (76s response time = real API calls)

🤖 Generated with Claude Code

mengwong and others added 12 commits March 13, 2026 16:43
Bring up jl4-service (the multi-tenant successor to jl4-decision-service)
as a NixOS-deployed service with nginx reverse proxy at /service/.

New jl4-service Nix module features:
- Systemd service with DynamicUser sandboxing
- Pre-seed mechanism: bundles option copies L4 files into the
  persistent store on first boot, compiled eagerly on startup
- CBOR cache for fast restarts after initial compilation

Thailand cosmetics Article 41 tribrid ruleset:
- Three-tier evaluation (category-specific, manual-specific, core Act)
- 26 product categories with Do/Don't rules from the Manual (BE 2567)
- @export annotations on evaluate sub-claim (default) and
  evaluate full statement for decision service discovery
- Prelude symlink for import resolution

Also adds thailand-cosmetics to jl4-decision-service sourcePaths
(Dockerfile, dev-start.sh, docker-compose, Nix config) for the
legacy service path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pre-seeds the classic L4 examples alongside thailand-cosmetics so
jl4-service boots with the same functions available as the legacy
jl4-decision-service.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The jl4-decision-service had compute_qualifies, vermin_and_rodent,
and the_answer as inline Haskell string literals in Examples.hs.
Extract them as proper .l4 files with @export annotations so
jl4-service can discover and serve them from the classic bundle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This change fixes the resolvconf signature mismatch issue that caused
network-setup.service to fail after manual DNS edits. systemd-networkd
with systemd-resolved provides more predictable DNS management and
avoids the fragile resolvconf signature checking.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The service was failing with status=226/NAMESPACE because systemd tried
to mount /var/lib/private/jl4-service/store before the preseed script
could create it. With DynamicUser=true, StateDirectory already provides
write access to the state directory and all subdirectories, making
ReadWritePaths redundant and problematic.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…osmetics

Replace the static tribrid rule engine with the full orchestrator that calls
Claude API for category detection and predicate evaluation, then runs the
three-tier regulatory logic. Adds EnvironmentFile support for ANTHROPIC_API_KEY
and nye SSH key for staging access.

Bundle now includes: orchestrator.l4, anthropicClient.l4, promptLibrary.l4
(51 prompts across 3 tiers).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "-" prefix to EnvironmentFile path so systemd continues startup
even if the .env file doesn't exist yet. This prevents deployment
failures on fresh systems while still loading secrets when present.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The orchestrator makes ~20 sequential Claude API calls which exceeds
the default 60s timeout. Also fix nginx proxy_read_timeout to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ache

- Replace prelude.l4 symlink with actual file so nix store derivation
  includes the real content (symlinks break in sandboxed builds)
- Add SHA-256 hash check to pre-seed script: when source files change
  between deploys, the old bundle (including CBOR cache) is wiped and
  re-seeded, ensuring the service always compiles the latest sources

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The orchestrator expects JSON: {"verdict": true, "confidence": 85, "reasoning": "..."}
But prompts were asking for text: "YES/NO, Confidence: X%, Reasoning: ..."

This caused JSONDECODE to fail, returning LEFT err, which isViolation
treated as FALSE - making all tests appear to pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mengwong mengwong merged commit 14729c8 into main Mar 16, 2026
5 checks passed
@mengwong mengwong deleted the mengwong/nix-service branch March 16, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant