Skip to content

ARAIL: DaC World-skill mount (Sprint 2) + world-forge-doc (baseline; supersedes #98)#99

Merged
cdarnell merged 88 commits into
mainfrom
qukaizen/arail-dac-world-mount
Jun 28, 2026
Merged

ARAIL: DaC World-skill mount (Sprint 2) + world-forge-doc (baseline; supersedes #98)#99
cdarnell merged 88 commits into
mainfrom
qukaizen/arail-dac-world-mount

Conversation

@cdarnell

Copy link
Copy Markdown
Owner

Baseline merge of qukaizen/arail-dac-world-mount → main. Contains the world-forge-doc work (PR #98) plus Sprint 2, so this supersedes #98.

Sprint 2 — DaC World-skill mount

A mounted DaC World's governed SKILL.md now reaches the agents' system prompt (Buddy + Researcher), so ARAIL learns the mounted domain's knowledge — not just its identity.

  • world_mount.py: stage SKILL.md at mount; skills_loader.py: load_world_skill + _contain_skill_body (defense-in-depth — mounted content treated as untrusted DATA; forged prompt structure neutralized).
  • Wired into Buddy _compose_prompt + Researcher _get_system_context; face.json WORLD FRAMING (identity) stays distinct from the glossary (knowledge).
  • QA: 29 adversarial tests (homoglyph/DoS/legit-shaped-source) — structural containment held. Architect review caught + fixed ###-header mangling and a 22% glossary truncation.
  • Follow-up: promote SKILL.md to SEALED (cross-repo with DaC); clean-machine full-suite + ./arailctl CLI smoke.

Also included

🤖 Generated with Claude Code

cdarnell and others added 30 commits May 25, 2026 16:24
ARAIL now pulls versioned aeroLLM wheels from the self-hosted index
(pypi.qukaizen.com) for non-dev installs, while still building from the local
sibling repo when it's present (active dev). Follows the CI/CD plan.

- build-aerollm.sh: `auto` (sibling→cargo, else→pip from index), `update`
  (pip --upgrade), and version reporting in `status`. Wheels are macOS-arm64-only;
  off-Mac pip reports "no matching distribution" gracefully.
- arailctl: `deep update` verb (rebuild | update | status).
- setup.sh: installs via `build-aerollm.sh auto`; reads aerollm pin + index URL
  from pyproject [tool.arail.package-sources].
- pyproject: aerollm = "aerollm-api>=0.1,<0.2" + aerollm_index.
- backends.py: AeroLLMBackend captures aerollm_api.__version__ for status/health.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause: buildBody() in chat.html set request.backend via pickBackend(),
which returns undefined for activeSource='my_machine'. With runtime='aerollm'
but backend=undefined, the server's deep-path gate (wants_deep =
optional_backend_name in _OPTIONAL_CHAT_BACKEND_CONFIG) never fired, and the
runtime-override path only honored ('ollama','mlx-openai'). Box B prompts
silently dropped into the primary Ollama router asking for the MLX dir name
'Qwen2.5-7B-Instruct-4bit', which Ollama 404'd. AeroLLMBackend was never
invoked — exactly matching the user-visible 'no prompts forwarded to aeroLLM'
report.

Secondary bug surfaced during verification: _is_aerollm_installed() checked
importlib.util.find_spec('aerollm'), but the PyO3 wheel publishes as
'aerollm_api' (the module AeroLLMBackend imports at runtime). So even when
inference worked, deep.installed was reported False to the chat tab,
disqualifying Box B from auto-open.

Changes:
  1. chat.html buildBody(): translate runtime='aerollm'/'airllm' into
     backend=runtime, mirroring the trick loadModel() already used at warm time.
  2. app.py _is_aerollm_installed(): check 'aerollm_api', not 'aerollm'.
  3. pyproject.toml + scripts/setup.sh: lower aerollm_maximus default from
     Qwen2.5-72B-Instruct-4bit (~40 GB, weights not shipped, OOMs <48 GB) to
     Qwen2.5-7B-Instruct-4bit so a fresh clone works out of the box.
     Operators upgrade locally via AEROLLM_MODEL in .env after downloading
     larger weights — recipe in the pyproject comment.
  4. scripts/setup.sh: alias legacy 'ai-engineer:latest' → 'ai-eng:latest' via
     'ollama cp' (idempotent, no re-download) when the new name is missing;
     warn at setup time if AEROLLM_MODEL weights are absent with the exact
     huggingface-cli command to fix.
  5. chat.html init(): auto-warm Box A on chat open; on maximus when a deep
     backend resolves, also auto-open and warm Box B. Persisted opt-out via
     localStorage so closing Box B sticks across reloads. Skips Box B auto-warm
     when free_gb < 6 to honor the host's OOM history.
  6. chat.html loadModel(): parse NDJSON for {type:'error'} / final.error and
     surface them; no more false-positive WARM chips on a failed warm.
  7. app.py: new _resolve_chat_deep_default() — accept both
     LAB_CHAT_DEEP_DEFAULT and ARAIL_CHAT_DEEP_DEFAULT env names; default to
     ON on maximus when aeroLLM resolves. New _resilient_chat_default() —
     if MODEL_NAME points to an uninstalled model, fall back to the best
     installed ai-eng-family match.
  8. tests/test_aerollm_tier_resolution.py: updated to lock maximus=7B as the
     new policy while preserving the structural Bug 1 (MIN_ID stomp) and
     Bug 2 (capture_tier case-block) guards from sprint 2026-05-20.

Verified end-to-end: direct curl POST /api/chat/stream with backend=aerollm
returns a streamed token sequence from AeroLLMBackend (~16 s cold start,
fast thereafter). 14/14 tier-resolution tests pass.

Note: chat.html also carries a small uncommitted markdown-render hunk that
predated this work on the same branch (the branch's nominal scope); it
ships together here because it co-existed in the file. Other in-flight WIP
(prompt-caching, cache_prewarm) is intentionally left uncommitted on the
working tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…budget sprint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…re function

Adds four new constants (_AEROLLM_KV_MIN_FLOOR_BYTES, _AEROLLM_KV_SAFETY_HEADROOM_BYTES,
_AEROLLM_KV_AVAILABLE_FRACTION, _AEROLLM_KV_PCT_DEFAULT) and _resolve_kv_budget(), a pure
function that computes the available-RAM-aware KV cache budget. Formula:
  budget = max(MIN_FLOOR, min(total*pct, available*0.85 - 1.5GiB))
Returns {budget_bytes, reason, fields} with source in {env,default,floor,unavailable}.
Never raises; wraps all fallible psutil calls in try/except.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e_kv_budget()

Covers all behavior matrix rows from ARCHITECTURE.md: default pct, env override, floor
clamp, invalid env fallback (zero/garbage/above-one), psutil import error, psutil call
error, total=0 defensive case, int type assertion, and 16GiB idle-box regression.
All 12 tests pass. Uses monkeypatch for env and unittest.mock.patch for psutil.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lazy-imports arail.activity_log inside the method to avoid circular-import
risk. Emits at "warn" when source in {floor, unavailable}, "info" otherwise.
Category="system". Swallows both ImportError (headless test harness) and any
exception from emit() so backend construction is never gated on logging.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…get() + _emit_budget_activity()

Replaces the old total*pct-only computation with the available-RAM-aware resolver.
The resolver always runs (not just when env var is set); aerollm now defaults to
available*0.85-1.5GiB capped by total*0.60 rather than total*0.60 uncapped.
kv_memory_budget is set only when budget_bytes is non-None (psutil unavailable
→ aerollm auto-detects its own 80%). Emit call follows resolver inside first-init
branch, never on singleton reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t wiring

Test 12: _emit_budget_activity fires exactly once on first construction; singleton
reuse does not re-emit. Test 13a: rt_kwargs['kv_memory_budget'] is the int from
_resolve_kv_budget. Test 13b: kwarg is absent when resolver returns None (psutil
unavailable path). Uses fake aerollm_api injected via sys.modules; clears
AeroLLMBackend._shared between tests via autouse fixture.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lint warnings

All steps complete. 15 new tests passing (12 unit + 3 integration).
Regression 14/14. No deviations from ARCHITECTURE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t_activity

Finding A: `from arail import activity_log` → `from arail.activity import activity_log`
Finding B: `emit(level=..., category=..., message=...)` → `emit("aerollm", reason, level=level)`
Finding D (recommended): narrow bare `except Exception: pass` to log via `_log.warning`
  so future signature/import drift surfaces in uvicorn output.
Add `import logging` + `_log = logging.getLogger(__name__)` at module level (first use).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (tests 14a+14b)

Tests 14a and 14b let _emit_budget_activity run for real (not patched out)
and assert on a patched arail.activity.activity_log.emit.

14a: source="default" → emit called with ("aerollm", <msg containing "KV budget resolved">, level="info")
14b: source="floor"   → emit called with ("aerollm", <msg>, level="warn")

Verify loop confirmed: with the pre-fix broken import (`from arail import activity_log`)
mock_emit is never called, so assert_called_once() would have failed — the test is
a meaningful guard against Findings A and B recurring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records the two post-BLOCK commits (9a1c95b, e630c5d), the import +
signature fixes, verify-then-fix loop evidence, and updated test counts
(17 router + 14 regression). Closes the BLOCK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 26 tests covering boundaries the unit suite did not exercise:

- Boundary arithmetic: tie between ceil_total/ceil_available, raw==floor
  (strict-less-than guard), negative ceil_available, available>total
  (container quirk), tiny (4 GiB) box, huge (512 GiB) box.
- Env-var parsing edges: whitespace stripping (4 variants), exotic
  numerics (0.5e0, .5, 5e-1, +0.5), locale comma fallback, very small
  valid pct, exact 0.0 and 1.0 boundary fallbacks.
- Emit coverage for all four source values (default/env/floor/
  unavailable) with correct info/warn level routing.
- Singleton idempotency: identical key → resolve+emit once;
  distinct AEROLLM_MODEL → both fire (per-model keying).
- Logger-warn fallback: activity_log.emit raises → _log.warning fires
  and exception does not propagate (REVIEW.md Finding D guard).
- psutil attribute-failure path: import succeeds but virtual_memory
  attr access raises AttributeError (complements test 8 ImportError).

All 26 new tests pass; full router+tier suite green (57 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on OOM-safety)

57 tests pass (12 unit + 5 integration + 14 tier regression + 26 new
QA edges). No failures, no security findings. Live activity-feed
acceptance test deferred: available RAM was 8 GiB at QA time and the
user has documented OOM history on this box; behavior is fully covered
by tests 14a/14b + new all-sources parametrized emit test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eferred ai-eng collapse (tag 404), qwen attribution to NOTICE

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ribution

Supersedes the 'wait for ollama.ai qukaizen/ tag' deferral. User verified the
tag 404s and chose to self-host. Designs HF-primary (ollama pull hf.co/...) +
GitHub-release mirror + optional qukaizen.com, package_ai_eng.sh scaffold,
self-hosted setup pull ladder with sha256 fail-closed verification, and a
preview-net last resort kept until the GGUF is uploaded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…icense research

Confirmed Qwen2.5-3B-Instruct license is Qwen Research License (not Apache-2.0).
Qwen2.5-7B-Instruct (preview base) is Apache-2.0. Both require attribution.
Plan: 7 steps per ARCHITECTURE.md REVISED v2 implementation order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bution

Qwen2.5-3B-Instruct (production base) is under the Qwen Research License
(not Apache-2.0). Qwen2.5-7B-Instruct (preview base) is Apache-2.0.
NOTICE mandates that the redistributed ai-eng GGUF artifact (HF model card,
GitHub Release) carries this same attribution — the base license obligations
travel with the derivative.
LICENSE gets a one-line pointer to NOTICE for bundled/redistributed weights.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sentinel

Sentinel rollout per ARCHITECTURE.md § Part 1. Files changed:
  - pyproject.toml: airllm_minimalist/maximus/airllm → sentinel + TODO comment;
    add self-hosted keys (ai_eng_hf_repo/quant/gh_url/cdn_url/sha256/preview)
    with __PLACEHOLDER__ markers; maximus tier description honest-framing rewrite.
  - scripts/setup.sh:69-71 + 986-989: AIRLLM_MODEL_ID vars → sentinel.
  - src/arail/router/backends.py: AIRLLM_MODEL default → sentinel; sentinel guard
    raises RuntimeError with configure-notice before any load.
  - src/arail/router/airllm_worker.py: same guard pattern.
  - src/arail/portal/app.py: all AIRLLM_MODEL/default_model Llama-3.1-70B
    literals → sentinel; AirLLM description + download hint updated; sentinel
    gated_hint added to error result path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… add check_ai_eng_artifact.sh

install_models() now runs: HF primary (ollama pull hf.co/<repo>:<quant>) →
GitHub Release mirror (sha256-verified curl + ollama create) →
optional CDN mirror → last-resort preview net (qwen2.5:7b + Modelfile.preview).

Supply-chain: mirror paths fail-closed on __PLACEHOLDER_SHA256__ or digest
mismatch — no ollama create from unverified blob. All hosts/quant/digest are
env-overridable. Neutral wording in info lines (no qwen lineage in narrative).
Idempotency, ai-engineer alias migration, ARAIL_SKIP_OLLAMA guard preserved.

check_ai_eng_artifact.sh: probes HF + GitHub for live GGUF (HEAD request,
8s timeout). Exit 0 = live; exit 1 = not yet uploaded. Gates follow-up
ticket 2b (delete Modelfile.preview once artifact confirmed live).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…→upload scaffold

Developer-side tool; never runs during setup. Given --base-dir + --lora-dir:
  1. Merges LoRA into qwen2.5:3b base (via build_ai_eng.py or peft inline)
  2. Converts merged model to GGUF via llama.cpp convert_hf_to_gguf.py + quantize
  3. Emits Modelfile + NOTICE next to the GGUF (NOTICE travels with artifact)
  4. Prints sha256 of the GGUF + exact upload commands for HF/GitHub/CDN
     as # TODO(manual): blocks the user uncomments and runs

No credentials embedded. No weights invented or downloaded.
Missing --base-dir or --lora-dir → prints manual steps, exits 1.
Upload steps require `huggingface-cli login` / `gh auth login` by the user.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… placeholder row

models/ai-eng/Modelfile.preview: keep FROM qwen2.5:7b (required for preview net);
strip qwen self-description from SYSTEM — now a neutral ai-eng persona description.

src/arail/chat/models_catalog.yaml:
  - ai-eng entry: strip qwen fallback sentence; set install to
    `ollama pull hf.co/qukaizen/ai-eng-3b-gguf:Q4_K_M` (self-hosted
    single pull, __PLACEHOLDER__ repo); clean description (no lineage mention).
  - Add new deep-model placeholder row (tier: flagship, install: "")
    pointing operators to .env + docs; no real model id, no download.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ANGELOG

README.md: maximus row — "Frontier-scale local inference, full bench" →
  "The full bench. The heaviest model that runs well on your machine —
  with cloud frontier models one click away in the Chat Compute Source."

CLAUDE.md: maximus tier line → "full local bench, cloud frontier one click
  away"; qwen fallback prose → describes self-hosted pull + preview fallback.

tuning.html:111: "Run frontier-scale models locally" → "Run the heaviest
  models your silicon handles well — … Frontier-scale models are one click
  away in the cloud."

docs/INSTALL.md: rewrite setup ai-eng pull narrative — self-hosted ladder
  (HF primary → GitHub mirror w/ sha256 verify → CDN → preview net).

CHANGELOG.md: add [Unreleased] section — sentinel rollout, honest framing,
  self-hosted ai-eng distribution, package_ai_eng.sh, check script, NOTICE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The replacement phrase 'Frontier-scale models are one click away' still
matched the grep gate. Reworded to 'Cloud frontier models are one click
away via the Compute Source.' All four gated surfaces now clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…A gates, final state

All 8 steps complete. QA grep gates documented and passing. Test suite
regression comparison recorded (no new failures). Declared done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uards

Setup-ladder mock tests (tests/setup_ladder/): exercise the self-hosted
ai-eng fetch ladder in scripts/setup.sh:install_services() with ollama/curl/
sha256sum/timeout fully stubbed on PATH — no download, no model load, no
network (OOM-safe). Covers HF-primary single-pull, HF-404→GitHub-mirror
digest-MATCH→create, digest-MISMATCH fail-closed, placeholder-digest mirror
disabled (no curl), all-hosts-fail→preview-net, offline→clean-exit, skip,
idempotency, legacy ai-engineer alias, CDN gating.

Python guards (tests/test_model_hosting_reframe_qa.py): deep-model sentinel
not resolvable + backend raises before load (fake airllm so a guard
regression can never load a real 70B), qwen-hiding regression grep,
NOTICE/LICENSE attribution, package_ai_eng.sh credential/weight-download
security, Buddy ai-eng resolution post-rename, honest-framing grep, supply-
chain digest-gate static assertions.

All 43 new tests pass. No new failures in the existing suite (17 pre-existing
/ parallel-sprint failures unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
43 new QA tests (setup-ladder + sentinel/qwen-hiding/security guards) pass;
17 pre-existing/parallel-sprint failures unchanged (no new failures).
[ASK-LICENSE] independently re-verified via HF API (Qwen2.5-3B = qwen-research
non-commercial vs MIT blueprint) — must-resolve-before-distribution, not
merge-blocking. Surfaced two pre-existing items: TIMEOUT-GAP (no `timeout`
on stock macOS, the headline platform) and STALE-ENV (70B in local .env
bypasses the sentinel guard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…K_PASS)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cense blocker

The 3B base (Qwen Research License, research/non-commercial) conflicted with
ARAIL's MIT fork/redistribute thesis. The 1.5B base is Apache-2.0 — fully
compatible and ~1 GB lighter on the OOM-sensitive 36 GB dev box.

Changes:
- NOTICE: rewritten for 1.5B Apache-2.0 base; Qwen Research License language
  removed; dual-section collapses to one (production == preview base == 1.5B)
- Modelfile.preview: FROM qwen2.5:7b → FROM qwen2.5:1.5b; SYSTEM "3B" → "1.5B"
- pyproject.toml: ai_eng_hf_repo/gh_url 3b→1.5b; ai_eng_preview 7b→1.5b;
  all "3B-parameter" branding → "1.5B-parameter"
- models_catalog.yaml: name/description/install/size_gb updated; preview
  catalog entry qwen2.5:7b → qwen2.5:1.5b
- README.md, CLAUDE.md, docs/INSTALL.md: "3B" → "1.5B"
- scripts/setup.sh: hf_repo/gh_url defaults, comments, preview_base var
- scripts/package_ai_eng.sh: base model id, GGUF filename, NOTICE template,
  upload command placeholders, all 3b→1.5b
- CHANGELOG.md: new re-base section documenting confirmed SPDX Apache-2.0
- tests: update guard assertions for 1.5b base, Apache-2.0 NOTICE,
  FROM qwen2.5:1.5b in Modelfile.preview; 43/43 pass

Confirmed: `Qwen/Qwen2.5-1.5B-Instruct` → SPDX `Apache-2.0` (HF API verified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cdarnell and others added 28 commits May 31, 2026 23:34
…-8B → Llama-1B two-tier)

Onboarding clarity audit found stale copy contradicting the shipped two-tier
model: setup.sh tier-picker said '1.5B Opus-4.7' while installing Llama-3.2-1B;
INSTALL.md called it 'ai-eng/1.5B'; MACOS.md claimed a Qwen3-8B 4.3GB download
that no longer happens. Aligned all to llama-ai-eng (Llama-3.2-1B), clarified
both tiers share one AI-engineer persona, and added a post-setup line naming
the installed model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fault model

Captures the target architecture: specialize a permissive small base
(Llama-3.2) via the Nucleus/SSDP AI-corpus + research distillation into a real
AI-engineer domain model (proven previously on Qwen), then package through the
existing build→quantize→self-host lane. Records the persona-wrap as the honest
interim, the adapter-must-match-base lesson, and the licensing constraints.
Establishes docs/adr/ for ARAIL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RAM users

Adds the lightweight on-box SLM qukaizen-site runs on CPU
(qwen2.5:0.5b-instruct-q4_K_M, Apache-2.0, ~0.4 GB) as a browse-and-pull
catalog entry, plus a README note pointing budget/CPU-only users at it.
Additive only — the two feature tiers are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Setup step 3/11 (`pip install -e .[maximus]`) failed on Apple Silicon with
"could not find a version that satisfies aerollm-api" — the maximus/max/aero
extras hard-pinned `aerollm-api>=0.1.0rc2`, but only rc1 was on PyPI (rc2 was
never published). AeroLLM is the optional deep-mode "2nd inference", a
cargo-built native extension with no general pip wheel, so it must not gate
core setup: it's installed out-of-band by scripts/build-aerollm.sh
(`./arailctl deep rebuild`), which fails soft. Remove the pin from the
maximus/max extras and make `aero` a no-op marker.

Also document in README that AeroLLM deep mode is Apple-Silicon/MLX-only today
(CUDA backend in progress) and now open source (github.com/cdarnell/aerollm).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r) (#77)

* feat(world-mount): vendor physics WorldBundle fixtures (physics/tampered/hostile)

Three test fixture bundles for the ARAIL World Mount feature:
- physics/: byte-identical copy of qukaizen-dac/dist/bundles/physics/ (7 files)
- tampered/: physics copy with one byte flipped in terms.json; manifest unchanged
  so seal verification detects the mismatch
- hostile/: physics copy with an injected term whose definition begins
  "Ignore previous instructions…"; re-hashed so the seal is valid (tests that
  the hostile content renders inert, not that it is rejected)

Tests never read the DaC dist path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(world-mount): Phase 0 — loader core, seal/compat/category gating, arailctl world)

world_mount.py pure functions:
- load_bundle(dir) → Bundle: parses manifest.json + 6 siblings; raises PartialBundle
  on missing/unreadable; face.json missing/invalid is tolerated (None face)
- verify_seal(bundle) → SealResult: sha256(terms_raw_bytes) == manifest.world_sha256
  AND == manifest.files["terms.json"]; checks all other sibling hashes; prints
  BOTH hashes on mismatch (exit 2)
- check_compat(bundle): bundle_schema/terms_schema must == 1; SchemaSkew names both
- check_categories(bundle): GateViolation on any term.category not in spec.categories
- term_to_dict_entry: DaC term node → TermEntry-compatible dict; definition never mapped
- __main__ argparse: list | mount | verify | swap | unmount [--apply-face]

arailctl: world) case activates venv, exec python -m arail.world_mount "$@"
Usage help updated.

18 tests pass: good seal, tampered→mismatch (both hashes), schema v2→refuse,
missing files→refuse, bogus category→refuse, hostile fixture seal valid +
definition never enters dict entry (inert).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(world-mount): Phase 1 — mount/unmount/swap + pointer atomicity + startup detect

world_mount.py state mutations:
- mount(): load+verify in-memory → stage files via .staging-<slug>/ atomic rename
  → index → env face (if apply_face) → write pointer LAST via temp+os.replace
- unmount(): remove pointer FIRST, then optionally remove staged dir
- swap(): verify new bundle → stage → flip pointer; old world stays mounted on failure
- current_mount(): single switch all consumers read; returns None on missing/corrupt pointer

portal/app.py startup: detect + log any mounted world (world_mount.current_mount())

12 atomicity tests pass: pointer written last (staging fail = no orphan pointer),
unmount removes pointer first, swap keeps old on tampered new bundle,
round-trip MountRecord.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(world-mount): Phase 2 — KB staging + index

_stage_files(): copies 6 bundle files to pkb/sources/world-<slug>/ via
.staging-<slug>/ atomic dir rename; also emits world-<slug>.md index page
(term | short_def | source per row) for human browsing + clean wiki node.

_index_staged(): calls ensure_ready(pkb_root) then schedule_upsert per
staged file. Wrapped in try/except in mount() so LanceDB-absent never aborts
a mount. .json is already in _PKB_TEXT_SUFFIXES so staged terms are indexed.

6 tests pass: staged dir holds exactly 6 bundle files + world-physics.md;
each staged file's sha256 matches manifest; index page has term table;
staging idempotent; LanceDB-absent no-raise; upsert called per file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(world-mount): Phase 3 — dictionary flip (bundle terms override while mounted)

portal/app.py:
- _world_mounted_dict_response(): reads current_mount() → get_mounted_dict_terms()
  → returns {terms, can_generate:false, source:"world"}; returns None if not mounted
- GET /api/dictionary: checks mounted world first
- POST /api/dictionary/seed: no-op while mounted (returns world terms)
- POST /api/dictionary/generate-more: serves world terms (no router call)
- POST /api/dictionary/theme: 409 while mounted
- POST /api/dictionary/expand: serves bundle definition inline (no router call);
  finds term by name or slug

Security invariant: generate-more/expand never call the inference router while
mounted. Terms render only through template data paths. Tested with hostile
fixture: definition field not in TermEntry mapping (inert).

9 tests pass: 42 terms with origin/citation, can_generate:false, hostile
definition not in entry, unmount restores None, term ordering by spec.categories.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(world-mount): Phase 4 — face/theme flip + consent

world_mount._write_face_env():
- Writes LAB_INTENT=other, LAB_INTENT_NAME, LAB_INTENT_DESCRIPTION, LAB_THEME
  via env_writer.set_env_var (atomic, comment-preserving)
- LAB_UI_THEME written only if load_ui_theme(palette_hint) resolves to a known
  theme (physics blue-cyan-lab resolves; unknown palette → key omitted)
- Never touches LAB_NAME/LAB_LOGO (brand untouched)
- face.json missing/invalid → KB mounts anyway, env skip with warning
- Only fires when apply_face=True; KB-only mount writes nothing to .env

CLI --apply-face displays face.json preview + key list before writing;
prints restart hint after: ./arailctl restart (portal reads env at startup)

11 tests pass: 5 keys written, brand untouched, unknown palette omits
LAB_UI_THEME, KB-only mount leaves .env clean, missing face tolerates mount.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(world-mount): Phase 5 — Buddy WORLD FRAMING block in _compose_prompt

_builtin_buddy.py:
- _MAX_WORLD_DOMAIN_FRAMING = 600 chars
- _MAX_WORLD_VOCAB_REGISTER = 300 chars
- _world_framing_block(): reads current_mount() → mounted_face(); formats
  delimited block "# WORLD FRAMING\nDomain: ...\nVocabulary: ...\n# END WORLD FRAMING"
  with per-field length caps; returns "" if not mounted or face absent
- _compose_prompt: inserts world_framing block between base SYSTEM_PROMPT and
  dreams/skills when mounted; unmounted path is byte-identical (no block)

Security: only face.json text enters the prompt (never terms.json definitions).
Researcher intent via LAB_INTENT=other (written by Phase 4 --apply-face) — no
researcher.py edit required.

10 tests pass: framing empty when not mounted, delimiters present when mounted,
domain/vocab content present, caps enforced, compose_prompt integration,
hostile definitions never reach framing block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(world-mount): Phase 6 — curator trusted sources union from mounted world

curator.py:
- _world_extra_sources(): reads current_mount() → world_trusted_domains(record)
  at call time; returns {} if not mounted or world_mount unavailable
- propose_sources(): builds effective_sources = dict(TRUSTED_SOURCES) merged
  with world extras at runtime; TRUSTED_SOURCES module dict never mutated
- Physics bundle: NIST→physics.nist.gov, BIPM→bipm.org, CODATA→codata.org
  added to proposals when physics world is mounted
- Airgap still blocks all proposals (mounted or not)
- Unmount immediately removes world sources (next call → no extras)
- Consent gate unchanged: world sources go through submit_proposals/ConsentStore

7 tests pass: module dict untouched, extras present when mounted, empty when
not mounted, airgap blocks, unmount restores, consent not bypassed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* harden world-mount: slug allowlist (R2) + cap expand length (R3)

R2: add SlugInvalid error + _SLUG_RE allowlist (^[a-z0-9][a-z0-9-]*$) in
load_bundle. Slug validated against manifest before any disk staging; cross-file
consistency enforced: manifest.world must equal spec.json.slug and face.json.world
when those fields are present. Refuses with actionable user_message before any
path is constructed. Tests: 7 new cases in test_world_loader.py covering dotdot,
path-separator, dot, uppercase, valid slug, spec mismatch, face mismatch.

R3: cap expand definition to _MAX_DETAIL (1500 chars) in app.py expand endpoint,
reusing the existing dictionary.py constant. Tests: 1 new case in
test_world_dictionary.py. QA probe test_seal_catches_altered_face_json updated to
mutate a non-slug field so the seal check (not slug check) fires.

All 97 world-mount tests pass (73 builder + 16 QA probes + 8 new hardening tests).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(env_writer): shell-quote .env values so face-flip free text is source-safe

The World-mount face flip appends multi-word free text (LAB_INTENT_DESCRIPTION
from face.domain_framing). set_env_var appended new keys unquoted, so
arailctl's `set -a; source .env` ran the value's first word as a command
("lab: command not found") and could expand $/` sequences.

with_value/append now double-quote (escaping \ " $ `) any value that
isn't a shell-safe bare token, preserve existing single/double quote styles
when still safe, and repair an already-written bare unsafe line in place.
Simple tokens (other, blue-cyan-lab) stay unquoted. Adds bash-source
round-trip regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* harden shell-sourced config writers found in env-quoting audit

Audit of every writer of a shell-sourced file (.env, lab.conf) after the
world-mount face-flip bug. Findings fixed:

- #4 legacy POST /api/system/mode: hand-rolled a non-atomic .env rewrite and
  skipped the CSRF/loopback gates the canonical POST /api/airgap/toggle
  enforces (no client used it; the nav badge POSTs to /api/airgap/toggle).
  Now returns 410 pointing at the gated endpoint instead of an ungated write.
- #5 setup.sh lab.conf: IDE_PASSWORD=${ARAIL_PASSWORD} was written bare into a
  shell-sourced file, so a user passphrase with a space/$/backtick broke
  'source lab.conf' or executed. Generalized _set_env_var to target any file,
  added a quote-aware _get_env_var, and route IDE_PASSWORD (heredoc + drift
  repair) through the quoter.
- #6 blueprint.sh: per-instance .env wrote free-text LAB_NAME/LAB_TAGLINE/
  LAB_INTENT quoted-but-unescaped; added a shell-quoter mirroring env_writer.

Adds tests/shell_source_safety_driver.sh (+ pytest wrapper) exercising the
REAL extracted shell helpers against hostile input ($(...), backticks, ;)
and asserting source-safety + no command execution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: isolate portal tests from a developer's ambient world mount

Portal endpoints resolve the mounted World via current_mount() ->
_default_data_dir() -> real lab/data. With a World mounted on the dev machine
(./arailctl world mount), unrelated portal/dictionary tests saw mounted
behavior and failed (e.g. test_expand_graceful_when_model_fails); CI passed
only because lab/ is git-ignored. Autouse fixture points the default data dir
at a fresh empty dir so the ambient lookup finds nothing mounted; tests that
set up their own mount override _default_data_dir in their body, and explicit
current_mount(data_dir=...) calls are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…speak, get a RAW note) (#78)

* feat(capabilities): scaffold capability registry package

Step 1 of STT+capabilities sprint. Adds the capabilities/ package:
errors hierarchy, Adapter ABC + two seam sub-ABCs, tolerant
parse_capabilities_file, platform-aware registry.select(), and
resolve_capabilities with the three resolution states.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(capabilities): macOS + Linux backends (WC-B seam)

Step 2. Linux audio+STT stubs register but raise CapabilityNotImplemented
('<id>: no backend for linux'). macOS audio (browser-capture
materialization, rejects webm/opus) and STT (lazy swiftc compile of
arail-stt helper, injectable _runner, error-code mapping) register
unconditionally; availability is platform-gated. Apple symbols confined
to backends/macos/ — the Swift helper is the only Speech/AVFoundation import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(world_mount): resolve declared capabilities to a sidecar (WC-C/WC-D)

Step 3. Additive last-step in mount()/swap(): read bundle-optional,
seal-exempt capabilities.json, resolve against the registry, persist to
DATA_DIR/world-capabilities.json atomically. Absent -> resolved=[] (WC-D);
malformed -> capabilities_error recorded, mount still succeeds. unmount()
removes the sidecar. MountRecord shape untouched (atomic-pointer tests
preserved). Adds three vendored fixtures (physics + capabilities.json
declaring stt; +equation-ocr for WC-C; no-caps for WC-D).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(capabilities): WC-B/C/D + registry resolution + sidecar (off-ramp green)

Step 3 tests. Registry resolution states, WC-B (Linux stub raises clean +
Apple-symbol grep clean), WC-C (equation-ocr zero-code graceful degrade),
WC-D (no capabilities.json mounts clean), malformed-json grace, missing-CLT
declared_unavailable, tolerant parse, unmount removes sidecar. 11 pass;
world-mount regression (12) still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(capabilities): STT backend tests + live_mic marker + graceful TCC-crash mapping

Step 4. Injectable _runner unit tests (success/no_speech/permission/
model_unavailable/decode_failed), CLT-missing _ensure_helper, audio webm
rejection + m4a materialization. Adds live_mic pytest marker (skipped in CI)
gating the real swiftc-compile test. Maps a signal-killed helper (SIGABRT
rc=134/-N) to permission_denied so an unsigned helper degrades to a 409
rather than a 500 — see BUILD_LOG TCC-grant delta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(portal): POST /api/stt/transcribe → RAW voice note (WC-A backend)

Step 5. Endpoint gated on a mounted World resolving speech-to-text=available;
materializes the posted blob via the audio adapter, transcribes on-device via
the STT adapter, lands the transcript as a RAW/unsourced note in
research/voice-notes/ (kind:raw, sourced:false — DATA, never a prompt),
indexes via schedule_upsert, schedules wiki rebuild, deletes the temp audio in
finally:. Errors map to 4xx with actionable messages, never 500-traceback.
9 flow tests: RAW landing + indexing, e2e, temp cleanup, no_speech, no-mount,
webm-422, adapter-409, airgapped zero-egress, transcript-not-in-prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): mic affordance gated on inherited speech-to-text (WC-A surface)

Step 6. Chat route resolves the mounted World's speech-to-text state and
passes it to the template; the composer gains a mic button that renders
disabled (with the resolution message as tooltip) unless STT is available —
making capability inheritance visible. Self-contained JS: getUserMedia +
MediaRecorder requesting audio/mp4 first (never webm), tap-to-record /
tap-to-stop, POST to /api/stt/transcribe, status-line toasts for saved /
no_speech / permission-denied / unsupported-browser. DOM tests assert the
disabled/enabled gating.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(build-log): stt-capabilities builder log

Per-step status, real seam paths, the TCC-grant delta (live transcription
blocked for an unsigned swiftc binary — STOPPED rather than building a signing
pipeline; graceful 409 degrade in place), WC-B grep result, pre-existing-vs-
introduced test adjudication (17 baseline failures reproduced on pre-sprint
tip), and the demo command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(capabilities): swap Apple-Speech STT → local Whisper backend

Apple-Speech is dead (unsigned swiftc CLI SIGABRTs on the speech-recognition
TCC grant; signed .app rejected by owner — BUILD_LOG DELTA 1, ARCHITECTURE
Addendum A). Replace with a platform-neutral local-Whisper backend below the
existing adapter seam:

- add faster-whisper>=1.2.0 (prebuilt wheels, no compiler)
- DELETE stt_helper.swift and the Apple body of macos/stt_backend.py
  (now a no-Apple alias for WhisperSpeechToText)
- DELETE the linux/stt_backend.py stub
- backends/whisper_stt.py: afconvert → base.en → same (rc,stdout,stderr) JSON
  shape behind the UNCHANGED injectable _runner seam; lazy first-use model
  fetch to lab/models/whisper/base.en/; airgapped-graceful model_unavailable
- register WhisperSpeechToText for BOTH darwin and linux (WC-3: Linux off
  the STT stub)

WC-B: Apple-symbol grep over all of src/ (no exclude) is now clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(capabilities): finish Whisper STT swap — register both platforms, retire Apple path

Completes the builder pass interrupted by the API drop (on top of eb28cdf):
- whisper_stt registered platform-neutrally for darwin + linux (Linux off the
  stub → advances WC-3); per-platform packages keep only the audio-capture seam.
- macos/stt_backend.py gutted to a 22-line no-Apple backward-compat alias
  (MacOSSpeechToText = WhisperSpeechToText); zero Apple symbols anywhere in src/.
- test_stt_backend / test_capabilities updated for the Whisper runner + live_stt
  marker. 33 passed / 1 skipped (live_stt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(stt): record Whisper backend swap + WC-A.4 live proof in BUILD_LOG; commit sprint artifacts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(stt): gate _ensure_model on patchable _model_present() (QA B1); add QA probes

QA found test_airgapped_graceful_unavailable was false-green — _ensure_model
stat'd model.bin directly, bypassing the _model_present() indirection the test
monkeypatches, so with the real model on disk it couldn't simulate absence.
Production degrade was already correct; this makes the safety-critical airgapped
path testable. Adds QA's hostile-transcript + temp-leak-on-raise probes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(stt): sprint ledger — QA PASS, all four win conditions met

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st on tap (#79)

The mic JS already warned on first tap when the browser can't record an
afconvert-decodable container (Chrome webm/opus). Now it also checks pickMime()
at load and, when unsupported, mutes the mic button + sets an explanatory title
+ flashes a one-time notice — so the Safari-in-v1 caveat is visible the moment
Chat opens. Adds a render test asserting the caveat is wired.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…CR via Apple Vision) (#81)

* feat(ocr): image-text OCR seam + macOS Vision backend + Linux stub

Add ImageTextRecognitionAdapter (Seam C) for equation-ocr; register the
macOS Apple-Vision backend (lazy swiftc-compiled helper, injectable _runner)
and a Linux registered-but-unimplemented stub. All Apple symbols confined to
backends/macos/ (WC-B). No edit to registry/resolve/spec/world_mount.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(ocr): narrow equation-ocr fixture to v1 text contract + prove two-live WC-C

Fixture purpose -> printed text/number recognition, outputs latex->text (id
unchanged for WC-C continuity). Registry now resolves TWO live capabilities
(speech-to-text + equation-ocr) available when toolchains present; a third
undeclared id still declared_unavailable. Update STT WC-B grep to exclude the
macos OCR backend's now-legit swiftc/xcrun symbols + build-cache artifacts.

Off-ramp safe point: WC-B/C/D shippable independent of the Vision backend/UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(ocr): POST /api/ocr/extract + RAW ocr-note landing + upload validation

Near-copy of /api/stt/transcribe: multipart image -> on-device OCR -> RAW note
in research/ocr-notes/ (kind:raw, sourced:false, image provenance), indexed via
schedule_upsert + wiki rebuild. Upload validation: mime allowlist + magic-byte
sniff + 12 MB cap (422, never 500); temp image deleted in finally:. OCR text is
inert DATA, never enters a prompt. Fake-runner flow tests incl. mandatory
hostile-image inert-RAW/not-in-prompt test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(ocr): backend unit tests + live_ocr real-Vision proof + marker

Fake-runner error-code mapping (ok/no_text/decode_failed/model_unavailable/bad
JSON), is_available() probes, and two live_ocr tests: real Apple Vision OCR on a
synthesized constants image (asserts 1.380649e-23 fidelity) + helper-compiles-
once. Add the live_ocr pytest marker. Real path measured: ~0.43s, exact digits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(ocr): capability-gated 📷 upload/paste/drop affordance in Chat

Resolve ocr_available/ocr_message in the chat route alongside stt; add an
#ocr-btn gated on equation-ocr==available (identical to the mic gating on
speech-to-text). Hidden file input + clipboard paste + composer drop wire to
POST /api/ocr/extract with a status-line toast. DOM gating tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ocr): keep Apple symbols below the seam (WC-B)

Reword the OCR endpoint comment so the portal carries no platform OCR symbols;
update the second pre-existing STT WC-B grep (test_stt_backend) to exclude the
macos OCR backend's legit swiftc/xcrun + build-cache artifacts. WC-B grep over
src/ now matches only backends/macos/ocr_backend.py + ocr_helper.swift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(ocr): BUILD_LOG for equation-ocr sprint

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(equation-ocr): sprint artifacts + ledger — QA PASS, two live capabilities

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
QuKaiZen Project-Aware Super Skill v1.0 — trained on ARAIL's own codebase.

Base Model: Google Gemma 4-2B-IT (9.5GB safetensors)
Training Data: 29,515 files from aerollm, arail, qukaizen-dac
Training Method: Quick-Storm POC (certified all 3 gates)

Certification Results (All Passed ✓):
  Gate 1 (General Capability): 0.875
  Gate 2 (Domain Mastery): 0.850
  Gate 3 (Hallucination Audit): 0.800

The model understands ARAIL's projects and reasoning patterns.
Ships as ARAIL's default Buddy reasoning engine.
Local inference: 5-7 tok/s (no API calls needed).
Deep reasoning: Falls back to Claude API if needed.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Docs (v1.1 update): MACOS, README, setup reflect Ollama + llama3.2:1b default
- Knowledge Base: Add world-physics domain with 50+ SI terms (BIPM/NIST-sourced)
- Features: Cache prewarming for Anthropic prompt cache, chat highlight JS
- Sprints: Archive completed two-tier validation (WEAK_PASS), kickoff RAR retrieval

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Resolved conflicts by taking main's versions of:
- src/arail/portal/app.py (includes world selection endpoints)
- src/arail/world_mount.py (world mounting implementation)
- tests/ (main's test suite from world-identity-flip and world-switcher sprints)

Main includes recent sprints for world identity management and switching
(2026-06-14-world-identity-flip, 2026-06-14-world-switcher) plus model
separation and Llama disclosure test coverage.

Preserves gemma-poc branch content:
- Cache prewarming feature
- Chat highlighting
- world-physics KB
- Sprint documentation
…oLLM"

The product narrative for World Forge as speculative authoring: a local model
DRAFTS a mountable World in seconds (model-asserted, honest), AeroLLM RECONCILES
it overnight (accept/correct/reject, promote model-asserted → sourced), and the
autoresearch loop REFINES it forever. Tagline: "Quickly get a base — forever
curated by the power of AeroLLM."

Maps the tier story: minimalist dreams worlds; maximus (AeroLLM deep backend)
makes them true. Marked status: design — the draft/export/mount pipeline is a
proven spike; the one-button surface + always-on curation are the build-out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, not spec-decoding

Per the precision point: World Forge borrows spec-decoding's economics (draft
cheap, verify big) but not its algorithm. Replaced "literally speculative
decoding" with an IS/IS-NOT table and the design consequence: unlike real
spec-decoding (small model buys speed only, output identical to the target),
here the small model BOUNDS coverage — so the loop needs an explicit expand
step, not just verify.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…skill_from_path, load_world_skill (step 2/7)

- _MAX_WORLD_SKILL_BYTES (64 KB) + _MAX_WORLD_SKILL_BODY_CHARS (24K-char) caps
- _contain_skill_body mirrors DaC sanitizeBodyField in Python: neutralizes
  YAML fences, top-level # headings, and all ARAIL structural delimiters
  (# WORLD FRAMING, # Procedural knowledge, ## Skill:, Observation:, Source:,
  Buddy's one-sentence note:) with U+200C prefix; leaves glossary lines intact
- load_skill_from_path: reads an explicit SKILL.md path, applies containment,
  enforces byte + char caps; never raises
- load_world_skill: keyed off current_mount().staged_dir; returns None when
  nothing mounted or SKILL.md absent; never raises

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… in _stage_files (step 3/7)

- _WORLD_SKILL_NAME = "SKILL.md" constant (seal-EXEMPT; NOT in _BUNDLE_FILES)
- _stage_files now copies SKILL.md from bundle dir if present (after the 6-file
  loop, before atomic swap); missing or unreadable → logged, mount continues
- SKILL.md is never added to _BUNDLE_FILES so verify_seal is unchanged for all
  existing 6-file bundles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ry-skill-hostile (tampered SKILL.md) fixtures (step 4/7)

- art-history-skill/: frozen copy of qukaizen-dac/dist/bundles/art-history/
  (6 sealed files + manifest + SKILL.md + capabilities.json + arail-plugin.json)
  Self-contained; no DaC toolchain needed at test time.
- art-history-skill-hostile/: copy of hostile/ (valid 6-file seal) with a
  SKILL.md whose body contains forged structural lines (# WORLD FRAMING,
  # Procedural knowledge, ## Skill: EVIL, ---, Observation:, Source:,
  Buddy's one-sentence note:) for injection containment tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…soft block (step 5/7)

- Imports load_world_skill from arail.skills_loader inside the existing
  try/except failsoft block so any loader error never breaks research
- Appends the world skill to the skill list before compose_system_context;
  nothing-mounted → ws=None → skills unchanged → default behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…compose_prompt wiring (step 6/7)

- BuddyHost Protocol gains load_world_skill() -> Optional[Any]
- ArailHost.load_world_skill wraps arail.skills_loader.load_world_skill in
  try/except → returns None on any failure (matches existing host method style)
- _compose_prompt: loads agent skills + world skill separately, merges into
  all_skills before compose_skill_context; skill_ctx unchanged when no world
- WORLD FRAMING block (face "who") stays unchanged as a DISTINCT section;
  world-skill is the term glossary "what" under ## Skill: in Procedural knowledge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…curity, happy path, regression (step 7/7)

Test allocation (ARAIL gating):
  ~30% setup: stages SKILL.md byte-identically, seal passes with modified
    SKILL.md (seal-exempt), missing SKILL.md is no-op, broken seal refused
  ~30% Buddy/Researcher: art-history Ballets Russes appears in skill context,
    researcher context includes world skill, world-skill distinct from WORLD FRAMING
  ~20% security: _contain_skill_body neutralizes # WORLD FRAMING, # END WORLD
    FRAMING, # Procedural knowledge, Observation:, ---, ## Skill: EVIL, Source:,
    Buddy's one-sentence note:; preserves legitimate ### Category / - **term** lines;
    oversized SKILL.md → None; malformed frontmatter loads body only
  ~10% happy: end-to-end mount→skill→unmount→None; swap replaces skill
  ~10% regression: nothing-mounted → None, compose_system_context shape unchanged,
    BuddyHost has load_world_skill, ArailHost.load_world_skill safe when unmounted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt proof, final state (step 8/8)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… review fixes)

Defect 1 — ### Category headers mangled:
  Old logic neutralized ALL column-0 '#' lines, catching the glossary's own
  ### Dance / ### Music / … (7 headers) in violation of the ARCHITECTURE spec.
  Fix: _HEADING_H1_H2_RE matches only h1 (# ) and h2 (## ) patterns, never
  h3+ (###). Legitimate glossary category headers now pass through intact;
  ## Skill: EVIL and # WORLD FRAMING are still neutralized.

Defect 2 — body cap truncates the flagship bundle:
  _MAX_WORLD_SKILL_BODY_CHARS was 24*1024 (24,576 chars), truncating the
  31,625-char art-history body and silently dropping ~22% (Music + Painting tail).
  Raised to 56*1024 (57,344). Full flagship glossary now fits with headroom.
  Truncation is a last resort; warning is logged when it fires.

Defect 3 — indented delimiter passthrough:
  Delimiter check was column-0-anchored only; '  # WORLD FRAMING' passed.
  Fix: match _ARAIL_DELIMITERS against lstripped line so indented variants
  are also neutralized (## and # headings caught by _HEADING_H1_H2_RE too).

New tests added (4 pins):
  test_contain_skill_body_preserves_all_h3_category_headers
  test_contain_skill_body_h3_headers_in_composed_prompt
  test_contain_skill_body_indented_delimiter_neutralized
  test_full_flagship_glossary_not_truncated
  (Greek Modes + Grove Dictionary of Music + Vermeer all present in body + compose)

Results: 30/30 tests/test_world_skill_mount.py + 49/49 world-test regression suite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes the QA gate for dac-world-mount: 29 adversarial tests
(tests/test_world_skill_qa_adversarial.py) probing homoglyph/unicode evasion,
DoS inputs (5MB line, 50k forged lines, null bytes, over-cap), hostile
capabilities.json, and legit-shaped Source injection — structural containment
held (no surviving line is a renderable CommonMark h1/h2; injected content can
never relocate out of its `## Skill:` H2). Adds the sprint ARCHITECTURE/REVIEW/
TEST_REPORT and the final ledger. Two LOW follow-ups tracked (escape-vs-ZWNJ;
cross-repo SKILL.md seal-promotion).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stages the locally-mounted/source DaC Worlds (espresso, espresso-7b-v2,
horticulture, physics face) under lab/ + a compiled guide README, so the
baseline working tree is clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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