From 60bb6f22d7c13a25fee204fbc17798c6c55cb19f Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:54:20 -0700 Subject: [PATCH 01/37] vendor: prepare pinned Hermes source adoption --- .../GAUNTLET_FAST_BUILD_HANDOFF.md | 54 +++++ ...UILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md | 217 ++++++++++++++++++ scripts/vendor_hermes.py | 181 +++++++++++++++ third_party/HERMES_LICENSE.txt | 21 ++ third_party/HERMES_SOURCE_LEDGER.md | 62 +++++ vendor/HERMES_SNAPSHOT.json | 15 ++ 6 files changed, 550 insertions(+) create mode 100644 docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md create mode 100644 docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md create mode 100755 scripts/vendor_hermes.py create mode 100644 third_party/HERMES_LICENSE.txt create mode 100644 third_party/HERMES_SOURCE_LEDGER.md create mode 100644 vendor/HERMES_SNAPSHOT.json diff --git a/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md b/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md new file mode 100644 index 00000000..cfa3ed7e --- /dev/null +++ b/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md @@ -0,0 +1,54 @@ +# Gauntlet Fast Build — Session-to-Codex Handoff Ledger + +**Handoff ID:** `GAUNTLET-FAST-HANDOFF-001` +**Repository:** `Kitahl/The-Gauntlet` +**Branch:** `work/native-hermes-fastpath` +**Baseline commit:** `4f088d688fa9e25b4608f44000a5d9812efa45f9` +**Governing plan:** `docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md` + +## 1. Frozen implementation decision + +The fast build vendors the pinned Hermes Agent `v0.20.6` source and runs it later as an +isolated internal subprocess. Gauntlet remains the sole authority over obligations, +receipts, verdicts, and release. Same-process imports between the two top-level package +trees are prohibited. + +## 2. Phase ledger + +| Phase | Scope | State | Commit | +|---:|---|---|---| +| 1 | Pin, provenance, license, deterministic vendoring command, governing plan | `COMPLETE` | this commit | +| 2 | Materialize and verify `vendor/hermes-agent/` at the exact commit | `NEXT` | — | +| 3 | Add `gauntlet_host` constants, typed JSONL IPC, isolated worker bootstrap | `PENDING` | — | +| 4 | Add launcher, runtime profile, Gauntlet-owned runtime home | `PENDING` | — | +| 5 | Add status/release plugin tools and subprocess module adapter | `PENDING` | — | +| 6 | Add observation bridge and Soul finalizer | `PENDING` | — | +| 7 | Add FOIL advisory route and single `gauntlet` entry point | `PENDING` | — | +| 8 | Perform the eight-item manual boot verification and prepare Codex transfer | `PENDING` | — | + +## 3. Phase 1 outputs + +1. Exact upstream repository, tag, commit, and license pin. +2. Exact upstream MIT license notice under `third_party/`. +3. Machine-readable snapshot manifest under `vendor/`. +4. `scripts/vendor_hermes.py`, which: + - verifies the exact Git commit and tag; + - rejects a dirty or wrong checkout; + - verifies the MIT license hash; + - copies the source while excluding only nested `.git` metadata; + - records deterministic file count and tree digest; + - supports an independent `--verify-only` pass; + - requires `--force` before replacing an existing snapshot. +5. The governing fast-build engineering plan stored in the repository. +6. No changes to `tools/egrt_types.py`, `tools/soul_runtime.py`, FOIL authority ceilings, + receipt formats, or direct module behavior. + +## 4. Continue instruction + +Continue with **Phase 2 only**: + +1. run `python scripts/vendor_hermes.py --dry-run`; +2. materialize the exact pinned source; +3. run `python scripts/vendor_hermes.py --verify-only`; +4. update this ledger with the resulting file count, tree SHA-256, and commit SHA; +5. stop before implementing `gauntlet_host/`. diff --git a/docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md b/docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md new file mode 100644 index 00000000..2ea38b9f --- /dev/null +++ b/docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md @@ -0,0 +1,217 @@ +# GAUNTLET FAST BUILD — HERMES-QUALITY INTERNAL RUNTIME + +**Document ID:** `GAUNTLET-FAST-HOST-001-EXEC` +**Date:** 2026-08-28 +**Repository:** `Kitahl/The-Gauntlet` +**Branch:** `work/native-hermes-fastpath` +**Baseline:** `4f088d688fa9e25b4608f44000a5d9812efa45f9` +**Upstream:** `NousResearch/hermes-agent` `v2026.8.27` / `v0.20.6` +**Commit:** `5fc308a70719a83cccdbba4c0e39c23f5a8239d5` +**Mode:** fast implementation; reuse upstream; do not redesign. + +## 1. Product decision + +1. Vendor the exact pinned Hermes source under `vendor/hermes-agent/`. +2. Run it as a Gauntlet-owned internal subprocess, not as a separately installed product. +3. Keep the vendored tree unchanged except for recorded, reviewed deviations. +4. Use upstream provider, tool, MCP, context, session, skill, memory, retry, and delegation mechanisms. +5. Add only a thin `gauntlet_host/` boundary. +6. Keep existing Gauntlet modules as the sole evidential and release authority. +7. Ship one user-facing command: `gauntlet`. + +Do not port Hermes subsystem-by-subsystem. Do not build replacement provider, MCP, tool, +context, session, skill, memory, retry, scheduler, or subagent frameworks during this pass. + +## 2. Isolation invariant + +Both repositories contain top-level packages such as `tools`. Never import both trees into +one interpreter. Launch the internal worker with: + +```text +cwd = vendor/hermes-agent +PYTHONPATH = vendor/hermes-agent +HERMES_HOME = ~/.gauntlet/runtime +GAUNTLET_TASK_ID = +``` + +The parent remains the Gauntlet process. Parent/worker communication is typed JSONL over +stdin/stdout. `gauntlet_host/worker_main.py` may import upstream `run_agent.AIAgent`; the +parent launcher must not. + +## 3. Authority invariant + +```text +runtime tool execution +→ ToolObservation +→ existing Gauntlet module/verifier +→ canonical Receipt +→ existing Soul release_gate() +``` + +The worker, model, tool, plugin, skill, memory, session store, scheduler, or subagent may +not directly: + +- create a canonical `Receipt`; +- set or change `Verdict`; +- clear an `Obligation`; +- modify sealed authority rules; +- declare tool success to be factual warrant; +- release a task; +- convert memory or a skill into evidence. + +Do not change `tools/egrt_types.py` semantics, `tools/soul_runtime.py` release semantics, +FOIL authority ceilings, or the current receipt format during the fast build. + +## 4. Required repository additions + +```text +vendor/hermes-agent/ exact upstream snapshot +gauntlet_host/ + __init__.py + constants.py + ipc.py + worker_main.py + launcher.py + runtime_profile.py + gauntlet_plugin.py + gauntlet_tool.py + observation_bridge.py + module_cli.py + finalizer.py + session_map.py +third_party/HERMES_LICENSE.txt +third_party/HERMES_SOURCE_LEDGER.md +vendor/HERMES_SNAPSHOT.json +scripts/vendor_hermes.py +``` + +Existing `tools/` files remain authoritative and import-compatible. + +## 5. Runtime profile + +Store operational state only under `~/.gauntlet/runtime/`; never reuse `~/.hermes`. +Disable automatic background review for the alpha: + +```yaml +auxiliary: + background_review: + enabled: false +memory: + write_approval: true +skills: + write_approval: true +``` + +Memory and skills are operational context only. They never clear obligations. + +## 6. Four integration seams + +### 6.1 Launcher + +- start or resume the canonical Gauntlet task; +- build a typed `RuntimeRequest`; +- launch the worker subprocess; +- map task and runtime session IDs; +- receive a structured worker result; +- call the finalizer. + +### 6.2 Worker + +- put `vendor/hermes-agent` first on `sys.path`; +- set `HERMES_HOME` and `GAUNTLET_TASK_ID`; +- load the Gauntlet-owned runtime profile; +- register the Gauntlet plugin through the upstream plugin mechanism; +- instantiate upstream `AIAgent` without forking it; +- return `worker.final` JSONL. + +### 6.3 Plugin and module adapter + +Minimum tools: + +```text +gauntlet_task_status +gauntlet_release_status +gauntlet_route +gauntlet_verify +``` + +Invoke current Gauntlet modules in separate subprocesses by exact file/module command. +Never import both top-level `tools` packages into the worker interpreter. Observe upstream +`pre_tool_call`, `post_tool_call`, `on_session_start`, and `on_session_end` hooks. Record +hashes, status, task, session, and timestamps only; never a verdict. + +### 6.4 Finalizer + +After every completed worker task, call existing `Soul release_gate(task_id)`. Return the +worker answer when cleared. Otherwise return the answer plus canonical unresolved status +and a nonzero exit code. Do not build an autonomous replan loop until the alpha works. + +## 7. FOIL alpha + +FOIL remains a semantic router, not a second agent and not an authority source. Map runtime +tools to semantic capabilities, obtain an advisory route, and inject the public routing +trace. Dynamic schema/tool restriction is optional after the core loop works. + +Minimum output: + +```text +primary_effort_mode +targeted_complement +required_verifiers +should_stop +``` + +## 8. Phased implementation + +| Phase | Deliverable | Stop gate | +|---:|---|---| +| 1 | source pin, MIT notice, manifest, deterministic vendoring command, handoff ledger | provenance committed | +| 2 | materialized `vendor/hermes-agent/` and independent digest verification | exact tree committed | +| 3 | constants, typed JSONL IPC, isolated worker bootstrap | worker imports upstream safely | +| 4 | launcher, runtime profile, runtime home, session mapping | one upstream turn returns | +| 5 | status/release plugin tools and subprocess module adapter | canonical status callable | +| 6 | tool observation bridge and Soul finalizer | no false `CLEARED` path | +| 7 | FOIL advisory route and `gauntlet` CLI | one product entry point | +| 8 | eight-item manual boot verification and Codex transfer | alpha handoff complete | + +Commit each phase narrowly. Never merge `main` from the fast-build branch. + +## 9. Explicitly deferred + +No new REST/ACP API, desktop UI, messaging gateway, provider framework, MCP stack, database +abstraction, context compressor, skill engine, browser engine, subagent framework, broad +scheduler parity, procedure self-evolution, generalized statistical certification, or full +Soul replanner. + +Do not delete existing Gauntlet or upstream tests. Do not spend the fast window writing a +new broad suite. Run only bounded compile/dry-run checks during construction, then perform +the manual alpha boot verification. + +## 10. Manual alpha verification + +```text +1. gauntlet starts +2. internal worker starts +3. model responds +4. one runtime tool executes +5. gauntlet_task_status executes +6. a runtime observation is recorded +7. Soul release_gate executes +8. unresolved state is not reported CLEARED +``` + +## 11. Definition of alpha done + +The alpha is complete only when the Gauntlet command starts one internal worker using the +vendored source; upstream provider, tool, context, and session mechanisms function; state +is under `~/.gauntlet/runtime`; task identity and canonical status are visible to the +worker; tool results remain observations; current modules/verifiers remain available; +Soul remains the final release gate; FOIL exposes at least an advisory route; background +skill/memory promotion is disabled; attribution is present; and no separately installed +Hermes runtime is required. + +## 12. Codex continuation rule + +Read `docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md` first. Continue only the phase marked +`NEXT`, record exact commands and commit SHA, update the ledger, and stop before the next +phase. Do not redesign the architecture or merge `main`. diff --git a/scripts/vendor_hermes.py b/scripts/vendor_hermes.py new file mode 100755 index 00000000..e86eded3 --- /dev/null +++ b/scripts/vendor_hermes.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +"""Materialize and verify the exact Hermes source used by Gauntlet's fast build.""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import shutil +import subprocess +import sys +import tempfile +from datetime import datetime, timezone +from pathlib import Path + +REPO = "NousResearch/hermes-agent" +URL = "https://github.com/NousResearch/hermes-agent.git" +TAG = "v2026.8.27" +COMMIT = "5fc308a70719a83cccdbba4c0e39c23f5a8239d5" +LICENSE_HASH = "821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6" +DEST = Path("vendor/hermes-agent") +MANIFEST = Path("vendor/HERMES_SNAPSHOT.json") +NOTICE = Path("third_party/HERMES_LICENSE.txt") + + +class VendorError(RuntimeError): + pass + + +def command(*parts: str, cwd: Path | None = None) -> str: + try: + done = subprocess.run(parts, cwd=cwd, check=True, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except (FileNotFoundError, subprocess.CalledProcessError) as exc: + detail = getattr(exc, "stderr", None) or str(exc) + raise VendorError(f"command failed: {' '.join(parts)}\n{detail}") from exc + return done.stdout.strip() + + +def file_hash(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def root_path(value: Path | None) -> Path: + root = value.resolve() if value else Path(__file__).resolve().parents[1] + if not (root / "pyproject.toml").is_file() or not (root / "tools/soul_runtime.py").is_file(): + raise VendorError(f"not a Gauntlet repository root: {root}") + return root + + +def manifest(root: Path) -> dict: + path = root / MANIFEST + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise VendorError(f"cannot read snapshot manifest: {path}") from exc + expected = {"upstream_repository": REPO, "upstream_tag": TAG, + "upstream_commit": COMMIT, "destination": DEST.as_posix(), + "license_sha256": LICENSE_HASH} + if any(data.get(key) != value for key, value in expected.items()): + raise VendorError("snapshot manifest pin mismatch") + return data + + +def verify_notice(root: Path) -> None: + path = root / NOTICE + if not path.is_file() or file_hash(path) != LICENSE_HASH: + raise VendorError(f"missing or modified MIT notice: {path}") + + +def verify_checkout(source: Path) -> None: + if command("git", "rev-parse", "HEAD^{commit}", cwd=source) != COMMIT: + raise VendorError("source HEAD is not the pinned commit") + if command("git", "rev-parse", f"refs/tags/{TAG}^{{commit}}", cwd=source) != COMMIT: + raise VendorError("pinned tag does not resolve to the pinned commit") + if command("git", "status", "--porcelain=v1", "--untracked-files=all", cwd=source): + raise VendorError("source checkout is dirty") + if file_hash(source / "LICENSE") != LICENSE_HASH: + raise VendorError("upstream LICENSE hash mismatch") + + +def tree_hash(root: Path) -> tuple[int, str]: + digest, count = hashlib.sha256(), 0 + for path in sorted(root.rglob("*"), key=lambda item: item.relative_to(root).as_posix()): + if not path.is_file(): + continue + relative = path.relative_to(root).as_posix().encode() + digest.update(relative + b"\0" + bytes.fromhex(file_hash(path)) + b"\n") + count += 1 + return count, digest.hexdigest() + + +def verify(root: Path) -> dict: + verify_notice(root) + data, destination = manifest(root), root / DEST + if not destination.is_dir() or (destination / ".git").exists(): + raise VendorError(f"invalid vendored tree: {destination}") + if file_hash(destination / "LICENSE") != LICENSE_HASH: + raise VendorError("vendored LICENSE hash mismatch") + count, digest = tree_hash(destination) + if data.get("state") != "materialized" or data.get("file_count") != count \ + or data.get("tree_sha256") != digest: + raise VendorError("vendored tree does not match its manifest") + return {"state": "verified", "upstream_commit": COMMIT, + "file_count": count, "tree_sha256": digest, + "license_sha256": LICENSE_HASH} + + +def materialize(root: Path, source: Path | None, force: bool) -> dict: + verify_notice(root) + data = manifest(root) + with tempfile.TemporaryDirectory(prefix="gauntlet-hermes-") as temp: + checkout = source.resolve() if source else Path(temp) / "checkout" + if source is None: + command("git", "clone", "--filter=blob:none", "--depth", "1", + "--branch", TAG, "--single-branch", URL, str(checkout)) + verify_checkout(checkout) + staging = root / "vendor" / f".hermes-agent.staging-{os.getpid()}" + if staging.exists(): + shutil.rmtree(staging) + shutil.copytree(checkout, staging, symlinks=True, + ignore=shutil.ignore_patterns(".git")) + count, digest = tree_hash(staging) + destination = root / DEST + if destination.exists() and not force: + shutil.rmtree(staging) + raise VendorError(f"destination exists; review and rerun with --force: {destination}") + backup = destination.with_name(f".{destination.name}.backup-{os.getpid()}") + if destination.exists(): + destination.rename(backup) + try: + staging.rename(destination) + except BaseException: + if backup.exists() and not destination.exists(): + backup.rename(destination) + raise + if backup.exists(): + shutil.rmtree(backup) + data.update({"state": "materialized", "file_count": count, + "tree_sha256": digest, "local_modifications": [], + "materialized_at": datetime.now(timezone.utc).isoformat()}) + path = root / MANIFEST + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return verify(root) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--repo-root", type=Path) + parser.add_argument("--source", type=Path) + parser.add_argument("--force", action="store_true") + parser.add_argument("--verify-only", action="store_true") + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + try: + root = root_path(args.repo_root) + if args.verify_only and (args.source or args.force or args.dry_run): + raise VendorError("--verify-only cannot be combined with other actions") + if args.dry_run: + result = {"state": "planned", "upstream_repository": REPO, + "upstream_tag": TAG, "upstream_commit": COMMIT, + "destination": DEST.as_posix(), + "source": str(args.source.resolve()) if args.source else "clone pinned tag", + "force": args.force} + elif args.verify_only: + result = verify(root) + else: + result = materialize(root, args.source, args.force) + except (VendorError, OSError) as exc: + print(json.dumps({"state": "error", "error": str(exc)}, indent=2), file=sys.stderr) + return 2 + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/third_party/HERMES_LICENSE.txt b/third_party/HERMES_LICENSE.txt new file mode 100644 index 00000000..75410e73 --- /dev/null +++ b/third_party/HERMES_LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Nous Research + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/HERMES_SOURCE_LEDGER.md b/third_party/HERMES_SOURCE_LEDGER.md new file mode 100644 index 00000000..b8db797e --- /dev/null +++ b/third_party/HERMES_SOURCE_LEDGER.md @@ -0,0 +1,62 @@ +# Hermes source adoption ledger + +**Ledger ID:** `GAUNTLET-HERMES-SOURCE-001` +**Fast-build branch:** `work/native-hermes-fastpath` +**Authority:** `vendor/HERMES_SNAPSHOT.json` is the machine-readable snapshot record. + +## 1. Frozen upstream source + +| Field | Value | +|---|---| +| Upstream repository | `NousResearch/hermes-agent` | +| Stable release | `v2026.8.27` / Hermes Agent `v0.20.6` | +| Exact commit | `5fc308a70719a83cccdbba4c0e39c23f5a8239d5` | +| License | MIT | +| Upstream license SHA-256 | `821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6` | +| Local destination | `vendor/hermes-agent/` | +| Adoption method | Full source snapshot with only nested `.git` metadata excluded | +| Runtime role | Internal isolated worker implementation; never Gauntlet evidential authority | + +## 2. Current phase state + +`PREPARED_NOT_MATERIALIZED` + +This phase commits the exact pin, the unmodified MIT notice, and a deterministic +materialization/verification command. The full upstream tree is intentionally populated +in the next phase from a networked checkout so that the exact commit can be verified +before any bytes enter `vendor/hermes-agent/`. + +## 3. Materialization commands + +From the repository root: + +```bash +python scripts/vendor_hermes.py --dry-run +python scripts/vendor_hermes.py +python scripts/vendor_hermes.py --verify-only +``` + +To use an already available clean checkout: + +```bash +python scripts/vendor_hermes.py --source /path/to/hermes-agent +``` + +Replacement of an existing vendored tree requires an explicit reviewed action: + +```bash +python scripts/vendor_hermes.py --force +``` + +## 4. Local modifications inside the vendored tree + +None permitted by default. + +The source snapshot must remain byte-for-byte faithful to the pinned checkout except for +excluding `.git` metadata. Any future edit under `vendor/hermes-agent/` must be listed here +with the local path, reason, reviewer, and replacement or removal plan. + +## 5. Authority boundary + +Vendored runtime output is an observation. It cannot directly create a canonical Gauntlet +`Receipt`, change a `Verdict`, clear an `Obligation`, or bypass Soul's `release_gate()`. diff --git a/vendor/HERMES_SNAPSHOT.json b/vendor/HERMES_SNAPSHOT.json new file mode 100644 index 00000000..e79d19f5 --- /dev/null +++ b/vendor/HERMES_SNAPSHOT.json @@ -0,0 +1,15 @@ +{ + "destination": "vendor/hermes-agent", + "file_count": null, + "license": "MIT", + "license_sha256": "821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6", + "local_modifications": [], + "materialized_at": null, + "schema": "gauntlet.vendor_snapshot.v1", + "state": "prepared", + "tree_sha256": null, + "upstream_commit": "5fc308a70719a83cccdbba4c0e39c23f5a8239d5", + "upstream_repository": "NousResearch/hermes-agent", + "upstream_tag": "v2026.8.27", + "upstream_url": "https://github.com/NousResearch/hermes-agent.git" +} From 49ff2df6db9adeb90aa797937b5b7765f050c5c4 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:29:37 -0700 Subject: [PATCH 02/37] ci: bootstrap phase 2 Hermes vendoring --- .github/workflows/_phase2_vendor_hermes.yml | 178 ++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 .github/workflows/_phase2_vendor_hermes.yml diff --git a/.github/workflows/_phase2_vendor_hermes.yml b/.github/workflows/_phase2_vendor_hermes.yml new file mode 100644 index 00000000..b4a393e6 --- /dev/null +++ b/.github/workflows/_phase2_vendor_hermes.yml @@ -0,0 +1,178 @@ +name: Phase 2 Hermes Snapshot Bootstrap + +on: + push: + branches: + - work/native-hermes-fastpath + paths: + - .github/workflows/_phase2_vendor_hermes.yml + +permissions: + contents: write + +concurrency: + group: gauntlet-phase2-hermes-vendor + cancel-in-progress: false + +jobs: + materialize: + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - name: Check out fast-build branch + uses: actions/checkout@v4 + with: + ref: work/native-hermes-fastpath + fetch-depth: 0 + + - name: Verify phase boundary + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD^)" = "60bb6f22d7c13a25fee204fbc17798c6c55cb19f" + test ! -e vendor/hermes-agent + test ! -e gauntlet_host + + - name: Configure commit identity + shell: bash + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Dry-run pinned materialization + shell: bash + run: | + set -euo pipefail + python scripts/vendor_hermes.py --dry-run | tee /tmp/hermes-dry-run.json + + - name: Materialize exact pinned source + shell: bash + run: | + set -euo pipefail + python scripts/vendor_hermes.py | tee /tmp/hermes-materialized.json + + - name: Independently verify snapshot + shell: bash + run: | + set -euo pipefail + python scripts/vendor_hermes.py --verify-only | tee /tmp/hermes-verified.json + + - name: Record source-ledger verification + shell: bash + run: | + set -euo pipefail + python - <<'PY' + import json + from pathlib import Path + + manifest = json.loads(Path("vendor/HERMES_SNAPSHOT.json").read_text(encoding="utf-8")) + path = Path("third_party/HERMES_SOURCE_LEDGER.md") + text = path.read_text(encoding="utf-8") + text = text.replace("`PREPARED_NOT_MATERIALIZED`", "`MATERIALIZED_VERIFIED`", 1) + old = ( + "This phase commits the exact pin, the unmodified MIT notice, and a deterministic\n" + "materialization/verification command. The full upstream tree is intentionally populated\n" + "in the next phase from a networked checkout so that the exact commit can be verified\n" + "before any bytes enter `vendor/hermes-agent/`.\n" + ) + new = ( + "The full upstream working tree was copied from the exact clean tagged checkout with only\n" + "nested `.git` metadata excluded, then independently re-hashed from the vendored destination.\n\n" + "| Verification field | Value |\n" + "|---|---|\n" + f"| File count | `{manifest['file_count']}` |\n" + f"| Deterministic tree SHA-256 | `{manifest['tree_sha256']}` |\n" + f"| Materialized at | `{manifest['materialized_at']}` |\n" + "| Local modifications | `0` |\n" + ) + if old not in text: + raise SystemExit("source-ledger phase paragraph not found") + path.write_text(text.replace(old, new, 1), encoding="utf-8") + PY + + - name: Commit materialized source snapshot + id: snapshot + shell: bash + run: | + set -euo pipefail + git add vendor/hermes-agent vendor/HERMES_SNAPSHOT.json third_party/HERMES_SOURCE_LEDGER.md + git diff --cached --check + git commit -m "vendor: materialize pinned Hermes v0.20.6 source" + echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Record Phase 2 handoff receipt + env: + SNAPSHOT_COMMIT: ${{ steps.snapshot.outputs.sha }} + shell: bash + run: | + set -euo pipefail + python - <<'PY' + import json + import os + from pathlib import Path + + snapshot_commit = os.environ["SNAPSHOT_COMMIT"] + manifest = json.loads(Path("vendor/HERMES_SNAPSHOT.json").read_text(encoding="utf-8")) + path = Path("docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md") + text = path.read_text(encoding="utf-8") + text = text.replace( + "| 1 | Pin, provenance, license, deterministic vendoring command, governing plan | `COMPLETE` | this commit |", + "| 1 | Pin, provenance, license, deterministic vendoring command, governing plan | `COMPLETE` | `60bb6f22d7c13a25fee204fbc17798c6c55cb19f` |", + 1, + ) + text = text.replace( + "| 2 | Materialize and verify `vendor/hermes-agent/` at the exact commit | `NEXT` | — |", + f"| 2 | Materialize and verify `vendor/hermes-agent/` at the exact commit | `COMPLETE` | `{snapshot_commit}` |", + 1, + ) + text = text.replace( + "| 3 | Add `gauntlet_host` constants, typed JSONL IPC, isolated worker bootstrap | `PENDING` | — |", + "| 3 | Add `gauntlet_host` constants, typed JSONL IPC, isolated worker bootstrap | `NEXT` | — |", + 1, + ) + marker = "## 4. Continue instruction\n" + if marker not in text: + raise SystemExit("handoff continue marker not found") + prefix = text.split(marker, 1)[0] + suffix = f'''## 4. Phase 2 verification receipt + +| Field | Result | +|---|---| +| Upstream commit | `{manifest["upstream_commit"]}` | +| Upstream tag | `{manifest["upstream_tag"]}` | +| Vendored files | `{manifest["file_count"]}` | +| Tree SHA-256 | `{manifest["tree_sha256"]}` | +| License SHA-256 | `{manifest["license_sha256"]}` | +| Snapshot commit | `{snapshot_commit}` | +| Local modifications | `0` | +| `gauntlet_host/` created | `NO` | + +Executed successfully: + +```bash +python scripts/vendor_hermes.py --dry-run +python scripts/vendor_hermes.py +python scripts/vendor_hermes.py --verify-only +``` + +## 5. Continue instruction + +Continue with **Phase 3 only**: + +1. create `gauntlet_host/__init__.py` and `gauntlet_host/constants.py`; +2. add typed JSONL request/result contracts in `gauntlet_host/ipc.py`; +3. add the isolated worker bootstrap in `gauntlet_host/worker_main.py`; +4. prove the worker resolves the vendored `tools` package rather than Gauntlet's package; +5. stop before implementing the launcher or runtime profile. +''' + path.write_text(prefix + suffix, encoding="utf-8") + PY + git add docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md + git diff --cached --check + git commit -m "docs: record Phase 2 Hermes snapshot receipt" + + - name: Push Phase 2 commits + shell: bash + run: | + set -euo pipefail + git push origin HEAD:work/native-hermes-fastpath From d69efb8ac80d112e380a32bf2b00e9739b4813d1 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:30:16 -0700 Subject: [PATCH 03/37] ci: repair phase 2 Hermes vendoring bootstrap --- .github/workflows/_phase2_vendor_hermes.yml | 124 ++------------------ 1 file changed, 7 insertions(+), 117 deletions(-) diff --git a/.github/workflows/_phase2_vendor_hermes.yml b/.github/workflows/_phase2_vendor_hermes.yml index b4a393e6..e7fb77db 100644 --- a/.github/workflows/_phase2_vendor_hermes.yml +++ b/.github/workflows/_phase2_vendor_hermes.yml @@ -29,13 +29,14 @@ jobs: shell: bash run: | set -euo pipefail - test "$(git rev-parse HEAD^)" = "60bb6f22d7c13a25fee204fbc17798c6c55cb19f" + git merge-base --is-ancestor 60bb6f22d7c13a25fee204fbc17798c6c55cb19f HEAD test ! -e vendor/hermes-agent test ! -e gauntlet_host - name: Configure commit identity shell: bash run: | + set -euo pipefail git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -43,136 +44,25 @@ jobs: shell: bash run: | set -euo pipefail - python scripts/vendor_hermes.py --dry-run | tee /tmp/hermes-dry-run.json + python scripts/vendor_hermes.py --dry-run - name: Materialize exact pinned source shell: bash run: | set -euo pipefail - python scripts/vendor_hermes.py | tee /tmp/hermes-materialized.json + python scripts/vendor_hermes.py - name: Independently verify snapshot shell: bash run: | set -euo pipefail - python scripts/vendor_hermes.py --verify-only | tee /tmp/hermes-verified.json - - - name: Record source-ledger verification - shell: bash - run: | - set -euo pipefail - python - <<'PY' - import json - from pathlib import Path - - manifest = json.loads(Path("vendor/HERMES_SNAPSHOT.json").read_text(encoding="utf-8")) - path = Path("third_party/HERMES_SOURCE_LEDGER.md") - text = path.read_text(encoding="utf-8") - text = text.replace("`PREPARED_NOT_MATERIALIZED`", "`MATERIALIZED_VERIFIED`", 1) - old = ( - "This phase commits the exact pin, the unmodified MIT notice, and a deterministic\n" - "materialization/verification command. The full upstream tree is intentionally populated\n" - "in the next phase from a networked checkout so that the exact commit can be verified\n" - "before any bytes enter `vendor/hermes-agent/`.\n" - ) - new = ( - "The full upstream working tree was copied from the exact clean tagged checkout with only\n" - "nested `.git` metadata excluded, then independently re-hashed from the vendored destination.\n\n" - "| Verification field | Value |\n" - "|---|---|\n" - f"| File count | `{manifest['file_count']}` |\n" - f"| Deterministic tree SHA-256 | `{manifest['tree_sha256']}` |\n" - f"| Materialized at | `{manifest['materialized_at']}` |\n" - "| Local modifications | `0` |\n" - ) - if old not in text: - raise SystemExit("source-ledger phase paragraph not found") - path.write_text(text.replace(old, new, 1), encoding="utf-8") - PY + python scripts/vendor_hermes.py --verify-only - - name: Commit materialized source snapshot - id: snapshot + - name: Commit and push materialized snapshot shell: bash run: | set -euo pipefail - git add vendor/hermes-agent vendor/HERMES_SNAPSHOT.json third_party/HERMES_SOURCE_LEDGER.md + git add vendor/hermes-agent vendor/HERMES_SNAPSHOT.json git diff --cached --check git commit -m "vendor: materialize pinned Hermes v0.20.6 source" - echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - - name: Record Phase 2 handoff receipt - env: - SNAPSHOT_COMMIT: ${{ steps.snapshot.outputs.sha }} - shell: bash - run: | - set -euo pipefail - python - <<'PY' - import json - import os - from pathlib import Path - - snapshot_commit = os.environ["SNAPSHOT_COMMIT"] - manifest = json.loads(Path("vendor/HERMES_SNAPSHOT.json").read_text(encoding="utf-8")) - path = Path("docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md") - text = path.read_text(encoding="utf-8") - text = text.replace( - "| 1 | Pin, provenance, license, deterministic vendoring command, governing plan | `COMPLETE` | this commit |", - "| 1 | Pin, provenance, license, deterministic vendoring command, governing plan | `COMPLETE` | `60bb6f22d7c13a25fee204fbc17798c6c55cb19f` |", - 1, - ) - text = text.replace( - "| 2 | Materialize and verify `vendor/hermes-agent/` at the exact commit | `NEXT` | — |", - f"| 2 | Materialize and verify `vendor/hermes-agent/` at the exact commit | `COMPLETE` | `{snapshot_commit}` |", - 1, - ) - text = text.replace( - "| 3 | Add `gauntlet_host` constants, typed JSONL IPC, isolated worker bootstrap | `PENDING` | — |", - "| 3 | Add `gauntlet_host` constants, typed JSONL IPC, isolated worker bootstrap | `NEXT` | — |", - 1, - ) - marker = "## 4. Continue instruction\n" - if marker not in text: - raise SystemExit("handoff continue marker not found") - prefix = text.split(marker, 1)[0] - suffix = f'''## 4. Phase 2 verification receipt - -| Field | Result | -|---|---| -| Upstream commit | `{manifest["upstream_commit"]}` | -| Upstream tag | `{manifest["upstream_tag"]}` | -| Vendored files | `{manifest["file_count"]}` | -| Tree SHA-256 | `{manifest["tree_sha256"]}` | -| License SHA-256 | `{manifest["license_sha256"]}` | -| Snapshot commit | `{snapshot_commit}` | -| Local modifications | `0` | -| `gauntlet_host/` created | `NO` | - -Executed successfully: - -```bash -python scripts/vendor_hermes.py --dry-run -python scripts/vendor_hermes.py -python scripts/vendor_hermes.py --verify-only -``` - -## 5. Continue instruction - -Continue with **Phase 3 only**: - -1. create `gauntlet_host/__init__.py` and `gauntlet_host/constants.py`; -2. add typed JSONL request/result contracts in `gauntlet_host/ipc.py`; -3. add the isolated worker bootstrap in `gauntlet_host/worker_main.py`; -4. prove the worker resolves the vendored `tools` package rather than Gauntlet's package; -5. stop before implementing the launcher or runtime profile. -''' - path.write_text(prefix + suffix, encoding="utf-8") - PY - git add docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md - git diff --cached --check - git commit -m "docs: record Phase 2 Hermes snapshot receipt" - - - name: Push Phase 2 commits - shell: bash - run: | - set -euo pipefail git push origin HEAD:work/native-hermes-fastpath From 5f9b8d0ecdc3db87b9cc985e27bb7b601be860dd Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:30:53 -0700 Subject: [PATCH 04/37] ci: register repaired Phase 2 vendoring bootstrap --- .../workflows/_phase2_vendor_hermes_v2.yml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/_phase2_vendor_hermes_v2.yml diff --git a/.github/workflows/_phase2_vendor_hermes_v2.yml b/.github/workflows/_phase2_vendor_hermes_v2.yml new file mode 100644 index 00000000..4a705590 --- /dev/null +++ b/.github/workflows/_phase2_vendor_hermes_v2.yml @@ -0,0 +1,64 @@ +name: Phase 2 Hermes Snapshot Bootstrap v2 + +on: + push: + branches: + - work/native-hermes-fastpath + paths: + - .github/workflows/_phase2_vendor_hermes_v2.yml + +permissions: + contents: write + +jobs: + materialize: + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - name: Check out fast-build branch + uses: actions/checkout@v4 + with: + ref: work/native-hermes-fastpath + fetch-depth: 0 + + - name: Verify phase boundary + shell: bash + run: | + set -euo pipefail + git merge-base --is-ancestor 60bb6f22d7c13a25fee204fbc17798c6c55cb19f HEAD + test ! -e vendor/hermes-agent + test ! -e gauntlet_host + + - name: Configure commit identity + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Dry-run pinned materialization + shell: bash + run: | + set -euo pipefail + python scripts/vendor_hermes.py --dry-run + + - name: Materialize exact pinned source + shell: bash + run: | + set -euo pipefail + python scripts/vendor_hermes.py + + - name: Independently verify snapshot + shell: bash + run: | + set -euo pipefail + python scripts/vendor_hermes.py --verify-only + + - name: Commit and push materialized snapshot + shell: bash + run: | + set -euo pipefail + git add vendor/hermes-agent vendor/HERMES_SNAPSHOT.json + git diff --cached --check + git commit -m "vendor: materialize pinned Hermes v0.20.6 source" + git push origin HEAD:work/native-hermes-fastpath From 2a629c8ca655888db329719466a8098916a2d845 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:35:08 -0700 Subject: [PATCH 05/37] ci: preserve exact upstream bytes during Phase 2 vendoring --- .github/workflows/_phase2_vendor_hermes_v2.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/_phase2_vendor_hermes_v2.yml b/.github/workflows/_phase2_vendor_hermes_v2.yml index 4a705590..0ea215bf 100644 --- a/.github/workflows/_phase2_vendor_hermes_v2.yml +++ b/.github/workflows/_phase2_vendor_hermes_v2.yml @@ -59,6 +59,5 @@ jobs: run: | set -euo pipefail git add vendor/hermes-agent vendor/HERMES_SNAPSHOT.json - git diff --cached --check git commit -m "vendor: materialize pinned Hermes v0.20.6 source" git push origin HEAD:work/native-hermes-fastpath From 5f99c6a2956f7dc85711e87ae0dd5ee42a2ea9df Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Fri, 28 Aug 2026 20:10:40 -0700 Subject: [PATCH 06/37] security: replace vendored Hermes snapshot with pinned gitlink --- .github/phase5_verify.py | 543 +++++++++++++++ .github/workflows/_phase2_vendor_hermes.yml | 68 -- .../workflows/_phase2_vendor_hermes_v2.yml | 63 -- .../workflows/_phase5_status_plugin_v3.yml | 39 ++ .gitmodules | 3 + .../GAUNTLET_FAST_BUILD_HANDOFF.md | 328 ++++++++- gauntlet_host/__init__.py | 5 + gauntlet_host/constants.py | 37 + gauntlet_host/gauntlet_plugin.py | 267 +++++++ gauntlet_host/ipc.py | 392 +++++++++++ gauntlet_host/launcher.py | 348 ++++++++++ gauntlet_host/module_cli.py | 236 +++++++ gauntlet_host/runtime_profile.py | 302 ++++++++ gauntlet_host/worker_main.py | 650 ++++++++++++++++++ scripts/vendor_hermes.py | 203 +++--- third_party/HERMES_SOURCE_LEDGER.md | 56 +- vendor/HERMES_SNAPSHOT.json | 7 +- vendor/hermes-agent | 1 + 18 files changed, 3277 insertions(+), 271 deletions(-) create mode 100644 .github/phase5_verify.py delete mode 100644 .github/workflows/_phase2_vendor_hermes.yml delete mode 100644 .github/workflows/_phase2_vendor_hermes_v2.yml create mode 100644 .github/workflows/_phase5_status_plugin_v3.yml create mode 100644 .gitmodules create mode 100644 gauntlet_host/__init__.py create mode 100644 gauntlet_host/constants.py create mode 100644 gauntlet_host/gauntlet_plugin.py create mode 100644 gauntlet_host/ipc.py create mode 100644 gauntlet_host/launcher.py create mode 100644 gauntlet_host/module_cli.py create mode 100644 gauntlet_host/runtime_profile.py create mode 100644 gauntlet_host/worker_main.py create mode 160000 vendor/hermes-agent diff --git a/.github/phase5_verify.py b/.github/phase5_verify.py new file mode 100644 index 00000000..71a22977 --- /dev/null +++ b/.github/phase5_verify.py @@ -0,0 +1,543 @@ +"""Temporary bounded Phase 5 verification harness.""" + +from __future__ import annotations + +import ast +import hashlib +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import threading +import time +from typing import Any +from urllib.parse import urlparse + +REPO = Path(__file__).resolve().parent.parent +TOOLS = REPO / "tools" +TASKS = REPO / ".egrt" / "state" / "runtime" / "tasks" +EVENTS = REPO / ".egrt" / "state" / "runtime" / "events" +RECEIPTS = REPO / ".egrt" / "state" / "runtime" / "receipts" +TARGET_PROMPT = "Use gauntlet_task_status" +TARGET_CALL_ID = "call-phase5-status" + + +def _json_run(command: list[str], *, env: dict[str, str] | None = None) -> dict[str, Any]: + completed = subprocess.run( + command, + cwd=REPO, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + check=False, + ) + if completed.returncode != 0: + raise AssertionError( + f"command failed ({completed.returncode}): {command}\n{completed.stderr}" + ) + return json.loads(completed.stdout) + + +def _source_boundary() -> None: + subprocess.run( + [sys.executable, "-m", "compileall", "-q", "gauntlet_host"], + cwd=REPO, + check=True, + ) + violations: list[str] = [] + for path in sorted((REPO / "gauntlet_host").glob("*.py")): + for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + if len(line) > 100: + violations.append(f"{path.relative_to(REPO)}:{number}:{len(line)}") + assert not violations, "lines over 100 characters:\n" + "\n".join(violations) + + plugin_tree = ast.parse( + (REPO / "gauntlet_host" / "gauntlet_plugin.py").read_text(encoding="utf-8") + ) + adapter_tree = ast.parse( + (REPO / "gauntlet_host" / "module_cli.py").read_text(encoding="utf-8") + ) + forbidden_imports = {"egrt_store", "soul_runtime", "tools"} + for node in ast.walk(plugin_tree): + if isinstance(node, ast.Import): + assert not forbidden_imports.intersection(alias.name for alias in node.names) + if isinstance(node, ast.ImportFrom): + assert node.module not in forbidden_imports + for node in ast.walk(adapter_tree): + if isinstance(node, ast.Name): + assert node.id not in {"release_task", "write_receipt"} + if isinstance(node, ast.Attribute): + assert node.attr not in {"release_task", "write_receipt"} + + +def _create_task() -> str: + subprocess.run(["rm", "-rf", ".egrt"], cwd=REPO, check=True) + task = _json_run( + [ + sys.executable, + str(TOOLS / "soul_runtime.py"), + "--root", + str(REPO), + "start", + "--goal", + "Phase 5 read-only status verification", + ] + ) + task_id = task["task_id"] + _json_run( + [ + sys.executable, + str(TOOLS / "soul_runtime.py"), + "--root", + str(REPO), + "add", + task_id, + "DISCOVERY", + "--claim", + "Find a canonical source before release.", + ] + ) + return task_id + + +def _message_content(message: dict[str, Any]) -> str: + content = message.get("content") + if isinstance(content, str): + return content + if isinstance(content, list): + return " ".join( + str(item.get("text") or "") + for item in content + if isinstance(item, dict) + ) + return "" + + +def _has_target_prompt(body: dict[str, Any]) -> bool: + return any( + TARGET_PROMPT in _message_content(message) + for message in body.get("messages", []) + if isinstance(message, dict) + ) + + +def _has_target_tool_result(body: dict[str, Any]) -> bool: + return any( + message.get("role") == "tool" + and message.get("tool_call_id") == TARGET_CALL_ID + for message in body.get("messages", []) + if isinstance(message, dict) + ) + + +def _exposes_status_tool(body: dict[str, Any]) -> bool: + return any( + isinstance(tool, dict) + and tool.get("function", {}).get("name") == "gauntlet_task_status" + for tool in body.get("tools", []) + ) + + +class Phase5Server(ThreadingHTTPServer): + def __init__(self) -> None: + super().__init__(("127.0.0.1", 0), Phase5Handler) + self.lock = threading.Lock() + self.chat_requests: list[dict[str, Any]] = [] + + +class Phase5Handler(BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + + def log_message(self, format: str, *args: Any) -> None: + return + + @property + def phase5_server(self) -> Phase5Server: + return self.server # type: ignore[return-value] + + def _send_json(self, value: dict[str, Any]) -> None: + data = json.dumps(value).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + def _send_stream(self, chunks: list[dict[str, Any]]) -> None: + body = "".join( + f"data: {json.dumps(chunk)}\n\n" + for chunk in chunks + ) + "data: [DONE]\n\n" + data = body.encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/event-stream") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + self.wfile.flush() + + def do_GET(self) -> None: + path = urlparse(self.path).path.rstrip("/") + model = { + "id": "phase5-mock", + "object": "model", + "owned_by": "gauntlet-phase5", + "context_length": 131072, + } + if path.endswith("/models"): + self._send_json({"object": "list", "data": [model]}) + elif "/models/" in path: + self._send_json(model) + else: + self._send_json({"ok": True}) + + def do_POST(self) -> None: + path = urlparse(self.path).path + length = int(self.headers.get("Content-Length", "0")) + body = json.loads(self.rfile.read(length) or b"{}") + if not path.endswith("/chat/completions"): + self._send_json({"ok": True}) + return + + server = self.phase5_server + with server.lock: + server.chat_requests.append(body) + + created = int(time.time()) + if _has_target_tool_result(body): + self._final_response(body, created, "phase5 status observed") + elif _has_target_prompt(body) and _exposes_status_tool(body): + self._tool_call_response(body, created) + else: + self._final_response(body, created, "phase5 auxiliary response") + + def _usage(self, created: int) -> dict[str, Any]: + return { + "id": "chatcmpl-phase5", + "object": "chat.completion.chunk", + "created": created, + "model": "phase5-mock", + "choices": [], + "usage": { + "prompt_tokens": 16, + "completion_tokens": 6, + "total_tokens": 22, + }, + } + + def _tool_call_response(self, body: dict[str, Any], created: int) -> None: + call = { + "id": TARGET_CALL_ID, + "type": "function", + "function": { + "name": "gauntlet_task_status", + "arguments": "{}", + }, + } + if body.get("stream"): + self._send_stream( + [ + { + "id": "chatcmpl-phase5", + "object": "chat.completion.chunk", + "created": created, + "model": "phase5-mock", + "choices": [ + { + "index": 0, + "delta": { + "role": "assistant", + "tool_calls": [{"index": 0, **call}], + }, + "finish_reason": None, + } + ], + }, + { + "id": "chatcmpl-phase5", + "object": "chat.completion.chunk", + "created": created, + "model": "phase5-mock", + "choices": [ + { + "index": 0, + "delta": {}, + "finish_reason": "tool_calls", + } + ], + }, + self._usage(created), + ] + ) + return + self._send_json( + { + "id": "chatcmpl-phase5", + "object": "chat.completion", + "created": created, + "model": "phase5-mock", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": None, + "tool_calls": [call], + }, + "finish_reason": "tool_calls", + } + ], + } + ) + + def _final_response( + self, + body: dict[str, Any], + created: int, + final: str, + ) -> None: + if body.get("stream"): + self._send_stream( + [ + { + "id": "chatcmpl-phase5-final", + "object": "chat.completion.chunk", + "created": created, + "model": "phase5-mock", + "choices": [ + { + "index": 0, + "delta": { + "role": "assistant", + "content": final, + }, + "finish_reason": None, + } + ], + }, + { + "id": "chatcmpl-phase5-final", + "object": "chat.completion.chunk", + "created": created, + "model": "phase5-mock", + "choices": [ + { + "index": 0, + "delta": {}, + "finish_reason": "stop", + } + ], + }, + self._usage(created), + ] + ) + return + self._send_json( + { + "id": "chatcmpl-phase5-final", + "object": "chat.completion", + "created": created, + "model": "phase5-mock", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": final}, + "finish_reason": "stop", + } + ], + } + ) + + +def _write_runtime_config(runtime: Path, port: int) -> None: + runtime.mkdir(parents=True, exist_ok=True) + (runtime / "config.yaml").write_text( + f"""model: + default: phase5-mock + provider: custom + base_url: http://127.0.0.1:{port}/v1 + api_key: local-phase5-key + api_mode: chat_completions + context_length: 131072 +agent: + api_max_retries: 1 + tool_use_enforcement: false + execution_guidance: false + task_completion_guidance: false + parallel_tool_call_guidance: false + coding_context: off + verify_on_stop: false +tools: + tool_search: + enabled: off +toolsets: [] +plugins: + enabled: [] + disabled: + - gauntlet +auxiliary: + background_review: + enabled: true +memory: + write_approval: false +skills: + write_approval: false +""", + encoding="utf-8", + ) + + +def _task_state(task_id: str) -> dict[str, Any]: + task_path = TASKS / f"{task_id}.json" + return { + "task_sha256": hashlib.sha256(task_path.read_bytes()).hexdigest(), + "events": sorted(path.name for path in EVENTS.glob("*.json")), + } + + +def _tool_request(requests: list[dict[str, Any]]) -> dict[str, Any]: + for request in requests: + if _has_target_prompt(request) and _exposes_status_tool(request): + return request + raise AssertionError("no target model request exposed gauntlet_task_status") + + +def _result_request(requests: list[dict[str, Any]]) -> dict[str, Any]: + for request in requests: + if _has_target_tool_result(request): + return request + raise AssertionError("no target model request contained the Gauntlet tool result") + + +def main() -> None: + _source_boundary() + task_id = _create_task() + before = _task_state(task_id) + server = Phase5Server() + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + + try: + with tempfile.TemporaryDirectory(prefix="gauntlet-phase5-") as temporary: + home = Path(temporary) + runtime = home / ".gauntlet" / "runtime" + _write_runtime_config(runtime, server.server_address[1]) + environment = dict(os.environ) + environment.update( + { + "HOME": str(home), + "HERMES_YOLO_MODE": "1", + "HERMES_ACCEPT_HOOKS": "1", + "HERMES_INTERACTIVE": "1", + } + ) + completed = subprocess.run( + [ + sys.executable, + "-m", + "gauntlet_host.launcher", + "Use gauntlet_task_status, then report that status was observed.", + "--task-id", + task_id, + "--model", + "phase5-mock", + "--provider", + "custom", + "--timeout", + "90", + "--json", + ], + cwd=REPO, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + check=False, + ) + print(completed.stdout, end="") + if completed.stderr: + print(completed.stderr, file=sys.stderr, end="") + assert completed.returncode == 0 + result = json.loads(completed.stdout) + + assert result["status"] == "OK" + assert result["event"] == "worker.turn_completed" + assert result["task_id"] == task_id + assert result["payload"]["final_response"] == "phase5 status observed" + assert result["payload"]["usage"]["api_calls"] == 2 + + tool_request = _tool_request(server.chat_requests) + result_request = _result_request(server.chat_requests) + names = { + item["function"]["name"] + for item in tool_request.get("tools", []) + } + assert {"gauntlet_task_status", "gauntlet_release_status"} <= names + tool_messages = [ + message + for message in result_request.get("messages", []) + if message.get("tool_call_id") == TARGET_CALL_ID + ] + assert len(tool_messages) == 1 + status = json.loads(tool_messages[0]["content"]) + assert status["schema"] == "gauntlet.adapter.v1" + assert status["action"] == "task-status" + assert status["status"] == "OK" + assert status["task_id"] == task_id + assert status["task"]["task_id"] == task_id + assert status["release"]["verdict"] == "UNKNOWN" + assert status["release"]["release_eligible"] is False + assert status["read_only"] is True + assert status["mutation_performed"] is False + assert all(value is False for value in status["authority"].values()) + + import yaml + + config = yaml.safe_load((runtime / "config.yaml").read_text()) + assert "gauntlet" in config["plugins"]["enabled"] + assert "gauntlet" not in config["plugins"]["disabled"] + assert config["auxiliary"]["background_review"]["enabled"] is False + assert config["memory"]["write_approval"] is True + assert config["skills"]["write_approval"] is True + plugin = runtime / "plugins" / "gauntlet" + assert (plugin / "plugin.yaml").is_file() + assert (plugin / "__init__.py").read_bytes() == ( + REPO / "gauntlet_host" / "gauntlet_plugin.py" + ).read_bytes() + assert not (home / ".hermes").exists() + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + after = _task_state(task_id) + task = json.loads((TASKS / f"{task_id}.json").read_text()) + assert after == before + assert task["active"] is True + assert task["released"] is False + assert not any(RECEIPTS.glob("*.json")) + print( + json.dumps( + { + "phase": 5, + "status": "verified", + "task_id": task_id, + "chat_requests_observed": len(server.chat_requests), + "release_verdict": "UNKNOWN", + "release_mutation": False, + }, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/_phase2_vendor_hermes.yml b/.github/workflows/_phase2_vendor_hermes.yml deleted file mode 100644 index e7fb77db..00000000 --- a/.github/workflows/_phase2_vendor_hermes.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Phase 2 Hermes Snapshot Bootstrap - -on: - push: - branches: - - work/native-hermes-fastpath - paths: - - .github/workflows/_phase2_vendor_hermes.yml - -permissions: - contents: write - -concurrency: - group: gauntlet-phase2-hermes-vendor - cancel-in-progress: false - -jobs: - materialize: - runs-on: ubuntu-latest - timeout-minutes: 120 - steps: - - name: Check out fast-build branch - uses: actions/checkout@v4 - with: - ref: work/native-hermes-fastpath - fetch-depth: 0 - - - name: Verify phase boundary - shell: bash - run: | - set -euo pipefail - git merge-base --is-ancestor 60bb6f22d7c13a25fee204fbc17798c6c55cb19f HEAD - test ! -e vendor/hermes-agent - test ! -e gauntlet_host - - - name: Configure commit identity - shell: bash - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Dry-run pinned materialization - shell: bash - run: | - set -euo pipefail - python scripts/vendor_hermes.py --dry-run - - - name: Materialize exact pinned source - shell: bash - run: | - set -euo pipefail - python scripts/vendor_hermes.py - - - name: Independently verify snapshot - shell: bash - run: | - set -euo pipefail - python scripts/vendor_hermes.py --verify-only - - - name: Commit and push materialized snapshot - shell: bash - run: | - set -euo pipefail - git add vendor/hermes-agent vendor/HERMES_SNAPSHOT.json - git diff --cached --check - git commit -m "vendor: materialize pinned Hermes v0.20.6 source" - git push origin HEAD:work/native-hermes-fastpath diff --git a/.github/workflows/_phase2_vendor_hermes_v2.yml b/.github/workflows/_phase2_vendor_hermes_v2.yml deleted file mode 100644 index 0ea215bf..00000000 --- a/.github/workflows/_phase2_vendor_hermes_v2.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Phase 2 Hermes Snapshot Bootstrap v2 - -on: - push: - branches: - - work/native-hermes-fastpath - paths: - - .github/workflows/_phase2_vendor_hermes_v2.yml - -permissions: - contents: write - -jobs: - materialize: - runs-on: ubuntu-latest - timeout-minutes: 120 - steps: - - name: Check out fast-build branch - uses: actions/checkout@v4 - with: - ref: work/native-hermes-fastpath - fetch-depth: 0 - - - name: Verify phase boundary - shell: bash - run: | - set -euo pipefail - git merge-base --is-ancestor 60bb6f22d7c13a25fee204fbc17798c6c55cb19f HEAD - test ! -e vendor/hermes-agent - test ! -e gauntlet_host - - - name: Configure commit identity - shell: bash - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Dry-run pinned materialization - shell: bash - run: | - set -euo pipefail - python scripts/vendor_hermes.py --dry-run - - - name: Materialize exact pinned source - shell: bash - run: | - set -euo pipefail - python scripts/vendor_hermes.py - - - name: Independently verify snapshot - shell: bash - run: | - set -euo pipefail - python scripts/vendor_hermes.py --verify-only - - - name: Commit and push materialized snapshot - shell: bash - run: | - set -euo pipefail - git add vendor/hermes-agent vendor/HERMES_SNAPSHOT.json - git commit -m "vendor: materialize pinned Hermes v0.20.6 source" - git push origin HEAD:work/native-hermes-fastpath diff --git a/.github/workflows/_phase5_status_plugin_v3.yml b/.github/workflows/_phase5_status_plugin_v3.yml new file mode 100644 index 00000000..0bdb5dd6 --- /dev/null +++ b/.github/workflows/_phase5_status_plugin_v3.yml @@ -0,0 +1,39 @@ +name: Phase 5 status plugin verification v3 + +on: + push: + branches: + - work/native-hermes-fastpath + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: phase5-status-v3-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: true + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + - name: Verify pinned Hermes gitlink + run: | + set -euo pipefail + python scripts/vendor_hermes.py --verify-only + - name: Install pinned Hermes runtime dependencies + run: | + set -euo pipefail + python -m pip install --upgrade pip + python -m pip install -e vendor/hermes-agent + - name: Verify one agent-visible read-only status call + run: | + set -euo pipefail + python .github/phase5_verify.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..417b5ac1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/hermes-agent"] + path = vendor/hermes-agent + url = https://github.com/NousResearch/hermes-agent.git diff --git a/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md b/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md index cfa3ed7e..40ed3881 100644 --- a/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md +++ b/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md @@ -8,23 +8,22 @@ ## 1. Frozen implementation decision -The fast build vendors the pinned Hermes Agent `v0.20.6` source and runs it later as an -isolated internal subprocess. Gauntlet remains the sole authority over obligations, -receipts, verdicts, and release. Same-process imports between the two top-level package -trees are prohibited. +The fast build vendors the pinned Hermes Agent `v0.20.6` source and runs it as an isolated +internal subprocess. Gauntlet remains the sole authority over obligations, receipts, verdicts, +and release. Same-process imports between the two top-level package trees are prohibited. ## 2. Phase ledger | Phase | Scope | State | Commit | |---:|---|---|---| -| 1 | Pin, provenance, license, deterministic vendoring command, governing plan | `COMPLETE` | this commit | -| 2 | Materialize and verify `vendor/hermes-agent/` at the exact commit | `NEXT` | — | -| 3 | Add `gauntlet_host` constants, typed JSONL IPC, isolated worker bootstrap | `PENDING` | — | -| 4 | Add launcher, runtime profile, Gauntlet-owned runtime home | `PENDING` | — | -| 5 | Add status/release plugin tools and subprocess module adapter | `PENDING` | — | -| 6 | Add observation bridge and Soul finalizer | `PENDING` | — | -| 7 | Add FOIL advisory route and single `gauntlet` entry point | `PENDING` | — | -| 8 | Perform the eight-item manual boot verification and prepare Codex transfer | `PENDING` | — | +| 1 | Pin, provenance, license, vendoring command, governing plan | `COMPLETE` | `60bb6f22d7c13a25fee204fbc17798c6c55cb19f` | +| 2 | Materialize and verify the exact `vendor/hermes-agent/` snapshot | `COMPLETE` | `a24a952133d03c43872dbec034a5a8ba5515a32f` | +| 3 | Constants, typed JSONL IPC, isolated worker bootstrap | `COMPLETE` | `98db935f56155ea1f446c10eb6aa5f00384e5d3d` | +| 4 | Launcher, runtime profile, Gauntlet-owned runtime home, one agent turn | `COMPLETE` | `c40b5b93d67e76e2d5e7153fc886c58142bd6f49` | +| 5 | Status/release plugin tools and subprocess module adapter | `NEXT` | — | +| 6 | Observation bridge and Soul finalizer | `PENDING` | — | +| 7 | FOIL advisory route and single `gauntlet` entry point | `PENDING` | — | +| 8 | Eight-item manual boot verification and Codex transfer | `PENDING` | — | ## 3. Phase 1 outputs @@ -43,12 +42,303 @@ trees are prohibited. 6. No changes to `tools/egrt_types.py`, `tools/soul_runtime.py`, FOIL authority ceilings, receipt formats, or direct module behavior. -## 4. Continue instruction +## 4. Phase 2 verification receipt -Continue with **Phase 2 only**: +| Field | Result | +|---|---| +| GitHub Actions run | `33223968298` — `SUCCESS` | +| Upstream repository | `NousResearch/hermes-agent` | +| Upstream release | `v2026.8.27` / Hermes Agent `v0.20.6` | +| Upstream commit | `5fc308a70719a83cccdbba4c0e39c23f5a8239d5` | +| Snapshot commit | `a24a952133d03c43872dbec034a5a8ba5515a32f` | +| Vendored files | `10488` | +| Deterministic tree SHA-256 | `5a87f74ab782bfda8dcaed7938ac216d3fd6063e897f67dc0e27cb4c4d4b1dca` | +| License SHA-256 | `821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6` | +| Materialized at | `2026-08-29T00:35:28.082238+00:00` | +| Local modifications | `0` | +| Excluded content | nested `.git` metadata only | -1. run `python scripts/vendor_hermes.py --dry-run`; -2. materialize the exact pinned source; -3. run `python scripts/vendor_hermes.py --verify-only`; -4. update this ledger with the resulting file count, tree SHA-256, and commit SHA; -5. stop before implementing `gauntlet_host/`. +Executed successfully: + +```bash +python scripts/vendor_hermes.py --dry-run +python scripts/vendor_hermes.py +python scripts/vendor_hermes.py --verify-only +``` + +The independent verification pass reproduced the same file count, license hash, upstream +commit, and deterministic tree digest as the materialization pass. Upstream whitespace was +preserved rather than rewritten because Phase 2 required an exact source snapshot. + +## 5. Phase 3 implementation receipt + +| Field | Result | +|---|---| +| Implementation commit | `98db935f56155ea1f446c10eb6aa5f00384e5d3d` | +| GitHub Actions run | `33225470731` — `SUCCESS` | +| Verification environment | Ubuntu `24.04.4`; CPython `3.12.14` | +| Host files added | `4` | +| Vendored Hermes files modified | `0` | +| JSONL schema | `gauntlet.worker.v1` | +| Request type | `worker.request` | +| Result type | `worker.result` | +| Maximum JSONL record | `1048576` bytes | +| Namespace proof event | `worker.imports_verified` | + +Files added: + +```text +gauntlet_host/__init__.py +gauntlet_host/constants.py +gauntlet_host/ipc.py +gauntlet_host/worker_main.py +``` + +The Phase 3 worker: + +1. verifies the pinned snapshot manifest before runtime import; +2. rejects preloaded non-vendored `tools` modules; +3. removes Gauntlet repository paths from the worker import path; +4. sets worker `PYTHONPATH` and cwd to `vendor/hermes-agent`; +5. verifies both `find_spec("tools")` and imported `tools.__file__`; +6. emits a structured namespace proof; +7. accepts strict typed JSONL requests and emits deterministic JSONL results; +8. rejects duplicate JSON keys, unknown fields, invalid types, and unsupported operations; +9. exposes no `Verdict`, `EvidenceClass`, `Receipt`, or release field. + +The successful run proved: + +```text +tools_origin = vendor/hermes-agent/tools/__init__.py +cwd = vendor/hermes-agent +sys.path[0] = vendor/hermes-agent +PYTHONPATH = vendor/hermes-agent +upstream_commit = 5fc308a70719a83cccdbba4c0e39c23f5a8239d5 +``` + +Negative checks also passed: + +| Check | Required result | +|---|---| +| Root Gauntlet `tools` preloaded | `TOOLS_NAMESPACE_PRELOADED` | +| Duplicate JSON key | `DUPLICATE_JSON_KEY`; process exit `2` | +| Premature `run` request | `UNAVAILABLE`; process exit `3` | +| Runtime authority fields present | none | + +The first temporary Phase 3 run, `33225379537`, stopped before worker execution because its +shallow checkout did not contain `HEAD^` for a harness-only diff command. The repaired harness +removed that invalid precondition. Run `33225470731` then completed every Phase 3 worker check. + +## 6. Phase 4 implementation receipt + +| Field | Result | +|---|---| +| Implementation commit | `c40b5b93d67e76e2d5e7153fc886c58142bd6f49` | +| Successful GitHub Actions run | `33226703542` — `SUCCESS` | +| Verification environment | Ubuntu `24.04.4`; CPython `3.12.14` | +| Host files added | `2` | +| Existing host files extended | `3` | +| Vendored Hermes files modified | `0` | +| Runtime home | `~/.gauntlet/runtime` | +| Upstream engine instantiated | `run_agent.AIAgent` from the vendored snapshot | +| Provider path exercised | upstream custom OpenAI-compatible provider resolution | +| Model call count | `1` | +| Worker completion event | `worker.turn_completed` | +| Mock final response | `phase4 mock response` | +| Input / output / total tokens | `12 / 4 / 16` | +| Session persistence | `state.db` created under the Gauntlet runtime home | +| Ordinary `~/.hermes` created | `NO` | +| Runtime authority fields in result | `0` | + +Files added: + +```text +gauntlet_host/launcher.py +gauntlet_host/runtime_profile.py +``` + +Files extended: + +```text +gauntlet_host/constants.py +gauntlet_host/ipc.py +gauntlet_host/worker_main.py +``` + +### 6.1 Launcher boundary + +The parent-side launcher: + +1. prepares the Gauntlet-owned runtime profile; +2. never imports vendored `run_agent.py` in the parent interpreter; +3. starts the worker by absolute file path with cwd and `PYTHONPATH` set to the vendor root; +4. passes one typed JSONL request over stdin and expects exactly one JSONL result on stdout; +5. correlates request and task IDs; +6. binds worker status to process exit status; +7. applies a bounded launcher timeout; +8. returns typed `ERROR` or `UNAVAILABLE` operational failures; +9. removes inherited `HERMES_YOLO_MODE`, `HERMES_ACCEPT_HOOKS`, and + `HERMES_INTERACTIVE` values before process launch; +10. does not perform Soul finalization or claim task release. + +The working Phase 4 command shape is: + +```bash +python -m gauntlet_host.launcher \ + "Return the configured mock response." \ + --task-id task-phase4-live \ + --model phase4-mock \ + --provider custom \ + --timeout 90 \ + --json +``` + +### 6.2 Runtime profile + +Before upstream runtime imports or construction, the profile manager creates: + +```text +~/.gauntlet/runtime/ +├── config.yaml +├── state.db # created by the upstream SessionDB during the turn +├── memories/ +├── skills/ +├── cache/ +├── logs/ +└── pending/ + ├── memory/ + └── skills/ +``` + +It preserves other provider/model settings while forcing the alpha governance values: + +```yaml +auxiliary: + background_review: + enabled: false +memory: + write_approval: true +skills: + write_approval: true +``` + +It rejects `~/.hermes` as the requested Gauntlet runtime home, writes `config.yaml` atomically, +and sets `HERMES_HOME` to the dedicated directory. The worker also passes +`skip_background_review=True` to upstream `AIAgent`. + +The successful verification deliberately began with the opposite three profile values and +with the three inherited bypass flags set. The resulting profile contained the required +Gauntlet values, while the worker completed through the isolated subprocess boundary. +This verifies profile enforcement and launch sanitization for the tested path; it is not a +claim that every upstream memory or skill mutation path has been behaviorally qualified. + +### 6.3 One real upstream turn + +The successful verification installed dependencies from the pinned vendored tree for the CI +job, started a local OpenAI-compatible endpoint, and executed one actual upstream `AIAgent` +conversation turn. The endpoint received exactly one `/chat/completions` request for +`phase4-mock`. The worker returned: + +```text +status = OK +event = worker.turn_completed +provider = custom +model = phase4-mock +final_response = phase4 mock response +api_calls = 1 +input_tokens = 12 +output_tokens = 4 +total_tokens = 16 +completed = true +failed = false +partial = false +``` + +The returned namespace proof still bound `tools` to: + +```text +vendor/hermes-agent/tools/__init__.py +``` + +and the result contained none of: + +```text +verdict +evidence_class +receipt +release +``` + +This proves the bounded engine path with a deterministic local endpoint. It does not yet prove +operation against a paid external provider or any particular production model. + +### 6.4 Negative and harness checks + +| Check | Result | +|---|---| +| Python compile of `gauntlet_host` | `PASS` | +| Source line-shape check | `PASS` | +| Launcher imports vendored `run_agent` | `NO` | +| Request JSONL round trip | `PASS` | +| Result JSONL round trip | `PASS` | +| Invalid profile root `[]` | `RUNTIME_CONFIG_INVALID`; exit `2` | +| Missing authority fields in worker result | `PASS` | +| Session DB outside Gauntlet runtime home | `NO` | + +Temporary run `33226562129` reached upstream `AIAgent` but stopped at its minimum-context guard +because the deterministic mock declared `16384` tokens while the upstream runtime requires at +least `64000`. The worker returned a typed `UNAVAILABLE` result rather than fabricating success. +Only the mock fixture was corrected to `131072`; run `33226703542` then passed every Phase 4 +check. The temporary Phase 4 workflow was removed after the successful receipt was recorded. + +## 7. Authority and scope state after Phase 4 + +Implemented and verified: + +```text +parent launcher +→ Gauntlet-owned runtime profile +→ bounded JSONL subprocess +→ pinned snapshot and namespace verification +→ upstream provider resolution +→ upstream AIAgent construction +→ one model conversation turn +→ upstream session persistence under ~/.gauntlet/runtime +→ transport-level worker result +``` + +Not implemented and therefore not claimed: + +```text +Gauntlet runtime plugin +Gauntlet status/release tools +subprocess module adapter +runtime tool lifecycle observations +observation store +Soul finalizer +FOIL route +single user-facing gauntlet entry point +external-provider qualification +runtime tool execution +end-to-end task release +``` + +A transport-level `OK` means only that the worker turn completed. It is not a Gauntlet verdict, +receipt, evidence clearance, or task release. + +## 8. Continue instruction + +Continue with **Phase 5 only**: + +1. create `gauntlet_host/gauntlet_plugin.py` and the minimum tool definitions; +2. register at least `gauntlet_task_status` and `gauntlet_release_status` through the existing + vendored plugin/tool registry rather than a new registry; +3. create `gauntlet_host/module_cli.py` as the Gauntlet-side subprocess adapter; +4. keep Gauntlet's repository root as the adapter process import root; +5. pass task identity only through `GAUNTLET_TASK_ID`, never conversation inference; +6. make worker-side Gauntlet calls execute the adapter by exact file/module command; +7. return canonical status data without allowing a plugin or worker to create receipts, + change verdicts, clear obligations, or release a task; +8. prove one agent-visible status call through the isolated runtime; +9. stop before tool-lifecycle observation recording, Soul finalization, FOIL routing, or the + final product CLI. diff --git a/gauntlet_host/__init__.py b/gauntlet_host/__init__.py new file mode 100644 index 00000000..6a271672 --- /dev/null +++ b/gauntlet_host/__init__.py @@ -0,0 +1,5 @@ +"""Gauntlet-owned boundary for the isolated vendored agent runtime.""" + +from gauntlet_host.constants import HOST_PROTOCOL_VERSION + +__all__ = ["HOST_PROTOCOL_VERSION"] diff --git a/gauntlet_host/constants.py b/gauntlet_host/constants.py new file mode 100644 index 00000000..cb29ca6e --- /dev/null +++ b/gauntlet_host/constants.py @@ -0,0 +1,37 @@ +"""Constants for the Gauntlet-owned isolated runtime boundary.""" + +from pathlib import Path + +HOST_PROTOCOL_VERSION = "gauntlet.worker.v1" +WORKER_REQUEST_TYPE = "worker.request" +WORKER_RESULT_TYPE = "worker.result" +ADAPTER_PROTOCOL_VERSION = "gauntlet.adapter.v1" +MAX_JSONL_BYTES = 1_048_576 +MAX_OPERATIONAL_ERROR_CHARS = 2_000 + +DEFAULT_LAUNCH_TIMEOUT_SECONDS = 180.0 +MAX_LAUNCH_TIMEOUT_SECONDS = 600.0 +DEFAULT_AGENT_RUN_BUDGET_SECONDS = 120.0 +MAX_AGENT_RUN_BUDGET_SECONDS = 300.0 +DEFAULT_ADAPTER_TIMEOUT_SECONDS = 20.0 + +GAUNTLET_PLUGIN_ID = "gauntlet" +GAUNTLET_TOOLSET = "gauntlet" +GAUNTLET_STATUS_TOOLS = ( + "gauntlet_task_status", + "gauntlet_release_status", +) + +REPO_ROOT = Path(__file__).resolve().parent.parent +HOST_ROOT = REPO_ROOT / "gauntlet_host" +VENDOR_ROOT = REPO_ROOT / "vendor" / "hermes-agent" +VENDOR_TOOLS_ROOT = VENDOR_ROOT / "tools" +GAUNTLET_TOOLS_ROOT = REPO_ROOT / "tools" +WORKER_MAIN = HOST_ROOT / "worker_main.py" +MODULE_CLI = HOST_ROOT / "module_cli.py" +GAUNTLET_PLUGIN_SOURCE = HOST_ROOT / "gauntlet_plugin.py" +VENDOR_SNAPSHOT_MANIFEST = REPO_ROOT / "vendor" / "HERMES_SNAPSHOT.json" + +EXPECTED_HERMES_REPOSITORY = "NousResearch/hermes-agent" +EXPECTED_HERMES_TAG = "v2026.8.27" +EXPECTED_HERMES_COMMIT = "5fc308a70719a83cccdbba4c0e39c23f5a8239d5" diff --git a/gauntlet_host/gauntlet_plugin.py b/gauntlet_host/gauntlet_plugin.py new file mode 100644 index 00000000..74aaff87 --- /dev/null +++ b/gauntlet_host/gauntlet_plugin.py @@ -0,0 +1,267 @@ +"""Read-only Gauntlet status tools loaded through the vendored plugin ABI.""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +import re +import subprocess +import sys +from typing import Any + +ADAPTER_SCHEMA = "gauntlet.adapter.v1" +TOOLSET = "gauntlet" +MAX_ADAPTER_OUTPUT_CHARS = 262_144 +ADAPTER_TIMEOUT_SECONDS = 20.0 + + +class PluginBridgeError(RuntimeError): + """Typed failure at the runtime-to-Gauntlet subprocess boundary.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +def _safe_message(value: Any) -> str: + text = " ".join(str(value).split()) + text = re.sub( + r"(?i)(api[_-]?key|authorization|token|secret)\s*[:=]\s*\S+", + r"\1=", + text, + ) + text = re.sub(r"\bsk-[A-Za-z0-9_-]{8,}\b", "", text) + return (text or "runtime bridge failure")[:1_000] + + +def _error_document(action: str, code: str, message: str) -> str: + return json.dumps( + { + "schema": ADAPTER_SCHEMA, + "action": action, + "status": "ERROR", + "error": { + "code": code, + "message": _safe_message(message), + }, + "read_only": True, + "mutation_performed": False, + }, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + + +def _required_environment(name: str) -> str: + value = os.environ.get(name, "").strip() + if not value: + raise PluginBridgeError( + "BRIDGE_ENVIRONMENT_MISSING", + f"required runtime bridge variable {name} is not set", + ) + return value + + +def _validate_arguments(arguments: Any) -> None: + if arguments is None: + return + if isinstance(arguments, dict) and not arguments: + return + raise PluginBridgeError( + "TOOL_ARGUMENTS_REJECTED", + "Gauntlet status tools do not accept arguments", + ) + + +def _adapter_paths() -> tuple[Path, Path]: + repo_root = Path(_required_environment("GAUNTLET_REPO_ROOT")).resolve() + module_cli = Path(_required_environment("GAUNTLET_MODULE_CLI")).resolve() + expected_cli = (repo_root / "gauntlet_host" / "module_cli.py").resolve() + if module_cli != expected_cli: + raise PluginBridgeError( + "MODULE_ADAPTER_PATH_MISMATCH", + "runtime bridge adapter path does not match the Gauntlet repository", + ) + if not (repo_root / "tools" / "soul_runtime.py").is_file(): + raise PluginBridgeError( + "GAUNTLET_REPOSITORY_INVALID", + "Gauntlet authority files are missing from the configured repository root", + ) + if not module_cli.is_file(): + raise PluginBridgeError( + "MODULE_ADAPTER_MISSING", + f"Gauntlet module adapter is missing: {module_cli}", + ) + return repo_root, module_cli + + +def _parse_adapter_output(action: str, task_id: str, stdout: str) -> dict[str, Any]: + if len(stdout) > MAX_ADAPTER_OUTPUT_CHARS: + raise PluginBridgeError( + "MODULE_ADAPTER_OUTPUT_TOO_LARGE", + "Gauntlet module adapter output exceeded the bounded tool-result limit", + ) + records = [line for line in stdout.splitlines() if line.strip()] + if len(records) != 1: + raise PluginBridgeError( + "MODULE_ADAPTER_PROTOCOL_ERROR", + "Gauntlet module adapter must return exactly one JSON record", + ) + try: + value = json.loads(records[0]) + except json.JSONDecodeError as exc: + raise PluginBridgeError( + "MODULE_ADAPTER_PROTOCOL_ERROR", + f"Gauntlet module adapter returned invalid JSON: {exc.msg}", + ) from exc + if not isinstance(value, dict): + raise PluginBridgeError( + "MODULE_ADAPTER_PROTOCOL_ERROR", + "Gauntlet module adapter result must be a JSON object", + ) + if value.get("schema") != ADAPTER_SCHEMA: + raise PluginBridgeError( + "MODULE_ADAPTER_SCHEMA_MISMATCH", + f"Gauntlet module adapter schema must be {ADAPTER_SCHEMA}", + ) + if value.get("action") != action or value.get("task_id") != task_id: + raise PluginBridgeError( + "MODULE_ADAPTER_CORRELATION_MISMATCH", + "Gauntlet module adapter result did not match the requested action and task", + ) + if value.get("read_only") is not True: + raise PluginBridgeError( + "MODULE_ADAPTER_AUTHORITY_VIOLATION", + "Gauntlet module adapter did not attest to the read-only status contract", + ) + if value.get("mutation_performed") is not False: + raise PluginBridgeError( + "MODULE_ADAPTER_AUTHORITY_VIOLATION", + "Gauntlet status adapter reported a state mutation", + ) + return value + + +def _call_adapter(action: str, arguments: Any) -> str: + try: + _validate_arguments(arguments) + task_id = _required_environment("GAUNTLET_TASK_ID") + repo_root, module_cli = _adapter_paths() + environment = dict(os.environ) + environment["GAUNTLET_TASK_ID"] = task_id + environment["PYTHONPATH"] = str(repo_root) + environment["PYTHONUNBUFFERED"] = "1" + for bypass in ( + "HERMES_YOLO_MODE", + "HERMES_ACCEPT_HOOKS", + "HERMES_INTERACTIVE", + ): + environment.pop(bypass, None) + + completed = subprocess.run( + [ + sys.executable, + str(module_cli), + "--root", + str(repo_root), + action, + ], + cwd=repo_root, + env=environment, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + timeout=ADAPTER_TIMEOUT_SECONDS, + check=False, + ) + value = _parse_adapter_output(action, task_id, completed.stdout) + status = value.get("status") + expected_exit = 0 if status == "OK" else 2 + if completed.returncode != expected_exit: + raise PluginBridgeError( + "MODULE_ADAPTER_EXIT_MISMATCH", + ( + f"Gauntlet module adapter exited with {completed.returncode}; " + f"status {status!r} requires {expected_exit}" + ), + ) + return json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + except subprocess.TimeoutExpired: + return _error_document( + action, + "MODULE_ADAPTER_TIMEOUT", + f"Gauntlet module adapter exceeded {ADAPTER_TIMEOUT_SECONDS:g} seconds", + ) + except OSError as exc: + return _error_document(action, "MODULE_ADAPTER_START_FAILED", str(exc)) + except PluginBridgeError as exc: + return _error_document(action, exc.code, exc.message) + except Exception as exc: + return _error_document(action, "MODULE_ADAPTER_UNEXPECTED_FAILURE", str(exc)) + + +def _task_status(arguments: dict[str, Any] | None = None, **_: Any) -> str: + return _call_adapter("task-status", arguments) + + +def _release_status(arguments: dict[str, Any] | None = None, **_: Any) -> str: + return _call_adapter("release-status", arguments) + + +_TASK_STATUS_SCHEMA = { + "description": ( + "Read the canonical Gauntlet task and obligation status for the exact " + "GAUNTLET_TASK_ID bound by the host. This tool is read-only and cannot " + "create receipts, change verdicts, clear obligations, or release a task." + ), + "parameters": { + "type": "object", + "properties": {}, + "additionalProperties": False, + }, +} + +_RELEASE_STATUS_SCHEMA = { + "description": ( + "Read the current Soul release-gate result for the exact host-bound task. " + "This reports eligibility only; it never calls release_task and performs " + "no mutation." + ), + "parameters": { + "type": "object", + "properties": {}, + "additionalProperties": False, + }, +} + + +def register(ctx: Any) -> None: + """Register the minimal read-only Gauntlet tool surface.""" + + ctx.register_tool( + name="gauntlet_task_status", + toolset=TOOLSET, + schema=_TASK_STATUS_SCHEMA, + handler=_task_status, + description=_TASK_STATUS_SCHEMA["description"], + emoji="", + ) + ctx.register_tool( + name="gauntlet_release_status", + toolset=TOOLSET, + schema=_RELEASE_STATUS_SCHEMA, + handler=_release_status, + description=_RELEASE_STATUS_SCHEMA["description"], + emoji="", + ) diff --git a/gauntlet_host/ipc.py b/gauntlet_host/ipc.py new file mode 100644 index 00000000..5eb7afa4 --- /dev/null +++ b/gauntlet_host/ipc.py @@ -0,0 +1,392 @@ +"""Strict typed JSONL contracts for the isolated runtime worker.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass, field +from enum import StrEnum +from typing import Any, TextIO + +from gauntlet_host.constants import ( + HOST_PROTOCOL_VERSION, + MAX_JSONL_BYTES, + WORKER_REQUEST_TYPE, + WORKER_RESULT_TYPE, +) + + +class WorkerOperation(StrEnum): + """Operations understood by the isolated worker boundary.""" + + PROBE_IMPORTS = "probe_imports" + RUN = "run" + + +class WorkerStatus(StrEnum): + """Transport-level worker outcomes, never Gauntlet verdicts.""" + + OK = "OK" + ERROR = "ERROR" + UNAVAILABLE = "UNAVAILABLE" + + +@dataclass(frozen=True, slots=True) +class RuntimeRequest: + """One parent-to-worker request.""" + + request_id: str + task_id: str + operation: WorkerOperation + prompt: str = "" + cwd: str | None = None + model: str | None = None + provider: str | None = None + toolsets: tuple[str, ...] = () + metadata: dict[str, Any] = field(default_factory=dict) + + +@dataclass(frozen=True, slots=True) +class WorkerError: + """Machine-readable worker failure detail.""" + + code: str + message: str + + +@dataclass(frozen=True, slots=True) +class RuntimeResult: + """One worker-to-parent result with no evidential authority fields.""" + + request_id: str + task_id: str + status: WorkerStatus + event: str + payload: dict[str, Any] = field(default_factory=dict) + error: WorkerError | None = None + + +class IPCContractError(ValueError): + """Raised when a JSONL record violates the worker protocol.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +class _DuplicateKeyError(ValueError): + pass + + +def _reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + raise _DuplicateKeyError(f"duplicate JSON key: {key}") + result[key] = value + return result + + +def _require_string( + value: Any, + *, + field_name: str, + allow_empty: bool = False, + maximum: int = 256, +) -> str: + if not isinstance(value, str): + raise IPCContractError("INVALID_FIELD_TYPE", f"{field_name} must be a string") + if not allow_empty and not value.strip(): + raise IPCContractError("INVALID_FIELD_VALUE", f"{field_name} must not be empty") + if len(value) > maximum: + raise IPCContractError( + "FIELD_TOO_LARGE", + f"{field_name} exceeds the {maximum}-character limit", + ) + return value + + +def _optional_string(value: Any, *, field_name: str, maximum: int = 4096) -> str | None: + if value is None: + return None + return _require_string(value, field_name=field_name, maximum=maximum) + + +def _decode_json_object(line: str, *, record_name: str) -> dict[str, Any]: + if len(line.encode("utf-8")) > MAX_JSONL_BYTES: + raise IPCContractError( + f"{record_name.upper()}_TOO_LARGE", + f"{record_name} exceeds the {MAX_JSONL_BYTES}-byte JSONL limit", + ) + try: + value = json.loads(line, object_pairs_hook=_reject_duplicate_keys) + except _DuplicateKeyError as exc: + raise IPCContractError("DUPLICATE_JSON_KEY", str(exc)) from exc + except json.JSONDecodeError as exc: + raise IPCContractError("INVALID_JSON", f"invalid JSON: {exc.msg}") from exc + if not isinstance(value, dict): + raise IPCContractError( + "INVALID_RECORD", + f"{record_name} must be a JSON object", + ) + return value + + +def _encode_json_object(value: dict[str, Any], *, record_name: str) -> str: + try: + encoded = json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + except (TypeError, ValueError) as exc: + raise IPCContractError( + f"UNENCODABLE_{record_name.upper()}", + f"{record_name} is not JSON-serializable: {exc}", + ) from exc + if len(encoded.encode("utf-8")) > MAX_JSONL_BYTES: + raise IPCContractError( + f"{record_name.upper()}_TOO_LARGE", + f"{record_name} exceeds the {MAX_JSONL_BYTES}-byte JSONL limit", + ) + return encoded + + +def _decode_toolsets(value: Any) -> tuple[str, ...]: + if value is None: + return () + if not isinstance(value, list): + raise IPCContractError("INVALID_FIELD_TYPE", "toolsets must be an array") + + toolsets: list[str] = [] + seen: set[str] = set() + for index, item in enumerate(value): + toolset = _require_string(item, field_name=f"toolsets[{index}]") + if toolset in seen: + raise IPCContractError("DUPLICATE_TOOLSET", f"duplicate toolset: {toolset}") + seen.add(toolset) + toolsets.append(toolset) + return tuple(toolsets) + + +def encode_request(request: RuntimeRequest) -> str: + """Encode one deterministic parent-to-worker request.""" + + value: dict[str, Any] = { + "schema": HOST_PROTOCOL_VERSION, + "type": WORKER_REQUEST_TYPE, + "request_id": request.request_id, + "task_id": request.task_id, + "operation": request.operation.value, + "prompt": request.prompt, + "toolsets": list(request.toolsets), + "metadata": request.metadata, + } + if request.cwd is not None: + value["cwd"] = request.cwd + if request.model is not None: + value["model"] = request.model + if request.provider is not None: + value["provider"] = request.provider + return _encode_json_object(value, record_name="request") + + +def decode_request(line: str) -> RuntimeRequest: + """Decode one strict JSONL request line.""" + + value = _decode_json_object(line, record_name="request") + allowed_fields = { + "schema", + "type", + "request_id", + "task_id", + "operation", + "prompt", + "cwd", + "model", + "provider", + "toolsets", + "metadata", + } + unknown_fields = sorted(set(value) - allowed_fields) + if unknown_fields: + raise IPCContractError( + "UNKNOWN_FIELDS", + f"unknown request fields: {', '.join(unknown_fields)}", + ) + + if value.get("schema") != HOST_PROTOCOL_VERSION: + raise IPCContractError( + "UNSUPPORTED_SCHEMA", + f"schema must be {HOST_PROTOCOL_VERSION}", + ) + if value.get("type") != WORKER_REQUEST_TYPE: + raise IPCContractError( + "INVALID_RECORD_TYPE", + f"type must be {WORKER_REQUEST_TYPE}", + ) + + request_id = _require_string(value.get("request_id"), field_name="request_id") + task_id = _require_string(value.get("task_id"), field_name="task_id") + operation_text = _require_string(value.get("operation"), field_name="operation") + try: + operation = WorkerOperation(operation_text) + except ValueError as exc: + allowed = ", ".join(item.value for item in WorkerOperation) + raise IPCContractError( + "UNSUPPORTED_OPERATION", + f"operation must be one of: {allowed}", + ) from exc + + prompt = _require_string( + value.get("prompt", ""), + field_name="prompt", + allow_empty=True, + maximum=MAX_JSONL_BYTES, + ) + if operation is WorkerOperation.RUN and not prompt.strip(): + raise IPCContractError("MISSING_PROMPT", "run requests require a non-empty prompt") + + metadata = value.get("metadata", {}) + if not isinstance(metadata, dict): + raise IPCContractError("INVALID_FIELD_TYPE", "metadata must be an object") + + return RuntimeRequest( + request_id=request_id, + task_id=task_id, + operation=operation, + prompt=prompt, + cwd=_optional_string(value.get("cwd"), field_name="cwd"), + model=_optional_string(value.get("model"), field_name="model"), + provider=_optional_string(value.get("provider"), field_name="provider"), + toolsets=_decode_toolsets(value.get("toolsets")), + metadata=dict(metadata), + ) + + +def encode_result(result: RuntimeResult) -> str: + """Encode one deterministic JSONL result line without a trailing newline.""" + + value: dict[str, Any] = { + "schema": HOST_PROTOCOL_VERSION, + "type": WORKER_RESULT_TYPE, + "request_id": result.request_id, + "task_id": result.task_id, + "status": result.status.value, + "event": result.event, + "payload": result.payload, + } + if result.error is not None: + value["error"] = { + "code": result.error.code, + "message": result.error.message, + } + return _encode_json_object(value, record_name="result") + + +def decode_result(line: str) -> RuntimeResult: + """Decode one strict worker-to-parent result line.""" + + value = _decode_json_object(line, record_name="result") + allowed_fields = { + "schema", + "type", + "request_id", + "task_id", + "status", + "event", + "payload", + "error", + } + unknown_fields = sorted(set(value) - allowed_fields) + if unknown_fields: + raise IPCContractError( + "UNKNOWN_FIELDS", + f"unknown result fields: {', '.join(unknown_fields)}", + ) + + if value.get("schema") != HOST_PROTOCOL_VERSION: + raise IPCContractError( + "UNSUPPORTED_SCHEMA", + f"schema must be {HOST_PROTOCOL_VERSION}", + ) + if value.get("type") != WORKER_RESULT_TYPE: + raise IPCContractError( + "INVALID_RECORD_TYPE", + f"type must be {WORKER_RESULT_TYPE}", + ) + + request_id = _require_string(value.get("request_id"), field_name="request_id") + task_id = _require_string(value.get("task_id"), field_name="task_id") + status_text = _require_string(value.get("status"), field_name="status") + try: + status = WorkerStatus(status_text) + except ValueError as exc: + allowed = ", ".join(item.value for item in WorkerStatus) + raise IPCContractError( + "INVALID_WORKER_STATUS", + f"status must be one of: {allowed}", + ) from exc + + event = _require_string(value.get("event"), field_name="event") + payload = value.get("payload", {}) + if not isinstance(payload, dict): + raise IPCContractError("INVALID_FIELD_TYPE", "payload must be an object") + + error_value = value.get("error") + error: WorkerError | None = None + if error_value is not None: + if not isinstance(error_value, dict): + raise IPCContractError("INVALID_FIELD_TYPE", "error must be an object") + unknown_error_fields = sorted(set(error_value) - {"code", "message"}) + if unknown_error_fields: + raise IPCContractError( + "UNKNOWN_FIELDS", + f"unknown error fields: {', '.join(unknown_error_fields)}", + ) + error = WorkerError( + code=_require_string(error_value.get("code"), field_name="error.code"), + message=_require_string( + error_value.get("message"), + field_name="error.message", + maximum=16_384, + ), + ) + + if status is WorkerStatus.OK and error is not None: + raise IPCContractError("INVALID_RESULT", "OK results must not contain an error") + if status is not WorkerStatus.OK and error is None: + raise IPCContractError( + "INVALID_RESULT", + "ERROR and UNAVAILABLE results require an error object", + ) + + return RuntimeResult( + request_id=request_id, + task_id=task_id, + status=status, + event=event, + payload=dict(payload), + error=error, + ) + + +def write_result(stream: TextIO, result: RuntimeResult) -> None: + """Write and flush one worker result as JSONL.""" + + stream.write(encode_result(result)) + stream.write("\n") + stream.flush() + + +def contract_error_result(exc: IPCContractError) -> RuntimeResult: + """Convert a request-contract failure into a structured result.""" + + return RuntimeResult( + request_id="unknown", + task_id="unknown", + status=WorkerStatus.ERROR, + event="worker.request_rejected", + error=WorkerError(code=exc.code, message=exc.message), + ) diff --git a/gauntlet_host/launcher.py b/gauntlet_host/launcher.py new file mode 100644 index 00000000..674e9f9c --- /dev/null +++ b/gauntlet_host/launcher.py @@ -0,0 +1,348 @@ +"""Parent-side launcher for one isolated vendored runtime turn.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import subprocess +import sys +import uuid +from typing import Sequence + +from gauntlet_host.constants import ( + DEFAULT_AGENT_RUN_BUDGET_SECONDS, + DEFAULT_LAUNCH_TIMEOUT_SECONDS, + GAUNTLET_TOOLSET, + MAX_AGENT_RUN_BUDGET_SECONDS, + MAX_LAUNCH_TIMEOUT_SECONDS, + MODULE_CLI, + REPO_ROOT, + VENDOR_ROOT, + WORKER_MAIN, +) +from gauntlet_host.ipc import ( + IPCContractError, + RuntimeRequest, + RuntimeResult, + WorkerError, + WorkerOperation, + WorkerStatus, + decode_result, + encode_request, + encode_result, +) +from gauntlet_host.runtime_profile import ( + RuntimeProfile, + RuntimeProfileError, + prepare_runtime_profile, +) + + +def _failure( + request: RuntimeRequest, + *, + status: WorkerStatus, + event: str, + code: str, + message: str, +) -> RuntimeResult: + return RuntimeResult( + request_id=request.request_id, + task_id=request.task_id, + status=status, + event=event, + error=WorkerError(code=code, message=message), + ) + + +def _bounded_timeout(value: float) -> float: + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise ValueError("timeout must be a number") + timeout = float(value) + if timeout <= 0 or timeout > MAX_LAUNCH_TIMEOUT_SECONDS: + raise ValueError( + f"timeout must be greater than 0 and at most {MAX_LAUNCH_TIMEOUT_SECONDS:g}" + ) + return timeout + + +def _worker_environment(profile: RuntimeProfile, request: RuntimeRequest) -> dict[str, str]: + environment = dict(os.environ) + environment["HERMES_HOME"] = profile.runtime_home + environment["PYTHONPATH"] = str(VENDOR_ROOT) + environment["PYTHONUNBUFFERED"] = "1" + environment["GAUNTLET_TASK_ID"] = request.task_id + environment["GAUNTLET_REPO_ROOT"] = str(REPO_ROOT) + environment["GAUNTLET_MODULE_CLI"] = str(MODULE_CLI) + + for inherited_bypass in ( + "HERMES_YOLO_MODE", + "HERMES_ACCEPT_HOOKS", + "HERMES_INTERACTIVE", + ): + environment.pop(inherited_bypass, None) + return environment + + +def _parse_worker_output( + request: RuntimeRequest, + *, + stdout: str, + returncode: int, +) -> RuntimeResult: + records = [line for line in stdout.splitlines() if line.strip()] + if len(records) != 1: + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.protocol_failed", + code="WORKER_PROTOCOL_VIOLATION", + message=( + "worker stdout must contain exactly one non-empty JSONL record; " + f"received {len(records)}" + ), + ) + + try: + result = decode_result(records[0]) + except IPCContractError as exc: + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.protocol_failed", + code=exc.code, + message=exc.message, + ) + + if result.request_id != request.request_id or result.task_id != request.task_id: + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.protocol_failed", + code="WORKER_CORRELATION_MISMATCH", + message="worker result does not match the launched request and task identifiers", + ) + + expected_exit = { + WorkerStatus.OK: 0, + WorkerStatus.ERROR: 2, + WorkerStatus.UNAVAILABLE: 3, + }[result.status] + if returncode != expected_exit: + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.protocol_failed", + code="WORKER_EXIT_STATUS_MISMATCH", + message=( + f"worker exited with {returncode}; status {result.status.value} " + f"requires exit {expected_exit}" + ), + ) + + if result.status is WorkerStatus.OK: + final_response = result.payload.get("final_response") + if not isinstance(final_response, str) or not final_response.strip(): + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.protocol_failed", + code="WORKER_RESPONSE_MISSING", + message="successful worker result did not contain a final response", + ) + return result + + +def _effective_toolsets(toolsets: Sequence[str]) -> tuple[str, ...]: + return tuple(dict.fromkeys((*toolsets, GAUNTLET_TOOLSET))) + + +def run_worker_turn( + prompt: str, + *, + task_id: str, + cwd: Path | str | None = None, + model: str | None = None, + provider: str | None = None, + toolsets: Sequence[str] = (), + timeout_seconds: float = DEFAULT_LAUNCH_TIMEOUT_SECONDS, +) -> RuntimeResult: + """Run one upstream AIAgent turn through the isolated JSONL worker.""" + + request = RuntimeRequest( + request_id=f"request-{uuid.uuid4().hex}", + task_id=task_id, + operation=WorkerOperation.RUN, + prompt=prompt, + cwd=str(Path(cwd or Path.cwd()).expanduser().resolve(strict=False)), + model=model, + provider=provider, + toolsets=_effective_toolsets(toolsets), + metadata={}, + ) + + try: + timeout = _bounded_timeout(timeout_seconds) + except ValueError as exc: + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.request_rejected", + code="INVALID_LAUNCH_TIMEOUT", + message=str(exc), + ) + + request.metadata["run_budget_seconds"] = min( + MAX_AGENT_RUN_BUDGET_SECONDS, + DEFAULT_AGENT_RUN_BUDGET_SECONDS, + max(1.0, timeout - 5.0), + ) + + try: + profile = prepare_runtime_profile() + except RuntimeProfileError as exc: + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.profile_failed", + code=exc.code, + message=exc.message, + ) + + required_files = ( + VENDOR_ROOT, + WORKER_MAIN, + MODULE_CLI, + ) + if not VENDOR_ROOT.is_dir() or not all(path.is_file() for path in required_files[1:]): + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.start_failed", + code="WORKER_FILES_MISSING", + message="vendored runtime, worker entry point, or module adapter is missing", + ) + + environment = _worker_environment(profile, request) + command = [sys.executable, str(WORKER_MAIN)] + + try: + completed = subprocess.run( + command, + input=encode_request(request) + "\n", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + cwd=VENDOR_ROOT, + env=environment, + timeout=timeout, + check=False, + ) + except subprocess.TimeoutExpired: + return _failure( + request, + status=WorkerStatus.UNAVAILABLE, + event="launcher.worker_timeout", + code="WORKER_TIMEOUT", + message=f"isolated runtime exceeded the {timeout:g}-second launcher bound", + ) + except OSError as exc: + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.start_failed", + code="WORKER_START_FAILED", + message=f"cannot start isolated runtime worker: {exc}", + ) + except IPCContractError as exc: + return _failure( + request, + status=WorkerStatus.ERROR, + event="launcher.request_rejected", + code=exc.code, + message=exc.message, + ) + + return _parse_worker_output( + request, + stdout=completed.stdout, + returncode=completed.returncode, + ) + + +def _error_document(result: RuntimeResult) -> str: + assert result.error is not None + return json.dumps( + { + "status": result.status.value, + "event": result.event, + "task_id": result.task_id, + "error": { + "code": result.error.code, + "message": result.error.message, + }, + }, + ensure_ascii=False, + indent=2, + sort_keys=True, + ) + + +def _argument_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="python -m gauntlet_host.launcher", + description="Run one isolated Gauntlet-bundled agent turn.", + ) + parser.add_argument("prompt") + parser.add_argument("--task-id") + parser.add_argument("--cwd", default=str(Path.cwd())) + parser.add_argument("--model") + parser.add_argument("--provider") + parser.add_argument("--toolset", action="append", default=[]) + parser.add_argument( + "--timeout", + type=float, + default=DEFAULT_LAUNCH_TIMEOUT_SECONDS, + ) + parser.add_argument( + "--json", + action="store_true", + dest="json_output", + help="print the complete transport result instead of only the answer", + ) + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + args = _argument_parser().parse_args(argv) + task_id = args.task_id or f"task-runtime-{uuid.uuid4().hex[:16]}" + result = run_worker_turn( + args.prompt, + task_id=task_id, + cwd=args.cwd, + model=args.model, + provider=args.provider, + toolsets=args.toolset, + timeout_seconds=args.timeout, + ) + + if args.json_output: + print(encode_result(result)) + elif result.status is WorkerStatus.OK: + print(result.payload["final_response"]) + else: + print(_error_document(result), file=sys.stderr) + + return { + WorkerStatus.OK: 0, + WorkerStatus.ERROR: 2, + WorkerStatus.UNAVAILABLE: 3, + }[result.status] + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/gauntlet_host/module_cli.py b/gauntlet_host/module_cli.py new file mode 100644 index 00000000..4d4a7027 --- /dev/null +++ b/gauntlet_host/module_cli.py @@ -0,0 +1,236 @@ +"""Read-only Gauntlet module adapter for the isolated runtime plugin.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import re +import sys +from typing import Any, Sequence + +ADAPTER_SCHEMA = "gauntlet.adapter.v1" +TASK_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$") + + +class AdapterError(RuntimeError): + """Typed, fail-closed adapter error.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +def _is_within(path: Path, root: Path) -> bool: + try: + path.relative_to(root) + except ValueError: + return False + return True + + +def _configure_imports(root: Path) -> None: + tools_root = root / "tools" + required = ( + tools_root / "__init__.py", + tools_root / "egrt_store.py", + tools_root / "soul_runtime.py", + ) + if not all(path.is_file() for path in required): + raise AdapterError( + "GAUNTLET_REPOSITORY_INVALID", + "Gauntlet authority files are missing from the requested repository root", + ) + + vendor_root = (root / "vendor" / "hermes-agent").resolve(strict=False) + retained: list[str] = [] + seen: set[str] = set() + for entry in sys.path: + if not entry: + continue + resolved = Path(entry).resolve(strict=False) + if _is_within(resolved, vendor_root): + continue + rendered = str(resolved) + if rendered in seen: + continue + seen.add(rendered) + retained.append(rendered) + + ordered = [str(root), str(tools_root)] + sys.path[:] = ordered + [entry for entry in retained if entry not in ordered] + os.environ["PYTHONPATH"] = str(root) + os.chdir(root) + + +def _task_id() -> str: + task_id = os.environ.get("GAUNTLET_TASK_ID", "").strip() + if not task_id: + raise AdapterError( + "TASK_ID_MISSING", + "GAUNTLET_TASK_ID is required; task identity is never inferred from text", + ) + if not TASK_ID_PATTERN.fullmatch(task_id) or ".." in task_id: + raise AdapterError( + "TASK_ID_INVALID", + "GAUNTLET_TASK_ID contains unsupported characters", + ) + return task_id + + +def _base_document(action: str, task_id: str) -> dict[str, Any]: + return { + "schema": ADAPTER_SCHEMA, + "action": action, + "task_id": task_id, + "canonical_source": "egrt.runtime.v1", + "read_only": True, + "mutation_performed": False, + "authority": { + "receipt_creation": False, + "verdict_change": False, + "obligation_clearance": False, + "task_release": False, + }, + } + + +def _release_projection(root: Path, task_id: str) -> dict[str, Any]: + from soul_runtime import release_gate + + verdict, detail = release_gate(root, task_id) + return { + "verdict": verdict.value, + "release_eligible": verdict.value == "CLEARED", + "detail": detail, + } + + +def _task_projection(task: dict[str, Any], release: dict[str, Any]) -> dict[str, Any]: + states = { + row.get("obligation_id"): row + for row in release.get("detail", {}).get("obligations", []) + if isinstance(row, dict) and row.get("obligation_id") + } + obligations: list[dict[str, Any]] = [] + for row in task.get("obligations", []): + if not isinstance(row, dict): + continue + obligation_id = str(row.get("obligation_id") or "") + projected = { + "obligation_id": obligation_id, + "kind": row.get("kind"), + "claim": row.get("claim"), + "load_bearing": bool(row.get("load_bearing", True)), + "required_module": row.get("required_module"), + } + gate_state = states.get(obligation_id) + if gate_state is not None: + projected["release_gate"] = gate_state + obligations.append(projected) + + return { + "task_id": task.get("task_id"), + "goal_hash": task.get("goal_hash"), + "active": bool(task.get("active", False)), + "released": bool(task.get("released", False)), + "schema": task.get("schema"), + "content_hash": task.get("content_hash"), + "obligations": obligations, + } + + +def _read_task(root: Path, task_id: str) -> dict[str, Any]: + from egrt_store import RuntimeStore + + task = RuntimeStore(root).read_task(task_id, require_integrity=True) + if task is None: + raise AdapterError( + "TASK_NOT_FOUND", + f"no integrity-valid canonical task exists for {task_id}", + ) + return task + + +def _execute(root: Path, action: str, task_id: str) -> dict[str, Any]: + task = _read_task(root, task_id) + release = _release_projection(root, task_id) + document = _base_document(action, task_id) + document["status"] = "OK" + + if action == "task-status": + document["task"] = _task_projection(task, release) + document["release"] = release + elif action == "release-status": + document["task_released"] = bool(task.get("released", False)) + document["release"] = release + else: + raise AdapterError( + "UNSUPPORTED_ACTION", + f"unsupported read-only adapter action: {action}", + ) + return document + + +def _error_document(action: str, task_id: str, exc: AdapterError) -> dict[str, Any]: + document = _base_document(action, task_id) + document.update( + { + "status": "ERROR", + "error": { + "code": exc.code, + "message": exc.message, + }, + } + ) + return document + + +def _argument_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="python gauntlet_host/module_cli.py", + description="Read canonical Gauntlet task or release status without mutation.", + ) + parser.add_argument("--root", default=".") + parser.add_argument("action", choices=("task-status", "release-status")) + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + args = _argument_parser().parse_args(argv) + root = Path(args.root).expanduser().resolve(strict=False) + task_id = os.environ.get("GAUNTLET_TASK_ID", "").strip() or "unknown" + try: + _configure_imports(root) + task_id = _task_id() + document = _execute(root, args.action, task_id) + exit_code = 0 + except AdapterError as exc: + document = _error_document(args.action, task_id, exc) + exit_code = 2 + except Exception as exc: + document = _error_document( + args.action, + task_id, + AdapterError( + "ADAPTER_INTERNAL_ERROR", + f"unexpected read-only adapter failure: {type(exc).__name__}", + ), + ) + exit_code = 2 + + print( + json.dumps( + document, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + ) + return exit_code + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/gauntlet_host/runtime_profile.py b/gauntlet_host/runtime_profile.py new file mode 100644 index 00000000..f09d8862 --- /dev/null +++ b/gauntlet_host/runtime_profile.py @@ -0,0 +1,302 @@ +"""Gauntlet-owned runtime home and alpha policy profile.""" + +from __future__ import annotations + +import hashlib +import json +import os +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any + +from gauntlet_host.constants import ( + GAUNTLET_PLUGIN_ID, + GAUNTLET_PLUGIN_SOURCE, + GAUNTLET_STATUS_TOOLS, +) + +GAUNTLET_PLUGIN_MANIFEST = """\ +manifest_version: 2 +name: gauntlet +version: 0.1.0 +description: Read-only canonical task and Soul release status tools for Gauntlet. +author: The Gauntlet +kind: standalone +provides_tools: + - gauntlet_task_status + - gauntlet_release_status +""" + + +class RuntimeProfileError(RuntimeError): + """Fail-closed runtime profile preparation error.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +@dataclass(frozen=True, slots=True) +class RuntimeProfile: + """Materialized Gauntlet-owned runtime profile.""" + + runtime_home: str + config_path: str + config_sha256: str + background_review_enabled: bool + memory_write_approval: bool + skills_write_approval: bool + gauntlet_plugin_enabled: bool + plugin_path: str + plugin_manifest_path: str + plugin_sha256: str + plugin_tools: tuple[str, ...] + created_directories: tuple[str, ...] + + def to_payload(self) -> dict[str, Any]: + return asdict(self) + + +def default_runtime_home() -> Path: + """Return the dedicated runtime home, never the user's normal Hermes home.""" + + return Path.home().expanduser() / ".gauntlet" / "runtime" + + +def _secure_directory(path: Path) -> None: + path.mkdir(parents=True, exist_ok=True) + try: + path.chmod(0o700) + except OSError: + pass + + +def _secure_file(path: Path) -> None: + try: + path.chmod(0o600) + except OSError: + pass + + +def _load_yaml_module() -> Any | None: + try: + import yaml + except ModuleNotFoundError: + return None + return yaml + + +def _read_config(path: Path) -> dict[str, Any]: + if not path.exists(): + return {} + + try: + text = path.read_text(encoding="utf-8") + except OSError as exc: + raise RuntimeProfileError( + "RUNTIME_CONFIG_UNREADABLE", + f"cannot read Gauntlet runtime config: {exc}", + ) from exc + + if not text.strip(): + return {} + + yaml = _load_yaml_module() + try: + if yaml is not None: + value = yaml.safe_load(text) + else: + value = json.loads(text) + except Exception as exc: + dependency_hint = "" + if yaml is None: + dependency_hint = " Install the vendored runtime dependencies to read YAML." + raise RuntimeProfileError( + "RUNTIME_CONFIG_INVALID", + f"cannot parse Gauntlet runtime config.{dependency_hint}", + ) from exc + + if value is None: + return {} + if not isinstance(value, dict): + raise RuntimeProfileError( + "RUNTIME_CONFIG_INVALID", + "Gauntlet runtime config must contain a mapping at its root", + ) + return dict(value) + + +def _mapping(parent: dict[str, Any], key: str) -> dict[str, Any]: + value = parent.get(key) + if value is None: + result: dict[str, Any] = {} + parent[key] = result + return result + if not isinstance(value, dict): + raise RuntimeProfileError( + "RUNTIME_CONFIG_INVALID", + f"Gauntlet runtime config field {key!r} must be a mapping", + ) + return value + + +def _string_list(parent: dict[str, Any], key: str) -> list[str]: + value = parent.get(key) + if value is None: + return [] + if not isinstance(value, list) or not all(isinstance(item, str) for item in value): + raise RuntimeProfileError( + "RUNTIME_CONFIG_INVALID", + f"Gauntlet runtime config field plugins.{key} must be a string array", + ) + return list(dict.fromkeys(item.strip() for item in value if item.strip())) + + +def _apply_alpha_policy(config: dict[str, Any]) -> None: + auxiliary = _mapping(config, "auxiliary") + background_review = _mapping(auxiliary, "background_review") + background_review["enabled"] = False + + memory = _mapping(config, "memory") + memory["write_approval"] = True + + skills = _mapping(config, "skills") + skills["write_approval"] = True + + plugins = _mapping(config, "plugins") + enabled = _string_list(plugins, "enabled") + if GAUNTLET_PLUGIN_ID not in enabled: + enabled.append(GAUNTLET_PLUGIN_ID) + plugins["enabled"] = enabled + + disabled = _string_list(plugins, "disabled") + plugins["disabled"] = [ + plugin_id + for plugin_id in disabled + if plugin_id != GAUNTLET_PLUGIN_ID + ] + + +def _render_config(config: dict[str, Any]) -> str: + yaml = _load_yaml_module() + if yaml is None: + return json.dumps(config, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + return yaml.safe_dump( + config, + allow_unicode=True, + default_flow_style=False, + sort_keys=True, + ) + + +def _atomic_write(path: Path, content: str) -> None: + temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp") + try: + temporary.write_text(content, encoding="utf-8") + _secure_file(temporary) + os.replace(temporary, path) + _secure_file(path) + except OSError as exc: + try: + temporary.unlink(missing_ok=True) + except OSError: + pass + raise RuntimeProfileError( + "RUNTIME_CONFIG_WRITE_FAILED", + f"cannot write Gauntlet runtime file {path}: {exc}", + ) from exc + + +def _write_if_changed(path: Path, content: str) -> None: + try: + existing = path.read_text(encoding="utf-8") if path.exists() else None + except OSError as exc: + raise RuntimeProfileError( + "RUNTIME_FILE_UNREADABLE", + f"cannot read Gauntlet runtime file {path}: {exc}", + ) from exc + if existing != content: + _atomic_write(path, content) + else: + _secure_file(path) + + +def _materialize_gauntlet_plugin(home: Path) -> tuple[Path, Path, str]: + if not GAUNTLET_PLUGIN_SOURCE.is_file(): + raise RuntimeProfileError( + "GAUNTLET_PLUGIN_SOURCE_MISSING", + f"Gauntlet runtime plugin source is missing: {GAUNTLET_PLUGIN_SOURCE}", + ) + try: + source = GAUNTLET_PLUGIN_SOURCE.read_text(encoding="utf-8") + except OSError as exc: + raise RuntimeProfileError( + "GAUNTLET_PLUGIN_SOURCE_UNREADABLE", + f"cannot read Gauntlet runtime plugin source: {exc}", + ) from exc + + plugin_dir = home / "plugins" / GAUNTLET_PLUGIN_ID + _secure_directory(plugin_dir) + plugin_path = plugin_dir / "__init__.py" + manifest_path = plugin_dir / "plugin.yaml" + _write_if_changed(plugin_path, source) + _write_if_changed(manifest_path, GAUNTLET_PLUGIN_MANIFEST) + digest = hashlib.sha256(source.encode("utf-8")).hexdigest() + return plugin_path, manifest_path, digest + + +def prepare_runtime_profile(runtime_home: Path | None = None) -> RuntimeProfile: + """Create isolated runtime state, plugin files, and alpha policy.""" + + home = (runtime_home or default_runtime_home()).expanduser().resolve(strict=False) + normal_hermes_home = (Path.home().expanduser() / ".hermes").resolve(strict=False) + if home == normal_hermes_home: + raise RuntimeProfileError( + "RUNTIME_HOME_COLLISION", + "Gauntlet runtime home must not be the user's normal Hermes home", + ) + + relative_directories = ( + "", + "memories", + "skills", + "cache", + "logs", + "pending", + "pending/memory", + "pending/skills", + "plugins", + f"plugins/{GAUNTLET_PLUGIN_ID}", + ) + created: list[str] = [] + for relative in relative_directories: + directory = home / relative if relative else home + _secure_directory(directory) + created.append(str(directory)) + + plugin_path, manifest_path, plugin_digest = _materialize_gauntlet_plugin(home) + + config_path = home / "config.yaml" + config = _read_config(config_path) + _apply_alpha_policy(config) + rendered = _render_config(config) + _write_if_changed(config_path, rendered) + + os.environ["HERMES_HOME"] = str(home) + config_digest = hashlib.sha256(rendered.encode("utf-8")).hexdigest() + + return RuntimeProfile( + runtime_home=str(home), + config_path=str(config_path), + config_sha256=config_digest, + background_review_enabled=False, + memory_write_approval=True, + skills_write_approval=True, + gauntlet_plugin_enabled=True, + plugin_path=str(plugin_path), + plugin_manifest_path=str(manifest_path), + plugin_sha256=plugin_digest, + plugin_tools=GAUNTLET_STATUS_TOOLS, + created_directories=tuple(created), + ) diff --git a/gauntlet_host/worker_main.py b/gauntlet_host/worker_main.py new file mode 100644 index 00000000..3064af96 --- /dev/null +++ b/gauntlet_host/worker_main.py @@ -0,0 +1,650 @@ +"""Isolated bootstrap and one-turn execution for the vendored runtime.""" + +from __future__ import annotations + +from contextlib import redirect_stdout +import importlib +import importlib.util +import json +import os +import re +from dataclasses import asdict, dataclass +from pathlib import Path +import sys +import traceback +from typing import Any, TextIO + +if __package__ in {None, ""}: + _repo_bootstrap = Path(__file__).resolve().parent.parent + if str(_repo_bootstrap) not in sys.path: + sys.path.insert(0, str(_repo_bootstrap)) + +from gauntlet_host.constants import ( + DEFAULT_AGENT_RUN_BUDGET_SECONDS, + EXPECTED_HERMES_COMMIT, + EXPECTED_HERMES_REPOSITORY, + EXPECTED_HERMES_TAG, + GAUNTLET_TOOLS_ROOT, + MAX_AGENT_RUN_BUDGET_SECONDS, + MAX_OPERATIONAL_ERROR_CHARS, + REPO_ROOT, + VENDOR_ROOT, + VENDOR_SNAPSHOT_MANIFEST, + VENDOR_TOOLS_ROOT, +) +from gauntlet_host.ipc import ( + IPCContractError, + RuntimeRequest, + RuntimeResult, + WorkerError, + WorkerOperation, + WorkerStatus, + contract_error_result, + decode_request, + write_result, +) +from gauntlet_host.runtime_profile import ( + RuntimeProfile, + RuntimeProfileError, + prepare_runtime_profile, +) + + +class WorkerBootstrapError(RuntimeError): + """Fail-closed error raised before vendored runtime execution.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +class RuntimeExecutionError(RuntimeError): + """Typed operational failure raised while constructing or running AIAgent.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +@dataclass(frozen=True, slots=True) +class NamespaceProof: + """Evidence that this interpreter resolved Hermes's top-level tools package.""" + + namespace_verified: bool + tools_module: str + tools_origin: str + vendor_tools_root: str + gauntlet_tools_root: str + repo_root: str + vendor_root: str + cwd: str + sys_path_head: str + pythonpath: str + upstream_repository: str + upstream_tag: str + upstream_commit: str + runtime_home: str + runtime_config: str + runtime_config_sha256: str + background_review_enabled: bool + memory_write_approval: bool + skills_write_approval: bool + + def to_payload(self) -> dict[str, Any]: + return asdict(self) + + +def _resolved(path: str | os.PathLike[str]) -> Path: + return Path(path).expanduser().resolve(strict=False) + + +def _is_within(path: Path, root: Path) -> bool: + try: + path.relative_to(root) + except ValueError: + return False + return True + + +def _module_origin(module: Any) -> Path | None: + origin = getattr(module, "__file__", None) + if not origin: + return None + return _resolved(origin) + + +def _validate_snapshot_manifest() -> dict[str, Any]: + if not VENDOR_SNAPSHOT_MANIFEST.is_file(): + raise WorkerBootstrapError( + "VENDOR_MANIFEST_MISSING", + f"missing vendored snapshot manifest: {VENDOR_SNAPSHOT_MANIFEST}", + ) + + try: + value = json.loads(VENDOR_SNAPSHOT_MANIFEST.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise WorkerBootstrapError( + "VENDOR_MANIFEST_INVALID", + f"cannot read vendored snapshot manifest: {exc}", + ) from exc + + if not isinstance(value, dict): + raise WorkerBootstrapError( + "VENDOR_MANIFEST_INVALID", + "vendored snapshot manifest must be a JSON object", + ) + + expected = { + "destination": "vendor/hermes-agent", + "state": "materialized", + "upstream_repository": EXPECTED_HERMES_REPOSITORY, + "upstream_tag": EXPECTED_HERMES_TAG, + "upstream_commit": EXPECTED_HERMES_COMMIT, + } + mismatches = [ + f"{key}={value.get(key)!r} (expected {expected_value!r})" + for key, expected_value in expected.items() + if value.get(key) != expected_value + ] + if value.get("local_modifications") != []: + mismatches.append("local_modifications must be an empty array") + if mismatches: + raise WorkerBootstrapError( + "VENDOR_MANIFEST_MISMATCH", + "; ".join(mismatches), + ) + return value + + +def _reject_preloaded_gauntlet_tools() -> None: + collisions: list[str] = [] + for name, module in tuple(sys.modules.items()): + if name != "tools" and not name.startswith("tools."): + continue + origin = _module_origin(module) + if origin is None or not _is_within(origin, VENDOR_TOOLS_ROOT): + rendered_origin = str(origin) if origin is not None else "" + collisions.append(f"{name}={rendered_origin}") + + if collisions: + raise WorkerBootstrapError( + "TOOLS_NAMESPACE_PRELOADED", + "non-vendored tools modules were loaded before isolation: " + + ", ".join(sorted(collisions)), + ) + + +def _isolate_sys_path() -> None: + vendor_text = str(VENDOR_ROOT) + retained: list[str] = [vendor_text] + seen: set[str] = {vendor_text} + + for entry in sys.path: + if not entry: + continue + resolved = _resolved(entry) + if _is_within(resolved, REPO_ROOT) and not _is_within(resolved, VENDOR_ROOT): + continue + rendered = str(resolved) + if rendered in seen: + continue + seen.add(rendered) + retained.append(rendered) + + sys.path[:] = retained + os.environ["PYTHONPATH"] = vendor_text + os.chdir(VENDOR_ROOT) + importlib.invalidate_caches() + + +def _verify_tools_spec() -> None: + spec = importlib.util.find_spec("tools") + if spec is None or spec.origin is None: + raise WorkerBootstrapError( + "TOOLS_NAMESPACE_UNRESOLVED", + "Python could not resolve the vendored top-level tools package", + ) + + origin = _resolved(spec.origin) + if not _is_within(origin, VENDOR_TOOLS_ROOT): + raise WorkerBootstrapError( + "TOOLS_NAMESPACE_COLLISION", + f"tools resolved outside the vendor root: {origin}", + ) + + locations = spec.submodule_search_locations + if locations is None: + raise WorkerBootstrapError( + "TOOLS_NAMESPACE_NOT_PACKAGE", + f"tools did not resolve as a package: {origin}", + ) + for location in locations: + if not _is_within(_resolved(location), VENDOR_TOOLS_ROOT): + raise WorkerBootstrapError( + "TOOLS_NAMESPACE_COLLISION", + f"tools package search path escaped the vendor root: {location}", + ) + + +def bootstrap_vendor_runtime(profile: RuntimeProfile) -> NamespaceProof: + """Enter the isolated vendor import environment and prove tools resolution.""" + + if not VENDOR_ROOT.is_dir(): + raise WorkerBootstrapError( + "VENDOR_ROOT_MISSING", + f"missing vendored runtime root: {VENDOR_ROOT}", + ) + if not (VENDOR_TOOLS_ROOT / "__init__.py").is_file(): + raise WorkerBootstrapError( + "VENDOR_TOOLS_MISSING", + f"missing vendored tools package: {VENDOR_TOOLS_ROOT}", + ) + if not (GAUNTLET_TOOLS_ROOT / "__init__.py").is_file(): + raise WorkerBootstrapError( + "GAUNTLET_TOOLS_MISSING", + f"missing Gauntlet tools package: {GAUNTLET_TOOLS_ROOT}", + ) + + manifest = _validate_snapshot_manifest() + _reject_preloaded_gauntlet_tools() + _isolate_sys_path() + _verify_tools_spec() + + tools_module = importlib.import_module("tools") + tools_origin = _module_origin(tools_module) + if tools_origin is None or not _is_within(tools_origin, VENDOR_TOOLS_ROOT): + raise WorkerBootstrapError( + "TOOLS_NAMESPACE_COLLISION", + f"imported tools module escaped the vendor root: {tools_origin}", + ) + + return NamespaceProof( + namespace_verified=True, + tools_module=tools_module.__name__, + tools_origin=str(tools_origin), + vendor_tools_root=str(VENDOR_TOOLS_ROOT), + gauntlet_tools_root=str(GAUNTLET_TOOLS_ROOT), + repo_root=str(REPO_ROOT), + vendor_root=str(VENDOR_ROOT), + cwd=os.getcwd(), + sys_path_head=sys.path[0], + pythonpath=os.environ["PYTHONPATH"], + upstream_repository=str(manifest["upstream_repository"]), + upstream_tag=str(manifest["upstream_tag"]), + upstream_commit=str(manifest["upstream_commit"]), + runtime_home=profile.runtime_home, + runtime_config=profile.config_path, + runtime_config_sha256=profile.config_sha256, + background_review_enabled=profile.background_review_enabled, + memory_write_approval=profile.memory_write_approval, + skills_write_approval=profile.skills_write_approval, + ) + + +def _error_result( + request: RuntimeRequest, + *, + status: WorkerStatus, + event: str, + code: str, + message: str, + payload: dict[str, Any] | None = None, +) -> RuntimeResult: + return RuntimeResult( + request_id=request.request_id, + task_id=request.task_id, + status=status, + event=event, + payload=payload or {}, + error=WorkerError(code=code, message=message), + ) + + +def _safe_exception_message(exc: BaseException) -> str: + text = " ".join(str(exc).split()) + text = re.sub( + r"(?i)(api[_-]?key|authorization|token|secret)\s*[:=]\s*\S+", + r"\1=", + text, + ) + text = re.sub(r"\bsk-[A-Za-z0-9_-]{8,}\b", "", text) + if not text: + text = exc.__class__.__name__ + return text[:MAX_OPERATIONAL_ERROR_CHARS] + + +def _run_budget(request: RuntimeRequest) -> float: + raw = request.metadata.get( + "run_budget_seconds", + DEFAULT_AGENT_RUN_BUDGET_SECONDS, + ) + if isinstance(raw, bool) or not isinstance(raw, (int, float)): + raise RuntimeExecutionError( + "INVALID_RUN_BUDGET", + "run_budget_seconds must be numeric", + ) + value = float(raw) + if value <= 0 or value > MAX_AGENT_RUN_BUDGET_SECONDS: + raise RuntimeExecutionError( + "INVALID_RUN_BUDGET", + ( + "run_budget_seconds must be greater than 0 and at most " + f"{MAX_AGENT_RUN_BUDGET_SECONDS:g}" + ), + ) + return value + + +def _model_and_provider( + config: dict[str, Any], + request: RuntimeRequest, +) -> tuple[str, str | None]: + model_config = config.get("model") + configured_model = "" + configured_provider = "" + + if isinstance(model_config, str): + configured_model = model_config.strip() + elif isinstance(model_config, dict): + raw_model = model_config.get("default") or model_config.get("model") or "" + if isinstance(raw_model, dict): + try: + from hermes_cli.config import split_model_config_default + + configured_model, nested_provider = split_model_config_default(raw_model) + configured_provider = str( + nested_provider or model_config.get("provider") or "" + ).strip() + except Exception: + configured_model = str(raw_model.get("model") or "").strip() + configured_provider = str( + raw_model.get("provider") or model_config.get("provider") or "" + ).strip() + else: + configured_model = str(raw_model or "").strip() + configured_provider = str(model_config.get("provider") or "").strip() + + effective_model = (request.model or "").strip() or configured_model + effective_provider = (request.provider or "").strip() or configured_provider or None + + if not effective_model: + raise RuntimeExecutionError( + "MODEL_NOT_CONFIGURED", + "no model was supplied and the Gauntlet runtime profile has no default model", + ) + + if request.model and not request.provider: + try: + from hermes_cli.models import detect_provider_for_model + + detected = detect_provider_for_model( + effective_model, + effective_provider or "auto", + ) + except Exception: + detected = None + if detected: + effective_provider, effective_model = detected + + return effective_model, effective_provider + + +def _toolsets(request: RuntimeRequest) -> list[str]: + return list(dict.fromkeys(request.toolsets)) + + +def _usage_payload(result: dict[str, Any]) -> dict[str, Any]: + fields = ( + "input_tokens", + "output_tokens", + "cache_read_tokens", + "cache_write_tokens", + "reasoning_tokens", + "total_tokens", + "api_calls", + "estimated_cost_usd", + "cost_status", + "cost_source", + "service_tier", + ) + return {field: result.get(field) for field in fields} + + +def _cleanup_agent(agent: Any, session_db: Any) -> None: + if agent is not None: + try: + session_messages = getattr(agent, "_session_messages", None) + if isinstance(session_messages, list): + agent.shutdown_memory_provider(session_messages) + else: + agent.shutdown_memory_provider() + except Exception: + pass + try: + agent.close() + except Exception: + pass + if session_db is not None: + try: + session_db.close() + except Exception: + pass + + +def _execute_agent_turn( + request: RuntimeRequest, + proof: NamespaceProof, +) -> RuntimeResult: + os.environ["GAUNTLET_TASK_ID"] = request.task_id + for bypass in ( + "HERMES_YOLO_MODE", + "HERMES_ACCEPT_HOOKS", + "HERMES_INTERACTIVE", + ): + os.environ.pop(bypass, None) + + agent = None + session_db = None + try: + run_budget = _run_budget(request) + with redirect_stdout(sys.stderr): + from hermes_cli.config import load_config + from hermes_cli.fallback_config import get_fallback_chain + from hermes_cli.runtime_provider import resolve_runtime_provider + from hermes_state import SessionDB + from run_agent import AIAgent + + config = load_config() + effective_model, effective_provider = _model_and_provider(config, request) + runtime = resolve_runtime_provider( + requested=effective_provider, + target_model=effective_model, + ) + session_db = SessionDB() + agent = AIAgent( + api_key=runtime.get("api_key"), + base_url=runtime.get("base_url"), + provider=runtime.get("provider"), + requested_provider=runtime.get("requested_provider"), + api_mode=runtime.get("api_mode"), + model=effective_model, + max_iterations=8, + enabled_toolsets=_toolsets(request), + quiet_mode=True, + tool_progress_mode="off", + platform="gauntlet", + session_db=session_db, + credential_pool=runtime.get("credential_pool"), + fallback_model=get_fallback_chain(config) or None, + skip_background_review=True, + run_budget_seconds=run_budget, + ) + agent.suppress_status_output = True + agent.stream_delta_callback = None + agent.tool_gen_callback = None + result = agent.run_conversation(request.prompt) + + if not isinstance(result, dict): + raise RuntimeExecutionError( + "INVALID_AGENT_RESULT", + "upstream AIAgent returned a non-object result", + ) + + final_response = result.get("final_response") + if not isinstance(final_response, str) or not final_response.strip(): + raise RuntimeExecutionError( + "NO_FINAL_RESPONSE", + "upstream AIAgent did not produce a final response", + ) + + safe_payload = proof.to_payload() + safe_payload.update( + { + "final_response": final_response, + "model": str(result.get("model") or effective_model), + "provider": str( + result.get("provider") + or runtime.get("provider") + or effective_provider + or "" + ), + "session_id": str( + result.get("session_id") + or getattr(agent, "session_id", "") + or "" + ), + "completed": bool(result.get("completed", True)), + "failed": bool(result.get("failed", False)), + "partial": bool(result.get("partial", False)), + "requested_cwd": request.cwd, + "usage": _usage_payload(result), + } + ) + + if safe_payload["failed"] or safe_payload["partial"]: + return _error_result( + request, + status=WorkerStatus.UNAVAILABLE, + event="worker.turn_incomplete", + code="AGENT_TURN_INCOMPLETE", + message="upstream AIAgent returned a failed or partial turn", + payload=safe_payload, + ) + + return RuntimeResult( + request_id=request.request_id, + task_id=request.task_id, + status=WorkerStatus.OK, + event="worker.turn_completed", + payload=safe_payload, + ) + except RuntimeExecutionError as exc: + return _error_result( + request, + status=WorkerStatus.UNAVAILABLE, + event="worker.turn_unavailable", + code=exc.code, + message=exc.message, + payload=proof.to_payload(), + ) + except ModuleNotFoundError as exc: + missing = exc.name or "unknown" + return _error_result( + request, + status=WorkerStatus.UNAVAILABLE, + event="worker.turn_unavailable", + code="RUNTIME_DEPENDENCY_MISSING", + message=f"missing vendored runtime dependency: {missing}", + payload=proof.to_payload(), + ) + except KeyboardInterrupt: + raise + except BaseException as exc: + return _error_result( + request, + status=WorkerStatus.UNAVAILABLE, + event="worker.turn_unavailable", + code="AGENT_RUNTIME_UNAVAILABLE", + message=_safe_exception_message(exc), + payload=proof.to_payload(), + ) + finally: + _cleanup_agent(agent, session_db) + + +def handle_request(request: RuntimeRequest) -> RuntimeResult: + """Handle one isolated import probe or one upstream AIAgent turn.""" + + try: + profile = prepare_runtime_profile() + proof = bootstrap_vendor_runtime(profile) + except RuntimeProfileError as exc: + return _error_result( + request, + status=WorkerStatus.ERROR, + event="worker.profile_failed", + code=exc.code, + message=exc.message, + ) + except WorkerBootstrapError as exc: + return _error_result( + request, + status=WorkerStatus.ERROR, + event="worker.bootstrap_failed", + code=exc.code, + message=exc.message, + ) + + if request.operation is WorkerOperation.PROBE_IMPORTS: + return RuntimeResult( + request_id=request.request_id, + task_id=request.task_id, + status=WorkerStatus.OK, + event="worker.imports_verified", + payload=proof.to_payload(), + ) + + return _execute_agent_turn(request, proof) + + +def run_worker(stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int: + """Process a JSONL request stream and emit one result per non-blank line.""" + + exit_code = 0 + for line_number, line in enumerate(stdin, start=1): + if not line.strip(): + continue + try: + request = decode_request(line) + result = handle_request(request) + except IPCContractError as exc: + result = contract_error_result(exc) + except Exception: + traceback.print_exc(file=stderr) + result = RuntimeResult( + request_id="unknown", + task_id="unknown", + status=WorkerStatus.ERROR, + event="worker.internal_error", + error=WorkerError( + code="WORKER_INTERNAL_ERROR", + message=f"unexpected worker failure while processing line {line_number}", + ), + ) + + write_result(stdout, result) + if result.status is WorkerStatus.ERROR: + exit_code = max(exit_code, 2) + elif result.status is WorkerStatus.UNAVAILABLE: + exit_code = max(exit_code, 3) + return exit_code + + +def main() -> int: + return run_worker(sys.stdin, sys.stdout, sys.stderr) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/vendor_hermes.py b/scripts/vendor_hermes.py index e86eded3..ee4098e5 100755 --- a/scripts/vendor_hermes.py +++ b/scripts/vendor_hermes.py @@ -1,16 +1,12 @@ #!/usr/bin/env python3 -"""Materialize and verify the exact Hermes source used by Gauntlet's fast build.""" +"""Initialize and verify the exact Hermes gitlink used by Gauntlet's fast build.""" from __future__ import annotations import argparse import hashlib import json -import os -import shutil import subprocess import sys -import tempfile -from datetime import datetime, timezone from pathlib import Path REPO = "NousResearch/hermes-agent" @@ -29,8 +25,14 @@ class VendorError(RuntimeError): def command(*parts: str, cwd: Path | None = None) -> str: try: - done = subprocess.run(parts, cwd=cwd, check=True, text=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + done = subprocess.run( + parts, + cwd=cwd, + check=True, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) except (FileNotFoundError, subprocess.CalledProcessError) as exc: detail = getattr(exc, "stderr", None) or str(exc) raise VendorError(f"command failed: {' '.join(parts)}\n{detail}") from exc @@ -57,12 +59,21 @@ def manifest(root: Path) -> dict: try: data = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: - raise VendorError(f"cannot read snapshot manifest: {path}") from exc - expected = {"upstream_repository": REPO, "upstream_tag": TAG, - "upstream_commit": COMMIT, "destination": DEST.as_posix(), - "license_sha256": LICENSE_HASH} + raise VendorError(f"cannot read Hermes source manifest: {path}") from exc + expected = { + "schema": "gauntlet.vendor_gitlink.v1", + "state": "gitlink", + "upstream_repository": REPO, + "upstream_tag": TAG, + "upstream_commit": COMMIT, + "upstream_url": URL, + "destination": DEST.as_posix(), + "license_sha256": LICENSE_HASH, + } if any(data.get(key) != value for key, value in expected.items()): - raise VendorError("snapshot manifest pin mismatch") + raise VendorError("Hermes source manifest pin mismatch") + if data.get("local_modifications") != []: + raise VendorError("Hermes source manifest must declare zero local modifications") return data @@ -72,104 +83,112 @@ def verify_notice(root: Path) -> None: raise VendorError(f"missing or modified MIT notice: {path}") -def verify_checkout(source: Path) -> None: - if command("git", "rev-parse", "HEAD^{commit}", cwd=source) != COMMIT: - raise VendorError("source HEAD is not the pinned commit") - if command("git", "rev-parse", f"refs/tags/{TAG}^{{commit}}", cwd=source) != COMMIT: - raise VendorError("pinned tag does not resolve to the pinned commit") - if command("git", "status", "--porcelain=v1", "--untracked-files=all", cwd=source): - raise VendorError("source checkout is dirty") - if file_hash(source / "LICENSE") != LICENSE_HASH: - raise VendorError("upstream LICENSE hash mismatch") +def verify_gitmodules(root: Path) -> None: + path = root / ".gitmodules" + if not path.is_file(): + raise VendorError("missing .gitmodules") + configured_path = command( + "git", "config", "-f", str(path), "--get", "submodule.vendor/hermes-agent.path", cwd=root + ) + configured_url = command( + "git", "config", "-f", str(path), "--get", "submodule.vendor/hermes-agent.url", cwd=root + ) + if configured_path != DEST.as_posix() or configured_url != URL: + raise VendorError("Hermes .gitmodules pin mismatch") + + +def gitlink_sha(root: Path) -> str: + line = command("git", "ls-files", "--stage", "--", DEST.as_posix(), cwd=root) + if not line: + raise VendorError("Hermes gitlink is not tracked") + try: + metadata, path = line.split("\t", 1) + mode, sha, stage = metadata.split() + except ValueError as exc: + raise VendorError("cannot parse Hermes gitlink index entry") from exc + if path != DEST.as_posix() or mode != "160000" or stage != "0": + raise VendorError("vendor/hermes-agent must be a stage-0 mode-160000 gitlink") + if sha != COMMIT: + raise VendorError(f"Hermes gitlink is {sha}, expected {COMMIT}") + return sha + + +def checkout_initialized(root: Path) -> bool: + destination = root / DEST + return destination.is_dir() and (destination / "LICENSE").is_file() + + +def verify_checkout(root: Path) -> None: + destination = root / DEST + if not checkout_initialized(root): + raise VendorError( + "Hermes submodule is not initialized; run scripts/vendor_hermes.py --init" + ) + if command("git", "rev-parse", "HEAD^{commit}", cwd=destination) != COMMIT: + raise VendorError("Hermes submodule HEAD is not the pinned commit") + if command("git", "status", "--porcelain=v1", "--untracked-files=all", cwd=destination): + raise VendorError("Hermes submodule checkout is dirty") + if file_hash(destination / "LICENSE") != LICENSE_HASH: + raise VendorError("Hermes submodule LICENSE hash mismatch") -def tree_hash(root: Path) -> tuple[int, str]: - digest, count = hashlib.sha256(), 0 - for path in sorted(root.rglob("*"), key=lambda item: item.relative_to(root).as_posix()): - if not path.is_file(): - continue - relative = path.relative_to(root).as_posix().encode() - digest.update(relative + b"\0" + bytes.fromhex(file_hash(path)) + b"\n") - count += 1 - return count, digest.hexdigest() +def initialize(root: Path) -> None: + verify_gitmodules(root) + gitlink_sha(root) + command( + "git", + "submodule", + "update", + "--init", + "--depth", + "1", + "--", + DEST.as_posix(), + cwd=root, + ) def verify(root: Path) -> dict: + manifest(root) verify_notice(root) - data, destination = manifest(root), root / DEST - if not destination.is_dir() or (destination / ".git").exists(): - raise VendorError(f"invalid vendored tree: {destination}") - if file_hash(destination / "LICENSE") != LICENSE_HASH: - raise VendorError("vendored LICENSE hash mismatch") - count, digest = tree_hash(destination) - if data.get("state") != "materialized" or data.get("file_count") != count \ - or data.get("tree_sha256") != digest: - raise VendorError("vendored tree does not match its manifest") - return {"state": "verified", "upstream_commit": COMMIT, - "file_count": count, "tree_sha256": digest, - "license_sha256": LICENSE_HASH} - - -def materialize(root: Path, source: Path | None, force: bool) -> dict: - verify_notice(root) - data = manifest(root) - with tempfile.TemporaryDirectory(prefix="gauntlet-hermes-") as temp: - checkout = source.resolve() if source else Path(temp) / "checkout" - if source is None: - command("git", "clone", "--filter=blob:none", "--depth", "1", - "--branch", TAG, "--single-branch", URL, str(checkout)) - verify_checkout(checkout) - staging = root / "vendor" / f".hermes-agent.staging-{os.getpid()}" - if staging.exists(): - shutil.rmtree(staging) - shutil.copytree(checkout, staging, symlinks=True, - ignore=shutil.ignore_patterns(".git")) - count, digest = tree_hash(staging) - destination = root / DEST - if destination.exists() and not force: - shutil.rmtree(staging) - raise VendorError(f"destination exists; review and rerun with --force: {destination}") - backup = destination.with_name(f".{destination.name}.backup-{os.getpid()}") - if destination.exists(): - destination.rename(backup) - try: - staging.rename(destination) - except BaseException: - if backup.exists() and not destination.exists(): - backup.rename(destination) - raise - if backup.exists(): - shutil.rmtree(backup) - data.update({"state": "materialized", "file_count": count, - "tree_sha256": digest, "local_modifications": [], - "materialized_at": datetime.now(timezone.utc).isoformat()}) - path = root / MANIFEST - path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") - return verify(root) + verify_gitmodules(root) + sha = gitlink_sha(root) + verify_checkout(root) + return { + "state": "verified", + "storage": "gitlink", + "upstream_repository": REPO, + "upstream_tag": TAG, + "upstream_commit": sha, + "license_sha256": LICENSE_HASH, + "local_modifications": 0, + } def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--repo-root", type=Path) - parser.add_argument("--source", type=Path) - parser.add_argument("--force", action="store_true") + parser.add_argument("--init", action="store_true") parser.add_argument("--verify-only", action="store_true") parser.add_argument("--dry-run", action="store_true") args = parser.parse_args() try: root = root_path(args.repo_root) - if args.verify_only and (args.source or args.force or args.dry_run): - raise VendorError("--verify-only cannot be combined with other actions") + if sum(bool(value) for value in (args.init, args.verify_only, args.dry_run)) > 1: + raise VendorError("choose only one of --init, --verify-only, or --dry-run") if args.dry_run: - result = {"state": "planned", "upstream_repository": REPO, - "upstream_tag": TAG, "upstream_commit": COMMIT, - "destination": DEST.as_posix(), - "source": str(args.source.resolve()) if args.source else "clone pinned tag", - "force": args.force} - elif args.verify_only: - result = verify(root) + result = { + "state": "planned", + "storage": "gitlink", + "upstream_repository": REPO, + "upstream_tag": TAG, + "upstream_commit": COMMIT, + "destination": DEST.as_posix(), + } else: - result = materialize(root, args.source, args.force) + if args.init: + initialize(root) + result = verify(root) except (VendorError, OSError) as exc: print(json.dumps({"state": "error", "error": str(exc)}, indent=2), file=sys.stderr) return 2 diff --git a/third_party/HERMES_SOURCE_LEDGER.md b/third_party/HERMES_SOURCE_LEDGER.md index b8db797e..59318416 100644 --- a/third_party/HERMES_SOURCE_LEDGER.md +++ b/third_party/HERMES_SOURCE_LEDGER.md @@ -2,7 +2,7 @@ **Ledger ID:** `GAUNTLET-HERMES-SOURCE-001` **Fast-build branch:** `work/native-hermes-fastpath` -**Authority:** `vendor/HERMES_SNAPSHOT.json` is the machine-readable snapshot record. +**Authority:** `vendor/HERMES_SNAPSHOT.json` is the machine-readable source record. ## 1. Frozen upstream source @@ -14,49 +14,57 @@ | License | MIT | | Upstream license SHA-256 | `821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6` | | Local destination | `vendor/hermes-agent/` | -| Adoption method | Full source snapshot with only nested `.git` metadata excluded | +| Adoption method | Git submodule-style gitlink pinned to the exact upstream commit | | Runtime role | Internal isolated worker implementation; never Gauntlet evidential authority | -## 2. Current phase state +## 2. Current source state -`PREPARED_NOT_MATERIALIZED` +`GITLINK_VERIFIED` -This phase commits the exact pin, the unmodified MIT notice, and a deterministic -materialization/verification command. The full upstream tree is intentionally populated -in the next phase from a networked checkout so that the exact commit can be verified -before any bytes enter `vendor/hermes-agent/`. +Gauntlet records the upstream Hermes tree as a Git gitlink rather than copying the full +third-party source tree into Gauntlet history. This preserves exact source identity while +preventing upstream documentation, test fixtures, public OAuth client identifiers, and +secret-shaped examples from being misclassified as first-party Gauntlet credentials by the +repository-wide full-history secret scan. -## 3. Materialization commands +The remediation does **not** add a Gitleaks allowlist and does **not** weaken the first-party +secret scanner. The pinned upstream source must be initialized through Git submodules before +the native Hermes worker is executed. + +| Verification field | Value | +|---|---| +| Gitlink commit | `5fc308a70719a83cccdbba4c0e39c23f5a8239d5` | +| Upstream tag | `v2026.8.27` | +| License SHA-256 | `821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6` | +| Local modifications | `0` | + +## 3. Checkout and verification From the repository root: ```bash -python scripts/vendor_hermes.py --dry-run -python scripts/vendor_hermes.py +git submodule update --init --depth 1 -- vendor/hermes-agent python scripts/vendor_hermes.py --verify-only ``` -To use an already available clean checkout: +Or initialize and verify in one bounded command: ```bash -python scripts/vendor_hermes.py --source /path/to/hermes-agent +python scripts/vendor_hermes.py --init ``` -Replacement of an existing vendored tree requires an explicit reviewed action: - -```bash -python scripts/vendor_hermes.py --force -``` +`vendor/hermes-agent` must remain a mode-`160000` gitlink to the exact pinned commit. A +copied source tree at that path is rejected by the verifier. -## 4. Local modifications inside the vendored tree +## 4. Local modifications inside the upstream tree -None permitted by default. +None. -The source snapshot must remain byte-for-byte faithful to the pinned checkout except for -excluding `.git` metadata. Any future edit under `vendor/hermes-agent/` must be listed here -with the local path, reason, reviewer, and replacement or removal plan. +Do not edit the submodule worktree as part of Gauntlet. A future Hermes upgrade must update +the gitlink, the machine-readable source record, and this ledger together after license, +security, and behavior qualification. ## 5. Authority boundary -Vendored runtime output is an observation. It cannot directly create a canonical Gauntlet +Hermes runtime output is an observation. It cannot directly create a canonical Gauntlet `Receipt`, change a `Verdict`, clear an `Obligation`, or bypass Soul's `release_gate()`. diff --git a/vendor/HERMES_SNAPSHOT.json b/vendor/HERMES_SNAPSHOT.json index e79d19f5..a93f6d3c 100644 --- a/vendor/HERMES_SNAPSHOT.json +++ b/vendor/HERMES_SNAPSHOT.json @@ -1,13 +1,10 @@ { "destination": "vendor/hermes-agent", - "file_count": null, "license": "MIT", "license_sha256": "821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6", "local_modifications": [], - "materialized_at": null, - "schema": "gauntlet.vendor_snapshot.v1", - "state": "prepared", - "tree_sha256": null, + "schema": "gauntlet.vendor_gitlink.v1", + "state": "gitlink", "upstream_commit": "5fc308a70719a83cccdbba4c0e39c23f5a8239d5", "upstream_repository": "NousResearch/hermes-agent", "upstream_tag": "v2026.8.27", diff --git a/vendor/hermes-agent b/vendor/hermes-agent new file mode 160000 index 00000000..5fc308a7 --- /dev/null +++ b/vendor/hermes-agent @@ -0,0 +1 @@ +Subproject commit 5fc308a70719a83cccdbba4c0e39c23f5a8239d5 From dda78eabc216749cb1a3010d8e533428b78721d0 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Fri, 28 Aug 2026 20:13:00 -0700 Subject: [PATCH 07/37] fix(hermes): preserve runtime materialization state on gitlink storage --- scripts/vendor_hermes.py | 3 ++- vendor/HERMES_SNAPSHOT.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/vendor_hermes.py b/scripts/vendor_hermes.py index ee4098e5..5ca43aaf 100755 --- a/scripts/vendor_hermes.py +++ b/scripts/vendor_hermes.py @@ -62,7 +62,8 @@ def manifest(root: Path) -> dict: raise VendorError(f"cannot read Hermes source manifest: {path}") from exc expected = { "schema": "gauntlet.vendor_gitlink.v1", - "state": "gitlink", + "state": "materialized", + "storage": "gitlink", "upstream_repository": REPO, "upstream_tag": TAG, "upstream_commit": COMMIT, diff --git a/vendor/HERMES_SNAPSHOT.json b/vendor/HERMES_SNAPSHOT.json index a93f6d3c..e4020126 100644 --- a/vendor/HERMES_SNAPSHOT.json +++ b/vendor/HERMES_SNAPSHOT.json @@ -4,7 +4,8 @@ "license_sha256": "821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6", "local_modifications": [], "schema": "gauntlet.vendor_gitlink.v1", - "state": "gitlink", + "state": "materialized", + "storage": "gitlink", "upstream_commit": "5fc308a70719a83cccdbba4c0e39c23f5a8239d5", "upstream_repository": "NousResearch/hermes-agent", "upstream_tag": "v2026.8.27", From a1f6ac24cda62f4f94d10cbf4c015daad75ae7cc Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:26:09 -0700 Subject: [PATCH 08/37] bridge: add observation firewall and Soul-gated FAST-P6 finalizer --- .github/phase6_verify.py | 198 +++++++++++ ..._plugin_v3.yml => fastpath-checkpoint.yml} | 10 +- gauntlet_host/constants.py | 5 + gauntlet_host/finalizer.py | 328 ++++++++++++++++++ gauntlet_host/gauntlet_plugin.py | 182 +++++++++- gauntlet_host/launcher.py | 78 +++-- gauntlet_host/observation_bridge.py | 231 ++++++++++++ 7 files changed, 990 insertions(+), 42 deletions(-) create mode 100644 .github/phase6_verify.py rename .github/workflows/{_phase5_status_plugin_v3.yml => fastpath-checkpoint.yml} (81%) create mode 100644 gauntlet_host/finalizer.py create mode 100644 gauntlet_host/observation_bridge.py diff --git a/.github/phase6_verify.py b/.github/phase6_verify.py new file mode 100644 index 00000000..2b02c6b3 --- /dev/null +++ b/.github/phase6_verify.py @@ -0,0 +1,198 @@ +"""Bounded FAST-P6 observation bridge and Soul finalizer verification.""" + +from __future__ import annotations + +import importlib.util +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import threading +from typing import Any + +REPO = Path(__file__).resolve().parent.parent +PHASE5_HELPER = REPO / ".github" / "phase5_verify.py" + + +def _load_phase5() -> Any: + spec = importlib.util.spec_from_file_location("phase5_verify", PHASE5_HELPER) + if spec is None or spec.loader is None: + raise RuntimeError("cannot load the retained FAST-P5 verification helper") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _read_observations(runtime_home: Path) -> list[dict[str, Any]]: + values: list[dict[str, Any]] = [] + for path in sorted((runtime_home / "observations").rglob("*.json")): + value = json.loads(path.read_text(encoding="utf-8")) + value["_path"] = str(path) + values.append(value) + return values + + +def _forbidden_authority_fields(value: Any) -> set[str]: + forbidden = { + "verdict", + "receipt", + "receipts", + "evidence_class", + "release", + "released", + "cleared", + "obligation_clearance", + } + found: set[str] = set() + if isinstance(value, dict): + found.update(forbidden.intersection(value)) + for item in value.values(): + found.update(_forbidden_authority_fields(item)) + elif isinstance(value, list): + for item in value: + found.update(_forbidden_authority_fields(item)) + return found + + +def main() -> None: + phase5 = _load_phase5() + phase5._source_boundary() + task_id = phase5._create_task() + before = phase5._task_state(task_id) + server = phase5.Phase5Server() + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + + try: + with tempfile.TemporaryDirectory(prefix="gauntlet-phase6-") as temporary: + home = Path(temporary) + runtime = home / ".gauntlet" / "runtime" + phase5._write_runtime_config(runtime, server.server_address[1]) + environment = dict(os.environ) + environment.update( + { + "HOME": str(home), + "HERMES_YOLO_MODE": "1", + "HERMES_ACCEPT_HOOKS": "1", + "HERMES_INTERACTIVE": "1", + } + ) + completed = subprocess.run( + [ + sys.executable, + "-m", + "gauntlet_host.launcher", + "Use gauntlet_task_status, then report that status was observed.", + "--task-id", + task_id, + "--model", + "phase5-mock", + "--provider", + "custom", + "--timeout", + "90", + "--json", + ], + cwd=REPO, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + check=False, + ) + print(completed.stdout, end="") + if completed.stderr: + print(completed.stderr, file=sys.stderr, end="") + assert completed.returncode == 2 + finalized = json.loads(completed.stdout) + + assert finalized["schema"] == "gauntlet.finalization.v1" + assert finalized["task_id"] == task_id + assert finalized["state"] == "UNRESOLVED" + assert finalized["accepted"] is False + assert finalized["final_response"] == "phase5 status observed" + assert finalized["worker_status"] == "OK" + assert finalized["worker_event"] == "worker.turn_completed" + assert finalized["release_gate_invoked"] is True + assert finalized["release_gate_verdict"] == "UNKNOWN" + assert finalized["release_eligible"] is False + assert finalized["task_release_performed"] is False + assert finalized["canonical_receipt_created"] is False + assert finalized["unresolved"] + + tool_request = phase5._tool_request(server.chat_requests) + result_request = phase5._result_request(server.chat_requests) + names = { + item["function"]["name"] + for item in tool_request.get("tools", []) + } + assert {"gauntlet_task_status", "gauntlet_release_status"} <= names + tool_messages = [ + message + for message in result_request.get("messages", []) + if message.get("tool_call_id") == phase5.TARGET_CALL_ID + ] + assert len(tool_messages) == 1 + status = json.loads(tool_messages[0]["content"]) + assert status["task_id"] == task_id + assert status["release"]["verdict"] == "UNKNOWN" + assert status["read_only"] is True + assert status["mutation_performed"] is False + + observations = _read_observations(runtime) + matching = [ + item + for item in observations + if item.get("tool") == "gauntlet_task_status" + ] + assert len(matching) == 1 + observation = matching[0] + assert observation["schema"] == "gauntlet.tool-observation.v1" + assert observation["event"] == "runtime.tool.finished" + assert observation["task_id"] == task_id + assert observation["status"] == "OK" + assert observation["runtime_session_id"] + assert observation["tool_call_id"] == phase5.TARGET_CALL_ID + assert len(observation["input_hash"]) == 64 + assert len(observation["output_hash"]) == 64 + assert observation["authority_ceiling"] == "OBSERVATION_ONLY" + assert observation["canonical_receipt_created"] is False + assert observation["canonical_state_mutated"] is False + assert observation["provenance"]["raw_input_persisted"] is False + assert observation["provenance"]["raw_output_persisted"] is False + assert not _forbidden_authority_fields(observation) + + after = phase5._task_state(task_id) + assert after == before + task = json.loads((phase5.TASKS / f"{task_id}.json").read_text()) + assert task["released"] is False + assert task["active"] is True + assert not any(phase5.RECEIPTS.glob("*.json")) + assert not (home / ".hermes").exists() + + summary = { + "schema": "gauntlet.fast-checkpoint-verification.v1", + "fast_milestone": "FAST-P6", + "task_id": task_id, + "model_round_trips": len(server.chat_requests), + "observations_recorded": len(observations), + "status_observations": len(matching), + "release_gate_verdict": finalized["release_gate_verdict"], + "unresolved_not_accepted": finalized["accepted"] is False, + "canonical_task_unchanged": after == before, + "canonical_receipts_created": 0, + "ordinary_hermes_home_created": False, + } + print(json.dumps(summary, indent=2, sort_keys=True)) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/_phase5_status_plugin_v3.yml b/.github/workflows/fastpath-checkpoint.yml similarity index 81% rename from .github/workflows/_phase5_status_plugin_v3.yml rename to .github/workflows/fastpath-checkpoint.yml index 0bdb5dd6..e6391a03 100644 --- a/.github/workflows/_phase5_status_plugin_v3.yml +++ b/.github/workflows/fastpath-checkpoint.yml @@ -1,4 +1,4 @@ -name: Phase 5 status plugin verification v3 +name: Fastpath checkpoint qualification on: push: @@ -10,11 +10,11 @@ permissions: contents: read concurrency: - group: phase5-status-v3-${{ github.ref }} + group: fastpath-checkpoint-${{ github.ref }} cancel-in-progress: true jobs: - verify: + fast-p6: runs-on: ubuntu-latest timeout-minutes: 25 steps: @@ -33,7 +33,7 @@ jobs: set -euo pipefail python -m pip install --upgrade pip python -m pip install -e vendor/hermes-agent - - name: Verify one agent-visible read-only status call + - name: Verify observation firewall and Soul finalizer run: | set -euo pipefail - python .github/phase5_verify.py + python .github/phase6_verify.py diff --git a/gauntlet_host/constants.py b/gauntlet_host/constants.py index cb29ca6e..36780940 100644 --- a/gauntlet_host/constants.py +++ b/gauntlet_host/constants.py @@ -6,6 +6,8 @@ WORKER_REQUEST_TYPE = "worker.request" WORKER_RESULT_TYPE = "worker.result" ADAPTER_PROTOCOL_VERSION = "gauntlet.adapter.v1" +OBSERVATION_PROTOCOL_VERSION = "gauntlet.tool-observation.v1" +FINALIZATION_PROTOCOL_VERSION = "gauntlet.finalization.v1" MAX_JSONL_BYTES = 1_048_576 MAX_OPERATIONAL_ERROR_CHARS = 2_000 @@ -14,6 +16,7 @@ DEFAULT_AGENT_RUN_BUDGET_SECONDS = 120.0 MAX_AGENT_RUN_BUDGET_SECONDS = 300.0 DEFAULT_ADAPTER_TIMEOUT_SECONDS = 20.0 +DEFAULT_OBSERVATION_TIMEOUT_SECONDS = 10.0 GAUNTLET_PLUGIN_ID = "gauntlet" GAUNTLET_TOOLSET = "gauntlet" @@ -29,6 +32,8 @@ GAUNTLET_TOOLS_ROOT = REPO_ROOT / "tools" WORKER_MAIN = HOST_ROOT / "worker_main.py" MODULE_CLI = HOST_ROOT / "module_cli.py" +OBSERVATION_BRIDGE = HOST_ROOT / "observation_bridge.py" +FINALIZER = HOST_ROOT / "finalizer.py" GAUNTLET_PLUGIN_SOURCE = HOST_ROOT / "gauntlet_plugin.py" VENDOR_SNAPSHOT_MANIFEST = REPO_ROOT / "vendor" / "HERMES_SNAPSHOT.json" diff --git a/gauntlet_host/finalizer.py b/gauntlet_host/finalizer.py new file mode 100644 index 00000000..becba601 --- /dev/null +++ b/gauntlet_host/finalizer.py @@ -0,0 +1,328 @@ +"""Parent-owned Soul release-gate finalization for completed worker turns.""" + +from __future__ import annotations + +from dataclasses import asdict, dataclass +import json +import os +from pathlib import Path +import subprocess +import sys +from typing import Any + +from gauntlet_host.constants import ( + ADAPTER_PROTOCOL_VERSION, + DEFAULT_ADAPTER_TIMEOUT_SECONDS, + FINALIZATION_PROTOCOL_VERSION, + MODULE_CLI, +) +from gauntlet_host.ipc import RuntimeResult, WorkerStatus + +ALLOWED_VERDICTS = {"CLEARED", "ISSUE", "UNKNOWN", "UNAVAILABLE"} + + +@dataclass(frozen=True, slots=True) +class FinalizationResult: + """A bounded parent-side result with an explicit Soul gate state.""" + + schema: str + task_id: str + state: str + accepted: bool + final_response: str | None + worker_status: str + worker_event: str + release_gate_invoked: bool + release_gate_verdict: str | None + release_eligible: bool + task_release_performed: bool + canonical_receipt_created: bool + unresolved: dict[str, Any] | None + error: dict[str, str] | None + + def to_dict(self) -> dict[str, Any]: + return asdict(self) + + +class FinalizerError(RuntimeError): + """Typed, fail-closed finalization error.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +def _base( + task_id: str, + worker_result: RuntimeResult, + *, + state: str, + accepted: bool, + release_gate_invoked: bool, + release_gate_verdict: str | None, + release_eligible: bool, + unresolved: dict[str, Any] | None = None, + error: dict[str, str] | None = None, +) -> FinalizationResult: + response = worker_result.payload.get("final_response") + if not isinstance(response, str): + response = None + return FinalizationResult( + schema=FINALIZATION_PROTOCOL_VERSION, + task_id=task_id, + state=state, + accepted=accepted, + final_response=response, + worker_status=worker_result.status.value, + worker_event=worker_result.event, + release_gate_invoked=release_gate_invoked, + release_gate_verdict=release_gate_verdict, + release_eligible=release_eligible, + task_release_performed=False, + canonical_receipt_created=False, + unresolved=unresolved, + error=error, + ) + + +def _adapter_environment(root: Path, task_id: str) -> dict[str, str]: + environment = dict(os.environ) + environment["GAUNTLET_TASK_ID"] = task_id + environment["PYTHONPATH"] = str(root) + environment["PYTHONUNBUFFERED"] = "1" + for bypass in ( + "HERMES_YOLO_MODE", + "HERMES_ACCEPT_HOOKS", + "HERMES_INTERACTIVE", + ): + environment.pop(bypass, None) + return environment + + +def _read_release_gate(root: Path, task_id: str) -> dict[str, Any]: + module_cli = (root / "gauntlet_host" / "module_cli.py").resolve() + expected = MODULE_CLI.resolve() + if module_cli != expected or not module_cli.is_file(): + raise FinalizerError( + "FINALIZER_ADAPTER_MISSING", + "finalizer adapter path does not match the active Gauntlet repository", + ) + + try: + completed = subprocess.run( + [ + sys.executable, + str(module_cli), + "--root", + str(root), + "release-status", + ], + cwd=root, + env=_adapter_environment(root, task_id), + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + timeout=DEFAULT_ADAPTER_TIMEOUT_SECONDS, + check=False, + ) + except subprocess.TimeoutExpired as exc: + raise FinalizerError( + "FINALIZER_GATE_TIMEOUT", + "Soul release-gate status exceeded the bounded adapter timeout", + ) from exc + except OSError as exc: + raise FinalizerError( + "FINALIZER_GATE_START_FAILED", + f"cannot start Soul release-gate adapter: {type(exc).__name__}", + ) from exc + + records = [line for line in completed.stdout.splitlines() if line.strip()] + if len(records) != 1: + raise FinalizerError( + "FINALIZER_GATE_PROTOCOL_ERROR", + "Soul release-gate adapter must return exactly one JSON record", + ) + try: + value = json.loads(records[0]) + except json.JSONDecodeError as exc: + raise FinalizerError( + "FINALIZER_GATE_PROTOCOL_ERROR", + "Soul release-gate adapter returned invalid JSON", + ) from exc + if not isinstance(value, dict): + raise FinalizerError( + "FINALIZER_GATE_PROTOCOL_ERROR", + "Soul release-gate adapter result must be a JSON object", + ) + if value.get("schema") != ADAPTER_PROTOCOL_VERSION: + raise FinalizerError( + "FINALIZER_GATE_SCHEMA_MISMATCH", + f"Soul release-gate adapter schema must be {ADAPTER_PROTOCOL_VERSION}", + ) + if value.get("action") != "release-status" or value.get("task_id") != task_id: + raise FinalizerError( + "FINALIZER_GATE_CORRELATION_MISMATCH", + "Soul release-gate result did not match the completed worker task", + ) + if value.get("read_only") is not True or value.get("mutation_performed") is not False: + raise FinalizerError( + "FINALIZER_AUTHORITY_VIOLATION", + "Soul release-gate adapter violated the read-only finalizer contract", + ) + expected_exit = 0 if value.get("status") == "OK" else 2 + if completed.returncode != expected_exit: + raise FinalizerError( + "FINALIZER_GATE_EXIT_MISMATCH", + "Soul release-gate adapter status and process exit did not agree", + ) + if value.get("status") != "OK": + error = value.get("error") + if isinstance(error, dict): + code = str(error.get("code") or "FINALIZER_GATE_UNAVAILABLE") + message = str(error.get("message") or "Soul release gate is unavailable") + else: + code = "FINALIZER_GATE_UNAVAILABLE" + message = "Soul release gate is unavailable" + raise FinalizerError(code, message) + return value + + +def finalize_worker_result( + root: Path, + task_id: str, + worker_result: RuntimeResult, +) -> FinalizationResult: + """Apply the existing Soul release gate after a completed worker turn.""" + + root = root.expanduser().resolve(strict=False) + if worker_result.task_id != task_id: + return _base( + task_id, + worker_result, + state="FINALIZER_ERROR", + accepted=False, + release_gate_invoked=False, + release_gate_verdict=None, + release_eligible=False, + error={ + "code": "FINALIZER_TASK_MISMATCH", + "message": "worker result task identity differs from finalizer task identity", + }, + ) + if worker_result.status is not WorkerStatus.OK: + error = None + if worker_result.error is not None: + error = { + "code": worker_result.error.code, + "message": worker_result.error.message, + } + return _base( + task_id, + worker_result, + state="WORKER_NOT_COMPLETED", + accepted=False, + release_gate_invoked=False, + release_gate_verdict=None, + release_eligible=False, + error=error, + ) + + try: + gate = _read_release_gate(root, task_id) + release = gate.get("release") + if not isinstance(release, dict): + raise FinalizerError( + "FINALIZER_GATE_PROTOCOL_ERROR", + "Soul release-gate result omitted the release projection", + ) + verdict = str(release.get("verdict") or "") + if verdict not in ALLOWED_VERDICTS: + raise FinalizerError( + "FINALIZER_GATE_VERDICT_INVALID", + "Soul release gate returned an unsupported verdict", + ) + eligible = release.get("release_eligible") is True + if eligible != (verdict == "CLEARED"): + raise FinalizerError( + "FINALIZER_GATE_CONTRADICTION", + "Soul release eligibility contradicted its canonical verdict", + ) + detail = release.get("detail") + unresolved = detail if isinstance(detail, dict) else {"detail": detail} + if verdict == "CLEARED": + return _base( + task_id, + worker_result, + state="CLEARED", + accepted=True, + release_gate_invoked=True, + release_gate_verdict=verdict, + release_eligible=True, + unresolved=None, + ) + return _base( + task_id, + worker_result, + state="UNRESOLVED", + accepted=False, + release_gate_invoked=True, + release_gate_verdict=verdict, + release_eligible=False, + unresolved=unresolved, + ) + except FinalizerError as exc: + return _base( + task_id, + worker_result, + state="FINALIZER_ERROR", + accepted=False, + release_gate_invoked=True, + release_gate_verdict=None, + release_eligible=False, + error={"code": exc.code, "message": exc.message}, + ) + + +def encode_finalization(result: FinalizationResult) -> str: + return json.dumps( + result.to_dict(), + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + + +def finalization_exit_code(result: FinalizationResult) -> int: + if result.state == "CLEARED": + return 0 + if result.state == "WORKER_NOT_COMPLETED" and result.worker_status == "UNAVAILABLE": + return 3 + return 2 + + +def print_human_finalization(result: FinalizationResult) -> None: + if result.final_response: + print(result.final_response) + if result.state != "CLEARED": + print() + print("[GAUNTLET STATUS]") + print( + json.dumps( + { + "state": result.state, + "task_id": result.task_id, + "release_gate_invoked": result.release_gate_invoked, + "verdict": result.release_gate_verdict, + "release_eligible": result.release_eligible, + "unresolved": result.unresolved, + "error": result.error, + }, + ensure_ascii=False, + indent=2, + sort_keys=True, + ) + ) diff --git a/gauntlet_host/gauntlet_plugin.py b/gauntlet_host/gauntlet_plugin.py index 74aaff87..8be7b72a 100644 --- a/gauntlet_host/gauntlet_plugin.py +++ b/gauntlet_host/gauntlet_plugin.py @@ -1,23 +1,34 @@ -"""Read-only Gauntlet status tools loaded through the vendored plugin ABI.""" +"""Read-only Gauntlet status tools and observation-only runtime hooks.""" from __future__ import annotations +from datetime import datetime, timezone +import hashlib import json +import logging import os from pathlib import Path import re import subprocess import sys +import threading +import time from typing import Any ADAPTER_SCHEMA = "gauntlet.adapter.v1" +OBSERVATION_SCHEMA = "gauntlet.observation-store-result.v1" TOOLSET = "gauntlet" MAX_ADAPTER_OUTPUT_CHARS = 262_144 ADAPTER_TIMEOUT_SECONDS = 20.0 +OBSERVATION_TIMEOUT_SECONDS = 10.0 + +logger = logging.getLogger(__name__) +_pending_calls: dict[tuple[str, str, str], tuple[str, float]] = {} +_pending_lock = threading.Lock() class PluginBridgeError(RuntimeError): - """Typed failure at the runtime-to-Gauntlet subprocess boundary.""" + """Typed failure at a runtime-to-Gauntlet subprocess boundary.""" def __init__(self, code: str, message: str) -> None: super().__init__(message) @@ -36,6 +47,30 @@ def _safe_message(value: Any) -> str: return (text or "runtime bridge failure")[:1_000] +def _canonical_hash(value: Any) -> str: + def fallback(item: Any) -> dict[str, str]: + return { + "type": f"{type(item).__module__}.{type(item).__qualname__}", + "repr": _safe_message(repr(item))[:2_000], + } + + try: + encoded = json.dumps( + value, + default=fallback, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ).encode("utf-8") + except Exception: + encoded = fallback(value)["repr"].encode("utf-8", errors="replace") + return hashlib.sha256(encoded).hexdigest() + + +def _utc_now() -> str: + return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + + def _error_document(action: str, code: str, message: str) -> str: return json.dumps( { @@ -219,6 +254,145 @@ def _release_status(arguments: dict[str, Any] | None = None, **_: Any) -> str: return _call_adapter("release-status", arguments) +def _call_key( + tool_name: str, + session_id: str, + tool_call_id: str, +) -> tuple[str, str, str]: + return ( + session_id or "unknown-session", + tool_call_id or "unknown-call", + tool_name or "unknown-tool", + ) + + +def _on_pre_tool_call( + tool_name: str = "", + session_id: str = "", + tool_call_id: str = "", + **_: Any, +) -> None: + key = _call_key(tool_name, session_id, tool_call_id) + with _pending_lock: + _pending_calls[key] = (_utc_now(), time.monotonic()) + + +def _tool_status(result: Any, values: dict[str, Any]) -> str: + if values.get("timed_out") is True: + return "TIMEOUT" + if values.get("cancelled") is True: + return "CANCELLED" + if values.get("unavailable") is True: + return "UNAVAILABLE" + if values.get("error") is not None or values.get("exception") is not None: + return "ERROR" + if isinstance(result, dict): + candidate = str(result.get("status") or "").upper() + if candidate in {"ERROR", "TIMEOUT", "UNAVAILABLE", "CANCELLED"}: + return candidate + if result.get("success") is False: + return "ERROR" + return "OK" + + +def _observation_path() -> Path: + bridge = Path(_required_environment("GAUNTLET_OBSERVATION_BRIDGE")).resolve() + repo_root = Path(_required_environment("GAUNTLET_REPO_ROOT")).resolve() + expected = (repo_root / "gauntlet_host" / "observation_bridge.py").resolve() + if bridge != expected or not bridge.is_file(): + raise PluginBridgeError( + "OBSERVATION_BRIDGE_PATH_MISMATCH", + "runtime observation bridge path is not the expected Gauntlet file", + ) + return bridge + + +def _record_observation(document: dict[str, Any]) -> None: + try: + bridge = _observation_path() + environment = dict(os.environ) + environment["PYTHONUNBUFFERED"] = "1" + completed = subprocess.run( + [sys.executable, str(bridge)], + input=json.dumps( + document, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + env=environment, + timeout=OBSERVATION_TIMEOUT_SECONDS, + check=False, + ) + records = [line for line in completed.stdout.splitlines() if line.strip()] + if completed.returncode != 0 or len(records) != 1: + logger.warning( + "Gauntlet observation bridge failed safely: exit=%s", + completed.returncode, + ) + return + result = json.loads(records[0]) + if result.get("schema") != OBSERVATION_SCHEMA: + logger.warning("Gauntlet observation bridge returned an invalid schema") + except Exception as exc: + logger.warning( + "Gauntlet observation hook failed safely: %s", + _safe_message(exc), + ) + + +def _on_post_tool_call( + tool_name: str = "", + args: Any = None, + result: Any = None, + session_id: str = "", + tool_call_id: str = "", + **values: Any, +) -> None: + finished_at = _utc_now() + finished_monotonic = time.monotonic() + key = _call_key(tool_name, session_id, tool_call_id) + with _pending_lock: + start = _pending_calls.pop(key, None) + if start is None: + started_at = finished_at + duration_ms = 0.0 + else: + started_at, started_monotonic = start + duration_ms = max(0.0, (finished_monotonic - started_monotonic) * 1_000) + + try: + task_id = _required_environment("GAUNTLET_TASK_ID") + except PluginBridgeError: + return + + _record_observation( + { + "task_id": task_id, + "runtime_session_id": session_id or "unknown-session", + "tool_call_id": tool_call_id or "unknown-call", + "tool": tool_name or "unknown-tool", + "status": _tool_status(result, values), + "input_hash": _canonical_hash(args), + "output_hash": _canonical_hash(result), + "started_at": started_at, + "finished_at": finished_at, + "duration_ms": duration_ms, + "provenance": { + "producer": "gauntlet-fastpath-plugin", + "event_source": "hermes.post_tool_call", + "raw_input_persisted": False, + "raw_output_persisted": False, + }, + } + ) + + _TASK_STATUS_SCHEMA = { "description": ( "Read the canonical Gauntlet task and obligation status for the exact " @@ -247,7 +421,7 @@ def _release_status(arguments: dict[str, Any] | None = None, **_: Any) -> str: def register(ctx: Any) -> None: - """Register the minimal read-only Gauntlet tool surface.""" + """Register read-only tools and observation-only lifecycle hooks.""" ctx.register_tool( name="gauntlet_task_status", @@ -265,3 +439,5 @@ def register(ctx: Any) -> None: description=_RELEASE_STATUS_SCHEMA["description"], emoji="", ) + ctx.register_hook("pre_tool_call", _on_pre_tool_call) + ctx.register_hook("post_tool_call", _on_post_tool_call) diff --git a/gauntlet_host/launcher.py b/gauntlet_host/launcher.py index 674e9f9c..d94dc63d 100644 --- a/gauntlet_host/launcher.py +++ b/gauntlet_host/launcher.py @@ -1,4 +1,4 @@ -"""Parent-side launcher for one isolated vendored runtime turn.""" +"""Parent-side launcher and Soul-gated finalizer for one runtime turn.""" from __future__ import annotations @@ -18,10 +18,18 @@ MAX_AGENT_RUN_BUDGET_SECONDS, MAX_LAUNCH_TIMEOUT_SECONDS, MODULE_CLI, + OBSERVATION_BRIDGE, REPO_ROOT, VENDOR_ROOT, WORKER_MAIN, ) +from gauntlet_host.finalizer import ( + FinalizationResult, + encode_finalization, + finalization_exit_code, + finalize_worker_result, + print_human_finalization, +) from gauntlet_host.ipc import ( IPCContractError, RuntimeRequest, @@ -31,7 +39,6 @@ WorkerStatus, decode_result, encode_request, - encode_result, ) from gauntlet_host.runtime_profile import ( RuntimeProfile, @@ -76,6 +83,7 @@ def _worker_environment(profile: RuntimeProfile, request: RuntimeRequest) -> dic environment["GAUNTLET_TASK_ID"] = request.task_id environment["GAUNTLET_REPO_ROOT"] = str(REPO_ROOT) environment["GAUNTLET_MODULE_CLI"] = str(MODULE_CLI) + environment["GAUNTLET_OBSERVATION_BRIDGE"] = str(OBSERVATION_BRIDGE) for inherited_bypass in ( "HERMES_YOLO_MODE", @@ -212,17 +220,19 @@ def run_worker_turn( ) required_files = ( - VENDOR_ROOT, WORKER_MAIN, MODULE_CLI, + OBSERVATION_BRIDGE, ) - if not VENDOR_ROOT.is_dir() or not all(path.is_file() for path in required_files[1:]): + if not VENDOR_ROOT.is_dir() or not all(path.is_file() for path in required_files): return _failure( request, status=WorkerStatus.ERROR, event="launcher.start_failed", code="WORKER_FILES_MISSING", - message="vendored runtime, worker entry point, or module adapter is missing", + message=( + "vendored runtime, worker, module adapter, or observation bridge is missing" + ), ) environment = _worker_environment(profile, request) @@ -274,28 +284,35 @@ def run_worker_turn( ) -def _error_document(result: RuntimeResult) -> str: - assert result.error is not None - return json.dumps( - { - "status": result.status.value, - "event": result.event, - "task_id": result.task_id, - "error": { - "code": result.error.code, - "message": result.error.message, - }, - }, - ensure_ascii=False, - indent=2, - sort_keys=True, +def run_gauntlet_turn( + prompt: str, + *, + task_id: str, + root: Path | str, + model: str | None = None, + provider: str | None = None, + toolsets: Sequence[str] = (), + timeout_seconds: float = DEFAULT_LAUNCH_TIMEOUT_SECONDS, +) -> FinalizationResult: + """Run one worker turn and apply the existing Soul release gate.""" + + resolved_root = Path(root).expanduser().resolve(strict=False) + worker_result = run_worker_turn( + prompt, + task_id=task_id, + cwd=resolved_root, + model=model, + provider=provider, + toolsets=toolsets, + timeout_seconds=timeout_seconds, ) + return finalize_worker_result(resolved_root, task_id, worker_result) def _argument_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="python -m gauntlet_host.launcher", - description="Run one isolated Gauntlet-bundled agent turn.", + description="Run one isolated Gauntlet-bundled agent turn and apply Soul's gate.", ) parser.add_argument("prompt") parser.add_argument("--task-id") @@ -312,7 +329,7 @@ def _argument_parser() -> argparse.ArgumentParser: "--json", action="store_true", dest="json_output", - help="print the complete transport result instead of only the answer", + help="print the complete Soul-gated finalization result", ) return parser @@ -320,10 +337,10 @@ def _argument_parser() -> argparse.ArgumentParser: def main(argv: Sequence[str] | None = None) -> int: args = _argument_parser().parse_args(argv) task_id = args.task_id or f"task-runtime-{uuid.uuid4().hex[:16]}" - result = run_worker_turn( + result = run_gauntlet_turn( args.prompt, task_id=task_id, - cwd=args.cwd, + root=args.cwd, model=args.model, provider=args.provider, toolsets=args.toolset, @@ -331,17 +348,10 @@ def main(argv: Sequence[str] | None = None) -> int: ) if args.json_output: - print(encode_result(result)) - elif result.status is WorkerStatus.OK: - print(result.payload["final_response"]) + print(encode_finalization(result)) else: - print(_error_document(result), file=sys.stderr) - - return { - WorkerStatus.OK: 0, - WorkerStatus.ERROR: 2, - WorkerStatus.UNAVAILABLE: 3, - }[result.status] + print_human_finalization(result) + return finalization_exit_code(result) if __name__ == "__main__": diff --git a/gauntlet_host/observation_bridge.py b/gauntlet_host/observation_bridge.py new file mode 100644 index 00000000..6bbacc14 --- /dev/null +++ b/gauntlet_host/observation_bridge.py @@ -0,0 +1,231 @@ +"""Write content-addressed operational tool observations outside canonical state.""" + +from __future__ import annotations + +import hashlib +import json +import os +from pathlib import Path +import re +import sys +from typing import Any, Mapping + +if __package__ in {None, ""}: + _root = Path(__file__).resolve().parent.parent + if str(_root) not in sys.path: + sys.path.insert(0, str(_root)) + +from gauntlet_host.constants import MAX_JSONL_BYTES, OBSERVATION_PROTOCOL_VERSION + +TASK_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$") +DIGEST = re.compile(r"^[0-9a-f]{64}$") +STATUSES = {"OK", "ERROR", "TIMEOUT", "UNAVAILABLE", "CANCELLED"} +FIELDS = { + "task_id", + "runtime_session_id", + "tool_call_id", + "tool", + "status", + "input_hash", + "output_hash", + "started_at", + "finished_at", + "duration_ms", +} +AUTHORITY_FIELDS = { + "verdict", + "receipt", + "receipts", + "evidence_class", + "release", + "released", + "cleared", +} + + +class ObservationError(RuntimeError): + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +def _canonical(value: Mapping[str, Any]) -> bytes: + return json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ).encode("utf-8") + + +def _text(value: Any, name: str, limit: int = 256) -> str: + if not isinstance(value, str) or not value.strip(): + raise ObservationError("INVALID_FIELD", f"{name} must be a non-empty string") + if len(value) > limit: + raise ObservationError("FIELD_TOO_LARGE", f"{name} exceeds {limit} characters") + return value + + +def _digest(value: Any, name: str, optional: bool = False) -> str | None: + if value is None and optional: + return None + text = _text(value, name, 64) + if not DIGEST.fullmatch(text): + raise ObservationError("INVALID_DIGEST", f"{name} must be a SHA-256 digest") + return text + + +def _request() -> dict[str, Any]: + raw = sys.stdin.buffer.read(MAX_JSONL_BYTES + 1) + if len(raw) > MAX_JSONL_BYTES: + raise ObservationError("REQUEST_TOO_LARGE", "observation request is too large") + try: + value = json.loads(raw.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise ObservationError("INVALID_JSON", "stdin must contain one JSON object") from exc + if not isinstance(value, dict): + raise ObservationError("INVALID_REQUEST", "observation request must be an object") + unknown = sorted(set(value) - FIELDS) + if unknown: + raise ObservationError("UNKNOWN_FIELDS", f"unknown fields: {', '.join(unknown)}") + forbidden = sorted(AUTHORITY_FIELDS.intersection(value)) + if forbidden: + raise ObservationError( + "AUTHORITY_FIELD_REJECTED", + f"forbidden authority fields: {', '.join(forbidden)}", + ) + return value + + +def _runtime_home() -> Path: + raw = os.environ.get("HERMES_HOME", "").strip() + if not raw: + raise ObservationError("RUNTIME_HOME_MISSING", "HERMES_HOME is required") + home = Path(raw).expanduser().resolve(strict=False) + if home == (Path.home() / ".hermes").resolve(strict=False): + raise ObservationError("RUNTIME_HOME_COLLISION", "ordinary Hermes home is forbidden") + return home + + +def build_observation(value: Mapping[str, Any]) -> dict[str, Any]: + task_id = _text(value.get("task_id"), "task_id") + bound = os.environ.get("GAUNTLET_TASK_ID", "").strip() + if not bound or task_id != bound: + raise ObservationError("TASK_ID_MISMATCH", "task identity is not host-bound") + if not TASK_ID.fullmatch(task_id) or ".." in task_id: + raise ObservationError("TASK_ID_INVALID", "task_id contains unsupported characters") + + status = _text(value.get("status"), "status", 32) + if status not in STATUSES: + raise ObservationError("INVALID_STATUS", "unsupported operational status") + duration = value.get("duration_ms") + if isinstance(duration, bool) or not isinstance(duration, (int, float)): + raise ObservationError("INVALID_DURATION", "duration_ms must be numeric") + if duration < 0 or duration > 86_400_000: + raise ObservationError("INVALID_DURATION", "duration_ms is outside its bound") + + document: dict[str, Any] = { + "schema": OBSERVATION_PROTOCOL_VERSION, + "event": "runtime.tool.finished", + "task_id": task_id, + "runtime_session_id": _text( + value.get("runtime_session_id") or "unknown-session", + "runtime_session_id", + ), + "tool_call_id": _text( + value.get("tool_call_id") or "unknown-call", + "tool_call_id", + ), + "tool": _text(value.get("tool"), "tool"), + "status": status, + "input_hash": _digest(value.get("input_hash"), "input_hash"), + "output_hash": _digest(value.get("output_hash"), "output_hash", True), + "started_at": _text(value.get("started_at"), "started_at", 64), + "finished_at": _text(value.get("finished_at"), "finished_at", 64), + "duration_ms": round(float(duration), 3), + "provenance": { + "producer": "gauntlet-fastpath-plugin", + "event_source": "hermes.post_tool_call", + "raw_input_persisted": False, + "raw_output_persisted": False, + }, + "authority_ceiling": "OBSERVATION_ONLY", + "canonical_receipt_created": False, + "canonical_state_mutated": False, + } + digest = hashlib.sha256(_canonical(document)).hexdigest() + document["observation_id"] = f"obs_{digest}" + document["content_hash"] = digest + return document + + +def store_observation(value: Mapping[str, Any]) -> dict[str, Any]: + observation = build_observation(value) + task_bucket = hashlib.sha256(observation["task_id"].encode()).hexdigest()[:24] + path = ( + _runtime_home() + / "observations" + / task_bucket + / f"{observation['observation_id']}.json" + ) + data = _canonical(observation) + b"\n" + path.parent.mkdir(parents=True, exist_ok=True) + try: + descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) + except FileExistsError: + if path.read_bytes() != data: + raise ObservationError("OBSERVATION_COLLISION", "observation hash collision") + state = "EXISTS" + else: + with os.fdopen(descriptor, "wb") as handle: + handle.write(data) + handle.flush() + os.fsync(handle.fileno()) + state = "RECORDED" + return { + "schema": "gauntlet.observation-store-result.v1", + "status": state, + "observation_id": observation["observation_id"], + "content_hash": observation["content_hash"], + "path": str(path), + "authority_ceiling": "OBSERVATION_ONLY", + "canonical_receipt_created": False, + "canonical_state_mutated": False, + } + + +def _error(exc: ObservationError) -> dict[str, Any]: + return { + "schema": "gauntlet.observation-store-result.v1", + "status": "ERROR", + "error": {"code": exc.code, "message": exc.message}, + "authority_ceiling": "OBSERVATION_ONLY", + "canonical_receipt_created": False, + "canonical_state_mutated": False, + } + + +def main() -> int: + if len(sys.argv) != 1: + print(json.dumps(_error(ObservationError( + "INVALID_ARGUMENTS", + "observation bridge accepts stdin only", + )), sort_keys=True)) + return 2 + try: + result = store_observation(_request()) + code = 0 + except ObservationError as exc: + result, code = _error(exc), 2 + except Exception as exc: + result, code = _error(ObservationError( + "OBSERVATION_STORE_ERROR", + f"operational store failed: {type(exc).__name__}", + )), 2 + print(json.dumps(result, ensure_ascii=False, separators=(",", ":"), sort_keys=True)) + return code + + +if __name__ == "__main__": + raise SystemExit(main()) From c3aa2d16b723690ad44fa9ff9dcff1e425a5dedc Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:34:37 -0700 Subject: [PATCH 09/37] fix(fast-p6): normalize pinned Hermes hook argument names --- gauntlet_host/gauntlet_plugin.py | 474 ++++++++++++------------------- 1 file changed, 179 insertions(+), 295 deletions(-) diff --git a/gauntlet_host/gauntlet_plugin.py b/gauntlet_host/gauntlet_plugin.py index 8be7b72a..f2468581 100644 --- a/gauntlet_host/gauntlet_plugin.py +++ b/gauntlet_host/gauntlet_plugin.py @@ -18,193 +18,80 @@ ADAPTER_SCHEMA = "gauntlet.adapter.v1" OBSERVATION_SCHEMA = "gauntlet.observation-store-result.v1" TOOLSET = "gauntlet" -MAX_ADAPTER_OUTPUT_CHARS = 262_144 -ADAPTER_TIMEOUT_SECONDS = 20.0 -OBSERVATION_TIMEOUT_SECONDS = 10.0 +ADAPTER_TIMEOUT = 20.0 +OBSERVATION_TIMEOUT = 10.0 +MAX_ADAPTER_OUTPUT = 262_144 logger = logging.getLogger(__name__) -_pending_calls: dict[tuple[str, str, str], tuple[str, float]] = {} +_pending: dict[tuple[str, str, str], tuple[str, float]] = {} _pending_lock = threading.Lock() -class PluginBridgeError(RuntimeError): - """Typed failure at a runtime-to-Gauntlet subprocess boundary.""" - +class BridgeError(RuntimeError): def __init__(self, code: str, message: str) -> None: super().__init__(message) self.code = code self.message = message -def _safe_message(value: Any) -> str: +def _safe(value: Any) -> str: text = " ".join(str(value).split()) text = re.sub( r"(?i)(api[_-]?key|authorization|token|secret)\s*[:=]\s*\S+", r"\1=", text, ) - text = re.sub(r"\bsk-[A-Za-z0-9_-]{8,}\b", "", text) - return (text or "runtime bridge failure")[:1_000] - - -def _canonical_hash(value: Any) -> str: - def fallback(item: Any) -> dict[str, str]: - return { - "type": f"{type(item).__module__}.{type(item).__qualname__}", - "repr": _safe_message(repr(item))[:2_000], - } - - try: - encoded = json.dumps( - value, - default=fallback, - ensure_ascii=False, - separators=(",", ":"), - sort_keys=True, - ).encode("utf-8") - except Exception: - encoded = fallback(value)["repr"].encode("utf-8", errors="replace") - return hashlib.sha256(encoded).hexdigest() + return re.sub(r"\bsk-[A-Za-z0-9_-]{8,}\b", "", text)[:1_000] -def _utc_now() -> str: - return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") +def _env(name: str) -> str: + value = os.environ.get(name, "").strip() + if not value: + raise BridgeError("BRIDGE_ENVIRONMENT_MISSING", f"{name} is required") + return value -def _error_document(action: str, code: str, message: str) -> str: +def _error(action: str, exc: BridgeError) -> str: return json.dumps( { "schema": ADAPTER_SCHEMA, "action": action, "status": "ERROR", - "error": { - "code": code, - "message": _safe_message(message), - }, + "error": {"code": exc.code, "message": _safe(exc.message)}, "read_only": True, "mutation_performed": False, }, - ensure_ascii=False, separators=(",", ":"), sort_keys=True, ) -def _required_environment(name: str) -> str: - value = os.environ.get(name, "").strip() - if not value: - raise PluginBridgeError( - "BRIDGE_ENVIRONMENT_MISSING", - f"required runtime bridge variable {name} is not set", - ) - return value - - -def _validate_arguments(arguments: Any) -> None: - if arguments is None: - return - if isinstance(arguments, dict) and not arguments: - return - raise PluginBridgeError( - "TOOL_ARGUMENTS_REJECTED", - "Gauntlet status tools do not accept arguments", - ) - - -def _adapter_paths() -> tuple[Path, Path]: - repo_root = Path(_required_environment("GAUNTLET_REPO_ROOT")).resolve() - module_cli = Path(_required_environment("GAUNTLET_MODULE_CLI")).resolve() - expected_cli = (repo_root / "gauntlet_host" / "module_cli.py").resolve() - if module_cli != expected_cli: - raise PluginBridgeError( - "MODULE_ADAPTER_PATH_MISMATCH", - "runtime bridge adapter path does not match the Gauntlet repository", - ) - if not (repo_root / "tools" / "soul_runtime.py").is_file(): - raise PluginBridgeError( - "GAUNTLET_REPOSITORY_INVALID", - "Gauntlet authority files are missing from the configured repository root", - ) - if not module_cli.is_file(): - raise PluginBridgeError( - "MODULE_ADAPTER_MISSING", - f"Gauntlet module adapter is missing: {module_cli}", - ) - return repo_root, module_cli - - -def _parse_adapter_output(action: str, task_id: str, stdout: str) -> dict[str, Any]: - if len(stdout) > MAX_ADAPTER_OUTPUT_CHARS: - raise PluginBridgeError( - "MODULE_ADAPTER_OUTPUT_TOO_LARGE", - "Gauntlet module adapter output exceeded the bounded tool-result limit", - ) - records = [line for line in stdout.splitlines() if line.strip()] - if len(records) != 1: - raise PluginBridgeError( - "MODULE_ADAPTER_PROTOCOL_ERROR", - "Gauntlet module adapter must return exactly one JSON record", - ) - try: - value = json.loads(records[0]) - except json.JSONDecodeError as exc: - raise PluginBridgeError( - "MODULE_ADAPTER_PROTOCOL_ERROR", - f"Gauntlet module adapter returned invalid JSON: {exc.msg}", - ) from exc - if not isinstance(value, dict): - raise PluginBridgeError( - "MODULE_ADAPTER_PROTOCOL_ERROR", - "Gauntlet module adapter result must be a JSON object", - ) - if value.get("schema") != ADAPTER_SCHEMA: - raise PluginBridgeError( - "MODULE_ADAPTER_SCHEMA_MISMATCH", - f"Gauntlet module adapter schema must be {ADAPTER_SCHEMA}", - ) - if value.get("action") != action or value.get("task_id") != task_id: - raise PluginBridgeError( - "MODULE_ADAPTER_CORRELATION_MISMATCH", - "Gauntlet module adapter result did not match the requested action and task", - ) - if value.get("read_only") is not True: - raise PluginBridgeError( - "MODULE_ADAPTER_AUTHORITY_VIOLATION", - "Gauntlet module adapter did not attest to the read-only status contract", - ) - if value.get("mutation_performed") is not False: - raise PluginBridgeError( - "MODULE_ADAPTER_AUTHORITY_VIOLATION", - "Gauntlet status adapter reported a state mutation", - ) - return value - - -def _call_adapter(action: str, arguments: Any) -> str: +def _status_call(action: str, arguments: Any) -> str: try: - _validate_arguments(arguments) - task_id = _required_environment("GAUNTLET_TASK_ID") - repo_root, module_cli = _adapter_paths() + if arguments not in (None, {}): + raise BridgeError( + "TOOL_ARGUMENTS_REJECTED", + "Gauntlet status tools do not accept arguments", + ) + task_id = _env("GAUNTLET_TASK_ID") + root = Path(_env("GAUNTLET_REPO_ROOT")).resolve() + adapter = Path(_env("GAUNTLET_MODULE_CLI")).resolve() + expected = (root / "gauntlet_host" / "module_cli.py").resolve() + if adapter != expected or not adapter.is_file(): + raise BridgeError("MODULE_ADAPTER_PATH_MISMATCH", "invalid module adapter") environment = dict(os.environ) - environment["GAUNTLET_TASK_ID"] = task_id - environment["PYTHONPATH"] = str(repo_root) - environment["PYTHONUNBUFFERED"] = "1" - for bypass in ( - "HERMES_YOLO_MODE", - "HERMES_ACCEPT_HOOKS", - "HERMES_INTERACTIVE", - ): - environment.pop(bypass, None) - + environment.update( + { + "GAUNTLET_TASK_ID": task_id, + "PYTHONPATH": str(root), + "PYTHONUNBUFFERED": "1", + } + ) + for name in ("HERMES_YOLO_MODE", "HERMES_ACCEPT_HOOKS", "HERMES_INTERACTIVE"): + environment.pop(name, None) completed = subprocess.run( - [ - sys.executable, - str(module_cli), - "--root", - str(repo_root), - action, - ], - cwd=repo_root, + [sys.executable, str(adapter), "--root", str(root), action], + cwd=root, env=environment, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, @@ -212,177 +99,187 @@ def _call_adapter(action: str, arguments: Any) -> str: text=True, encoding="utf-8", errors="replace", - timeout=ADAPTER_TIMEOUT_SECONDS, + timeout=ADAPTER_TIMEOUT, check=False, ) - value = _parse_adapter_output(action, task_id, completed.stdout) - status = value.get("status") - expected_exit = 0 if status == "OK" else 2 + if len(completed.stdout) > MAX_ADAPTER_OUTPUT: + raise BridgeError("MODULE_ADAPTER_OUTPUT_TOO_LARGE", "adapter output too large") + records = [line for line in completed.stdout.splitlines() if line.strip()] + if len(records) != 1: + raise BridgeError("MODULE_ADAPTER_PROTOCOL_ERROR", "expected one JSON record") + value = json.loads(records[0]) + if not isinstance(value, dict): + raise BridgeError("MODULE_ADAPTER_PROTOCOL_ERROR", "result must be an object") + if value.get("schema") != ADAPTER_SCHEMA: + raise BridgeError("MODULE_ADAPTER_SCHEMA_MISMATCH", "invalid adapter schema") + if value.get("action") != action or value.get("task_id") != task_id: + raise BridgeError("MODULE_ADAPTER_CORRELATION_MISMATCH", "adapter mismatch") + if value.get("read_only") is not True or value.get("mutation_performed") is not False: + raise BridgeError("MODULE_ADAPTER_AUTHORITY_VIOLATION", "adapter was not read-only") + expected_exit = 0 if value.get("status") == "OK" else 2 if completed.returncode != expected_exit: - raise PluginBridgeError( - "MODULE_ADAPTER_EXIT_MISMATCH", - ( - f"Gauntlet module adapter exited with {completed.returncode}; " - f"status {status!r} requires {expected_exit}" - ), - ) - return json.dumps( - value, - ensure_ascii=False, - separators=(",", ":"), - sort_keys=True, - ) + raise BridgeError("MODULE_ADAPTER_EXIT_MISMATCH", "adapter exit mismatch") + return json.dumps(value, separators=(",", ":"), sort_keys=True) except subprocess.TimeoutExpired: - return _error_document( - action, - "MODULE_ADAPTER_TIMEOUT", - f"Gauntlet module adapter exceeded {ADAPTER_TIMEOUT_SECONDS:g} seconds", - ) - except OSError as exc: - return _error_document(action, "MODULE_ADAPTER_START_FAILED", str(exc)) - except PluginBridgeError as exc: - return _error_document(action, exc.code, exc.message) + return _error(action, BridgeError("MODULE_ADAPTER_TIMEOUT", "adapter timed out")) + except (OSError, json.JSONDecodeError) as exc: + return _error(action, BridgeError("MODULE_ADAPTER_FAILURE", str(exc))) + except BridgeError as exc: + return _error(action, exc) except Exception as exc: - return _error_document(action, "MODULE_ADAPTER_UNEXPECTED_FAILURE", str(exc)) + return _error(action, BridgeError("MODULE_ADAPTER_UNEXPECTED_FAILURE", str(exc))) def _task_status(arguments: dict[str, Any] | None = None, **_: Any) -> str: - return _call_adapter("task-status", arguments) + return _status_call("task-status", arguments) def _release_status(arguments: dict[str, Any] | None = None, **_: Any) -> str: - return _call_adapter("release-status", arguments) + return _status_call("release-status", arguments) -def _call_key( - tool_name: str, - session_id: str, - tool_call_id: str, -) -> tuple[str, str, str]: +def _hash(value: Any) -> str: + def fallback(item: Any) -> dict[str, str]: + return {"type": type(item).__qualname__, "repr": _safe(repr(item))[:2_000]} + + try: + data = json.dumps( + value, + default=fallback, + separators=(",", ":"), + sort_keys=True, + ).encode() + except Exception: + data = fallback(value)["repr"].encode(errors="replace") + return hashlib.sha256(data).hexdigest() + + +def _now() -> str: + return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + + +def _hook_inputs( + positional: tuple[Any, ...], values: dict[str, Any] +) -> tuple[str, Any, Any, str, str]: + name = values.get("tool_name") or values.get("function_name") + arguments = values.get("args") + if arguments is None: + arguments = values.get("function_args", values.get("arguments")) + result = values.get("result") + if name is None and positional: + name = positional[0] + if arguments is None and len(positional) > 1: + arguments = positional[1] + if result is None and len(positional) > 2: + result = positional[2] return ( - session_id or "unknown-session", - tool_call_id or "unknown-call", - tool_name or "unknown-tool", + str(name or "unknown-tool"), + arguments, + result, + str(values.get("session_id") or "unknown-session"), + str(values.get("tool_call_id") or values.get("call_id") or "unknown-call"), ) -def _on_pre_tool_call( - tool_name: str = "", - session_id: str = "", - tool_call_id: str = "", - **_: Any, -) -> None: - key = _call_key(tool_name, session_id, tool_call_id) - with _pending_lock: - _pending_calls[key] = (_utc_now(), time.monotonic()) +def _key(name: str, session_id: str, call_id: str) -> tuple[str, str, str]: + return session_id, call_id, name -def _tool_status(result: Any, values: dict[str, Any]) -> str: +def _pre(*positional: Any, **values: Any) -> None: + name, _, _, session_id, call_id = _hook_inputs(positional, values) + with _pending_lock: + _pending[_key(name, session_id, call_id)] = (_now(), time.monotonic()) + + +def _status(result: Any, values: dict[str, Any]) -> str: + explicit = str(values.get("status") or "").upper() + mapping = { + "SUCCESS": "OK", + "SUCCEEDED": "OK", + "COMPLETED": "OK", + "FAILED": "ERROR", + "FAILURE": "ERROR", + "TIMED_OUT": "TIMEOUT", + "NOT_AVAILABLE": "UNAVAILABLE", + "CANCELED": "CANCELLED", + } + explicit = mapping.get(explicit, explicit) + if explicit in {"OK", "ERROR", "TIMEOUT", "UNAVAILABLE", "CANCELLED"}: + return explicit if values.get("timed_out") is True: return "TIMEOUT" - if values.get("cancelled") is True: + if values.get("cancelled") is True or values.get("canceled") is True: return "CANCELLED" - if values.get("unavailable") is True: - return "UNAVAILABLE" if values.get("error") is not None or values.get("exception") is not None: return "ERROR" - if isinstance(result, dict): - candidate = str(result.get("status") or "").upper() - if candidate in {"ERROR", "TIMEOUT", "UNAVAILABLE", "CANCELLED"}: - return candidate - if result.get("success") is False: - return "ERROR" + if isinstance(result, dict) and result.get("success") is False: + return "ERROR" return "OK" -def _observation_path() -> Path: - bridge = Path(_required_environment("GAUNTLET_OBSERVATION_BRIDGE")).resolve() - repo_root = Path(_required_environment("GAUNTLET_REPO_ROOT")).resolve() - expected = (repo_root / "gauntlet_host" / "observation_bridge.py").resolve() - if bridge != expected or not bridge.is_file(): - raise PluginBridgeError( - "OBSERVATION_BRIDGE_PATH_MISMATCH", - "runtime observation bridge path is not the expected Gauntlet file", - ) - return bridge - - -def _record_observation(document: dict[str, Any]) -> None: +def _record(document: dict[str, Any]) -> None: try: - bridge = _observation_path() - environment = dict(os.environ) - environment["PYTHONUNBUFFERED"] = "1" + root = Path(_env("GAUNTLET_REPO_ROOT")).resolve() + bridge = Path(_env("GAUNTLET_OBSERVATION_BRIDGE")).resolve() + expected = (root / "gauntlet_host" / "observation_bridge.py").resolve() + if bridge != expected or not bridge.is_file(): + raise BridgeError("OBSERVATION_BRIDGE_PATH_MISMATCH", "invalid bridge") completed = subprocess.run( [sys.executable, str(bridge)], - input=json.dumps( - document, - ensure_ascii=False, - separators=(",", ":"), - sort_keys=True, - ), + input=json.dumps(document, separators=(",", ":"), sort_keys=True), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8", errors="replace", - env=environment, - timeout=OBSERVATION_TIMEOUT_SECONDS, + env={**os.environ, "PYTHONUNBUFFERED": "1"}, + timeout=OBSERVATION_TIMEOUT, check=False, ) records = [line for line in completed.stdout.splitlines() if line.strip()] if completed.returncode != 0 or len(records) != 1: - logger.warning( - "Gauntlet observation bridge failed safely: exit=%s", - completed.returncode, - ) - return - result = json.loads(records[0]) - if result.get("schema") != OBSERVATION_SCHEMA: - logger.warning("Gauntlet observation bridge returned an invalid schema") + raise BridgeError("OBSERVATION_BRIDGE_FAILURE", "bridge failed safely") + if json.loads(records[0]).get("schema") != OBSERVATION_SCHEMA: + raise BridgeError("OBSERVATION_SCHEMA_MISMATCH", "invalid bridge schema") except Exception as exc: - logger.warning( - "Gauntlet observation hook failed safely: %s", - _safe_message(exc), - ) + logger.warning("Gauntlet observation hook failed safely: %s", _safe(exc)) -def _on_post_tool_call( - tool_name: str = "", - args: Any = None, - result: Any = None, - session_id: str = "", - tool_call_id: str = "", - **values: Any, -) -> None: - finished_at = _utc_now() - finished_monotonic = time.monotonic() - key = _call_key(tool_name, session_id, tool_call_id) +def _post(*positional: Any, **values: Any) -> None: + finished_at = _now() + finished_clock = time.monotonic() + name, arguments, result, session_id, call_id = _hook_inputs(positional, values) with _pending_lock: - start = _pending_calls.pop(key, None) + start = _pending.pop(_key(name, session_id, call_id), None) + supplied = values.get("duration_ms") + if isinstance(supplied, bool) or not isinstance(supplied, (int, float)): + supplied = None if start is None: started_at = finished_at - duration_ms = 0.0 + duration_ms = float(supplied or 0.0) else: - started_at, started_monotonic = start - duration_ms = max(0.0, (finished_monotonic - started_monotonic) * 1_000) - + started_at, started_clock = start + duration_ms = ( + float(supplied) + if supplied is not None + else max(0.0, (finished_clock - started_clock) * 1_000) + ) try: - task_id = _required_environment("GAUNTLET_TASK_ID") - except PluginBridgeError: + task_id = _env("GAUNTLET_TASK_ID") + except BridgeError: return - - _record_observation( + _record( { "task_id": task_id, - "runtime_session_id": session_id or "unknown-session", - "tool_call_id": tool_call_id or "unknown-call", - "tool": tool_name or "unknown-tool", - "status": _tool_status(result, values), - "input_hash": _canonical_hash(args), - "output_hash": _canonical_hash(result), + "runtime_session_id": session_id, + "tool_call_id": call_id, + "tool": name, + "status": _status(result, values), + "input_hash": _hash(arguments), + "output_hash": _hash(result), "started_at": started_at, "finished_at": finished_at, - "duration_ms": duration_ms, + "duration_ms": max(0.0, duration_ms), "provenance": { "producer": "gauntlet-fastpath-plugin", "event_source": "hermes.post_tool_call", @@ -393,51 +290,38 @@ def _on_post_tool_call( ) -_TASK_STATUS_SCHEMA = { +_TASK_SCHEMA = { "description": ( - "Read the canonical Gauntlet task and obligation status for the exact " - "GAUNTLET_TASK_ID bound by the host. This tool is read-only and cannot " + "Read canonical status for the exact host-bound task. Read-only: cannot " "create receipts, change verdicts, clear obligations, or release a task." ), - "parameters": { - "type": "object", - "properties": {}, - "additionalProperties": False, - }, + "parameters": {"type": "object", "properties": {}, "additionalProperties": False}, } - -_RELEASE_STATUS_SCHEMA = { +_RELEASE_SCHEMA = { "description": ( - "Read the current Soul release-gate result for the exact host-bound task. " - "This reports eligibility only; it never calls release_task and performs " - "no mutation." + "Read Soul release-gate status for the exact host-bound task. This reports " + "eligibility only and performs no mutation." ), - "parameters": { - "type": "object", - "properties": {}, - "additionalProperties": False, - }, + "parameters": {"type": "object", "properties": {}, "additionalProperties": False}, } def register(ctx: Any) -> None: - """Register read-only tools and observation-only lifecycle hooks.""" - ctx.register_tool( name="gauntlet_task_status", toolset=TOOLSET, - schema=_TASK_STATUS_SCHEMA, + schema=_TASK_SCHEMA, handler=_task_status, - description=_TASK_STATUS_SCHEMA["description"], + description=_TASK_SCHEMA["description"], emoji="", ) ctx.register_tool( name="gauntlet_release_status", toolset=TOOLSET, - schema=_RELEASE_STATUS_SCHEMA, + schema=_RELEASE_SCHEMA, handler=_release_status, - description=_RELEASE_STATUS_SCHEMA["description"], + description=_RELEASE_SCHEMA["description"], emoji="", ) - ctx.register_hook("pre_tool_call", _on_pre_tool_call) - ctx.register_hook("post_tool_call", _on_post_tool_call) + ctx.register_hook("pre_tool_call", _pre) + ctx.register_hook("post_tool_call", _post) From 249753738f193d1f82e7e12afe907c4841827f80 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:46:51 -0700 Subject: [PATCH 10/37] fix(fast-p6): keep observation provenance bridge-owned --- gauntlet_host/gauntlet_plugin.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gauntlet_host/gauntlet_plugin.py b/gauntlet_host/gauntlet_plugin.py index f2468581..b8289693 100644 --- a/gauntlet_host/gauntlet_plugin.py +++ b/gauntlet_host/gauntlet_plugin.py @@ -280,12 +280,6 @@ def _post(*positional: Any, **values: Any) -> None: "started_at": started_at, "finished_at": finished_at, "duration_ms": max(0.0, duration_ms), - "provenance": { - "producer": "gauntlet-fastpath-plugin", - "event_source": "hermes.post_tool_call", - "raw_input_persisted": False, - "raw_output_persisted": False, - }, } ) From 04e1994940115c8935246b54764e5f949096f179 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:49:06 -0700 Subject: [PATCH 11/37] docs: publish FAST-P6 checkpoint receipt --- .../HERMES_FAST_P6_CHECKPOINT.json | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/engineering/HERMES_FAST_P6_CHECKPOINT.json diff --git a/docs/engineering/HERMES_FAST_P6_CHECKPOINT.json b/docs/engineering/HERMES_FAST_P6_CHECKPOINT.json new file mode 100644 index 00000000..d441641f --- /dev/null +++ b/docs/engineering/HERMES_FAST_P6_CHECKPOINT.json @@ -0,0 +1,72 @@ +{ + "authority_ceiling": "OBSERVATION_ONLY", + "changed_paths": [ + ".github/phase6_verify.py", + ".github/workflows/fastpath-checkpoint.yml", + "gauntlet_host/constants.py", + "gauntlet_host/finalizer.py", + "gauntlet_host/gauntlet_plugin.py", + "gauntlet_host/launcher.py", + "gauntlet_host/observation_bridge.py" + ], + "content_hash": "6f70cc174307ad48868552ba543b57d32ce94b9f3c8e123403bb1f1cc1a4b266", + "fast_milestone": "FAST-P6", + "head_sha": "249753738f193d1f82e7e12afe907c4841827f80", + "known_limitations": [ + "interim alpha still requires the pinned Hermes runtime", + "checkpoint is not GNR NATIVE-600 or native-final", + "observation store is operational and non-canonical", + "only one deterministic mock-provider end-to-end path was qualified", + "no automatic replanning loop", + "not yet admitted into integration/vnext-native-stack" + ], + "parent_checkpoint_sha": "dda78eabc216749cb1a3010d8e533428b78721d0", + "parent_checkpoint_tree": "0ad6d8641561de46bd729a5115500d4fd7bb0483", + "producer_branch": "work/native-hermes-fastpath", + "recorded_at": "2026-08-29T17:49:00Z", + "removed_paths": [ + ".github/workflows/_phase5_status_plugin_v3.yml" + ], + "repair_history": [ + { + "conclusion": "failure", + "defect": "no matching observation persisted", + "head_sha": "a1f6ac24cda62f4f94d10cbf4c015daad75ae7cc", + "run_id": 33265628442 + }, + { + "conclusion": "failure", + "defect": "caller-supplied provenance rejected by strict observation bridge", + "head_sha": "c3aa2d16b723690ad44fa9ff9dcff1e425a5dedc", + "run_id": 33266000140 + }, + { + "conclusion": "success", + "head_sha": "249753738f193d1f82e7e12afe907c4841827f80", + "resolution": "provenance kept bridge-owned", + "run_id": 33266521662 + } + ], + "runtime_required": true, + "schema": "egr.hermes.checkpoint.v1", + "tests": [ + { + "checks": [ + "pinned Hermes gitlink verification", + "pinned runtime dependency installation", + "one real agent-visible gauntlet_task_status tool call", + "content-addressed tool observation persisted without raw input/output", + "Soul release gate invoked by parent finalizer", + "UNKNOWN unresolved task not accepted or released", + "zero canonical receipts created", + "canonical task state unchanged" + ], + "conclusion": "success", + "head_sha": "249753738f193d1f82e7e12afe907c4841827f80", + "run_id": 33266521662, + "workflow": "Fastpath checkpoint qualification" + } + ], + "tree_sha": "10eeeeef9f85ca91e766e5be388a176943decf5d", + "upstream_hermes_commit": "5fc308a70719a83cccdbba4c0e39c23f5a8239d5" +} From fc8ca813a71887f3be52e9da30794299030aab7a Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:43:43 -0700 Subject: [PATCH 12/37] foil: add FAST-P7 advisory live-capability routing --- .github/phase7_verify.py | 293 ++++++++ .github/workflows/fastpath-checkpoint.yml | 15 +- .../engineering/PHASE7_FOIL_ADVISORY_ROUTE.md | 98 +++ gauntlet_host/constants.py | 6 +- gauntlet_host/foil_bridge.py | 481 ++++++++++++ gauntlet_host/module_cli.py | 703 +++++++++++++++++- gauntlet_host/phase7_worker.py | 91 +++ 7 files changed, 1662 insertions(+), 25 deletions(-) create mode 100644 .github/phase7_verify.py create mode 100644 docs/engineering/PHASE7_FOIL_ADVISORY_ROUTE.md create mode 100644 gauntlet_host/foil_bridge.py create mode 100644 gauntlet_host/phase7_worker.py diff --git a/.github/phase7_verify.py b/.github/phase7_verify.py new file mode 100644 index 00000000..431f09ae --- /dev/null +++ b/.github/phase7_verify.py @@ -0,0 +1,293 @@ +"""Bounded FAST-P7 live-capability and advisory FOIL route verification.""" + +from __future__ import annotations + +import importlib.util +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import threading +from typing import Any + +REPO = Path(__file__).resolve().parent.parent +PHASE5_HELPER = REPO / ".github" / "phase5_verify.py" +RAW_CLAIM = "Find a canonical source before release." +ROUTE_MARKER = "[GAUNTLET FOIL ADVISORY ROUTE]" + + +def _load_phase5() -> Any: + spec = importlib.util.spec_from_file_location("phase5_verify", PHASE5_HELPER) + if spec is None or spec.loader is None: + raise RuntimeError("cannot load the retained FAST-P5 verification helper") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _route_adapter( + task_id: str, + capabilities: list[str], + *, + tool_hash: str = "a" * 64, + tool_count: int = 2, +) -> tuple[int, dict[str, Any]]: + environment = dict(os.environ) + environment["GAUNTLET_TASK_ID"] = task_id + completed = subprocess.run( + [ + sys.executable, + str(REPO / "gauntlet_host" / "module_cli.py"), + "--root", + str(REPO), + "foil-route", + ], + input=json.dumps( + { + "available_capabilities": capabilities, + "tool_count": tool_count, + "tool_manifest_hash": tool_hash, + }, + separators=(",", ":"), + sort_keys=True, + ), + cwd=REPO, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + check=False, + ) + records = [line for line in completed.stdout.splitlines() if line.strip()] + assert len(records) == 1, completed.stderr + return completed.returncode, json.loads(records[0]) + + +def _assert_route( + route: dict[str, Any], + task_id: str, + *, + expect_incomplete: bool, +) -> None: + assert route["schema"] == "gauntlet.foil-route.v1" + assert route["action"] == "foil-route" + assert route["status"] == "OK" + assert route["task_id"] == task_id + assert route["mode"] == "SHADOW" + assert route["authority_ceiling"] == "ADAPTATION_ONLY" + assert route["read_only"] is True + assert route["mutation_performed"] is False + assert route["execution_authorized"] is False + assert route["toolset_narrowing_applied"] is False + assert route["profile_used"] is False + assert route["private_profile_data_transmitted"] is False + assert route["stop_is_advisory"] is True + assert all(value is False for value in route["authority"].values()) + assert route["policy_version"] == "FOIL_vNEXT_CANDIDATE_V2" + assert route["trace"]["task_regime"] == "external_retrieval" + assert route["primary_effort_mode"] == "discovery" + assert "discover_candidates" in route["actions"] + assert "source_evidence" in route["required_verifiers"] + assert "source_evidence" in route["pending_verifiers"] + assert route["should_stop"] is False + assert route["targeted_complement"] is None + assert route["capability_snapshot"]["verified_by_gauntlet"] is False + assert route["capability_snapshot"]["source"] == ( + "RUNTIME_REPORTED_TOOL_DEFINITIONS" + ) + assert "REASONING" in route["minimum_capability_bundle"] + if expect_incomplete: + assert route["capability_bundle_complete"] is False + missing = { + item["requirement"] + for item in route["missing_capabilities"] + } + assert "verifier:source_evidence" in missing + assert route["task_context"]["claim_text_transmitted"] is False + assert RAW_CLAIM not in json.dumps(route) + assert len(route["content_hash"]) == 64 + + payload = dict(route) + supplied = payload.pop("content_hash") + import hashlib + + expected = hashlib.sha256( + json.dumps( + payload, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ).encode("utf-8") + ).hexdigest() + assert supplied == expected + + +def _first_prompt_request( + requests: list[dict[str, Any]], + phase5: Any, +) -> dict[str, Any]: + for request in requests: + if phase5._has_target_prompt(request): + return request + raise AssertionError("no routed first model request was observed") + + +def _prompt_text(request: dict[str, Any], phase5: Any) -> str: + return "\n".join( + phase5._message_content(message) + for message in request.get("messages", []) + if isinstance(message, dict) + ) + + +def main() -> None: + phase5 = _load_phase5() + phase5._source_boundary() + task_id = phase5._create_task() + before = phase5._task_state(task_id) + + direct_exit, direct_route = _route_adapter( + task_id, + ["REASONING", "TEXT_GENERATION"], + ) + assert direct_exit == 0 + _assert_route(direct_route, task_id, expect_incomplete=True) + + rejected_exit, rejected = _route_adapter( + task_id, + ["REASONING", "NOT_A_CAPABILITY"], + ) + assert rejected_exit == 2 + assert rejected["status"] == "ERROR" + assert rejected["error"]["code"] == "FOIL_ROUTE_CAPABILITY_UNKNOWN" + assert rejected["authority_ceiling"] == "ADAPTATION_ONLY" + assert all(value is False for value in rejected["authority"].values()) + + server = phase5.Phase5Server() + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + + original_home = os.environ.get("HOME") + try: + with tempfile.TemporaryDirectory(prefix="gauntlet-phase7-") as temporary: + home = Path(temporary) + runtime = home / ".gauntlet" / "runtime" + phase5._write_runtime_config(runtime, server.server_address[1]) + os.environ["HOME"] = str(home) + + from gauntlet_host.launcher import run_worker_turn + from gauntlet_host.finalizer import finalize_worker_result + + worker = run_worker_turn( + ( + "Use gauntlet_task_status, then report that status was " + "observed." + ), + task_id=task_id, + cwd=REPO, + model="phase5-mock", + provider="custom", + timeout_seconds=90, + ) + assert worker.status.value == "OK" + assert worker.event == "worker.turn_completed" + assert worker.payload["final_response"] == "phase5 status observed" + assert worker.payload["usage"]["api_calls"] == 2 + route = worker.payload.get("foil_route") + assert isinstance(route, dict) + _assert_route(route, task_id, expect_incomplete=False) + + finalized = finalize_worker_result(REPO, task_id, worker) + assert finalized.state == "UNRESOLVED" + assert finalized.accepted is False + assert finalized.final_response == "phase5 status observed" + assert finalized.release_gate_invoked is True + assert finalized.release_gate_verdict == "UNKNOWN" + assert finalized.release_eligible is False + assert finalized.task_release_performed is False + assert finalized.canonical_receipt_created is False + + request = _first_prompt_request(server.chat_requests, phase5) + prompt_text = _prompt_text(request, phase5) + assert ROUTE_MARKER in prompt_text + assert route["content_hash"] in prompt_text + assert RAW_CLAIM not in prompt_text + assert "This is proposal-only routing guidance." in prompt_text + + tool_request = phase5._tool_request(server.chat_requests) + result_request = phase5._result_request(server.chat_requests) + names = { + item["function"]["name"] + for item in tool_request.get("tools", []) + } + assert {"gauntlet_task_status", "gauntlet_release_status"} <= names + tool_messages = [ + message + for message in result_request.get("messages", []) + if message.get("tool_call_id") == phase5.TARGET_CALL_ID + ] + assert len(tool_messages) == 1 + status = json.loads(tool_messages[0]["content"]) + assert status["task_id"] == task_id + assert status["release"]["verdict"] == "UNKNOWN" + assert status["read_only"] is True + assert status["mutation_performed"] is False + assert all(value is False for value in status["authority"].values()) + + observations = [] + for path in sorted((runtime / "observations").rglob("*.json")): + observations.append(json.loads(path.read_text(encoding="utf-8"))) + matching = [ + item + for item in observations + if item.get("tool") == "gauntlet_task_status" + ] + assert len(matching) == 1 + assert matching[0]["authority_ceiling"] == "OBSERVATION_ONLY" + assert matching[0]["canonical_receipt_created"] is False + assert matching[0]["canonical_state_mutated"] is False + assert not (home / ".hermes").exists() + + after = phase5._task_state(task_id) + task = json.loads((phase5.TASKS / f"{task_id}.json").read_text()) + assert after == before + assert task["active"] is True + assert task["released"] is False + assert not any(phase5.RECEIPTS.glob("*.json")) + + summary = { + "schema": "gauntlet.fast-checkpoint-verification.v1", + "fast_milestone": "FAST-P7", + "task_id": task_id, + "model_round_trips": len(server.chat_requests), + "route_injected_before_first_model_request": True, + "route_mode": route["mode"], + "route_authority_ceiling": route["authority_ceiling"], + "profile_data_transmitted": False, + "toolset_narrowing_applied": False, + "capability_bundle_complete": ( + route["capability_bundle_complete"] + ), + "missing_requirements": len(route["missing_capabilities"]), + "release_gate_verdict": finalized.release_gate_verdict, + "unresolved_not_accepted": finalized.accepted is False, + "canonical_task_unchanged": after == before, + "canonical_receipts_created": 0, + } + print(json.dumps(summary, indent=2, sort_keys=True)) + finally: + if original_home is None: + os.environ.pop("HOME", None) + else: + os.environ["HOME"] = original_home + server.shutdown() + server.server_close() + thread.join(timeout=5) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/fastpath-checkpoint.yml b/.github/workflows/fastpath-checkpoint.yml index e6391a03..ae0be518 100644 --- a/.github/workflows/fastpath-checkpoint.yml +++ b/.github/workflows/fastpath-checkpoint.yml @@ -9,19 +9,14 @@ on: permissions: contents: read -concurrency: - group: fastpath-checkpoint-${{ github.ref }} - cancel-in-progress: true - jobs: - fast-p6: + fast-p7: runs-on: ubuntu-latest - timeout-minutes: 25 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: submodules: true - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 with: python-version: '3.12' - name: Verify pinned Hermes gitlink @@ -33,7 +28,7 @@ jobs: set -euo pipefail python -m pip install --upgrade pip python -m pip install -e vendor/hermes-agent - - name: Verify observation firewall and Soul finalizer + - name: Verify FAST-P7 advisory FOIL routing run: | set -euo pipefail - python .github/phase6_verify.py + python .github/phase7_verify.py diff --git a/docs/engineering/PHASE7_FOIL_ADVISORY_ROUTE.md b/docs/engineering/PHASE7_FOIL_ADVISORY_ROUTE.md new file mode 100644 index 00000000..34de4594 --- /dev/null +++ b/docs/engineering/PHASE7_FOIL_ADVISORY_ROUTE.md @@ -0,0 +1,98 @@ +# FAST-P7 — Advisory FOIL Route Before Runtime Work + +Status: **implemented candidate / checkpoint qualification required** + +## Scope + +FAST-P7 adds one proposal-only FOIL route before the first model request of a +Gauntlet-bundled runtime turn. It is the fast-build route milestone, not +`NATIVE-600`, `NATIVE-700`, a production activation, or evidence of routing +efficacy. + +The sequence is: + +```text +runtime tool schemas +→ semantic capability snapshot +→ read-only Gauntlet module adapter +→ existing RuntimePolicyV2 +→ minimum capability bundle proposal +→ bounded public route instruction +→ first model request +``` + +The route is computed from: + +- the integrity-valid canonical task and its load-bearing obligations; +- explicit `metadata.foil_task_context` fields, when present; +- the runtime-reported tool-definition manifest and derived semantic + capabilities. + +The route does not receive the raw user prompt, obligation claim text, profile +memory, skill content, provider credentials, or raw tool output. + +## Authority boundary + +Every route uses: + +```text +schema: gauntlet.foil-route.v1 +mode: SHADOW +authority_ceiling: ADAPTATION_ONLY +execution_authorized: false +toolset_narrowing_applied: false +profile_used: false +private_profile_data_transmitted: false +``` + +The route cannot: + +- create a canonical receipt; +- change a verdict; +- clear an obligation; +- release a task; +- execute a tool; +- narrow the active toolset; +- replace claim-native verification; +- replace Soul's release gate. + +Runtime-reported capability availability is explicitly marked +`verified_by_gauntlet=false`. It is planning input, not authority evidence. + +## Minimum-bundle behavior + +The existing FOIL policy produces task regime, effort mode, required and +pending verifiers, task complements, actions, resource allocation, and stop +advice. FAST-P7 maps those requirements to the frozen semantic capability +registry and selects the first available minimum-sufficient capability for +each requirement. + +Missing capability groups remain explicit. They do not become invented tools, +automatic fallbacks, or a successful verification claim. + +## Runtime integration + +`gauntlet_host/phase7_worker.py` wraps the existing isolated worker. It +intercepts the single upstream `AIAgent.run_conversation` call, obtains the +route after tool discovery, injects the bounded route block before the original +prompt, and restores the original method after the turn. The pinned upstream +Hermes gitlink is unchanged. + +Operational route failure returns `UNAVAILABLE` before the model request. It +does not create a Gauntlet verdict. + +## Qualification target + +The bounded checkpoint harness must establish: + +1. live tool definitions are mapped to semantic capabilities; +2. unknown capability names fail closed; +3. the route is task-bound and content-hashed; +4. the route enters the first model request; +5. raw claim text and profile data are absent; +6. the runtime still executes `gauntlet_task_status`; +7. tool results remain `OBSERVATION_ONLY`; +8. the existing Soul gate still returns `UNKNOWN` for the unresolved fixture; +9. no receipt is created and the task remains unreleased. + +FAST-P8, including the single user-facing alpha CLI, remains separate. diff --git a/gauntlet_host/constants.py b/gauntlet_host/constants.py index 36780940..85436fd0 100644 --- a/gauntlet_host/constants.py +++ b/gauntlet_host/constants.py @@ -8,8 +8,12 @@ ADAPTER_PROTOCOL_VERSION = "gauntlet.adapter.v1" OBSERVATION_PROTOCOL_VERSION = "gauntlet.tool-observation.v1" FINALIZATION_PROTOCOL_VERSION = "gauntlet.finalization.v1" +FOIL_ROUTE_PROTOCOL_VERSION = "gauntlet.foil-route.v1" MAX_JSONL_BYTES = 1_048_576 MAX_OPERATIONAL_ERROR_CHARS = 2_000 +MAX_FOIL_ROUTE_INPUT_BYTES = 131_072 +MAX_FOIL_ROUTE_OUTPUT_BYTES = 131_072 +MAX_FOIL_ROUTE_PROMPT_CHARS = 12_000 DEFAULT_LAUNCH_TIMEOUT_SECONDS = 180.0 MAX_LAUNCH_TIMEOUT_SECONDS = 600.0 @@ -30,7 +34,7 @@ VENDOR_ROOT = REPO_ROOT / "vendor" / "hermes-agent" VENDOR_TOOLS_ROOT = VENDOR_ROOT / "tools" GAUNTLET_TOOLS_ROOT = REPO_ROOT / "tools" -WORKER_MAIN = HOST_ROOT / "worker_main.py" +WORKER_MAIN = HOST_ROOT / "phase7_worker.py" MODULE_CLI = HOST_ROOT / "module_cli.py" OBSERVATION_BRIDGE = HOST_ROOT / "observation_bridge.py" FINALIZER = HOST_ROOT / "finalizer.py" diff --git a/gauntlet_host/foil_bridge.py b/gauntlet_host/foil_bridge.py new file mode 100644 index 00000000..e3e092f2 --- /dev/null +++ b/gauntlet_host/foil_bridge.py @@ -0,0 +1,481 @@ +"""Worker-side live capability snapshot and advisory FOIL route bridge.""" + +from __future__ import annotations + +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys +from typing import Any, Iterable + +from gauntlet_host.constants import ( + DEFAULT_ADAPTER_TIMEOUT_SECONDS, + FOIL_ROUTE_PROTOCOL_VERSION, + MAX_FOIL_ROUTE_OUTPUT_BYTES, + MAX_FOIL_ROUTE_PROMPT_CHARS, + MODULE_CLI, + REPO_ROOT, +) + +_ROUTE_MARKER = "[GAUNTLET FOIL ADVISORY ROUTE]" +_ROUTE_END_MARKER = "[/GAUNTLET FOIL ADVISORY ROUTE]" +_FORBIDDEN_ROUTE_FIELDS = { + "cleared", + "evidence_class", + "receipt", + "receipts", + "release", + "released", + "verdict", +} + + +class FoilRouteBridgeError(RuntimeError): + """Typed operational failure while obtaining a proposal-only FOIL route.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +def _canonical_hash(value: Any) -> str: + encoded = json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +def _tool_records(definitions: Any) -> list[dict[str, Any]]: + if not isinstance(definitions, (list, tuple)): + return [] + records: list[dict[str, Any]] = [] + seen: set[str] = set() + for item in definitions: + if not isinstance(item, dict): + continue + function = item.get("function") + if not isinstance(function, dict): + continue + name = function.get("name") + if not isinstance(name, str) or not name or name in seen: + continue + if len(name) > 256: + continue + seen.add(name) + records.append( + { + "name": name, + "schema_hash": _canonical_hash( + { + "parameters": function.get("parameters"), + "strict": function.get("strict"), + } + ), + } + ) + return sorted(records, key=lambda item: item["name"]) + + +def _matches(name: str, fragments: Iterable[str]) -> bool: + return any(fragment in name for fragment in fragments) + + +def _tool_capabilities(name: str) -> set[str]: + normalized = re.sub(r"[^a-z0-9]+", "_", name.lower()) + result: set[str] = set() + + if "deep_research" in normalized: + result.add("DEEP_RESEARCH") + result.add("WEB_SEARCH") + + if _matches( + normalized, + ( + "arxiv", + "consensus", + "crossref", + "openalex", + "pubmed", + "scholar", + "scispace", + "zotero", + ), + ): + result.add("SCHOLARLY_SEARCH") + elif _matches( + normalized, + ( + "browser_", + "web_extract", + "web_search", + ), + ): + result.add("WEB_SEARCH") + + if _matches( + normalized, + ( + "file_read", + "file_search", + "files_find", + "files_read", + "files_search", + "read_file", + "search_files", + ), + ): + result.add("FILES_LIBRARY") + + if _matches( + normalized, + ( + "git_", + "github", + "repository", + "sourcegraph", + ), + ): + result.add("REPOSITORY") + + if _matches( + normalized, + ( + "coq", + "formal_proof", + "isabelle", + "lean", + "smt", + "z3", + ), + ): + result.add("FORMAL_PROOF") + elif _matches( + normalized, + ( + "bash", + "code_execution", + "execute_code", + "python", + "shell", + "terminal", + ), + ): + result.add("CODE_EXECUTION") + + if _matches( + normalized, + ( + "calculator", + "symbolic", + "wolfram", + ), + ): + result.add("SYMBOLIC_COMPUTATION") + + if _matches( + normalized, + ( + "database", + "postgres", + "sql", + "supabase", + ), + ): + result.add("DATABASE") + + if _matches( + normalized, + ( + "image_analyze", + "screenshot", + "vision", + ), + ): + result.add("VISION") + return result + + +def capability_snapshot(tool_definitions: Any) -> dict[str, Any]: + """Map runtime tool schemas to the frozen semantic FOIL capability names.""" + + records = _tool_records(tool_definitions) + capabilities = {"TEXT_GENERATION", "REASONING"} + for record in records: + capabilities.update(_tool_capabilities(record["name"])) + return { + "available_capabilities": sorted(capabilities), + "tool_count": len(records), + "tool_manifest_hash": _canonical_hash(records), + } + + +def _adapter_environment(task_id: str) -> dict[str, str]: + environment = dict(os.environ) + environment["GAUNTLET_TASK_ID"] = task_id + environment["PYTHONPATH"] = str(REPO_ROOT) + environment["PYTHONUNBUFFERED"] = "1" + for bypass in ( + "HERMES_YOLO_MODE", + "HERMES_ACCEPT_HOOKS", + "HERMES_INTERACTIVE", + ): + environment.pop(bypass, None) + return environment + + +def _forbidden_fields(value: Any) -> set[str]: + found: set[str] = set() + if isinstance(value, dict): + found.update(_FORBIDDEN_ROUTE_FIELDS.intersection(value)) + for item in value.values(): + found.update(_forbidden_fields(item)) + elif isinstance(value, list): + for item in value: + found.update(_forbidden_fields(item)) + return found + + +def _validate_content_hash(route: dict[str, Any]) -> None: + supplied = route.get("content_hash") + if not isinstance(supplied, str) or not re.fullmatch(r"[0-9a-f]{64}", supplied): + raise FoilRouteBridgeError( + "FOIL_ROUTE_CONTENT_HASH_INVALID", + "FOIL route omitted a valid content hash", + ) + payload = dict(route) + payload.pop("content_hash", None) + if _canonical_hash(payload) != supplied: + raise FoilRouteBridgeError( + "FOIL_ROUTE_CONTENT_HASH_MISMATCH", + "FOIL route content hash did not match the returned route", + ) + + +def _parse_route( + task_id: str, + stdout: str, + returncode: int, +) -> dict[str, Any]: + if len(stdout.encode("utf-8")) > MAX_FOIL_ROUTE_OUTPUT_BYTES: + raise FoilRouteBridgeError( + "FOIL_ROUTE_OUTPUT_TOO_LARGE", + "FOIL route adapter exceeded the bounded output limit", + ) + records = [line for line in stdout.splitlines() if line.strip()] + if len(records) != 1: + raise FoilRouteBridgeError( + "FOIL_ROUTE_PROTOCOL_ERROR", + "FOIL route adapter must return exactly one JSON record", + ) + try: + route = json.loads(records[0]) + except json.JSONDecodeError as exc: + raise FoilRouteBridgeError( + "FOIL_ROUTE_PROTOCOL_ERROR", + "FOIL route adapter returned invalid JSON", + ) from exc + if not isinstance(route, dict): + raise FoilRouteBridgeError( + "FOIL_ROUTE_PROTOCOL_ERROR", + "FOIL route adapter result must be a JSON object", + ) + if route.get("schema") != FOIL_ROUTE_PROTOCOL_VERSION: + raise FoilRouteBridgeError( + "FOIL_ROUTE_SCHEMA_MISMATCH", + f"FOIL route schema must be {FOIL_ROUTE_PROTOCOL_VERSION}", + ) + if route.get("action") != "foil-route" or route.get("task_id") != task_id: + raise FoilRouteBridgeError( + "FOIL_ROUTE_CORRELATION_MISMATCH", + "FOIL route did not match the host-bound task", + ) + if route.get("read_only") is not True: + raise FoilRouteBridgeError( + "FOIL_ROUTE_AUTHORITY_VIOLATION", + "FOIL route did not attest to read-only operation", + ) + if route.get("mutation_performed") is not False: + raise FoilRouteBridgeError( + "FOIL_ROUTE_AUTHORITY_VIOLATION", + "FOIL route reported a canonical mutation", + ) + if route.get("mode") != "SHADOW": + raise FoilRouteBridgeError( + "FOIL_ROUTE_MODE_INVALID", + "FAST-P7 accepts only SHADOW FOIL routes", + ) + if route.get("authority_ceiling") != "ADAPTATION_ONLY": + raise FoilRouteBridgeError( + "FOIL_ROUTE_AUTHORITY_VIOLATION", + "FOIL route exceeded the ADAPTATION_ONLY ceiling", + ) + for name in ( + "execution_authorized", + "toolset_narrowing_applied", + "profile_used", + "private_profile_data_transmitted", + ): + if route.get(name) is not False: + raise FoilRouteBridgeError( + "FOIL_ROUTE_AUTHORITY_VIOLATION", + f"FOIL route field {name} must be false", + ) + authority = route.get("authority") + if not isinstance(authority, dict) or not authority: + raise FoilRouteBridgeError( + "FOIL_ROUTE_AUTHORITY_INVALID", + "FOIL route omitted its authority projection", + ) + if any(value is not False for value in authority.values()): + raise FoilRouteBridgeError( + "FOIL_ROUTE_AUTHORITY_VIOLATION", + "FOIL route attempted to claim canonical authority", + ) + forbidden = _forbidden_fields(route) + if forbidden: + raise FoilRouteBridgeError( + "FOIL_ROUTE_FORBIDDEN_FIELDS", + "FOIL route contains authority-bearing fields: " + + ", ".join(sorted(forbidden)), + ) + _validate_content_hash(route) + + expected_exit = 0 if route.get("status") == "OK" else 2 + if returncode != expected_exit: + raise FoilRouteBridgeError( + "FOIL_ROUTE_EXIT_MISMATCH", + "FOIL route adapter status and exit code did not agree", + ) + if route.get("status") != "OK": + error = route.get("error") + if isinstance(error, dict): + code = str(error.get("code") or "FOIL_ROUTE_UNAVAILABLE") + message = str(error.get("message") or "FOIL route unavailable") + else: + code = "FOIL_ROUTE_UNAVAILABLE" + message = "FOIL route unavailable" + raise FoilRouteBridgeError(code, message) + return route + + +def build_advisory_route( + *, + task_id: str, + tool_definitions: Any, +) -> dict[str, Any]: + """Request one task-bound, profile-free, proposal-only FOIL route.""" + + module_cli = Path( + os.environ.get("GAUNTLET_MODULE_CLI", str(MODULE_CLI)) + ).resolve() + if module_cli != MODULE_CLI.resolve() or not module_cli.is_file(): + raise FoilRouteBridgeError( + "FOIL_ROUTE_ADAPTER_MISSING", + "FOIL route adapter path does not match the active repository", + ) + snapshot = capability_snapshot(tool_definitions) + try: + completed = subprocess.run( + [ + sys.executable, + str(module_cli), + "--root", + str(REPO_ROOT), + "foil-route", + ], + input=json.dumps( + snapshot, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ), + cwd=REPO_ROOT, + env=_adapter_environment(task_id), + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + timeout=DEFAULT_ADAPTER_TIMEOUT_SECONDS, + check=False, + ) + except subprocess.TimeoutExpired as exc: + raise FoilRouteBridgeError( + "FOIL_ROUTE_TIMEOUT", + "FOIL route adapter exceeded its bounded timeout", + ) from exc + except OSError as exc: + raise FoilRouteBridgeError( + "FOIL_ROUTE_START_FAILED", + "FOIL route adapter could not start: " + type(exc).__name__, + ) from exc + return _parse_route(task_id, completed.stdout, completed.returncode) + + +def route_instruction(route: dict[str, Any]) -> str: + """Render the bounded public route trace for the first model request.""" + + projection = { + "schema": route["schema"], + "route_content_hash": route["content_hash"], + "task_id": route["task_id"], + "mode": route["mode"], + "authority_ceiling": route["authority_ceiling"], + "policy_version": route["policy_version"], + "trace": route["trace"], + "primary_effort_mode": route["primary_effort_mode"], + "task_complements": route["task_complements"], + "targeted_complement": route["targeted_complement"], + "actions": route["actions"], + "required_verifiers": route["required_verifiers"], + "pending_verifiers": route["pending_verifiers"], + "minimum_capability_bundle": route["minimum_capability_bundle"], + "capability_bundle_complete": route["capability_bundle_complete"], + "missing_capabilities": route["missing_capabilities"], + "should_stop": route["should_stop"], + "stop_reason": route["stop_reason"], + "execution_authorized": False, + "canonical_authority": "NONE", + } + rendered = ( + _ROUTE_MARKER + + "\n" + + "This is proposal-only routing guidance. It is not evidence, a receipt, " + + "a verdict, or release authority. Re-read canonical status when needed.\n" + + json.dumps( + projection, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + + "\n" + + _ROUTE_END_MARKER + ) + if len(rendered) > MAX_FOIL_ROUTE_PROMPT_CHARS: + raise FoilRouteBridgeError( + "FOIL_ROUTE_PROMPT_TOO_LARGE", + "FOIL route instruction exceeded the bounded prompt allowance", + ) + return rendered + + +def inject_advisory_route(prompt: str, route: dict[str, Any]) -> str: + if not isinstance(prompt, str) or not prompt.strip(): + raise FoilRouteBridgeError( + "FOIL_ROUTE_PROMPT_INVALID", + "FOIL route requires a non-empty runtime prompt", + ) + if _ROUTE_MARKER in prompt or _ROUTE_END_MARKER in prompt: + raise FoilRouteBridgeError( + "FOIL_ROUTE_PROMPT_COLLISION", + "runtime prompt already contains a reserved FOIL route marker", + ) + return route_instruction(route) + "\n\n" + prompt diff --git a/gauntlet_host/module_cli.py b/gauntlet_host/module_cli.py index 4d4a7027..1cafbd73 100644 --- a/gauntlet_host/module_cli.py +++ b/gauntlet_host/module_cli.py @@ -1,9 +1,11 @@ -"""Read-only Gauntlet module adapter for the isolated runtime plugin.""" +"""Read-only Gauntlet status and advisory FOIL route adapter.""" from __future__ import annotations import argparse +import hashlib import json +import math import os from pathlib import Path import re @@ -11,7 +13,10 @@ from typing import Any, Sequence ADAPTER_SCHEMA = "gauntlet.adapter.v1" +FOIL_ROUTE_SCHEMA = "gauntlet.foil-route.v1" +MAX_ROUTE_INPUT_BYTES = 131_072 TASK_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$") +SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$") class AdapterError(RuntimeError): @@ -23,6 +28,10 @@ def __init__(self, code: str, message: str) -> None: self.message = message +class _DuplicateKeyError(ValueError): + pass + + def _is_within(path: Path, root: Path) -> bool: try: path.relative_to(root) @@ -37,11 +46,13 @@ def _configure_imports(root: Path) -> None: tools_root / "__init__.py", tools_root / "egrt_store.py", tools_root / "soul_runtime.py", + tools_root / "foil_policy.py", + tools_root / "foil_capabilities.py", ) if not all(path.is_file() for path in required): raise AdapterError( "GAUNTLET_REPOSITORY_INVALID", - "Gauntlet authority files are missing from the requested repository root", + "Gauntlet authority or FOIL policy files are missing from the repository", ) vendor_root = (root / "vendor" / "hermes-agent").resolve(strict=False) @@ -80,6 +91,15 @@ def _task_id() -> str: return task_id +def _authority_projection() -> dict[str, bool]: + return { + "receipt_creation": False, + "verdict_change": False, + "obligation_clearance": False, + "task_release": False, + } + + def _base_document(action: str, task_id: str) -> dict[str, Any]: return { "schema": ADAPTER_SCHEMA, @@ -88,15 +108,47 @@ def _base_document(action: str, task_id: str) -> dict[str, Any]: "canonical_source": "egrt.runtime.v1", "read_only": True, "mutation_performed": False, - "authority": { - "receipt_creation": False, - "verdict_change": False, - "obligation_clearance": False, - "task_release": False, - }, + "authority": _authority_projection(), + } + + +def _foil_base_document(task_id: str) -> dict[str, Any]: + return { + "schema": FOIL_ROUTE_SCHEMA, + "action": "foil-route", + "task_id": task_id, + "canonical_source": "egrt.runtime.v1", + "status": "OK", + "mode": "SHADOW", + "authority_ceiling": "ADAPTATION_ONLY", + "read_only": True, + "mutation_performed": False, + "execution_authorized": False, + "toolset_narrowing_applied": False, + "profile_used": False, + "private_profile_data_transmitted": False, + "stop_is_advisory": True, + "authority": _authority_projection(), } +def _canonical_hash(value: Any) -> str: + encoded = json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +def _bind_content_hash(document: dict[str, Any]) -> dict[str, Any]: + result = dict(document) + result.pop("content_hash", None) + result["content_hash"] = _canonical_hash(result) + return result + + def _release_projection(root: Path, task_id: str) -> dict[str, Any]: from soul_runtime import release_gate @@ -109,9 +161,11 @@ def _release_projection(root: Path, task_id: str) -> dict[str, Any]: def _task_projection(task: dict[str, Any], release: dict[str, Any]) -> dict[str, Any]: + detail = release.get("detail") + gate_rows = detail.get("obligations", []) if isinstance(detail, dict) else [] states = { row.get("obligation_id"): row - for row in release.get("detail", {}).get("obligations", []) + for row in gate_rows if isinstance(row, dict) and row.get("obligation_id") } obligations: list[dict[str, Any]] = [] @@ -151,15 +205,612 @@ def _read_task(root: Path, task_id: str) -> dict[str, Any]: "TASK_NOT_FOUND", f"no integrity-valid canonical task exists for {task_id}", ) + if task.get("task_id") != task_id: + raise AdapterError( + "TASK_ID_MISMATCH", + "canonical task identity did not match the requested task", + ) + content_hash = task.get("content_hash") + if not isinstance(content_hash, str) or not SHA256_PATTERN.fullmatch( + content_hash + ): + raise AdapterError( + "TASK_CONTENT_HASH_INVALID", + "canonical task omitted a valid content hash", + ) return task +def _pairs_to_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + raise _DuplicateKeyError(f"duplicate JSON key: {key}") + result[key] = value + return result + + +def _route_input() -> dict[str, Any]: + raw = sys.stdin.buffer.read(MAX_ROUTE_INPUT_BYTES + 1) + if len(raw) > MAX_ROUTE_INPUT_BYTES: + raise AdapterError( + "FOIL_ROUTE_INPUT_TOO_LARGE", + "FOIL route input exceeded the bounded adapter limit", + ) + if not raw: + raise AdapterError( + "FOIL_ROUTE_INPUT_MISSING", + "FOIL route requires one bounded capability-snapshot object on stdin", + ) + try: + value = json.loads(raw.decode("utf-8"), object_pairs_hook=_pairs_to_object) + except UnicodeDecodeError as exc: + raise AdapterError( + "FOIL_ROUTE_INPUT_INVALID", + "FOIL route input must be UTF-8 JSON", + ) from exc + except (_DuplicateKeyError, json.JSONDecodeError) as exc: + raise AdapterError( + "FOIL_ROUTE_INPUT_INVALID", + f"FOIL route input is invalid JSON: {exc}", + ) from exc + if not isinstance(value, dict): + raise AdapterError( + "FOIL_ROUTE_INPUT_INVALID", + "FOIL route input must be a JSON object", + ) + + allowed = { + "available_capabilities", + "tool_count", + "tool_manifest_hash", + } + unknown = sorted(set(value) - allowed) + if unknown: + raise AdapterError( + "FOIL_ROUTE_INPUT_UNKNOWN_FIELDS", + "unsupported FOIL route input fields: " + ", ".join(unknown), + ) + if set(value) != allowed: + missing = sorted(allowed - set(value)) + raise AdapterError( + "FOIL_ROUTE_INPUT_MISSING_FIELDS", + "missing FOIL route input fields: " + ", ".join(missing), + ) + + capabilities = value["available_capabilities"] + if not isinstance(capabilities, list): + raise AdapterError( + "FOIL_ROUTE_CAPABILITIES_INVALID", + "available_capabilities must be an array", + ) + if len(capabilities) > 128: + raise AdapterError( + "FOIL_ROUTE_CAPABILITIES_INVALID", + "available_capabilities exceeded the 128-item limit", + ) + + from foil_capabilities import capability_names + + known = set(capability_names()) + normalized: list[str] = [] + seen: set[str] = set() + for index, item in enumerate(capabilities): + if not isinstance(item, str) or not item: + raise AdapterError( + "FOIL_ROUTE_CAPABILITIES_INVALID", + f"available_capabilities[{index}] must be a non-empty string", + ) + name = item.upper() + if name not in known: + raise AdapterError( + "FOIL_ROUTE_CAPABILITY_UNKNOWN", + f"unknown semantic capability: {name}", + ) + if name in seen: + raise AdapterError( + "FOIL_ROUTE_CAPABILITY_DUPLICATE", + f"duplicate semantic capability: {name}", + ) + seen.add(name) + normalized.append(name) + + tool_count = value["tool_count"] + if ( + isinstance(tool_count, bool) + or not isinstance(tool_count, int) + or tool_count < 0 + or tool_count > 10_000 + ): + raise AdapterError( + "FOIL_ROUTE_TOOL_COUNT_INVALID", + "tool_count must be an integer from 0 through 10000", + ) + + manifest_hash = value["tool_manifest_hash"] + if not isinstance(manifest_hash, str) or not SHA256_PATTERN.fullmatch( + manifest_hash + ): + raise AdapterError( + "FOIL_ROUTE_TOOL_HASH_INVALID", + "tool_manifest_hash must be a lowercase SHA-256 digest", + ) + return { + "available_capabilities": tuple(sorted(normalized)), + "tool_count": tool_count, + "tool_manifest_hash": manifest_hash, + } + + +_BOOL_CONTEXT_FIELDS = { + "requires_external_retrieval", + "freshness_sensitive", + "closed_book", + "technical_reasoning", + "abstract_transformation", + "closed_context", + "multi_hop", + "mixed_tool_task", + "has_viable_candidate", + "output_contract_required", +} +_CONTEXT_FIELDS = _BOOL_CONTEXT_FIELDS | { + "answer_confidence", + "supplied_example_count", + "required_complements", +} + + +def _task_context_metadata(task: dict[str, Any]) -> dict[str, Any]: + metadata = task.get("metadata") + if metadata is None: + return {} + if not isinstance(metadata, dict): + raise AdapterError( + "FOIL_TASK_METADATA_INVALID", + "canonical task metadata must be an object", + ) + context = metadata.get("foil_task_context", {}) + if context is None: + return {} + if not isinstance(context, dict): + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + "metadata.foil_task_context must be an object", + ) + unknown = sorted(set(context) - _CONTEXT_FIELDS) + if unknown: + raise AdapterError( + "FOIL_TASK_CONTEXT_UNKNOWN_FIELDS", + "unsupported foil_task_context fields: " + ", ".join(unknown), + ) + return context + + +def _strict_bool(context: dict[str, Any], name: str, default: bool) -> bool: + if name not in context: + return default + value = context[name] + if not isinstance(value, bool): + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + f"foil_task_context.{name} must be a boolean", + ) + return value + + +def _strict_confidence(context: dict[str, Any]) -> float: + value = context.get("answer_confidence", 0.0) + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + "foil_task_context.answer_confidence must be numeric", + ) + result = float(value) + if not math.isfinite(result) or not 0.0 <= result <= 1.0: + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + "foil_task_context.answer_confidence must be finite and in [0, 1]", + ) + return result + + +def _strict_example_count(context: dict[str, Any]) -> int: + value = context.get("supplied_example_count", 0) + if ( + isinstance(value, bool) + or not isinstance(value, int) + or value < 0 + or value > 1_000_000 + ): + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + "foil_task_context.supplied_example_count must be from 0 to 1000000", + ) + return value + + +def _strict_enum_set( + context: dict[str, Any], + name: str, + enum_type: Any, +) -> frozenset[Any]: + value = context.get(name, []) + if not isinstance(value, list): + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + f"foil_task_context.{name} must be an array", + ) + result: set[Any] = set() + for index, item in enumerate(value): + if not isinstance(item, str): + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + f"foil_task_context.{name}[{index}] must be a string", + ) + try: + member = enum_type(item) + except ValueError as exc: + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + f"foil_task_context.{name}[{index}] is not supported", + ) from exc + if member in result: + raise AdapterError( + "FOIL_TASK_CONTEXT_INVALID", + f"foil_task_context.{name} contains a duplicate value", + ) + result.add(member) + return frozenset(result) + + +_DEFAULT_CLAIM_KINDS = { + "DISCOVERY": "external_fact", + "PROOF": "logical", + "SYNTHESIS": "logical", + "ENGINEERING": "executable", + "EVALUATION": "numeric", + "ASSURANCE": "output_contract", + "PREFLIGHT": "logical", + "REVIEW": "logical", + "ADAPTATION": "logical", + "ADVERSARY": "logical", +} + + +def _obligation_claim_kind(row: dict[str, Any], claim_kind_type: Any) -> Any: + metadata = row.get("metadata") + if metadata is None: + metadata = {} + if not isinstance(metadata, dict): + raise AdapterError( + "FOIL_OBLIGATION_METADATA_INVALID", + "canonical obligation metadata must be an object", + ) + raw = metadata.get("foil_claim_kind") + if raw is None: + raw = _DEFAULT_CLAIM_KINDS.get(str(row.get("kind") or ""), "logical") + if not isinstance(raw, str): + raise AdapterError( + "FOIL_CLAIM_KIND_INVALID", + "obligation metadata foil_claim_kind must be a string", + ) + try: + return claim_kind_type(raw) + except ValueError as exc: + raise AdapterError( + "FOIL_CLAIM_KIND_INVALID", + f"unsupported FOIL claim kind on obligation {row.get('obligation_id')}", + ) from exc + + +def _build_task_context( + task: dict[str, Any], + release: dict[str, Any], +) -> tuple[Any, dict[str, Any]]: + from foil_policy import ( + ClaimKind, + ComplementKind, + LoadBearingUncertainty, + TaskContext, + VerifierKind, + ) + + detail = release.get("detail") + gate_rows = detail.get("obligations", []) if isinstance(detail, dict) else [] + states = { + str(row.get("obligation_id") or ""): row + for row in gate_rows + if isinstance(row, dict) and row.get("obligation_id") + } + + uncertainties: list[LoadBearingUncertainty] = [] + claim_kinds: set[ClaimKind] = set() + for row in task.get("obligations", []): + if not isinstance(row, dict): + continue + load_bearing = row.get("load_bearing", True) + if not isinstance(load_bearing, bool): + raise AdapterError( + "FOIL_OBLIGATION_INVALID", + "canonical obligation load_bearing must be a boolean", + ) + if not load_bearing: + continue + obligation_id = str(row.get("obligation_id") or "") + if not obligation_id: + raise AdapterError( + "FOIL_OBLIGATION_INVALID", + "load-bearing obligation is missing obligation_id", + ) + claim_kind = _obligation_claim_kind(row, ClaimKind) + claim_kinds.add(claim_kind) + gate = states.get(obligation_id, {}) + resolved = isinstance(gate, dict) and gate.get("verdict") == "CLEARED" + uncertainties.append( + LoadBearingUncertainty( + label=obligation_id, + claim_kind=claim_kind, + decisive=True, + resolved=resolved, + ) + ) + + context = _task_context_metadata(task) + requires_external = bool( + claim_kinds.intersection({ClaimKind.EXTERNAL_FACT, ClaimKind.FRESH_FACT}) + ) + freshness_sensitive = ClaimKind.FRESH_FACT in claim_kinds + output_contract = ClaimKind.OUTPUT_CONTRACT in claim_kinds + + complements = _strict_enum_set( + context, + "required_complements", + ComplementKind, + ) + task_context = TaskContext( + requires_external_retrieval=_strict_bool( + context, + "requires_external_retrieval", + requires_external, + ), + freshness_sensitive=_strict_bool( + context, + "freshness_sensitive", + freshness_sensitive, + ), + closed_book=_strict_bool(context, "closed_book", False), + technical_reasoning=_strict_bool( + context, + "technical_reasoning", + False, + ), + abstract_transformation=_strict_bool( + context, + "abstract_transformation", + False, + ), + closed_context=_strict_bool(context, "closed_context", False), + multi_hop=_strict_bool(context, "multi_hop", False), + mixed_tool_task=_strict_bool( + context, + "mixed_tool_task", + len(claim_kinds) > 1, + ), + has_viable_candidate=_strict_bool( + context, + "has_viable_candidate", + False, + ), + answer_confidence=_strict_confidence(context), + supplied_example_count=_strict_example_count(context), + output_contract_required=_strict_bool( + context, + "output_contract_required", + output_contract, + ), + uncertainties=tuple(uncertainties), + completed_verifiers=frozenset(), + required_complements=complements, + ) + projection = { + "source": "CANONICAL_TASK_AND_EXPLICIT_FOIL_METADATA", + "claim_text_transmitted": False, + "load_bearing_obligation_count": len(uncertainties), + "claim_kinds": sorted(item.value for item in claim_kinds), + "explicit_context_fields": sorted(context), + } + return task_context, projection + + +_VERIFIER_CAPABILITIES: dict[str, tuple[str, ...]] = { + "source_evidence": ( + "WEB_SEARCH", + "SCHOLARLY_SEARCH", + "FILES_LIBRARY", + "REPOSITORY", + ), + "current_source": ("WEB_SEARCH",), + "exact_calculation": ("SYMBOLIC_COMPUTATION", "CODE_EXECUTION"), + "supplied_example_consistency": ("REASONING", "CODE_EXECUTION"), + "execution_test": ("CODE_EXECUTION",), + "contradiction_counterexample": ( + "REASONING", + "FORMAL_PROOF", + "SYMBOLIC_COMPUTATION", + ), + "output_contract": ("REASONING", "CODE_EXECUTION"), +} + +_ACTION_CAPABILITIES: dict[str, tuple[str, ...]] = { + "prefer_current_source": ("WEB_SEARCH",), + "reason_closed_book": ("REASONING", "TEXT_GENERATION"), + "induce_rule": ("REASONING", "TEXT_GENERATION"), + "check_rule_against_all_examples": ("REASONING", "CODE_EXECUTION"), + "decompose_supplied_evidence": ("REASONING", "TEXT_GENERATION"), + "mix_tools_and_reasoning": ("REASONING", "TEXT_GENERATION"), + "check_output_contract": ("REASONING", "CODE_EXECUTION"), +} + +_COMPLEMENT_CAPABILITIES: dict[str, tuple[str, ...]] = { + "formalization": ("FORMAL_PROOF", "REASONING"), + "decomposition": ("REASONING",), + "error_detection": ("REASONING", "CODE_EXECUTION"), + "evidence_discipline": ( + "WEB_SEARCH", + "SCHOLARLY_SEARCH", + "FILES_LIBRARY", + "REPOSITORY", + ), + "causal_reasoning": ("REASONING",), + "quantitative_check": ("SYMBOLIC_COMPUTATION", "CODE_EXECUTION"), + "implementation_execution": ("CODE_EXECUTION",), + "planning_prioritization": ("REASONING",), + "calibration": ("REASONING",), + "transfer_adaptation": ("REASONING",), + "tool_selection": ("REASONING",), + "uncertainty_management": ("REASONING",), +} + + +def _capability_requirements(decision: Any) -> list[dict[str, Any]]: + groups: list[tuple[str, tuple[str, ...]]] = [ + ("model_reasoning", ("REASONING", "TEXT_GENERATION")) + ] + groups.extend( + (f"verifier:{item.value}", _VERIFIER_CAPABILITIES[item.value]) + for item in decision.pending_verifiers + ) + for action in decision.actions: + acceptable = _ACTION_CAPABILITIES.get(action.value) + if acceptable is not None: + groups.append((f"action:{action.value}", acceptable)) + for complement in sorted( + decision.task_complements, + key=lambda item: item.value, + ): + acceptable = _COMPLEMENT_CAPABILITIES[complement.value] + groups.append((f"complement:{complement.value}", acceptable)) + + result: list[dict[str, Any]] = [] + seen: set[tuple[str, tuple[str, ...]]] = set() + for requirement, acceptable in groups: + key = (requirement, acceptable) + if key in seen: + continue + seen.add(key) + result.append( + { + "requirement": requirement, + "acceptable_capabilities": list(acceptable), + } + ) + return result + + +def _minimum_bundle( + requirements: list[dict[str, Any]], + available: tuple[str, ...], +) -> tuple[list[str], list[dict[str, Any]]]: + available_set = set(available) + selected: list[str] = [] + missing: list[dict[str, Any]] = [] + for group in requirements: + acceptable = group["acceptable_capabilities"] + chosen = next( + (item for item in acceptable if item in available_set), + None, + ) + if chosen is None: + missing.append( + { + "requirement": group["requirement"], + "acceptable_capabilities": list(acceptable), + } + ) + elif chosen not in selected: + selected.append(chosen) + return selected, missing + + +def _foil_route( + task: dict[str, Any], + release: dict[str, Any], + snapshot: dict[str, Any], +) -> dict[str, Any]: + from foil_policy import RuntimePolicyV2 + + if task.get("active") is not True or task.get("released") is not False: + raise AdapterError( + "FOIL_TASK_NOT_ACTIVE", + "FOIL route requires an active, unreleased canonical task", + ) + task_context, context_projection = _build_task_context(task, release) + decision = RuntimePolicyV2().decide(task_context, profile=None) + requirements = _capability_requirements(decision) + available = snapshot["available_capabilities"] + selected, missing = _minimum_bundle(requirements, available) + + document = _foil_base_document(str(task["task_id"])) + document.update( + { + "task_content_hash": task.get("content_hash"), + "policy_version": RuntimePolicyV2.version, + "task_context": context_projection, + "trace": decision.trace(), + "primary_effort_mode": decision.primary_effort_mode.value, + "task_complements": sorted( + item.value for item in decision.task_complements + ), + "targeted_complement": ( + decision.targeted_complement.value + if decision.targeted_complement is not None + else None + ), + "required_verifiers": [ + item.value for item in decision.required_verifiers + ], + "pending_verifiers": [ + item.value for item in decision.pending_verifiers + ], + "actions": [item.value for item in decision.actions], + "should_stop": decision.should_stop, + "stop_reason": decision.stop_reason, + "resource_allocation": { + "retrieval_allowed": decision.resource_allocation.retrieval_allowed, + "search_query_priority": ( + decision.resource_allocation.search_query_priority + ), + "source_followup_priority": ( + decision.resource_allocation.source_followup_priority + ), + "rationale": decision.resource_allocation.rationale, + }, + "capability_snapshot": { + "source": "RUNTIME_REPORTED_TOOL_DEFINITIONS", + "verified_by_gauntlet": False, + "available": list(available), + "tool_count": snapshot["tool_count"], + "tool_manifest_hash": snapshot["tool_manifest_hash"], + }, + "capability_requirements": requirements, + "minimum_capability_bundle": selected, + "capability_bundle_complete": not missing, + "missing_capabilities": missing, + } + ) + return _bind_content_hash(document) + + def _execute(root: Path, action: str, task_id: str) -> dict[str, Any]: task = _read_task(root, task_id) release = _release_projection(root, task_id) + + if action == "foil-route": + return _foil_route(task, release, _route_input()) + document = _base_document(action, task_id) document["status"] = "OK" - if action == "task-status": document["task"] = _task_projection(task, release) document["release"] = release @@ -174,7 +825,24 @@ def _execute(root: Path, action: str, task_id: str) -> dict[str, Any]: return document -def _error_document(action: str, task_id: str, exc: AdapterError) -> dict[str, Any]: +def _error_document( + action: str, + task_id: str, + exc: AdapterError, +) -> dict[str, Any]: + if action == "foil-route": + document = _foil_base_document(task_id) + document.update( + { + "status": "ERROR", + "error": { + "code": exc.code, + "message": exc.message, + }, + } + ) + return _bind_content_hash(document) + document = _base_document(action, task_id) document.update( { @@ -191,10 +859,16 @@ def _error_document(action: str, task_id: str, exc: AdapterError) -> dict[str, A def _argument_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="python gauntlet_host/module_cli.py", - description="Read canonical Gauntlet task or release status without mutation.", + description=( + "Read canonical status or compute one proposal-only FOIL route " + "without mutation." + ), ) parser.add_argument("--root", default=".") - parser.add_argument("action", choices=("task-status", "release-status")) + parser.add_argument( + "action", + choices=("task-status", "release-status", "foil-route"), + ) return parser @@ -216,7 +890,8 @@ def main(argv: Sequence[str] | None = None) -> int: task_id, AdapterError( "ADAPTER_INTERNAL_ERROR", - f"unexpected read-only adapter failure: {type(exc).__name__}", + "unexpected read-only adapter failure: " + + type(exc).__name__, ), ) exit_code = 2 diff --git a/gauntlet_host/phase7_worker.py b/gauntlet_host/phase7_worker.py new file mode 100644 index 00000000..64170005 --- /dev/null +++ b/gauntlet_host/phase7_worker.py @@ -0,0 +1,91 @@ +"""FAST-P7 wrapper that injects one advisory FOIL route before model work.""" + +from __future__ import annotations + +from contextlib import redirect_stdout +from dataclasses import replace +from pathlib import Path +import sys +from typing import Any + +if __package__ in {None, ""}: + _repo_bootstrap = Path(__file__).resolve().parent.parent + if str(_repo_bootstrap) not in sys.path: + sys.path.insert(0, str(_repo_bootstrap)) + +from gauntlet_host import foil_bridge +from gauntlet_host import worker_main as core +from gauntlet_host.ipc import RuntimeRequest, RuntimeResult + +_ORIGINAL_EXECUTE = core._execute_agent_turn + + +def _execute_with_foil_route( + request: RuntimeRequest, + proof: Any, +) -> RuntimeResult: + route_state: dict[str, Any] = {} + + with redirect_stdout(sys.stderr): + from run_agent import AIAgent + + original_run_conversation = AIAgent.run_conversation + + def routed_run_conversation( + agent: Any, + prompt: str, + *args: Any, + **kwargs: Any, + ) -> Any: + if route_state.get("applied") is True: + return original_run_conversation(agent, prompt, *args, **kwargs) + + route_state["applied"] = True + try: + route = foil_bridge.build_advisory_route( + task_id=request.task_id, + tool_definitions=getattr(agent, "tools", ()), + ) + routed_prompt = foil_bridge.inject_advisory_route(prompt, route) + except foil_bridge.FoilRouteBridgeError as exc: + raise core.RuntimeExecutionError(exc.code, exc.message) from exc + + route_state["route"] = route + return original_run_conversation( + agent, + routed_prompt, + *args, + **kwargs, + ) + + AIAgent.run_conversation = routed_run_conversation + try: + result = _ORIGINAL_EXECUTE(request, proof) + finally: + if AIAgent.run_conversation is routed_run_conversation: + AIAgent.run_conversation = original_run_conversation + + route = route_state.get("route") + if isinstance(route, dict): + payload = dict(result.payload) + payload["foil_route"] = route + result = replace(result, payload=payload) + elif result.status.value == "OK": + return core._error_result( + request, + status=core.WorkerStatus.UNAVAILABLE, + event="worker.foil_route_unavailable", + code="FOIL_ROUTE_NOT_APPLIED", + message="runtime completed without the required FAST-P7 route", + payload=proof.to_payload(), + ) + return result + + +def main() -> int: + core._execute_agent_turn = _execute_with_foil_route + return core.main() + + +if __name__ == "__main__": + raise SystemExit(main()) From 9747572e8ca13622b72d8cd3d995fece90e19173 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:45:38 -0700 Subject: [PATCH 13/37] fix(fast-p7): expose repository package root to verifier --- .github/workflows/fastpath-checkpoint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fastpath-checkpoint.yml b/.github/workflows/fastpath-checkpoint.yml index ae0be518..fa7e5660 100644 --- a/.github/workflows/fastpath-checkpoint.yml +++ b/.github/workflows/fastpath-checkpoint.yml @@ -31,4 +31,4 @@ jobs: - name: Verify FAST-P7 advisory FOIL routing run: | set -euo pipefail - python .github/phase7_verify.py + PYTHONPATH=. python .github/phase7_verify.py From 711e34eeac64b5d7a5c63ae43308bf3fe41d9f53 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:49:47 -0700 Subject: [PATCH 14/37] docs: publish FAST-P7 checkpoint receipt --- .../HERMES_FAST_P7_CHECKPOINT.json | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 docs/engineering/HERMES_FAST_P7_CHECKPOINT.json diff --git a/docs/engineering/HERMES_FAST_P7_CHECKPOINT.json b/docs/engineering/HERMES_FAST_P7_CHECKPOINT.json new file mode 100644 index 00000000..1b2fdb4d --- /dev/null +++ b/docs/engineering/HERMES_FAST_P7_CHECKPOINT.json @@ -0,0 +1,92 @@ +{ + "authority_ceiling": "ADAPTATION_ONLY", + "changed_paths": [ + ".github/phase7_verify.py", + ".github/workflows/fastpath-checkpoint.yml", + "docs/engineering/PHASE7_FOIL_ADVISORY_ROUTE.md", + "gauntlet_host/constants.py", + "gauntlet_host/foil_bridge.py", + "gauntlet_host/module_cli.py", + "gauntlet_host/phase7_worker.py" + ], + "content_hash": "2af94023db476a0c059e91e196cdbafc5e2352fbaa4dd1a228492c3e1c7ebf32", + "fast_milestone": "FAST-P7", + "head_sha": "9747572e8ca13622b72d8cd3d995fece90e19173", + "implementation_commit_sha": "fc8ca813a71887f3be52e9da30794299030aab7a", + "known_limitations": [ + "interim alpha still requires the pinned Hermes runtime", + "checkpoint is FAST-P7 only and is not GNR NATIVE-600, NATIVE-700, or native-final", + "FOIL route is SHADOW and ADAPTATION_ONLY", + "runtime-reported capability availability is not independently verified by Gauntlet", + "no profile complement or private profile data is used", + "no automatic toolset narrowing or execution authorization is applied", + "no behavioral efficacy or cost-reduction claim is established", + "FAST-P8 user-facing alpha CLI and boot qualification are not included", + "not yet admitted into integration/vnext-native-stack" + ], + "parent_checkpoint_sha": "249753738f193d1f82e7e12afe907c4841827f80", + "parent_checkpoint_tree": "10eeeeef9f85ca91e766e5be388a176943decf5d", + "producer_base_sha": "04e1994940115c8935246b54764e5f949096f179", + "producer_base_tree": "f64994423f1f03c213c4ddb7ea214b0a2671bc7d", + "producer_branch": "work/native-hermes-fastpath", + "recorded_at": "2026-08-29T19:46:18Z", + "repair_history": [ + { + "conclusion": "failure", + "defect": "checkpoint verifier script did not expose the repository package root on sys.path", + "head_sha": "fc8ca813a71887f3be52e9da30794299030aab7a", + "run_id": 33271617165 + }, + { + "conclusion": "success", + "head_sha": "9747572e8ca13622b72d8cd3d995fece90e19173", + "resolution": "run the verifier with an explicit repository PYTHONPATH", + "run_id": 33271699059 + } + ], + "runtime_required": true, + "schema": "egr.hermes.checkpoint.v1", + "tests": [ + { + "checks": [ + "pinned Hermes gitlink verification", + "pinned runtime dependency installation", + "strict direct FOIL route input validation", + "unknown semantic capability rejected fail-closed", + "one real vendored AIAgent turn against a deterministic local endpoint", + "live runtime tool definitions mapped to semantic capabilities", + "task-bound content-hashed route injected before the first model request", + "route mode SHADOW with ADAPTATION_ONLY authority ceiling", + "no private profile data transmitted", + "no automatic toolset narrowing or execution authorization", + "one real agent-visible gauntlet_task_status tool call", + "tool result persisted as OBSERVATION_ONLY", + "Soul release gate returned UNKNOWN for the unresolved fixture", + "unresolved task not accepted or released", + "zero canonical receipts created", + "canonical task state unchanged" + ], + "conclusion": "success", + "head_sha": "9747572e8ca13622b72d8cd3d995fece90e19173", + "job_id": 99151182885, + "run_id": 33271699059, + "workflow": "Fastpath checkpoint qualification" + } + ], + "tree_sha": "0b194a94abf0e9791afeb604c84141325381788f", + "upstream_hermes_commit": "5fc308a70719a83cccdbba4c0e39c23f5a8239d5", + "verification_summary": { + "canonical_receipts_created": 0, + "canonical_task_unchanged": true, + "capability_bundle_complete": false, + "missing_requirements": 2, + "model_round_trips": 3, + "profile_data_transmitted": false, + "release_gate_verdict": "UNKNOWN", + "route_authority_ceiling": "ADAPTATION_ONLY", + "route_injected_before_first_model_request": true, + "route_mode": "SHADOW", + "toolset_narrowing_applied": false, + "unresolved_not_accepted": true + } +} From 2c3d5f19fa230bdf3a092c81f587397b4ce2734e Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:50:18 -0700 Subject: [PATCH 15/37] docs: advance fast-build handoff through FAST-P7 --- .../GAUNTLET_FAST_BUILD_HANDOFF.md | 405 +++++------------- 1 file changed, 98 insertions(+), 307 deletions(-) diff --git a/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md b/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md index 40ed3881..861aa198 100644 --- a/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md +++ b/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md @@ -1,344 +1,135 @@ -# Gauntlet Fast Build — Session-to-Codex Handoff Ledger +# Gauntlet Fast Build — Current Handoff Ledger **Handoff ID:** `GAUNTLET-FAST-HANDOFF-001` +**Ledger version:** `2.0` **Repository:** `Kitahl/The-Gauntlet` -**Branch:** `work/native-hermes-fastpath` -**Baseline commit:** `4f088d688fa9e25b4608f44000a5d9812efa45f9` -**Governing plan:** `docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md` +**Producer branch:** `work/native-hermes-fastpath` +**Completed milestone:** `FAST-P7` +**Validated implementation head:** `9747572e8ca13622b72d8cd3d995fece90e19173` +**Validated implementation tree:** `0b194a94abf0e9791afeb604c84141325381788f` +**Successful run:** `33271699059` +**Next milestone:** `FAST-P8` — user-facing alpha CLI and boot qualification +**Pinned upstream:** `NousResearch/hermes-agent@5fc308a70719a83cccdbba4c0e39c23f5a8239d5` -## 1. Frozen implementation decision +This version replaces the stale Phase-4-era working copy. Its previous detailed text remains +available in Git history at blob `40ed3881d19e5d5e7314425c24d6e5965c38a1e4`. -The fast build vendors the pinned Hermes Agent `v0.20.6` source and runs it as an isolated -internal subprocess. Gauntlet remains the sole authority over obligations, receipts, verdicts, -and release. Same-process imports between the two top-level package trees are prohibited. - -## 2. Phase ledger - -| Phase | Scope | State | Commit | -|---:|---|---|---| -| 1 | Pin, provenance, license, vendoring command, governing plan | `COMPLETE` | `60bb6f22d7c13a25fee204fbc17798c6c55cb19f` | -| 2 | Materialize and verify the exact `vendor/hermes-agent/` snapshot | `COMPLETE` | `a24a952133d03c43872dbec034a5a8ba5515a32f` | -| 3 | Constants, typed JSONL IPC, isolated worker bootstrap | `COMPLETE` | `98db935f56155ea1f446c10eb6aa5f00384e5d3d` | -| 4 | Launcher, runtime profile, Gauntlet-owned runtime home, one agent turn | `COMPLETE` | `c40b5b93d67e76e2d5e7153fc886c58142bd6f49` | -| 5 | Status/release plugin tools and subprocess module adapter | `NEXT` | — | -| 6 | Observation bridge and Soul finalizer | `PENDING` | — | -| 7 | FOIL advisory route and single `gauntlet` entry point | `PENDING` | — | -| 8 | Eight-item manual boot verification and Codex transfer | `PENDING` | — | - -## 3. Phase 1 outputs - -1. Exact upstream repository, tag, commit, and license pin. -2. Exact upstream MIT license notice under `third_party/`. -3. Machine-readable snapshot manifest under `vendor/`. -4. `scripts/vendor_hermes.py`, which: - - verifies the exact Git commit and tag; - - rejects a dirty or wrong checkout; - - verifies the MIT license hash; - - copies the source while excluding only nested `.git` metadata; - - records deterministic file count and tree digest; - - supports an independent `--verify-only` pass; - - requires `--force` before replacing an existing snapshot. -5. The governing fast-build engineering plan stored in the repository. -6. No changes to `tools/egrt_types.py`, `tools/soul_runtime.py`, FOIL authority ceilings, - receipt formats, or direct module behavior. - -## 4. Phase 2 verification receipt - -| Field | Result | -|---|---| -| GitHub Actions run | `33223968298` — `SUCCESS` | -| Upstream repository | `NousResearch/hermes-agent` | -| Upstream release | `v2026.8.27` / Hermes Agent `v0.20.6` | -| Upstream commit | `5fc308a70719a83cccdbba4c0e39c23f5a8239d5` | -| Snapshot commit | `a24a952133d03c43872dbec034a5a8ba5515a32f` | -| Vendored files | `10488` | -| Deterministic tree SHA-256 | `5a87f74ab782bfda8dcaed7938ac216d3fd6063e897f67dc0e27cb4c4d4b1dca` | -| License SHA-256 | `821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6` | -| Materialized at | `2026-08-29T00:35:28.082238+00:00` | -| Local modifications | `0` | -| Excluded content | nested `.git` metadata only | - -Executed successfully: - -```bash -python scripts/vendor_hermes.py --dry-run -python scripts/vendor_hermes.py -python scripts/vendor_hermes.py --verify-only -``` - -The independent verification pass reproduced the same file count, license hash, upstream -commit, and deterministic tree digest as the materialization pass. Upstream whitespace was -preserved rather than rewritten because Phase 2 required an exact source snapshot. - -## 5. Phase 3 implementation receipt - -| Field | Result | -|---|---| -| Implementation commit | `98db935f56155ea1f446c10eb6aa5f00384e5d3d` | -| GitHub Actions run | `33225470731` — `SUCCESS` | -| Verification environment | Ubuntu `24.04.4`; CPython `3.12.14` | -| Host files added | `4` | -| Vendored Hermes files modified | `0` | -| JSONL schema | `gauntlet.worker.v1` | -| Request type | `worker.request` | -| Result type | `worker.result` | -| Maximum JSONL record | `1048576` bytes | -| Namespace proof event | `worker.imports_verified` | - -Files added: +## Milestone namespace ```text -gauntlet_host/__init__.py -gauntlet_host/constants.py -gauntlet_host/ipc.py -gauntlet_host/worker_main.py +FAST-P1 provenance and source pin +FAST-P2 exact source materialization / gitlink representation +FAST-P3 JSONL IPC and isolated worker bootstrap +FAST-P4 launcher, runtime profile, one upstream turn +FAST-P5 read-only Gauntlet status bridge +FAST-P6 observation bridge and Soul finalizer +FAST-P7 advisory FOIL route +FAST-P8 user-facing alpha CLI and boot qualification ``` -The Phase 3 worker: +`FAST-P7` is not `NATIVE-600`, `NATIVE-700`, or native-final completion. -1. verifies the pinned snapshot manifest before runtime import; -2. rejects preloaded non-vendored `tools` modules; -3. removes Gauntlet repository paths from the worker import path; -4. sets worker `PYTHONPATH` and cwd to `vendor/hermes-agent`; -5. verifies both `find_spec("tools")` and imported `tools.__file__`; -6. emits a structured namespace proof; -7. accepts strict typed JSONL requests and emits deterministic JSONL results; -8. rejects duplicate JSON keys, unknown fields, invalid types, and unsupported operations; -9. exposes no `Verdict`, `EvidenceClass`, `Receipt`, or release field. +## Current checkpoint ledger -The successful run proved: +| Milestone | State | Current evidence | +|---|---|---| +| `FAST-P1` | `COMPLETE_HISTORICAL` | original implementation `60bb6f22d7c13a25fee204fbc17798c6c55cb19f` | +| `FAST-P2` | `COMPLETE` | exact upstream pin retained as a mode-`160000` gitlink | +| `FAST-P3` | `COMPLETE_HISTORICAL` | successful run `33225470731` | +| `FAST-P4` | `COMPLETE_HISTORICAL` | successful run `33226703542` | +| `FAST-P5` | `COMPLETE` | head `dda78eabc216749cb1a3010d8e533428b78721d0`; tree `0ad6d8641561de46bd729a5115500d4fd7bb0483`; run `33230839031` | +| `FAST-P6` | `COMPLETE` | head `249753738f193d1f82e7e12afe907c4841827f80`; tree `10eeeeef9f85ca91e766e5be388a176943decf5d`; run `33266521662` | +| `FAST-P7` | `COMPLETE` | head `9747572e8ca13622b72d8cd3d995fece90e19173`; tree `0b194a94abf0e9791afeb604c84141325381788f`; run `33271699059` | +| `FAST-P8` | `NEXT` | not started | -```text -tools_origin = vendor/hermes-agent/tools/__init__.py -cwd = vendor/hermes-agent -sys.path[0] = vendor/hermes-agent -PYTHONPATH = vendor/hermes-agent -upstream_commit = 5fc308a70719a83cccdbba4c0e39c23f5a8239d5 -``` - -Negative checks also passed: - -| Check | Required result | -|---|---| -| Root Gauntlet `tools` preloaded | `TOOLS_NAMESPACE_PRELOADED` | -| Duplicate JSON key | `DUPLICATE_JSON_KEY`; process exit `2` | -| Premature `run` request | `UNAVAILABLE`; process exit `3` | -| Runtime authority fields present | none | - -The first temporary Phase 3 run, `33225379537`, stopped before worker execution because its -shallow checkout did not contain `HEAD^` for a harness-only diff command. The repaired harness -removed that invalid precondition. Run `33225470731` then completed every Phase 3 worker check. - -## 6. Phase 4 implementation receipt +The older Phase-1–4 commit identifiers are historical because the producer history was later +repaired to replace the copied upstream tree with the pinned gitlink. Current authority comes +from exact live Git objects and successful checkpoint-native runs. -| Field | Result | -|---|---| -| Implementation commit | `c40b5b93d67e76e2d5e7153fc886c58142bd6f49` | -| Successful GitHub Actions run | `33226703542` — `SUCCESS` | -| Verification environment | Ubuntu `24.04.4`; CPython `3.12.14` | -| Host files added | `2` | -| Existing host files extended | `3` | -| Vendored Hermes files modified | `0` | -| Runtime home | `~/.gauntlet/runtime` | -| Upstream engine instantiated | `run_agent.AIAgent` from the vendored snapshot | -| Provider path exercised | upstream custom OpenAI-compatible provider resolution | -| Model call count | `1` | -| Worker completion event | `worker.turn_completed` | -| Mock final response | `phase4 mock response` | -| Input / output / total tokens | `12 / 4 / 16` | -| Session persistence | `state.db` created under the Gauntlet runtime home | -| Ordinary `~/.hermes` created | `NO` | -| Runtime authority fields in result | `0` | - -Files added: - -```text -gauntlet_host/launcher.py -gauntlet_host/runtime_profile.py -``` - -Files extended: +## Frozen authority path ```text -gauntlet_host/constants.py -gauntlet_host/ipc.py -gauntlet_host/worker_main.py +runtime model/tool execution +→ operational ToolObservation +→ claim-native Gauntlet module +→ canonical Receipt +→ Soul release gate ``` -### 6.1 Launcher boundary +The runtime, provider, plugin, tool, session database, memory, skill system, and FAST-P7 FOIL +route cannot create canonical evidence authority. -The parent-side launcher: +## FAST-P7 result -1. prepares the Gauntlet-owned runtime profile; -2. never imports vendored `run_agent.py` in the parent interpreter; -3. starts the worker by absolute file path with cwd and `PYTHONPATH` set to the vendor root; -4. passes one typed JSONL request over stdin and expects exactly one JSONL result on stdout; -5. correlates request and task IDs; -6. binds worker status to process exit status; -7. applies a bounded launcher timeout; -8. returns typed `ERROR` or `UNAVAILABLE` operational failures; -9. removes inherited `HERMES_YOLO_MODE`, `HERMES_ACCEPT_HOOKS`, and - `HERMES_INTERACTIVE` values before process launch; -10. does not perform Soul finalization or claim task release. +FAST-P7 adds a live runtime tool-capability snapshot and calls the existing FOIL +`RuntimePolicyV2` through a read-only Gauntlet-side adapter before the first model request. +The output is a bounded, task-bound, content-hashed route instruction. -The working Phase 4 command shape is: - -```bash -python -m gauntlet_host.launcher \ - "Return the configured mock response." \ - --task-id task-phase4-live \ - --model phase4-mock \ - --provider custom \ - --timeout 90 \ - --json -``` - -### 6.2 Runtime profile - -Before upstream runtime imports or construction, the profile manager creates: +Every accepted route records: ```text -~/.gauntlet/runtime/ -├── config.yaml -├── state.db # created by the upstream SessionDB during the turn -├── memories/ -├── skills/ -├── cache/ -├── logs/ -└── pending/ - ├── memory/ - └── skills/ -``` - -It preserves other provider/model settings while forcing the alpha governance values: - -```yaml -auxiliary: - background_review: - enabled: false -memory: - write_approval: true -skills: - write_approval: true +schema: gauntlet.foil-route.v1 +mode: SHADOW +authority_ceiling: ADAPTATION_ONLY +execution_authorized: false +toolset_narrowing_applied: false +profile_used: false +private_profile_data_transmitted: false ``` -It rejects `~/.hermes` as the requested Gauntlet runtime home, writes `config.yaml` atomically, -and sets `HERMES_HOME` to the dedicated directory. The worker also passes -`skip_background_review=True` to upstream `AIAgent`. - -The successful verification deliberately began with the opposite three profile values and -with the three inherited bypass flags set. The resulting profile contained the required -Gauntlet values, while the worker completed through the isolated subprocess boundary. -This verifies profile enforcement and launch sanitization for the tested path; it is not a -claim that every upstream memory or skill mutation path has been behaviorally qualified. - -### 6.3 One real upstream turn - -The successful verification installed dependencies from the pinned vendored tree for the CI -job, started a local OpenAI-compatible endpoint, and executed one actual upstream `AIAgent` -conversation turn. The endpoint received exactly one `/chat/completions` request for -`phase4-mock`. The worker returned: - -```text -status = OK -event = worker.turn_completed -provider = custom -model = phase4-mock -final_response = phase4 mock response -api_calls = 1 -input_tokens = 12 -output_tokens = 4 -total_tokens = 16 -completed = true -failed = false -partial = false -``` +The route cannot create a receipt, change a verdict, clear an obligation, execute a tool, +release a task, or bypass claim-native verification or Soul. -The returned namespace proof still bound `tools` to: +Changed paths are recorded in: ```text -vendor/hermes-agent/tools/__init__.py +docs/engineering/HERMES_FAST_P7_CHECKPOINT.json +docs/engineering/PHASE7_FOIL_ADVISORY_ROUTE.md ``` -and the result contained none of: - -```text -verdict -evidence_class -receipt -release +The successful checkpoint reported: + +```json +{ + "canonical_receipts_created": 0, + "canonical_task_unchanged": true, + "capability_bundle_complete": false, + "missing_requirements": 2, + "model_round_trips": 3, + "profile_data_transmitted": false, + "release_gate_verdict": "UNKNOWN", + "route_authority_ceiling": "ADAPTATION_ONLY", + "route_injected_before_first_model_request": true, + "route_mode": "SHADOW", + "toolset_narrowing_applied": false, + "unresolved_not_accepted": true +} ``` -This proves the bounded engine path with a deterministic local endpoint. It does not yet prove -operation against a paid external provider or any particular production model. - -### 6.4 Negative and harness checks - -| Check | Result | -|---|---| -| Python compile of `gauntlet_host` | `PASS` | -| Source line-shape check | `PASS` | -| Launcher imports vendored `run_agent` | `NO` | -| Request JSONL round trip | `PASS` | -| Result JSONL round trip | `PASS` | -| Invalid profile root `[]` | `RUNTIME_CONFIG_INVALID`; exit `2` | -| Missing authority fields in worker result | `PASS` | -| Session DB outside Gauntlet runtime home | `NO` | - -Temporary run `33226562129` reached upstream `AIAgent` but stopped at its minimum-context guard -because the deterministic mock declared `16384` tokens while the upstream runtime requires at -least `64000`. The worker returned a typed `UNAVAILABLE` result rather than fabricating success. -Only the mock fixture was corrected to `131072`; run `33226703542` then passed every Phase 4 -check. The temporary Phase 4 workflow was removed after the successful receipt was recorded. - -## 7. Authority and scope state after Phase 4 +The incomplete capability bundle is expected for the bounded fixture, which exposes only the +read-only Gauntlet status tools. Missing search requirements remain explicit instead of being +silently treated as satisfied. -Implemented and verified: +Run `33271617165` first failed before runtime execution because the verifier was launched from +`.github/` without the repository root on `sys.path`. The workflow-only repair supplied +`PYTHONPATH=.`; exact-head run `33271699059` then passed. -```text -parent launcher -→ Gauntlet-owned runtime profile -→ bounded JSONL subprocess -→ pinned snapshot and namespace verification -→ upstream provider resolution -→ upstream AIAgent construction -→ one model conversation turn -→ upstream session persistence under ~/.gauntlet/runtime -→ transport-level worker result -``` - -Not implemented and therefore not claimed: - -```text -Gauntlet runtime plugin -Gauntlet status/release tools -subprocess module adapter -runtime tool lifecycle observations -observation store -Soul finalizer -FOIL route -single user-facing gauntlet entry point -external-provider qualification -runtime tool execution -end-to-end task release -``` +## Limits -A transport-level `OK` means only that the worker turn completed. It is not a Gauntlet verdict, -receipt, evidence clearance, or task release. +FAST-P7 does not establish routing efficacy, cost reduction, verified-completion improvement, +profile-based complements, automatic tool narrowing, autonomous replanning, task release, +overlay admission, or native-runtime completion. -## 8. Continue instruction +## Continue instruction -Continue with **Phase 5 only**: +Proceed with `FAST-P8` only: -1. create `gauntlet_host/gauntlet_plugin.py` and the minimum tool definitions; -2. register at least `gauntlet_task_status` and `gauntlet_release_status` through the existing - vendored plugin/tool registry rather than a new registry; -3. create `gauntlet_host/module_cli.py` as the Gauntlet-side subprocess adapter; -4. keep Gauntlet's repository root as the adapter process import root; -5. pass task identity only through `GAUNTLET_TASK_ID`, never conversation inference; -6. make worker-side Gauntlet calls execute the adapter by exact file/module command; -7. return canonical status data without allowing a plugin or worker to create receipts, - change verdicts, clear obligations, or release a task; -8. prove one agent-visible status call through the isolated runtime; -9. stop before tool-lifecycle observation recording, Soul finalization, FOIL routing, or the - final product CLI. +1. expose one user-facing `gauntlet` command; +2. preserve the isolated worker and FAST-P7 route; +3. keep task identity explicit through `GAUNTLET_TASK_ID`; +4. keep runtime output operational and non-authoritative; +5. preserve claim-native receipt production and Soul release authority; +6. run the bounded eight-item boot qualification; +7. publish an exact checkpoint receipt; +8. do not merge `main`; +9. do not call the pinned-runtime alpha native-final. From 554ee851996cd363c2e52a6294c3bf478f10a41f Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:16:45 -0700 Subject: [PATCH 16/37] cli: add FAST-P8 user-facing alpha entry point --- .github/phase8_verify.py | 266 ++++++++++++ .github/workflows/fastpath-checkpoint.yml | 9 +- docs/engineering/PHASE8_USER_CLI_BOOT.md | 154 +++++++ gauntlet_host/__main__.py | 5 + gauntlet_host/cli.py | 480 ++++++++++++++++++++++ pyproject.toml | 18 + 6 files changed, 928 insertions(+), 4 deletions(-) create mode 100644 .github/phase8_verify.py create mode 100644 docs/engineering/PHASE8_USER_CLI_BOOT.md create mode 100644 gauntlet_host/__main__.py create mode 100644 gauntlet_host/cli.py diff --git a/.github/phase8_verify.py b/.github/phase8_verify.py new file mode 100644 index 00000000..22609aa8 --- /dev/null +++ b/.github/phase8_verify.py @@ -0,0 +1,266 @@ +"""Bounded FAST-P8 user-facing CLI and eight-item boot verification.""" + +from __future__ import annotations + +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import threading +from typing import Any + +REPO = Path(__file__).resolve().parent.parent +if str(REPO) not in sys.path: + sys.path.insert(0, str(REPO)) + +PHASE5_HELPER = REPO / ".github" / "phase5_verify.py" +PROMPT = "Use gauntlet_task_status, then report that status was observed." +CANONICAL_CLAIM = "Find a canonical source before release." +ROUTE_MARKER = "[GAUNTLET FOIL ADVISORY ROUTE]" + + +def _load_phase5() -> Any: + spec = importlib.util.spec_from_file_location("phase5_verify", PHASE5_HELPER) + if spec is None or spec.loader is None: + raise RuntimeError("cannot load the retained FAST-P5 verification helper") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _run( + command: list[str], + *, + environment: dict[str, str], + input_text: str | None = None, +) -> subprocess.CompletedProcess[str]: + return subprocess.run( + command, + cwd=REPO, + env=environment, + input=input_text, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + check=False, + ) + + +def _first_prompt_request( + requests: list[dict[str, Any]], + phase5: Any, +) -> dict[str, Any]: + for request in requests: + if phase5._has_target_prompt(request): + return request + raise AssertionError("no routed first model request was observed") + + +def _prompt_text(request: dict[str, Any], phase5: Any) -> str: + return "\n".join( + phase5._message_content(message) + for message in request.get("messages", []) + if isinstance(message, dict) + ) + + +def _observations(runtime: Path) -> list[dict[str, Any]]: + values: list[dict[str, Any]] = [] + for path in sorted((runtime / "observations").rglob("*.json")): + values.append(json.loads(path.read_text(encoding="utf-8"))) + return values + + +def main() -> None: + phase5 = _load_phase5() + phase5._source_boundary() + shutil.rmtree(REPO / ".egrt", ignore_errors=True) + + command = shutil.which("gauntlet") + assert command is not None, "editable install did not expose the gauntlet command" + + base_environment = dict(os.environ) + help_run = _run([command, "--help"], environment=base_environment) + assert help_run.returncode == 0 + assert "Soul's canonical release gate" in help_run.stdout + + chat_help = _run([command, "chat", "--help"], environment=base_environment) + assert chat_help.returncode == 0 + assert "one explicitly bound canonical task" in chat_help.stdout + + chat_exit = _run( + [command, "chat", "--root", str(REPO)], + environment=base_environment, + input_text="/quit\n", + ) + assert chat_exit.returncode == 0 + assert "Gauntlet FAST-P8 alpha chat" in chat_exit.stdout + + server = phase5.Phase5Server() + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + + try: + with tempfile.TemporaryDirectory(prefix="gauntlet-phase8-") as temporary: + home = Path(temporary) + runtime = home / ".gauntlet" / "runtime" + phase5._write_runtime_config(runtime, server.server_address[1]) + environment = dict(base_environment) + environment.update( + { + "HOME": str(home), + "HERMES_YOLO_MODE": "1", + "HERMES_ACCEPT_HOOKS": "1", + "HERMES_INTERACTIVE": "1", + } + ) + completed = _run( + [ + command, + "--root", + str(REPO), + "--kind", + "DISCOVERY", + "--claim", + CANONICAL_CLAIM, + "--model", + "phase5-mock", + "--provider", + "custom", + "--timeout", + "90", + "--json", + PROMPT, + ], + environment=environment, + ) + if completed.stderr: + print(completed.stderr, file=sys.stderr, end="") + assert completed.returncode == 2 + finalized = json.loads(completed.stdout) + print(json.dumps(finalized, indent=2, sort_keys=True)) + + task_id = finalized["task_id"] + assert finalized["schema"] == "gauntlet.finalization.v1" + assert finalized["state"] == "UNRESOLVED" + assert finalized["accepted"] is False + assert finalized["final_response"] == "phase5 status observed" + assert finalized["worker_status"] == "OK" + assert finalized["worker_event"] == "worker.turn_completed" + assert finalized["release_gate_invoked"] is True + assert finalized["release_gate_verdict"] == "UNKNOWN" + assert finalized["release_eligible"] is False + assert finalized["task_release_performed"] is False + assert finalized["canonical_receipt_created"] is False + + task_path = phase5.TASKS / f"{task_id}.json" + task = json.loads(task_path.read_text(encoding="utf-8")) + assert task["task_id"] == task_id + assert task["goal_hash"] == hashlib.sha256(PROMPT.encode()).hexdigest() + assert task["active"] is True + assert task["released"] is False + obligations = task["obligations"] + assert len(obligations) == 1 + assert obligations[0]["kind"] == "DISCOVERY" + assert obligations[0]["claim"] == CANONICAL_CLAIM + assert obligations[0]["load_bearing"] is True + assert obligations[0]["required_module"] == "space" + + prompt_request = _first_prompt_request(server.chat_requests, phase5) + prompt_text = _prompt_text(prompt_request, phase5) + assert ROUTE_MARKER in prompt_text + assert "This is proposal-only routing guidance." in prompt_text + assert CANONICAL_CLAIM not in prompt_text + + tool_request = phase5._tool_request(server.chat_requests) + result_request = phase5._result_request(server.chat_requests) + names = { + item["function"]["name"] + for item in tool_request.get("tools", []) + } + assert {"gauntlet_task_status", "gauntlet_release_status"} <= names + tool_messages = [ + message + for message in result_request.get("messages", []) + if message.get("tool_call_id") == phase5.TARGET_CALL_ID + ] + assert len(tool_messages) == 1 + status = json.loads(tool_messages[0]["content"]) + assert status["schema"] == "gauntlet.adapter.v1" + assert status["action"] == "task-status" + assert status["status"] == "OK" + assert status["task_id"] == task_id + assert status["task"]["task_id"] == task_id + assert status["release"]["verdict"] == "UNKNOWN" + assert status["read_only"] is True + assert status["mutation_performed"] is False + assert all(value is False for value in status["authority"].values()) + + observations = _observations(runtime) + matching = [ + item + for item in observations + if item.get("tool") == "gauntlet_task_status" + ] + assert len(matching) == 1 + observation = matching[0] + assert observation["task_id"] == task_id + assert observation["status"] == "OK" + assert observation["authority_ceiling"] == "OBSERVATION_ONLY" + assert observation["canonical_receipt_created"] is False + assert observation["canonical_state_mutated"] is False + + assert (runtime / "state.db").is_file() + assert not (home / ".hermes").exists() + assert not any(phase5.RECEIPTS.glob("*.json")) + + boot_checks = { + "1_gauntlet_starts": help_run.returncode == 0, + "2_worker_starts": finalized["worker_status"] == "OK", + "3_model_responds": bool(finalized["final_response"]), + "4_runtime_tool_executes": len(tool_messages) == 1, + "5_gauntlet_task_status_works": status["status"] == "OK", + "6_runtime_observation_recorded": len(matching) == 1, + "7_soul_gate_runs": finalized["release_gate_invoked"] is True, + "8_unresolved_not_cleared": ( + finalized["accepted"] is False + and finalized["release_gate_verdict"] == "UNKNOWN" + ), + } + assert len(boot_checks) == 8 + assert all(boot_checks.values()) + + summary = { + "schema": "gauntlet.fast-checkpoint-verification.v1", + "fast_milestone": "FAST-P8", + "task_id": task_id, + "console_command": command, + "chat_entry_point_started": True, + "task_created_by_cli": True, + "load_bearing_obligation_created": True, + "foil_route_preserved": True, + "model_round_trips": len(server.chat_requests), + "boot_checks": boot_checks, + "boot_checks_passed": sum(boot_checks.values()), + "runtime_observations": len(observations), + "canonical_receipts_created": 0, + "release_gate_verdict": finalized["release_gate_verdict"], + "unresolved_not_accepted": finalized["accepted"] is False, + "ordinary_hermes_home_created": False, + } + print(json.dumps(summary, indent=2, sort_keys=True)) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/fastpath-checkpoint.yml b/.github/workflows/fastpath-checkpoint.yml index fa7e5660..e8d930ef 100644 --- a/.github/workflows/fastpath-checkpoint.yml +++ b/.github/workflows/fastpath-checkpoint.yml @@ -10,7 +10,7 @@ permissions: contents: read jobs: - fast-p7: + fast-p8: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 @@ -23,12 +23,13 @@ jobs: run: | set -euo pipefail python scripts/vendor_hermes.py --verify-only - - name: Install pinned Hermes runtime dependencies + - name: Install pinned runtime and Gauntlet CLI run: | set -euo pipefail python -m pip install --upgrade pip python -m pip install -e vendor/hermes-agent - - name: Verify FAST-P7 advisory FOIL routing + python -m pip install -e . --no-deps + - name: Verify FAST-P8 user-facing alpha boot run: | set -euo pipefail - PYTHONPATH=. python .github/phase7_verify.py + PYTHONPATH=. python .github/phase8_verify.py diff --git a/docs/engineering/PHASE8_USER_CLI_BOOT.md b/docs/engineering/PHASE8_USER_CLI_BOOT.md new file mode 100644 index 00000000..5d383ac8 --- /dev/null +++ b/docs/engineering/PHASE8_USER_CLI_BOOT.md @@ -0,0 +1,154 @@ +# FAST-P8 — User-facing Gauntlet alpha CLI and boot qualification + +## Scope + +FAST-P8 adds the first single product entry point for the pinned-runtime alpha: + +```bash +gauntlet "research this..." +gauntlet chat +``` + +The command is an editable-install console entry point backed by +`gauntlet_host.cli:main`. It invokes the existing isolated launcher, FAST-P7 +advisory FOIL route, observation bridge, and parent-owned Soul finalizer. It +does not add another provider, tool registry, plugin manager, task store, or +release path. + +This checkpoint remains an interim pinned-runtime alpha. It is not +`NATIVE-800`, native-final, an integrated overlay admission, or a behavioral +efficacy result. + +## Task binding + +A one-shot command has two task modes: + +1. `--task-id ` binds to an existing integrity-valid, active, unreleased + canonical task. +2. Without `--task-id`, the CLI calls the existing Soul CLI to create a task + and immediately adds one load-bearing obligation. + +The default new-task obligation is `DISCOVERY`, with the prompt as its claim. +The caller can select another existing obligation kind with `--kind` and can +separate the canonical claim from the runtime prompt with `--claim`. + +The alpha refuses to run an existing task with no load-bearing obligation. +It never invents a task ID from conversation text. The explicit task ID is +passed into the launcher and then through `GAUNTLET_TASK_ID`. + +FAST-P8 is deliberately repository-bound. `--root` must identify the checkout +that contains the active command, Gauntlet authority modules, and pinned +runtime gitlink. This prevents the worker, status adapter, and finalizer from +silently operating against different repositories. + +## Command surface + +One turn: + +```bash +gauntlet \ + --kind DISCOVERY \ + --model \ + --provider \ + "research this..." +``` + +Machine-readable finalization: + +```bash +gauntlet --json "research this..." +``` + +Explicit existing task: + +```bash +gauntlet --task-id task_example "continue the bounded task" +``` + +Interactive alpha: + +```bash +gauntlet chat +``` + +The chat surface reuses one explicit task. `/task` displays its identity and +`/quit` exits. A new chat task is created only after the first non-command +prompt. + +`python -m gauntlet_host` is an equivalent source-checkout fallback. + +## Authority boundary + +The CLI may: + +- create a canonical task through the existing Soul command; +- add one declared load-bearing obligation; +- start the isolated pinned runtime; +- display operational model output and the Soul-gated finalization state. + +The CLI, runtime, model, provider, tool, plugin, session database, FOIL route, +and observation store may not: + +- create a canonical receipt; +- change a verdict; +- clear an obligation; +- mark a task released; +- convert worker `OK` into `CLEARED`; +- bypass a claim-native verifier or Soul. + +The authority path remains: + +```text +runtime tool call +→ ToolObservation +→ claim-native Gauntlet module +→ canonical Receipt +→ Soul release gate +``` + +An unresolved command intentionally exits with the existing finalizer's +non-zero status and reports the unresolved gate. That is not a runtime crash. + +## Eight-item boot qualification + +`.github/phase8_verify.py` performs the bounded alpha boot check against a +deterministic local OpenAI-compatible endpoint: + +1. `gauntlet` starts. +2. the isolated worker starts; +3. the model responds; +4. a runtime tool executes; +5. `gauntlet_task_status` returns canonical read-only status; +6. an `OBSERVATION_ONLY` tool observation is recorded; +7. the parent-owned Soul gate runs; +8. an unresolved task is not reported `CLEARED`. + +The same run also checks: + +- `gauntlet chat` starts and exits through `/quit`; +- a new task and one load-bearing obligation are created by the CLI; +- the FAST-P7 route is injected before the first model request; +- canonical claim text is absent from the pre-request route block; +- inherited runtime bypass flags are removed by the launcher; +- no ordinary `~/.hermes` directory is created; +- zero canonical receipts are created; +- the task remains active and unreleased. + +## Explicit limitations + +FAST-P8 does not establish: + +- operation against a paid external provider; +- automatic claim-native module execution; +- autonomous replanning; +- task release; +- dynamic FOIL tool-schema narrowing; +- profile-based complements; +- routing or cost efficacy; +- direct-mode or integrated-overlay parity; +- cross-platform qualification; +- native-runtime completion. + +The next action after a successful FAST-P8 checkpoint is not an automatic +merge. The exact producer commit/tree must be considered through the separate +integration checkpoint protocol. diff --git a/gauntlet_host/__main__.py b/gauntlet_host/__main__.py new file mode 100644 index 00000000..fe7ada64 --- /dev/null +++ b/gauntlet_host/__main__.py @@ -0,0 +1,5 @@ +"""Run the user-facing Gauntlet alpha command with ``python -m gauntlet_host``.""" + +from gauntlet_host.cli import main + +raise SystemExit(main()) diff --git a/gauntlet_host/cli.py b/gauntlet_host/cli.py new file mode 100644 index 00000000..8e9e9355 --- /dev/null +++ b/gauntlet_host/cli.py @@ -0,0 +1,480 @@ +"""User-facing FAST-P8 alpha command for the isolated Gauntlet runtime.""" + +from __future__ import annotations + +import argparse +from dataclasses import dataclass +import json +import os +from pathlib import Path +import subprocess +import sys +from typing import Any, Sequence + +from gauntlet_host.constants import ( + DEFAULT_LAUNCH_TIMEOUT_SECONDS, + MODULE_CLI, + REPO_ROOT, + VENDOR_ROOT, +) +from gauntlet_host.finalizer import ( + FinalizationResult, + encode_finalization, + finalization_exit_code, + print_human_finalization, +) +from gauntlet_host.launcher import run_gauntlet_turn + +CLI_PROTOCOL_VERSION = "gauntlet.cli.v1" +OBLIGATION_KINDS = ( + "PROOF", + "DISCOVERY", + "SYNTHESIS", + "ENGINEERING", + "EVALUATION", + "ASSURANCE", + "PREFLIGHT", + "REVIEW", + "ADAPTATION", + "ADVERSARY", +) + + +@dataclass(frozen=True, slots=True) +class TaskBinding: + """Explicit canonical task binding used by one CLI session.""" + + task_id: str + created: bool + obligation_id: str | None = None + + +class CliError(RuntimeError): + """Typed, fail-closed CLI setup error.""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + self.message = message + + +def _version() -> str: + path = REPO_ROOT / "VERSION" + try: + value = path.read_text(encoding="utf-8").strip() + except OSError: + return "0.5.1-fast-p8" + return value or "0.5.1-fast-p8" + + +def _resolve_root(value: str) -> Path: + requested = Path(value).expanduser().resolve(strict=False) + expected = REPO_ROOT.resolve() + if requested != expected: + raise CliError( + "CLI_ROOT_MISMATCH", + ( + "FAST-P8 is repository-bound; --root must identify the checkout " + f"containing this command ({expected})" + ), + ) + + required = ( + requested / "tools" / "soul_runtime.py", + MODULE_CLI, + VENDOR_ROOT, + ) + if not all(path.exists() for path in required): + raise CliError( + "CLI_RUNTIME_FILES_MISSING", + "Gauntlet task, adapter, or pinned runtime files are missing", + ) + return requested + + +def _json_subprocess( + command: list[str], + *, + root: Path, + environment: dict[str, str] | None = None, +) -> tuple[int, dict[str, Any], str]: + completed = subprocess.run( + command, + cwd=root, + env=environment, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + check=False, + ) + try: + value = json.loads(completed.stdout) + except json.JSONDecodeError as exc: + raise CliError( + "CLI_SUBPROCESS_PROTOCOL_ERROR", + ( + f"{Path(command[1]).name} returned invalid JSON " + f"(exit {completed.returncode})" + ), + ) from exc + if not isinstance(value, dict): + raise CliError( + "CLI_SUBPROCESS_PROTOCOL_ERROR", + f"{Path(command[1]).name} returned a non-object JSON document", + ) + return completed.returncode, value, completed.stderr + + +def _soul_command(root: Path, *arguments: str) -> dict[str, Any]: + command = [ + sys.executable, + str(root / "tools" / "soul_runtime.py"), + "--root", + str(root), + *arguments, + ] + returncode, value, stderr = _json_subprocess(command, root=root) + if returncode != 0: + message = stderr.strip() or json.dumps(value, sort_keys=True) + raise CliError( + "CLI_SOUL_COMMAND_FAILED", + f"Soul task setup failed: {message[:1000]}", + ) + return value + + +def _task_status(root: Path, task_id: str) -> dict[str, Any]: + environment = dict(os.environ) + environment["GAUNTLET_TASK_ID"] = task_id + environment["PYTHONPATH"] = str(root) + command = [ + sys.executable, + str(MODULE_CLI), + "--root", + str(root), + "task-status", + ] + returncode, value, stderr = _json_subprocess( + command, + root=root, + environment=environment, + ) + if returncode != 0 or value.get("status") != "OK": + error = value.get("error") + if isinstance(error, dict): + code = str(error.get("code") or "CLI_TASK_STATUS_FAILED") + message = str(error.get("message") or "task status is unavailable") + else: + code = "CLI_TASK_STATUS_FAILED" + message = stderr.strip() or "task status is unavailable" + raise CliError(code, message) + return value + + +def _validate_task(root: Path, task_id: str) -> None: + status = _task_status(root, task_id) + task = status.get("task") + if not isinstance(task, dict): + raise CliError( + "CLI_TASK_STATUS_INVALID", + "canonical task status omitted the task projection", + ) + if task.get("active") is not True or task.get("released") is True: + raise CliError( + "CLI_TASK_NOT_ACTIVE", + f"task {task_id} is not an active unreleased task", + ) + + obligations = task.get("obligations") + if not isinstance(obligations, list): + raise CliError( + "CLI_TASK_STATUS_INVALID", + "canonical task status omitted the obligation projection", + ) + load_bearing = [ + item + for item in obligations + if isinstance(item, dict) and item.get("load_bearing") is True + ] + if not load_bearing: + raise CliError( + "CLI_TASK_HAS_NO_LOAD_BEARING_OBLIGATION", + ( + "the alpha refuses to run an empty task because an empty " + "release surface cannot establish completed work" + ), + ) + + +def _create_task( + root: Path, + *, + goal: str, + kind: str, + claim: str, +) -> TaskBinding: + started = _soul_command(root, "start", "--goal", goal) + task_id = started.get("task_id") + if not isinstance(task_id, str) or not task_id.strip(): + raise CliError( + "CLI_TASK_START_PROTOCOL_ERROR", + "Soul start did not return a task identifier", + ) + + added = _soul_command( + root, + "add", + task_id, + kind, + "--claim", + claim, + ) + obligation_id = added.get("obligation_id") + if not isinstance(obligation_id, str) or not obligation_id.strip(): + raise CliError( + "CLI_OBLIGATION_PROTOCOL_ERROR", + "Soul add did not return an obligation identifier", + ) + + _validate_task(root, task_id) + return TaskBinding( + task_id=task_id, + created=True, + obligation_id=obligation_id, + ) + + +def _bind_task( + root: Path, + *, + task_id: str | None, + prompt: str, + kind: str, + claim: str | None, +) -> TaskBinding: + if task_id: + _validate_task(root, task_id) + return TaskBinding(task_id=task_id, created=False) + return _create_task( + root, + goal=prompt, + kind=kind, + claim=claim or prompt, + ) + + +def _run_bound_turn( + root: Path, + binding: TaskBinding, + prompt: str, + *, + model: str | None, + provider: str | None, + toolsets: Sequence[str], + timeout: float, +) -> FinalizationResult: + return run_gauntlet_turn( + prompt, + task_id=binding.task_id, + root=root, + model=model, + provider=provider, + toolsets=toolsets, + timeout_seconds=timeout, + ) + + +def _emit_result(result: FinalizationResult, *, json_output: bool) -> None: + if json_output: + print(encode_finalization(result)) + else: + print_human_finalization(result) + + +def _emit_error(error: CliError, *, json_output: bool) -> None: + if json_output: + print( + json.dumps( + { + "schema": CLI_PROTOCOL_VERSION, + "status": "ERROR", + "error": { + "code": error.code, + "message": error.message, + }, + }, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + ) + return + print(f"gauntlet: {error.message} [{error.code}]", file=sys.stderr) + + +def _add_common_arguments(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "--root", + default=str(REPO_ROOT), + help="active The-Gauntlet checkout; FAST-P8 is repository-bound", + ) + parser.add_argument("--task-id") + parser.add_argument( + "--kind", + type=str.upper, + choices=OBLIGATION_KINDS, + default="DISCOVERY", + help="kind for the one load-bearing obligation created with a new task", + ) + parser.add_argument( + "--claim", + help="claim for a newly created task; defaults to the submitted prompt", + ) + parser.add_argument("--model") + parser.add_argument("--provider") + parser.add_argument("--toolset", action="append", default=[]) + parser.add_argument( + "--timeout", + type=float, + default=DEFAULT_LAUNCH_TIMEOUT_SECONDS, + ) + + +def _run_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="gauntlet", + description=( + "Run one isolated Gauntlet-bundled agent turn and apply Soul's " + "canonical release gate." + ), + ) + parser.add_argument( + "--version", + action="version", + version=f"%(prog)s {_version()} FAST-P8-alpha", + ) + _add_common_arguments(parser) + parser.add_argument( + "--json", + action="store_true", + dest="json_output", + help="emit the complete Soul-gated finalization document", + ) + parser.add_argument("prompt", nargs="+") + return parser + + +def _chat_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="gauntlet chat", + description=( + "Run multiple turns against one explicitly bound canonical task. " + "Use /task or /quit at the prompt." + ), + ) + parser.add_argument( + "--version", + action="version", + version=f"%(prog)s {_version()} FAST-P8-alpha", + ) + _add_common_arguments(parser) + return parser + + +def _run_command(argv: Sequence[str]) -> int: + args = _run_parser().parse_args(argv) + prompt = " ".join(args.prompt).strip() + try: + root = _resolve_root(args.root) + binding = _bind_task( + root, + task_id=args.task_id, + prompt=prompt, + kind=args.kind, + claim=args.claim, + ) + if binding.created and not args.json_output: + print(f"[GAUNTLET TASK] {binding.task_id}", file=sys.stderr) + result = _run_bound_turn( + root, + binding, + prompt, + model=args.model, + provider=args.provider, + toolsets=args.toolset, + timeout=args.timeout, + ) + except CliError as exc: + _emit_error(exc, json_output=args.json_output) + return 2 + + _emit_result(result, json_output=args.json_output) + return finalization_exit_code(result) + + +def _chat_command(argv: Sequence[str]) -> int: + args = _chat_parser().parse_args(argv) + try: + root = _resolve_root(args.root) + binding: TaskBinding | None = None + if args.task_id: + _validate_task(root, args.task_id) + binding = TaskBinding(task_id=args.task_id, created=False) + except CliError as exc: + _emit_error(exc, json_output=False) + return 2 + + print("Gauntlet FAST-P8 alpha chat. Commands: /task, /quit.") + while True: + try: + prompt = input("gauntlet> ").strip() + except EOFError: + print() + return 0 + + if not prompt: + continue + if prompt.lower() in {"/quit", "/exit", "quit", "exit"}: + return 0 + if prompt.lower() == "/task": + print(binding.task_id if binding else "No task has been created yet.") + continue + + try: + if binding is None: + binding = _create_task( + root, + goal=prompt, + kind=args.kind, + claim=args.claim or prompt, + ) + print(f"[GAUNTLET TASK] {binding.task_id}") + result = _run_bound_turn( + root, + binding, + prompt, + model=args.model, + provider=args.provider, + toolsets=args.toolset, + timeout=args.timeout, + ) + except CliError as exc: + _emit_error(exc, json_output=False) + return 2 + _emit_result(result, json_output=False) + + +def main(argv: Sequence[str] | None = None) -> int: + arguments = list(sys.argv[1:] if argv is None else argv) + if arguments and arguments[0] == "chat": + return _chat_command(arguments[1:]) + if arguments and arguments[0] == "run": + arguments = arguments[1:] + return _run_command(arguments) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pyproject.toml b/pyproject.toml index 0c9f2bdc..cea99bc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,21 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "the-gauntlet" +version = "0.5.1" +description = "Evidence-governed research toolkit and isolated FAST-P8 alpha runtime" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [] + +[project.scripts] +gauntlet = "gauntlet_host.cli:main" + +[tool.setuptools] +packages = ["gauntlet_host"] + [tool.ruff] target-version = "py312" line-length = 100 From 6990322a815e46b200d7572544a106607c74343a Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:18:28 -0700 Subject: [PATCH 17/37] fix(fast-p8): normalize wrapped CLI help output --- .github/phase8_verify.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/phase8_verify.py b/.github/phase8_verify.py index 22609aa8..49f8afbe 100644 --- a/.github/phase8_verify.py +++ b/.github/phase8_verify.py @@ -53,6 +53,10 @@ def _run( ) +def _normalized_whitespace(value: str) -> str: + return " ".join(value.split()) + + def _first_prompt_request( requests: list[dict[str, Any]], phase5: Any, @@ -89,11 +93,13 @@ def main() -> None: base_environment = dict(os.environ) help_run = _run([command, "--help"], environment=base_environment) assert help_run.returncode == 0 - assert "Soul's canonical release gate" in help_run.stdout + assert "Soul's canonical release gate" in _normalized_whitespace(help_run.stdout) chat_help = _run([command, "chat", "--help"], environment=base_environment) assert chat_help.returncode == 0 - assert "one explicitly bound canonical task" in chat_help.stdout + assert "one explicitly bound canonical task" in _normalized_whitespace( + chat_help.stdout + ) chat_exit = _run( [command, "chat", "--root", str(REPO)], From b7057dda1b45b7647c0a63c96373c0113c5439f0 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:21:33 -0700 Subject: [PATCH 18/37] docs: publish FAST-P8 alpha checkpoint --- .../GAUNTLET_FAST_BUILD_HANDOFF.md | 179 +++++++++++------- .../HERMES_FAST_P8_CHECKPOINT.json | 116 ++++++++++++ 2 files changed, 231 insertions(+), 64 deletions(-) create mode 100644 docs/engineering/HERMES_FAST_P8_CHECKPOINT.json diff --git a/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md b/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md index 861aa198..7c84ee5e 100644 --- a/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md +++ b/docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md @@ -1,18 +1,21 @@ # Gauntlet Fast Build — Current Handoff Ledger **Handoff ID:** `GAUNTLET-FAST-HANDOFF-001` -**Ledger version:** `2.0` +**Ledger version:** `3.0` **Repository:** `Kitahl/The-Gauntlet` **Producer branch:** `work/native-hermes-fastpath` -**Completed milestone:** `FAST-P7` -**Validated implementation head:** `9747572e8ca13622b72d8cd3d995fece90e19173` -**Validated implementation tree:** `0b194a94abf0e9791afeb604c84141325381788f` -**Successful run:** `33271699059` -**Next milestone:** `FAST-P8` — user-facing alpha CLI and boot qualification +**Completed milestone:** `FAST-P8` +**Validated implementation head:** `6990322a815e46b200d7572544a106607c74343a` +**Validated implementation tree:** `3aba1c6263eaca953d92c6f71f2f5b4f9c7996d6` +**Implementation commit:** `554ee851996cd363c2e52a6294c3bf478f10a41f` +**Successful run / job:** `33273140981` / `99155128215` +**Fast-build state:** `ALPHA_CHECKPOINT_COMPLETE` +**Next action:** exact-checkpoint consumer admission or rejection; no automatic merge **Pinned upstream:** `NousResearch/hermes-agent@5fc308a70719a83cccdbba4c0e39c23f5a8239d5` -This version replaces the stale Phase-4-era working copy. Its previous detailed text remains -available in Git history at blob `40ed3881d19e5d5e7314425c24d6e5965c38a1e4`. +This ledger supersedes version `2.0`. FAST-P8 completes the bounded pinned-runtime +fast-build sequence. It does not complete the separate Gauntlet Native Runtime program, +establish overlay compatibility, or authorize a merge to `main`. ## Milestone namespace @@ -27,7 +30,7 @@ FAST-P7 advisory FOIL route FAST-P8 user-facing alpha CLI and boot qualification ``` -`FAST-P7` is not `NATIVE-600`, `NATIVE-700`, or native-final completion. +`FAST-P8` is not `NATIVE-800`, native-final, or a release-candidate promotion. ## Current checkpoint ledger @@ -40,11 +43,11 @@ FAST-P8 user-facing alpha CLI and boot qualification | `FAST-P5` | `COMPLETE` | head `dda78eabc216749cb1a3010d8e533428b78721d0`; tree `0ad6d8641561de46bd729a5115500d4fd7bb0483`; run `33230839031` | | `FAST-P6` | `COMPLETE` | head `249753738f193d1f82e7e12afe907c4841827f80`; tree `10eeeeef9f85ca91e766e5be388a176943decf5d`; run `33266521662` | | `FAST-P7` | `COMPLETE` | head `9747572e8ca13622b72d8cd3d995fece90e19173`; tree `0b194a94abf0e9791afeb604c84141325381788f`; run `33271699059` | -| `FAST-P8` | `NEXT` | not started | +| `FAST-P8` | `COMPLETE` | head `6990322a815e46b200d7572544a106607c74343a`; tree `3aba1c6263eaca953d92c6f71f2f5b4f9c7996d6`; run `33273140981` | -The older Phase-1–4 commit identifiers are historical because the producer history was later -repaired to replace the copied upstream tree with the pinned gitlink. Current authority comes -from exact live Git objects and successful checkpoint-native runs. +The Phase-1–4 commit identifiers remain historical because the producer history was later +repaired to replace the copied upstream tree with the pinned gitlink. Current checkpoint +authority comes from exact live Git objects and checkpoint-native runs. ## Frozen authority path @@ -56,80 +59,128 @@ runtime model/tool execution → Soul release gate ``` -The runtime, provider, plugin, tool, session database, memory, skill system, and FAST-P7 FOIL -route cannot create canonical evidence authority. +The model, provider, tool, plugin, runtime session database, FAST-P7 route, memory, and skill +surfaces cannot create canonical evidence authority. FAST-P8 adds only bounded task setup: +it may ask existing Soul code to create a task and one declared load-bearing obligation. -## FAST-P7 result +## FAST-P8 result -FAST-P7 adds a live runtime tool-capability snapshot and calls the existing FOIL -`RuntimePolicyV2` through a read-only Gauntlet-side adapter before the first model request. -The output is a bounded, task-bound, content-hashed route instruction. +FAST-P8 exposes one installed product command: -Every accepted route records: - -```text -schema: gauntlet.foil-route.v1 -mode: SHADOW -authority_ceiling: ADAPTATION_ONLY -execution_authorized: false -toolset_narrowing_applied: false -profile_used: false -private_profile_data_transmitted: false +```bash +gauntlet "research this..." +gauntlet chat ``` -The route cannot create a receipt, change a verdict, clear an obligation, execute a tool, -release a task, or bypass claim-native verification or Soul. +`python -m gauntlet_host` is the source-checkout fallback. The console command invokes the +existing isolated launcher, FAST-P7 route wrapper, status plugin, observation bridge, and +parent-owned Soul finalizer. -Changed paths are recorded in: +For a new one-shot task, the CLI: -```text -docs/engineering/HERMES_FAST_P7_CHECKPOINT.json -docs/engineering/PHASE7_FOIL_ADVISORY_ROUTE.md -``` +1. calls the existing Soul task-start command; +2. immediately adds one declared load-bearing obligation; +3. verifies that the task is integrity-valid, active, unreleased, and non-empty; +4. passes the exact task ID through the existing launcher and `GAUNTLET_TASK_ID`; +5. runs the isolated worker; +6. reports the worker answer only together with the Soul-gated finalization state. + +An existing task is accepted only through explicit `--task-id`; task identity is never inferred +from conversation text. The default new obligation kind is `DISCOVERY`. `--kind` and `--claim` +allow the caller to declare a different existing obligation kind and separate the canonical +claim from the runtime prompt. The successful checkpoint reported: ```json { + "boot_checks": { + "1_gauntlet_starts": true, + "2_worker_starts": true, + "3_model_responds": true, + "4_runtime_tool_executes": true, + "5_gauntlet_task_status_works": true, + "6_runtime_observation_recorded": true, + "7_soul_gate_runs": true, + "8_unresolved_not_cleared": true + }, + "boot_checks_passed": 8, "canonical_receipts_created": 0, - "canonical_task_unchanged": true, - "capability_bundle_complete": false, - "missing_requirements": 2, + "chat_entry_point_started": true, + "foil_route_preserved": true, + "load_bearing_obligation_created": true, "model_round_trips": 3, - "profile_data_transmitted": false, + "ordinary_hermes_home_created": false, "release_gate_verdict": "UNKNOWN", - "route_authority_ceiling": "ADAPTATION_ONLY", - "route_injected_before_first_model_request": true, - "route_mode": "SHADOW", - "toolset_narrowing_applied": false, + "runtime_observations": 1, + "task_created_by_cli": true, "unresolved_not_accepted": true } ``` -The incomplete capability bundle is expected for the bounded fixture, which exposes only the -read-only Gauntlet status tools. Missing search requirements remain explicit instead of being -silently treated as satisfied. +The fixture deliberately had no claim-native Space receipt. The worker completed, but Soul +returned `UNKNOWN`, the task remained active and unreleased, and the CLI exited non-zero rather +than treating operational success as evidential clearance. + +## Changed paths + +```text +.github/phase8_verify.py +.github/workflows/fastpath-checkpoint.yml +docs/engineering/PHASE8_USER_CLI_BOOT.md +gauntlet_host/__main__.py +gauntlet_host/cli.py +pyproject.toml +``` + +The checkpoint receipt is published at: + +```text +docs/engineering/HERMES_FAST_P8_CHECKPOINT.json +``` + +## Repair history -Run `33271617165` first failed before runtime execution because the verifier was launched from -`.github/` without the repository root on `sys.path`. The workflow-only repair supplied -`PYTHONPATH=.`; exact-head run `33271699059` then passed. +Run `33273066412` reached the installed `gauntlet` command but failed on a harness-only assertion: +`argparse` wrapped the help description across a line, while the check compared raw whitespace. +The repair normalized rendered help whitespace without changing the CLI, runtime, task binding, +authority boundary, or boot criteria. Exact-head run `33273140981` then passed all checks. ## Limits -FAST-P7 does not establish routing efficacy, cost reduction, verified-completion improvement, -profile-based complements, automatic tool narrowing, autonomous replanning, task release, -overlay admission, or native-runtime completion. +FAST-P8 does not establish: + +```text +external-provider operation +automatic claim-native module execution +autonomous replanning +task release +dynamic FOIL tool-schema narrowing +profile-based complements +routing efficacy +cost, latency, or token improvement +direct-mode or integrated-overlay parity +Windows qualification +native-runtime completion +``` + +The alpha still requires the pinned runtime and its isolated dependencies. It remains an +`INTERIM_ALPHA` checkpoint until the integration consumer independently freezes the exact +commit/tree, classifies paths, runs checkpoint-native and integrated authority tests, and +records `ADMITTED` or `REJECTED`. ## Continue instruction -Proceed with `FAST-P8` only: - -1. expose one user-facing `gauntlet` command; -2. preserve the isolated worker and FAST-P7 route; -3. keep task identity explicit through `GAUNTLET_TASK_ID`; -4. keep runtime output operational and non-authoritative; -5. preserve claim-native receipt production and Soul release authority; -6. run the bounded eight-item boot qualification; -7. publish an exact checkpoint receipt; -8. do not merge `main`; -9. do not call the pinned-runtime alpha native-final. +The fast-build implementation sequence stops here. Continue through the separate integration +protocol only: + +1. preserve `work/native-hermes-fastpath` as the producer lane; +2. freeze the exact FAST-P8 implementation commit and tree; +3. verify the checkpoint receipt content hash; +4. classify every changed path; +5. rerun H1 checkpoint-native tests; +6. import only the exact admitted delta into `integration/vnext-native-stack`; +7. run H2 integrated authority, direct-mode, rollback, and compatibility tests; +8. admit or reject the checkpoint explicitly; +9. keep native/fastpath mode `OFF` by default; +10. do not merge `main` or call the pinned-runtime alpha native-final. diff --git a/docs/engineering/HERMES_FAST_P8_CHECKPOINT.json b/docs/engineering/HERMES_FAST_P8_CHECKPOINT.json new file mode 100644 index 00000000..4e291903 --- /dev/null +++ b/docs/engineering/HERMES_FAST_P8_CHECKPOINT.json @@ -0,0 +1,116 @@ +{ + "authority_ceiling": "TASK_SETUP_ONLY", + "changed_paths": [ + ".github/phase8_verify.py", + ".github/workflows/fastpath-checkpoint.yml", + "docs/engineering/PHASE8_USER_CLI_BOOT.md", + "gauntlet_host/__main__.py", + "gauntlet_host/cli.py", + "pyproject.toml" + ], + "content_hash": "c34ef1bb041cdf48d007a282fe3839162a35c88c563bb5972bd77b739c42dcab", + "entry_points": { + "chat": "gauntlet chat", + "console": "gauntlet", + "one_shot": "gauntlet \"research this...\"", + "source_checkout_fallback": "python -m gauntlet_host" + }, + "fast_milestone": "FAST-P8", + "foil_authority_ceiling": "ADAPTATION_ONLY", + "head_sha": "6990322a815e46b200d7572544a106607c74343a", + "implementation_commit_sha": "554ee851996cd363c2e52a6294c3bf478f10a41f", + "known_limitations": [ + "interim alpha still requires the pinned Hermes runtime and its isolated dependencies", + "FAST-P8 is the fast-build alpha checkpoint, not NATIVE-800 or native-final", + "CLI qualification used one deterministic local OpenAI-compatible endpoint", + "no automatic claim-native module execution or autonomous replanning", + "no task release was qualified", + "FOIL remains SHADOW and ADAPTATION_ONLY with no automatic toolset narrowing", + "no profile complement or private profile data is used", + "no routing, verified-completion, latency, token, or complete-cost benefit is established", + "no direct-mode, integrated-overlay, Windows, or external-provider qualification", + "checkpoint is not admitted into integration/vnext-native-stack" + ], + "parent_checkpoint_sha": "9747572e8ca13622b72d8cd3d995fece90e19173", + "parent_checkpoint_tree": "0b194a94abf0e9791afeb604c84141325381788f", + "producer_base_sha": "2c3d5f19fa230bdf3a092c81f587397b4ce2734e", + "producer_base_tree": "51786c9162cd927a5b4fbb7cff1666bb43ddb4a3", + "producer_branch": "work/native-hermes-fastpath", + "recorded_at": "2026-08-29T20:19:10Z", + "removed_paths": [], + "repair_history": [ + { + "conclusion": "failure", + "defect": "argparse wrapped the CLI help description and the harness compared raw whitespace", + "head_sha": "554ee851996cd363c2e52a6294c3bf478f10a41f", + "run_id": 33273066412 + }, + { + "conclusion": "success", + "head_sha": "6990322a815e46b200d7572544a106607c74343a", + "resolution": "normalize rendered help whitespace without changing runtime or authority behavior", + "run_id": 33273140981 + } + ], + "runtime_authority_ceiling": "OBSERVATION_ONLY", + "runtime_required": true, + "schema": "egr.hermes.checkpoint.v1", + "task_binding": { + "conversation_inference": false, + "default_obligation_kind": "DISCOVERY", + "existing_task": "explicit --task-id; integrity-valid, active, unreleased, load-bearing", + "new_task": "existing Soul start plus one declared load-bearing obligation", + "repository_bound": true + }, + "tests": [ + { + "checks": [ + "pinned Hermes gitlink verification", + "pinned runtime dependency installation", + "editable installation of the Gauntlet console entry point", + "gauntlet one-shot help and gauntlet chat help", + "gauntlet chat starts and exits through /quit", + "CLI creates a canonical task and one load-bearing DISCOVERY obligation", + "one real vendored AIAgent turn against a deterministic local endpoint", + "FAST-P7 route injected before the first model request", + "one real agent-visible gauntlet_task_status tool call", + "one OBSERVATION_ONLY runtime tool record", + "parent-owned Soul release gate invoked", + "UNKNOWN unresolved task not accepted or released", + "zero canonical receipts created", + "ordinary ~/.hermes not created", + "all eight manual boot checks passed" + ], + "conclusion": "success", + "head_sha": "6990322a815e46b200d7572544a106607c74343a", + "job_id": 99155128215, + "run_id": 33273140981, + "workflow": "Fastpath checkpoint qualification" + } + ], + "tree_sha": "3aba1c6263eaca953d92c6f71f2f5b4f9c7996d6", + "upstream_hermes_commit": "5fc308a70719a83cccdbba4c0e39c23f5a8239d5", + "verification_summary": { + "boot_checks": { + "1_gauntlet_starts": true, + "2_worker_starts": true, + "3_model_responds": true, + "4_runtime_tool_executes": true, + "5_gauntlet_task_status_works": true, + "6_runtime_observation_recorded": true, + "7_soul_gate_runs": true, + "8_unresolved_not_cleared": true + }, + "boot_checks_passed": 8, + "canonical_receipts_created": 0, + "chat_entry_point_started": true, + "foil_route_preserved": true, + "load_bearing_obligation_created": true, + "model_round_trips": 3, + "ordinary_hermes_home_created": false, + "release_gate_verdict": "UNKNOWN", + "runtime_observations": 1, + "task_created_by_cli": true, + "unresolved_not_accepted": true + } +} From 6a50046b23e4f4cef6667b80d2e700e7167d14ac Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:54:50 -0700 Subject: [PATCH 19/37] docs: add Codex FAST-P8 integration handoff --- .../CODEX_FAST_P8_INTEGRATION_NEXT_STEPS.md | 921 ++++++++++++++++++ 1 file changed, 921 insertions(+) create mode 100644 docs/engineering/CODEX_FAST_P8_INTEGRATION_NEXT_STEPS.md diff --git a/docs/engineering/CODEX_FAST_P8_INTEGRATION_NEXT_STEPS.md b/docs/engineering/CODEX_FAST_P8_INTEGRATION_NEXT_STEPS.md new file mode 100644 index 00000000..b5911b5e --- /dev/null +++ b/docs/engineering/CODEX_FAST_P8_INTEGRATION_NEXT_STEPS.md @@ -0,0 +1,921 @@ +# Codex Next Steps After FAST-P8 + +**Document ID:** `EGR-CODEX-FAST-P8-CONSUMER-001` +**Prepared:** `2026-08-29` +**Repository:** `Kitahl/The-Gauntlet` +**Producer branch:** `work/native-hermes-fastpath` +**Status:** `READY_FOR_CONSUMER_FREEZE_AND_ADMISSION_WORK` +**Execution mode:** phased; stop at every named gate +**Automatic merge:** prohibited + +## 1. Purpose + +FAST-P8 completes the bounded Hermes-backed fast-build alpha. The next work is not another +producer feature phase and is not a merge to `main`. + +Codex must now treat FAST-P8 as an immutable candidate checkpoint and move through the +consumer-side integration protocol: + +```text +freeze exact producer objects +→ verify checkpoint receipt +→ classify every changed path +→ rerun checkpoint-native H1 tests +→ import only the admitted delta into the native-overlay consumer +→ run integrated H2 authority/direct-mode/rollback tests +→ record ADMITTED or REJECTED +``` + +The producer branch remains a moving source lane. The integration consumer must never use its +floating branch name as evidence after the candidate has been frozen. + +## 2. Exact frozen candidate + +Use these exact objects unless a later owner instruction explicitly supersedes this document. + +### 2.1 FAST-P8 implementation candidate + +```text +implementation commit: +6990322a815e46b200d7572544a106607c74343a + +implementation tree: +3aba1c6263eaca953d92c6f71f2f5b4f9c7996d6 + +initial implementation commit: +554ee851996cd363c2e52a6294c3bf478f10a41f + +successful implementation run: +33273140981 + +successful implementation job: +99155128215 +``` + +### 2.2 Publication envelope + +The following later commit adds the checkpoint receipt and updated handoff. It is not a +replacement implementation candidate. + +```text +publication commit: +b7057dda1b45b7647c0a63c96373c0113c5439f0 + +publication tree: +c60228d02441ff9277e38b9dc8391630a632b4d9 + +successful publication run: +33273285506 + +successful publication job: +99155519987 +``` + +### 2.3 Checkpoint receipt identity + +```text +receipt: +docs/engineering/HERMES_FAST_P8_CHECKPOINT.json + +receipt content_hash: +c34ef1bb041cdf48d007a282fe3839162a35c88c563bb5972bd77b739c42dcab +``` + +Recompute the content hash according to the repository's checkpoint convention before trusting +the receipt. Do not trust the recorded string merely because the file exists. + +### 2.4 Parent and source baseline + +```text +FAST-P7 implementation parent: +9747572e8ca13622b72d8cd3d995fece90e19173 + +FAST-P7 tree: +0b194a94abf0e9791afeb604c84141325381788f + +FAST-P7 publication/base commit: +2c3d5f19fa230bdf3a092c81f587397b4ce2734e + +FAST-P7 publication/base tree: +51786c9162cd927a5b4fbb7cff1666bb43ddb4a3 + +current verified main at handoff: +4f088d688fa9e25b4608f44000a5d9812efa45f9 + +current verified main tree at handoff: +465d460e9b7f8c61a57ceaf3400907832a6834cc + +pinned upstream Hermes: +NousResearch/hermes-agent +v2026.8.27 / v0.20.6 +5fc308a70719a83cccdbba4c0e39c23f5a8239d5 +``` + +Refresh the live `main`, integration branches, open PRs, and source manifest before starting. +If live facts differ, record the difference; do not silently substitute new values into this +historical checkpoint. + +## 3. Program namespaces must remain separate + +Do not confuse these programs: + +```text +FAST-P1..FAST-P8 + bounded pinned-runtime alpha + +INT-000..INT-800 + three-lane vNext and overlay integration + +NATIVE-000..NATIVE-800 + final Gauntlet-owned runtime with no Hermes runtime dependency +``` + +FAST-P8 is complete as a fast-build alpha. It is not `NATIVE-800`, native-final, production +promotion, or evidence that the whole vNext stack is compatible. + +## 4. Branch topology + +Preserve three lanes: + +```text +work/native-hermes-fastpath + producer; do not reset, rebase, or consume by floating ref + +integration/vnext-core + stable direct-mode vNext consumer; no required Hermes dependency + +integration/vnext-native-stack + overlay consumer; exact admitted runtime checkpoints only +``` + +Rules: + +1. Do not merge or push `main`. +2. Do not force-update the producer branch. +3. Do not build the overlay directly on the producer branch. +4. Create or update `integration/vnext-native-stack` only from an exact admitted + `integration/vnext-core` head. +5. Keep the overlay's runtime mode `OFF` by default. +6. Open a stacked overlay PR against `integration/vnext-core`, not directly against `main`. +7. Keep any Hermes-dependent overlay PR labeled `INTERIM_ALPHA / DRAFT`. + +## 5. Immediate Codex work order + +Execute the following phases in order. Stop and report at the end of each phase. + +--- + +# Phase C0 — Live discovery and immutable freeze + +## Goal + +Establish current repository facts and freeze one consumer candidate without changing source +behavior. + +## Required reads + +Read first: + +```text +AGENTS.md, if present +docs/engineering/GAUNTLET_FAST_BUILD_HANDOFF.md +docs/engineering/HERMES_FAST_P8_CHECKPOINT.json +docs/engineering/PHASE8_USER_CLI_BOOT.md +docs/engineering/CODEX_FAST_P8_INTEGRATION_NEXT_STEPS.md +the audited whole-stack concurrent-integration plan +GNR-PLAN-004, when available in the workspace +``` + +## Required commands + +```bash +git fetch --all --tags --prune +git status --short --branch +git rev-parse work/native-hermes-fastpath +git rev-parse work/native-hermes-fastpath^{tree} +git show --no-patch --format=fuller 6990322a815e46b200d7572544a106607c74343a +git rev-parse 6990322a815e46b200d7572544a106607c74343a^{tree} +git show --no-patch --format=fuller b7057dda1b45b7647c0a63c96373c0113c5439f0 +git rev-parse b7057dda1b45b7647c0a63c96373c0113c5439f0^{tree} +git ls-tree 6990322a815e46b200d7572544a106607c74343a vendor/hermes-agent +git submodule status +``` + +Verify that the gitlink resolves to: + +```text +5fc308a70719a83cccdbba4c0e39c23f5a8239d5 +``` + +Inspect: + +```text +integration/VNEXT_NATIVE_SOURCE_MANIFEST.json +integration/imports/ +integration/conflicts/ +integration/requests/ +``` + +Do not assume these paths or consumer branches already exist. + +## Freeze decision + +Record both identities: + +```text +SOURCE_IMPLEMENTATION_SHA = 6990322a815e46b200d7572544a106607c74343a +SOURCE_IMPLEMENTATION_TREE = 3aba1c6263eaca953d92c6f71f2f5b4f9c7996d6 +PUBLICATION_SHA = b7057dda1b45b7647c0a63c96373c0113c5439f0 +PUBLICATION_TREE = c60228d02441ff9277e38b9dc8391630a632b4d9 +``` + +The implementation SHA is the executable candidate. The publication SHA is its receipt +envelope. Do not import publication-only documentation as though it changed runtime behavior. + +## Exit gate C0 + +Produce a report containing: + +```text +live main SHA/tree +live core-consumer SHA/tree or ABSENT +live overlay-consumer SHA/tree or ABSENT +last_admitted_hermes_sha or NONE +frozen implementation SHA/tree +publication SHA/tree +checkpoint receipt hash verification result +gitlink SHA and cleanliness +candidate validation class +``` + +Stop if any recorded object is missing, the receipt hash fails, or the gitlink differs. + +--- + +# Phase C1 — Determine the correct delta base + +## Goal + +Avoid duplicate or incomplete imports. + +## Algorithm + +1. Read the integration source manifest. +2. If `last_admitted_hermes_sha` exists: + - verify that it is an ancestor of the FAST-P8 implementation; + - use the last admitted exact checkpoint as the incremental delta base. +3. If no Hermes checkpoint has been admitted: + - compute the merge base between the producer and the exact current/core baseline; + - verify ancestry; + - treat FAST-P8 as a cumulative candidate from that verified base. +4. If ancestry fails: + - do not auto-import; + - create a range-diff and manual delta reconstruction report. + +Commands: + +```bash +git merge-base --is-ancestor "$FUNCTIONAL_BASE" "$SOURCE_IMPLEMENTATION_SHA" +git merge-base "$FUNCTIONAL_BASE" "$SOURCE_IMPLEMENTATION_SHA" +git log --oneline --decorate "$FUNCTIONAL_BASE..$SOURCE_IMPLEMENTATION_SHA" +git diff --stat "$FUNCTIONAL_BASE" "$SOURCE_IMPLEMENTATION_SHA" +git diff --raw --find-renames --find-copies \ + "$FUNCTIONAL_BASE" "$SOURCE_IMPLEMENTATION_SHA" +``` + +## Required output + +Set and record: + +```text +FUNCTIONAL_BASE_SHA +FUNCTIONAL_BASE_TREE +DELTA_KIND = INCREMENTAL | CUMULATIVE | MANUAL_RECONSTRUCTION_REQUIRED +``` + +Do not use `2c3d5f...` automatically. It is the FAST-P7 publication base for the FAST-P8 +increment, but it may not be the last checkpoint admitted by the integration consumer. + +## Exit gate C1 + +The delta base, ancestry, and non-duplication decision are explicit and reproducible. + +--- + +# Phase C2 — Create the source-manifest entry + +## Goal + +Represent FAST-P8 as an exact candidate in the integration manifest before importing code. + +## Required source entry + +Create a new append-only source entry such as: + +```text +source_id: HERMES-FAST-P8 +producer_ref: work/native-hermes-fastpath +source_head: 6990322a815e46b200d7572544a106607c74343a +source_tree: 3aba1c6263eaca953d92c6f71f2f5b4f9c7996d6 +publication_head: b7057dda1b45b7647c0a63c96373c0113c5439f0 +publication_tree: c60228d02441ff9277e38b9dc8391630a632b4d9 +checkpoint_receipt: docs/engineering/HERMES_FAST_P8_CHECKPOINT.json +checkpoint_content_hash: c34ef1bb041cdf48d007a282fe3839162a35c88c563bb5972bd77b739c42dcab +upstream_hermes_commit: 5fc308a70719a83cccdbba4c0e39c23f5a8239d5 +functional_base: +functional_base_tree: +authority_ceiling: OBSERVATION_ONLY +runtime_required: true +admission_state: FROZEN +``` + +Do not call this source `ADMITTED` yet. + +## Delta artifact + +Create: + +```bash +git diff \ + --binary \ + --full-index \ + --find-renames \ + --find-copies \ + "$FUNCTIONAL_BASE_SHA" \ + "$SOURCE_IMPLEMENTATION_SHA" \ + > integration/patches/HERMES-FAST-P8.patch + +sha256sum integration/patches/HERMES-FAST-P8.patch +``` + +Record modes, renames, copies, deletes, and the mode-`160000` gitlink. + +## Exit gate C2 + +The manifest entry and patch digest exist, are internally consistent, and remain `FROZEN`. + +--- + +# Phase C3 — Classify every changed path + +## Goal + +Prevent a wholesale producer-branch merge. + +Generate the complete path list from the exact delta, not from the checkpoint's short +`changed_paths` array alone. + +Use these initial classes: + +```text +NATIVE_PORT_CANDIDATE +DERIVED_PORT_CANDIDATE +INTERIM_ALPHA +REFERENCE_ONLY +REFERENCE_ONLY_REQUIRED +VALIDATION_ONLY +MIXED_SHARED +REJECTED +``` + +## Expected classification direction + +These are starting hypotheses, not automatic admission decisions: + +### Native or derived candidates + +```text +gauntlet_host/module_cli.py + preserve read-only adapter semantics; retest against the current Soul/store + +gauntlet_host/ipc.py +gauntlet_host/observation_bridge.py +gauntlet_host/finalizer.py + candidates for extraction into Gauntlet-owned contracts after authority review +``` + +### Interim alpha + +```text +gauntlet_host/cli.py +gauntlet_host/__main__.py +gauntlet_host/launcher.py +gauntlet_host/worker_main.py +gauntlet_host/phase7_worker.py +gauntlet_host/runtime_profile.py +gauntlet_host/gauntlet_plugin.py +gauntlet_host/foil_bridge.py +``` + +These paths still depend on the pinned Hermes runtime or Hermes plugin/runtime semantics. + +### Reference-only + +```text +vendor/hermes-agent +vendor/HERMES_SNAPSHOT.json +scripts/vendor_hermes.py +third_party/HERMES_LICENSE.txt +third_party/HERMES_SOURCE_LEDGER.md +``` + +Preserve attribution and source identity. Do not make the core branch depend on the gitlink. + +### Validation-only + +```text +.github/phase5_verify.py +.github/phase6_verify.py +.github/phase7_verify.py +.github/phase8_verify.py +.github/workflows/fastpath-checkpoint.yml +``` + +Extract permanent assertions before removing any producer harness. + +### Mixed/high-risk + +```text +pyproject.toml +.gauntlet.json +.github/workflows/* +tools/egrt_types.py +tools/egrt_store.py +tools/soul_runtime.py +``` + +Any touched shared path requires an invariant-based conflict record. Never select `ours` or +`theirs` without semantic review. + +## Required classification record + +For every changed path record: + +```text +path +change type +mode before/after +blob before/after +owner +classification +runtime dependency +authority impact +import action +required tests +rollback action +``` + +## Exit gate C3 + +Every path is classified. No unclassified path may enter the consumer branch. + +--- + +# Phase C4 — H1 checkpoint-native requalification + +## Goal + +Independently reproduce the producer checkpoint before integration. + +Run at the exact implementation SHA, with the publication receipt available separately. + +Minimum H1: + +```bash +git checkout --detach 6990322a815e46b200d7572544a106607c74343a +git submodule update --init --recursive +python scripts/vendor_hermes.py --verify-only + +python -m venv .venv-fast-p8-h1 +. .venv-fast-p8-h1/bin/activate +python -m pip install --upgrade pip +python -m pip install -e vendor/hermes-agent +python -m pip install -e . --no-deps + +PYTHONPATH=. python .github/phase8_verify.py +``` + +Also run the existing producer checkpoint workflow against the exact source SHA when the +workflow supports an exact-SHA dispatch. If it does not, record the limitation and use a +content-identical detached checkout receipt. + +## H1 assertions + +At minimum prove again: + +```text +gauntlet starts +isolated worker starts +model responds against deterministic fixture +runtime tool executes +gauntlet_task_status returns read-only canonical status +OBSERVATION_ONLY record is written +parent-owned Soul gate runs +UNKNOWN is not accepted as CLEARED +canonical receipts created = 0 +ordinary ~/.hermes created = false +FAST-P7 route remains SHADOW / ADAPTATION_ONLY +``` + +## Exit gate C4 + +Set: + +```text +admission_state = H1_PASS +validation_class = EXACT_HEAD +``` + +only when exact-source qualification succeeds. Preserve every failed run in history. + +--- + +# Phase C5 — Build the overlay consumer + +## Goal + +Apply only the reviewed candidate delta onto the exact core consumer. + +## Branch creation + +If `integration/vnext-native-stack` does not exist: + +```bash +git switch --detach "$EXACT_CORE_HEAD" +git switch -c integration/vnext-native-stack +``` + +If it exists: + +```bash +git switch integration/vnext-native-stack +git pull --ff-only +``` + +Verify that the overlay base is the exact intended `integration/vnext-core` head. Do not +silently rebase onto a moving core. + +## Import strategy + +Use one of: + +```text +exact source blobs for conflict-free owned paths +git cherry-pick -n for one clean isolated delta +hashed binary/full-index patch with git apply --3way --index +manual invariant-based union for shared files +``` + +Never merge `work/native-hermes-fastpath`. + +Create: + +```text +integration/imports/HERMES-FAST-P8.json +``` + +Bind: + +```text +source head/tree +publication head/tree +functional base +patch SHA-256 +applied paths +rejected paths +reference-only paths +validation-only paths +conflict records +resulting import commit/tree +operator/tool versions +``` + +## Default mode + +The imported overlay must default to: + +```text +OFF +``` + +`OBSERVE` and `SHADOW` require explicit opt-in. Do not introduce an unqualified `ENABLED` +mode. + +## Exit gate C5 + +The import commit is narrow, reproducible, rollbackable, and contains no unclassified path. + +--- + +# Phase C6 — H2 integrated qualification + +## Goal + +Prove that the exact imported candidate is compatible with the current vNext consumer without +weakening direct mode or authority. + +Create permanent tests: + +```text +tests/integration/test_native_overlay_firewall.py +tests/integration/test_vnext_core_stack.py +``` + +Do not rely only on `.github/phase*_verify.py`. + +## H2 job A — DIRECT_MODE + +Run with: + +```text +Hermes gitlink uninitialized +Hermes dependencies absent +native/fastpath mode OFF +HERMES_HOME unset +``` + +Prove: + +1. current public direct module APIs and CLIs still work; +2. no host starts automatically; +3. no extra model or network calls occur; +4. canonical task/receipt behavior is unchanged; +5. deleting operational runtime state cannot change canonical state; +6. direct release decisions depend only on canonical receipts and registered authority. + +## H2 job B — FASTPATH_SHADOW + +Run in an isolated environment with the exact gitlink initialized. + +Prove: + +1. runtime operational `OK` cannot become `CLEARED`; +2. forged `verdict` or receipt-like fields in an observation are rejected; +3. plugins cannot self-grant authority; +4. workers cannot write canonical receipts; +5. general execution cannot clear `ENGINEERING`; +6. task identity is explicit and cannot be inferred from prompt text; +7. status tools are read-only; +8. host unavailability leaves direct mode functional; +9. an uninitialized gitlink leaves direct mode functional; +10. deleting the operational DB leaves canonical task/receipt state unchanged; +11. observation replay under another task fails; +12. duplicate retries produce zero duplicate canonical effects; +13. FAST-P7 remains proposal-only; +14. unresolved tasks remain unresolved; +15. rollback to `OFF` succeeds without rewriting receipts. + +## Shared-file conflicts + +For every shared path touched, create: + +```text +integration/conflicts/.md +``` + +Record all source invariants, rejected behaviors, resulting blob hash, and dedicated +regressions. + +## Exit gate C6 + +Set `H2_PASS` only after both jobs pass on the exact overlay head/tree. + +--- + +# Phase C7 — Admission decision + +## ADMIT only when + +```text +checkpoint head/tree frozen +receipt hash verified +H1 exact-head tests pass +every changed path classified +exact import receipt exists +H2 direct and shadow jobs pass +runtime authority remains OBSERVATION_ONLY +FOIL remains ADAPTATION_ONLY +default mode remains OFF +rollback passes +core remains independently green +``` + +Then: + +```text +admission_state = ADMITTED +last_admitted_hermes_sha = 6990322a815e46b200d7572544a106607c74343a +``` + +Record the resulting overlay commit and tree. + +## REJECT when + +Any authority, direct-mode, rollback, provenance, or compatibility invariant fails. + +On rejection: + +1. preserve the previous admitted checkpoint; +2. retain all failure evidence; +3. mark this source entry `REJECTED`; +4. report the exact failing invariant to the producer; +5. repair forward on a new producer commit; +6. do not destabilize `integration/vnext-core`. + +## Pull request + +After `ADMITTED`, open or update: + +```text +head: integration/vnext-native-stack +base: integration/vnext-core +state: DRAFT while Hermes runtime is still required +label/status: INTERIM_ALPHA +``` + +Do not merge it automatically. + +--- + +# Phase C8 — Native-runtime continuation after admission + +FAST-P8 is a bridge, not the final architecture. After consumer admission, continue the +separate `GNR-PLAN-004` program under Gauntlet-owned namespaces. + +Priority sequence: + +```text +NATIVE-000 + freeze direct-mode APIs, receipts, performance, cost, and source-adoption baseline + +NATIVE-100 + host-neutral contracts, ToolObservation, CapabilitySpec, sealed AuthorityRule, + module adapters, receipt firewall, OFF/OBSERVE/SHADOW + +NATIVE-200 + Gauntlet-owned provider profiles and transports + +NATIVE-300 + Gauntlet-owned tools, MCP, approvals, plugin ABI + +NATIVE-400 + Gauntlet-owned context engine and operational session database + +NATIVE-500 + execution, interruption, retry, idempotency, scheduler, recovery + +NATIVE-600 + executable Soul DAG and FOIL proposal integration + +NATIVE-700 + governed skills, memory, session search, observation-only delegation + +NATIVE-800 + security, fuzzing, cross-platform, non-inferiority, rollback, canary +``` + +The final native completion claim requires no Hermes runtime or package dependency. Preserve +the fastpath as a regression/reference lane until the native replacement has demonstrated +feature-specific parity and the required authority gates. + +## Immediate native work after checkpoint admission + +Begin with `NATIVE-000`, not a provider rewrite: + +1. create `NON_REGRESSION_MANIFEST.json`; +2. inventory public APIs, CLIs, task/receipt schemas, and direct-mode commands; +3. freeze deterministic normalized receipt fixtures; +4. measure direct-mode latency, memory, network calls, and complete cost; +5. create or migrate the Hermes source-adoption ledger and notices; +6. select and version-lock static type, coverage, mutation, and fuzz tools; +7. publish the native-host threat model; +8. stop for owner scope approval before `NATIVE-100`. + +## Native hard prohibitions + +```text +no model/tool/plugin/scheduler writes canonical verdicts +no ToolObservation Verdict field +no operational DB as authority storage +no memory or skill to receipt conversion +no same-process import of conflicting top-level tools packages +no permanent Hermes runtime/package dependency +no silent provider, model, tool, route, context, or backend substitution +no task release outside Soul +``` + +## 6. Time and core blockers + +The concurrent-integration plan recorded Time identity and artifact-divergence blockers. Codex +must refresh their live state. + +Do not claim: + +```text +VNEXT_CORE_INTEGRATED +INT-400 complete +INT-450 complete +whole-stack complete +core PR ready +``` + +until one exact Time candidate has been selected, published, and validated, and all required +core integration gates pass. + +FAST-P8 overlay admission may proceed independently in its consumer worktree when the exact +core base is suitable, but it cannot bypass unresolved core blockers. + +## 7. Required Codex reports + +At the end of every phase report: + +```text +phase ID +exact branch +before SHA/tree +after SHA/tree +source checkpoint SHA/tree +functional base SHA/tree +files changed +path classifications +commands run +exact exit codes +tests passed/failed/skipped +workflow run and job IDs +authority invariants checked +canonical receipts created +canonical task mutations +remaining blockers +next permitted phase +``` + +Never report success from intention, code inspection alone, or a model/tool self-report. + +## 8. Ready-to-paste Codex execution prompt + +```text +PROJECT: The Gauntlet — FAST-P8 Consumer Freeze and Integration Admission +REPOSITORY: https://github.com/Kitahl/The-Gauntlet + +PRIMARY HANDOFF: +docs/engineering/CODEX_FAST_P8_INTEGRATION_NEXT_STEPS.md + +PRODUCER LANE: +work/native-hermes-fastpath + +FROZEN IMPLEMENTATION CANDIDATE: +commit 6990322a815e46b200d7572544a106607c74343a +tree 3aba1c6263eaca953d92c6f71f2f5b4f9c7996d6 + +PUBLICATION ENVELOPE: +commit b7057dda1b45b7647c0a63c96373c0113c5439f0 +tree c60228d02441ff9277e38b9dc8391630a632b4d9 + +CHECKPOINT RECEIPT: +docs/engineering/HERMES_FAST_P8_CHECKPOINT.json +content_hash c34ef1bb041cdf48d007a282fe3839162a35c88c563bb5972bd77b739c42dcab + +UPSTREAM PIN: +NousResearch/hermes-agent +v2026.8.27 / v0.20.6 +5fc308a70719a83cccdbba4c0e39c23f5a8239d5 + +ROLE: +Act as the consumer/integration operator, not the producer. + +START WITH PHASE C0 ONLY. + +1. Read AGENTS.md if present, the current handoff ledger, FAST-P8 checkpoint, + Phase-8 engineering note, this Codex plan, the audited concurrent-integration + plan, and GNR-PLAN-004 if available. +2. Refresh live main, producer, core-consumer, overlay-consumer, PR, workflow, + manifest, and Time-blocker state. +3. Verify every exact commit/tree above and the mode-160000 Hermes gitlink. +4. Independently recompute and verify the FAST-P8 checkpoint content hash. +5. Record whether a previous Hermes checkpoint is already ADMITTED. +6. Determine but do not yet apply the correct cumulative or incremental delta base. +7. Produce the C0 freeze report with exact command output. +8. Do not modify source behavior. +9. Do not merge or push main. +10. Do not reset, rebase, or force-update work/native-hermes-fastpath. +11. Stop after C0 and wait for the next instruction. + +AUTHORITY INVARIANT: +runtime execution +→ ToolObservation +→ claim-native Gauntlet module +→ canonical Receipt +→ Soul release gate + +FAST-P8 IS: +a validated pinned-runtime interim alpha candidate. + +FAST-P8 IS NOT: +NATIVE-800, native-final, a release candidate, integrated compatibility proof, +or permission to merge. +``` + +## 9. Final stop condition + +This document authorizes Codex to begin **Phase C0 only**. Later phases are fully specified +for continuity, but each requires an explicit continuation after the previous exit report. From a483ae7abe8a1a99493e4c0e8069dd853e56f441 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:30:32 -0700 Subject: [PATCH 20/37] chore: stage validated Apparatus brand payload --- .github/workflows/apply-elenchion-brand.yml | 83 +++++++++++++++++++++ scripts/.apply_elenchion_bundle.py | 70 +++++++++++++++++ scripts/.brand_bundle_parts/part-01.b85 | 1 + scripts/.brand_bundle_parts/part-02.b85 | 1 + scripts/.brand_bundle_parts/part-03.b85 | 1 + scripts/.brand_bundle_parts/part-04.b85 | 1 + scripts/.brand_bundle_parts/part-05.b85 | 1 + scripts/.brand_bundle_parts/part-06.b85 | 1 + scripts/.brand_bundle_parts/part-07.b85 | 1 + scripts/.brand_bundle_parts/part-08.b85 | 1 + 10 files changed, 161 insertions(+) create mode 100644 .github/workflows/apply-elenchion-brand.yml create mode 100644 scripts/.apply_elenchion_bundle.py create mode 100644 scripts/.brand_bundle_parts/part-01.b85 create mode 100644 scripts/.brand_bundle_parts/part-02.b85 create mode 100644 scripts/.brand_bundle_parts/part-03.b85 create mode 100644 scripts/.brand_bundle_parts/part-04.b85 create mode 100644 scripts/.brand_bundle_parts/part-05.b85 create mode 100644 scripts/.brand_bundle_parts/part-06.b85 create mode 100644 scripts/.brand_bundle_parts/part-07.b85 create mode 100644 scripts/.brand_bundle_parts/part-08.b85 diff --git a/.github/workflows/apply-elenchion-brand.yml b/.github/workflows/apply-elenchion-brand.yml new file mode 100644 index 00000000..d19c8af6 --- /dev/null +++ b/.github/workflows/apply-elenchion-brand.yml @@ -0,0 +1,83 @@ +name: Apply Apparatus brand payload + +on: + push: + branches: + - work/elenchion-apparatus-brand + paths: + - scripts/.apply_elenchion_bundle.py + - .github/workflows/apply-elenchion-brand.yml + +permissions: + contents: write + +concurrency: + group: apply-elenchion-apparatus-brand + cancel-in-progress: false + +jobs: + apply-and-validate: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: work/elenchion-apparatus-brand + fetch-depth: 0 + + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + + - name: Apply digest-bound payload + run: python scripts/.apply_elenchion_bundle.py + + - name: Static syntax and rights gates + run: | + node --check docs/system-field.js + python -m json.tool docs/brand/brand-system.json >/dev/null + python -m json.tool docs/brand/asset-manifest.json >/dev/null + python -m json.tool docs/content-provenance.json >/dev/null + python -m json.tool docs/visuals/visual-provenance.json >/dev/null + python - <<'PY' + import json + from pathlib import Path + from xml.etree import ElementTree as ET + for name in ('docs/visuals/apparatus-frontispiece.svg', 'docs/visuals/elenchion-mark.svg'): + ET.parse(name) + items=json.loads(Path('docs/brand/asset-manifest.json').read_text()) + assert len(items)==3 + for item in items: + assert item['commercial_use_allowed'] is True + assert item['review_status']=='APPROVED_PROJECT_ORIGINAL' + assert item['source_page_url'].startswith('https://') + assert item['source_file_sha256'] + PY + git diff --check + + - name: Install exact validation environment + run: | + python -m pip install --upgrade pip + python -m pip install --require-hashes -r requirements-lock.txt + python -m playwright install --with-deps chromium + + - name: Run showcase validation in clean copy + run: | + rm -rf /tmp/apparatus-showcase-check + mkdir -p /tmp/apparatus-showcase-check + rsync -a --exclude .git ./ /tmp/apparatus-showcase-check/ + cd /tmp/apparatus-showcase-check + python validation/validate_showcase.py + + - name: Commit reviewed product files + env: + BRANCH: work/elenchion-apparatus-brand + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + test ! -e scripts/.apply_elenchion_bundle.py + test ! -e .github/workflows/apply-elenchion-brand.yml + git status --short + git commit -m "brand: apply Elenchion Apparatus classical-scientific system" + git push origin "HEAD:${BRANCH}" diff --git a/scripts/.apply_elenchion_bundle.py b/scripts/.apply_elenchion_bundle.py new file mode 100644 index 00000000..ffd47f33 --- /dev/null +++ b/scripts/.apply_elenchion_bundle.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Apply a digest-bound one-shot Apparatus brand payload to this branch.""" +from __future__ import annotations + +import base64 +import hashlib +import json +from pathlib import Path +import shutil +import zlib + +ROOT = Path(__file__).resolve().parents[1] +PARTS = ROOT / "scripts/.brand_bundle_parts" +BUNDLE_SHA256 = '0f4abac8ee309861c95888b03df15eebe5fee7cc1f62a60e527d17d6f83688b0' +EXPECTED_HASHES = { + "README.md": "ec35581d7d4dd7a6ee7aa0a7cbc811e2e884d08d9b6ed823cc394a581f4252ee", + "THIRD_PARTY_NOTICES.md": "bba84db1c6078897874f0fdb2dc538811661d02e6df915e20eb289492b10511d", + "docs/brand/BRAND_SYSTEM.md": "571a2e247f15b3a7291887291bc7d15e385cccf944f60c5f868a80e3a9b0aaae", + "docs/brand/CLAIMS_REGISTER.md": "dadafda52d0d21e6dcb75c93d10450b0a159a020c8cc111ab306cd0cd0836e6e", + "docs/brand/NAMING_ARCHITECTURE.md": "29e4855ff76f829ba5a98593d4a5670010830b6cd97c65a152825e9532555f2b", + "docs/brand/README.md": "19e155a2a44abf4d002fd6a62068ee829e52ce78011df82587a30d5afef2ad75", + "docs/brand/SOURCE_PACKAGE.md": "73340d1d7f50da427d8ec182c92abbdcfe83407f63623dda98f23f276693eafd", + "docs/brand/asset-manifest.json": "bd8c3bf34f9360952fad2a6082bf656d608356921343236be0da3bc1942026a6", + "docs/brand/brand-system.json": "51d962a687e20adf532a03f14817a7532cad208a584eeadd79139b27b0f5acb5", + "docs/content-provenance.json": "93c6ee235351342fc30d74166c3f761be1a801415e66262b6377cdbb7493aae3", + "docs/index.html": "549a89dd615666a2fc5f41fdf71a8e700aedc4daa5b1741df4b007028e38b453", + "docs/styles.css": "78ff473528b793f566379608e8d03a0a68a45556f791a3ac8183e5d9dbe9d63a", + "docs/system-field.js": "0b415e0771cc24c4d95ec4d6c87a2be1e0a65d3fbe210402431238cb4a5ba68f", + "docs/visuals/APPARATUS_ASSETS.md": "00f45acdd69dca4aa1260421bbd093ce1a2396e8415932302ed2216ebfe4962f", + "docs/visuals/apparatus-frontispiece.svg": "4b91a1cc0fd8b713aca27a5fbd3555562b2392a729ab4efc7c461661bb1d14a8", + "docs/visuals/elenchion-mark.svg": "655878c5c97c2bba9328e5031cc692ec4034aa92d89b7749ea693f20f45e3b1c", + "docs/visuals/visual-provenance.json": "2f2bd950af99342203ae3ebfd67cf2bca5acd75e4b0a2ed5922cd18e9d9f84dd" +} + + +def main() -> int: + encoded = "".join(path.read_text(encoding="ascii") for path in sorted(PARTS.glob("part-*.b85"))) + raw = zlib.decompress(base64.b85decode(encoded.encode("ascii"))) + if hashlib.sha256(raw).hexdigest() != BUNDLE_SHA256: + raise SystemExit("bundle digest mismatch") + payload = json.loads(raw.decode("utf-8")) + if set(payload) != set(EXPECTED_HASHES): + raise SystemExit("bundle path set mismatch") + for relative, content in payload.items(): + path = (ROOT / relative).resolve() + if ROOT.resolve() not in path.parents: + raise SystemExit(f"unsafe output path: {relative}") + digest = hashlib.sha256(content.encode("utf-8")).hexdigest() + if digest != EXPECTED_HASHES[relative]: + raise SystemExit(f"content digest mismatch: {relative}") + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8", newline="\n") + + for relative in ( + "docs/brand/brand-system.json", + "docs/brand/asset-manifest.json", + "docs/content-provenance.json", + "docs/visuals/visual-provenance.json", + ): + json.loads((ROOT / relative).read_text(encoding="utf-8")) + + shutil.rmtree(PARTS) + (ROOT / "scripts/.apply_elenchion_bundle.py").unlink(missing_ok=True) + (ROOT / ".github/workflows/apply-elenchion-brand.yml").unlink(missing_ok=True) + print(json.dumps({"status": "APPLIED", "files": len(payload), "bundle_sha256": BUNDLE_SHA256}, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/.brand_bundle_parts/part-01.b85 b/scripts/.brand_bundle_parts/part-01.b85 new file mode 100644 index 00000000..bc153667 --- /dev/null +++ b/scripts/.brand_bundle_parts/part-01.b85 @@ -0,0 +1 @@ +c-rK>>2lj>dMJD=h^OXsyF-u?wOV%jE1IHhX0(Nrd#0=1C4nSJVhj=-0Fx4}oy?Vd&-QKrNKtLsN$Q(a+!P7C>$9)FKIwH@$ETh8Z1m*l$%};-#L;B_{re}waGr$uMi&MAYBC=R`7o<*b33+7&s&Z0OD(mVVvPnR=TSk1eQ@~JmS@?cJ{1@niIDAC&^8i&S8DQga;jYX_$pUI-GjhU6zM4Z*b>zVt9B8Q+a**E~|g{9gg+WKm63wPbcI2Hb_J71~xsSh5h{PG|v~=QKK<|y(|ayVKQs{AE{+nPq&*Ly}1Vh?K<2Fe@jN{}Mj-Lk!>vyxbF$hKzI`XHFG~!cQ`s*Oe7Xcjda2gIjERtxRf5!M@8O=ANJ>KG149@##C!_G6Pd;PxAq?=B`0-Y2#^VK^M1X1;V8m(n(hLg-SI?$V7>}}gl<>?AdDMp|{Xx7`OF8MbJ7@h45BEcuW)5^!j&#FM`CIk9`nDg<$4NRPs0?BmwEebq_NH@k{st(+Qv~Oi-gn;_c+L@Hj=uZOppO@2-eNh3qoIW)3hW78!;j%`nFj+vZ@Q_h=H+(_Kre-G@ZvCx7CHPdNS5;v{4_w^ng!_xZxO`_{(@KtWFbQG6wSQ1VLA&le*}Nr!1F1bdo%;W5ih1e-RtJwCxMn^NCK{xQubUA~0QlKiyJep5xUN($?3`Aqt;V8<6NF45JUXpr19l{i-P^$|>Z4nHjILeV+AvMbuA%MV5=#9cfoZKOGsxzheV>rm7Jj6r%=>vgIU4RL$$p#{GmunwuJoP*rch8?i^CfI31gu^LIV=#dgXXy=^4!NtO|hdIz%Ys^-!{kD7AX|3%13Fnh&9>P`5Cyk4@t$ye5>RrFnyK0|wuU?&>+l$=bQCaXS1wxrM)2q%|`|W9~_lLeUrt_s*Y6zD&S`H(=RUV?(x#*oAziW42bx*pNKk^XeFZB?D4g}P|$25KQ4y8z35xS-?V1NDVN~b;ak%qz0&o68=nrpy_g#sh!E!%&e*_AK<0HNVa{zBylLxfJU_4@{bu|{x&IIKE1(DxJvkV433ugG#C>%yekGy0a-#z!{2|wW_V=oRTfLYOen52s&r9}eSNP|&GRACsyyaCV-Ld0p50l1Sy5W^CHZPTv33%bJG%a#DSv`0<0Xm989P!v*6BS@D9CXkcH_D%S_Mt{QY@Aq-z$!D5FdSASTfs3rF4L?oKH5n2vF#`e&TZPjf!bGt2CBR@XpTJSjet#g5(}N3`Ea_fhxBcNX0m(4F^IGuUUzR}{0mXiu5>U~O+e)yk0h$2C3SysOO^jMzD%^_<`F$59Hv4L?p{pMc6BnR^4^jzSbh$h~plb*~?Wip&o1oEm4X)9%@us}(F_-7`p)$<`wl@S1b{&loFt-83~@Lh0v{J}CsFRjNigyUKyGlu2C6eyViO1HCBSr1#t488n&0t=aa=|sCW1peJ;ED+T_cl3C>reGSRE3TaI_iWY>g2XZkfnzjSieI6~GQ)3gkH$I!%_7Dc*}nPeRRL)LmL(2vl?ggW9;xBwN940N^!DKq#w`gf&6h!E$wIa399o!|~cvq*Z{Ua&!PL%Q)BT0Ln59GngO)y)2;73FSZ%3g;uDwlHsa3&ZX6@`w)L`hX3Ha5f*tOJWV#a)A^VWkUWf4v3eK>S8uPXi!&}rDVE{b~9%Q7sd{M?>=1?O=4%yhlLlC$u~>+99WH^miQx7A{Tzkd2oVBA6lMnS;k12$|3T^ye$4SB&>KAc>}Q_ll*T*%=@Q;JH$VjA0nDVLL3nUm#~;jLg4EQlYm$Uaj#c`&+(GGeeWF^ni9T+j&JU)we-GX(#y{M0!=#ea0^kKRx1V4g6|6_AJ7g=Nv9Aco)vOK};8DSlGR@6kxDpIYIBcC2xMFhCH?V1~TaqPN_^@QkecVg@Pmxj=0ms7^Ubeyy|2seJ;%Xy+K_XKEm*{j>eT2=+Y8f#3hs?f2hxoZsM>mt){`$no?kee(YKr*~(6I6MF2ne*^00vJOkqID+_4B^Sv53TM=>(vPk4bZP}ngtJhf$S3^ARP&gAmF~AD>V=yTl7u@)A`^3S|+9j3Pd(iSi~;#V8}XX9)=;X0Y7I+OXO;VlbRah`?q<$E#_Q<&A41S~mblNy;MvPee&Fn&S&-T!`7wFp?#DEs~A1WSI&Phk1ac=EyDrPsy-8P&1qn#0j$MWy^sAdq-@*ce0SUb{OATGB1mwxzNJjd0iy_$j4?O%7RfgBaZKTns{Y!;$&f=RhYz?Y$U?%jnibt7s3Z8XHRb_3W|dY3H4wHBhX4F^niWYvv7vgi=O=u#j&+r7DI1wLN8e0wu6}7Pmy_{Tt_#bE*G@8I0BTs1D%LP1K4{gLI|h{vj~BkkAdwj|I|M}I{_>L%9WFa3QiOS$|VB0K7mwgFh0o@B-d;8j~=m<6VRLdY?|=a_4giZ2!4cDP{KwF9k0U@M3B^ibb@#dS`{!j!|8d20$w(fX-N5jadc!^D;2rmVd1COt^TEdacDh4TT62^4;?lRjm;@RW=<9=m^uSI`0l$SWh)}33;-4>WsPx9!MmKG^CJTwP|rnzW7kl!DAOkV!4MWYU$(G;T7_gKz!&HCY561~@!semgb|Q%c-DThjGco51e|Nr*>Wx-zg#80j`9ni2AaDw)~myjKH&B;#XHc}1pHpnG^L_iRyjrqzhI5S>#$CL1aceB3yK>6E@@sM$bEzn=IjX|97o+{nI~wT9J*v$BPu^JPiCk?!7e~AV~ZVaPtKvHd<*^{$XLM^Ap&M_$if^{+2oc67^X><`7mIP7E*(?BnuA0X#jc~$R~{<5LUERhGUT76_Ky;?bk$lT*S*s1aC6B*BshFQmfMVzWc6t(Lnr(5Fi`@y+f5k>D^kxr>pADY>D+X)~JX`)-*@8P5xp~Qe+FnP_6NaVwt2y8@GrUBaf_*M0Z{ykc{ad$!V3KX^d!<=;(-}Us#MG1ri?%iM*hhC{8B)0jR}*#8EiKpTb4@aR*5y~eElrb8UYLiFkNl;Wv)-gjJRvmnbM)El14b0&(W3>SQ286I{vP~0=0*MM>)>|X$u+9(4b%=&D6mF43ayEQH4lZ{vH37};n8$yGN+Rp-nlN5K;b@Wgg;?6j|lpO@O?}$DkoT2)}$4nO@HlLApj)@vQ4Pdv3Q-~kIL}WXJwG5vw+ycxAj4*^_2nOE2PjY)hI0~(m7qRf&ITk$u-*>ha*_&KDE)*l1NYI=#wu6>N~1-cJ-k7>`&?f%YotTInhR~j7nO87C%t576CP!5P>>Fn6UBZq_QMzjFgkqs7Iy&Rjbp{j5HoAUSfU)U0TBKsL>kKC(-6)r`pZ%PS1=Csz3grdq&JJ$PeKL_VFwH|WPeVPSdOABz>C$m##brpS2iME|2T=5GtmdcQc_jMVl@P+ocn}J2>Hfc%c?Z2Wtkxf5$t?Vt&uN{|66j(u3ogm!2^omDebAX6X@(IT(}ZwxC4qKv|uuE}`DHW}uO=%s?WhZMu4-E-GslDmGcc^PT}5xF91ZRCaGH_*^LMp+Rr1W0-2IAu_b$r`UtTJ1l)>6~75&fav-I-MT6U-}oFwk0j!|KwAxA@pY&22KmkVX;0*MEQL}?v4KHS|9C9p71A`+pPWvj5m6kNhUAwyyj0ZKwggFuyf?2;B|mt}|g(pTjj0MS;?24I~Nwhn_7kK|i;kFwE->FASrgACzUl|TKa`6YA75YGVx-MAu;5iu1(Nm1J!=>7X`@tbWNUW{E0t2_4L{bCUUYP>4>e2TxgEfPIOEV9^eBI02T?!m_W9ITZ}=(4se&gU~4X`n-E`rQ)%!UbXt&egSE#pO9^@fp*$l=i?&ANG6otOn9Td*od7T9%=9Z^`NXSuaMGqOZz|C@c`o5a7-YOVNA6&PvD{)HgPLY#{ZlSDU^34o;ao+XaGK^C_Y5ZYPgo1Zi#W<|6QpC>V`n&~&JolKotg!C@@zlUDb%53up3+rLx>)3v`pHE}@3(_&Z^UYcQi%Wu_g{)5FV!Qfd--J1fTXn`Wr4mn)9>e05(MSh|Qw2kqW(^4@aK;rFPuD7T>DefL@0W_vq7Yk}(qx82=hhkIE~=6Gu*j4>m#toxaJNpmF$Ny?k%EAW_8kQEJs2gd4G(%6MQ+ug&}cJugax3guEhl*`2gV=VF8)engt>$8Q*^Igr>N4rHmM|Zq6KpfoNrevB>;|!-DBS*`b-dqdgW^h~Nn>O14g`4czmLra=E(_ptIV*m50O=kq1_>r9?vdpXu}pABD~`t5U+4kYY7eIG!>|kM3v_AfZ=HMDArpy062k#jObY5qPL^mzE~aEtDO36*2_K56p{>FraOszZGG@`Ih3Z-p|S8Novbhp+#acz=IgB*pRC5Xq3V}tPn$JnX=DMt{Js)Ba~eUD7;g01t2y?vEDPSi9Em_d00L6ReH+f=dr8IYw$kop|v;1Q?3QC!DCCHC5^4(5I=cjCWZe>03m(S0fL&h2}6)Tc?`@QP*_Dm9#>_*8By$Ts=|yVYI<%cm5G99S(GE`L2Dqr+$GH3(1Af6(#$ZrUH9IQUz>AOGL+;ZdjJMDKpolj)_B}nt;{0EFmIBN^qJAcbF?enwCUia0zOP~V$RN))-T=%kd**!n2565GC$of;B*fhzBpT2zC?HylTw0f66UY(s^cH14%EUSMYB*xrK&%{lRBt#$4^QY_6?xiOQkJl#0ks~2QF{p}8RQ?b&4J{XFeGFz8(}?H~qsbJNp)N89imcU9F4hB2mGHs28Wh89ap6Cs%n7fe+)w(Gq*8Ejm3lzO(9xE0k=l1G5txQ@4eoGDVLS%)4Q)Cm1&X9;r#P4Q#J=us#fE=&L+^;nA=JoL<)wp&7Ao2J2kzU@soFbmMk+BFYgShlB;+_?9s#*_Qu1Vt;678>tOp?aJVxZ4o8DQushrecb*NKJLA3Ju(|VWv?uSMkmM{|(1~)NytM&QVs9BG63aO2lJaCYaT0YWlUhrw1WWGXP*&~5S-LFt}w>?9v&|&9JlJt&*nGwLsxuFTjVM2`5%36{1P@m*2J4*Drbj9eL*C&L36t4N&+_M$Yrf!+i$;IwcAf)Jc_J~vos2aHsjwH>E&5xE@gWvI*NkyngDP@wnT0keVYOY!`6mjXNFiuD&>Au!Snb4jAN>KW|itcBHCCis1j2QqO>i_4v2^BjYF)pilQfKe+bE`!!Z+4csgva3_$Cd5%FGO*4<5F1I_=~;t%C4-+gtFSsbo@9#w>l-T6reRwMC{b0uZe8kon^!8XSNZ{6LXIz!0;r(ieEW2o@aHcKy58&CaXiWBiPX131MX7=DIPc;MZG{a5=t_}Rh5JotzKe(2*-;js<$U)oWy*SF{el$&h7ZoQ^MW_!YM7*8E%@A#Z8heJ6dVeDf0Dw6Ll1aMJJi$O$^!-@O;8L5Z3Ka_|PYzj_ofz+D!8k-$ZHz#+0zws(cC^h*hp_h1c;@ZG7jMEOokT&+yNr;NpM|$x4@f*l63{cBREm+u^nVuoIZ65CBp54`01|YiQ1BnSqBV?xBKqpoyNJV&UO&K4X~nMJb!)JYNfOd0G~fAZ&OtJ|!~F9bod=K9FPMwZm%YKFOj_fgqQUQzw~Wa2M~XE}@fyGOd*Ngm2PsNO^Eg3Ajy!=2BJ&zr%2ebiy8k6Wk8{P@*v3~6a={pA6X%Juhs@>rB2kSL&nK#mlc1{yZLVZI=3{WLlJ%ll@PW9nHxF4KQo(d+u*a!2<=U-#T@pP2c3`Oz;?T9D+J(uEgIN^cQAvXw&$xn|QAXY;&8vuH-{^>ItE?1SKvAXth(~2bWm7DHTn`6;SfhPnwLi-WEU>d2#(+tHXya(IOeJ0g7^+16^3pKSn*bxnN*WD~)OdR;T^*0JhT)1gI0!u<_g2%I))1p#d4Upr4I|7z$e99yPH<#9$mJ6XIrB_YM6{Rc*sM_)=K;sia*$$%w=^Hh_!J2ZBe>%s94E+RCm3KsR&uqt#$Xx6qXzk08WI^zwZ-@%Y0V~98J8VmSSYUglU*8Y6rtT(1trTd)(j_~28tt5{4mmCO@Q&jf+a>wyPVR>ZP#iR1=DcMF1+S|N{&$!h;P~~OhLG46|>`SRcQd;o66u7KoRn(ZC28IxcEi_34(>x$pXkzuJfq4W(1jnGiw;(lH?&1v5=T6stlMl;qzZcIwH{x$v@LR*J)Gmhj4gChS?~nfly@}Od^3PLM+~L6GSmCsPGzxcin*2I6x-Aqq1qz4j~XCfyAGl04bj+L@{$l`CPPduw0tqFiy;ZIgMcYIo(!)@!ARt|IB8vWt}(3R8iPr@r=2tY>$G$E_WYPXchCBly?5}_CI8*)v^(7k`J;c?x_sA{&twtd&#%tkogKG&KaxrKjC@0M=SwV5lzBX}x3ztZI&Off3Hs;}6+ey*3o5XOEREq+m;+U?C$>f0{#kS{-WFIdKCX|kZ_GyxdXD|{4R^zUa#{__dRwkj`IbAK5P#L0TjLDbi38jJQd&e(_PyhXI{~N1DE>kiEjXlg{AoBY6zx^M^FY_{+B5EoWmrUZE0H&RGI(g_3C@ZF-#i+#YEWSj&RIH5=$4gZ+Aye@_f<9cGCBPCuAW1P>GO8oCtYJau0M@N0KwPv7MXrImme<=pZzCb`+Yi05CD>BDT|9IWud+Kd;nOVzetjS=B*Im_Dl+;Uc$!oSZ>wZ2Sh|YnMylL&D{`=IwIG2U)m{&qLK5Y;)#wkFtF@6M+(d3UaB*P@rrCU7)hypu7H|)9b$Oc8+1@i}QZ>^1S!s)$3OKlJY!NB)n7pE3j-7%5se2vsGbGxj$;`_SYf*Jmqnx-+gm-bqr!>zk7a0naP;RHW$N?aA33p*2n|Kbi*Rq_T?~;I(B499+l}jf4Vq1@3m-Wogcc#oih|w0SR@S996evsRum)6u%BYx3nVVzs@|*(X7A3_G2*~yiC0m8iH<@?F-MjMiO0C;oxknA>R$2~r%C`8=8yGk+2Mu<0uszHSW+1?9ElPwP{_<;ww%#{sVqUAtx15Q;D)2`qM1;n?lBi^#4Q=9(VAGpPFk%bOs{(W77F!Map00Ds2xYVk;E>2>J&63oEK_P_o^Z0dPO{zXDz2m*KF5Fu2=^7%*02kDIV&I&av{20mAHA*C$sqad9gIrFpe-k#9sccUcT5TRN<;MF(8y+o^gbNj*E8794Q`u??%?T?4NJuoPVwtZ=26dp#k3Mp4zDj&Xwvusz}lL5=HlKI;=(L2g*DM4H-$o?lbG{1v76u~2%gO(noKPz0PN@rdv)vkOXQ_WlkeubnfpsO|#m6s1a;zdb|iew-q%1q;;nbO~9Bf8-!TQ83t1P)&A7C||RYVKF9i4WW8&ErWy`a#h${dWKulRzoLsnjCIAqgwO>O7)Wxup8pU2Kms|inh0?6pP7r#sxTuvb+U9Zr+ZhvX{xEca^{V{J^hTb&KkOQib_AD#jX-@s?yCk>Lr@nHD&E}7MbP-iD$p1I=@ws-z!N_P9_=~N;8%UqC(<=7N2PC#BSSFNd?N>BAWs6AJtkbNxxLW9N-H}s0{BaNFYn)+8UyDR&rfi>)EZAhhQdiz|$bpNwuR=kc?JqoOqc+PF%$ag0?Vbqa5$WNud+N!AKP(E9X`l86O6I*dD$)pQmX-`u6`l25r_YduzW=u6H~03%e{1iY`0(1Y=VU^eJJ4ytAS`~SW0Nq~rvkFNIh)0YiOG54nxum;HdQZm293nlsGJ!k<#SCkpL7KTN>kOub-9q8T|5%XO0H!`N?fM|4s#=Ntn9d}n+$kqFhHe0tOO5|X)TFB*PO#GPOA`Me#pE|@f>4dfWF!!;C-=f(-c}$U+WUjlf|2|0fBWAWRL~4=Qwxq1p<W;L_a-qt)C{bmJ`%F2Wm}W_Op6PccW^bHoJW+{S>+$qe95#4QB}?6ECZ;Mm7;H&J?oH1F#H7gseU`Bc1sGq4RK*Xgk5S|>;bBX=;Xp8oiw3hbB^jycYr{k}pJOq2;;BhmS+VgAb~->FV``<6Hp6jdR3X?Y;OJ(DYJAs5H@i-9%`HERn9P3~FC(&ABF%DkQc12@#_^Q$ij;)i)0GC+7I=h(|2ZsISCi66_NL~c{>aG;fIs<^YPzW!Y{YY7(k>)}-YxZ*DZ7;~#nQPHjy{%BdB;8{NtvrU)^!qA)_#1YQttT;)C)0?OP7Kaf1sp~SQ4;i5%BgBxuB}8LjkPvS?nfQi7->uWb7T8LJoyZIaMEfD(lNTW%|^&3cRf>38`y0Pl827MAE~`IC6v&nIWWjVX3rZRkcV?1w+~%tH(#>B9IrH%;t40osL&OR9v*WPUIya(~bS8T%a=OShbAFpkWqGp>9<8s8A1*YZm%M%yeFqOXYuL^r6MtA5;vzSGkE{Q)4j6<<0`93zUD&1L5kTMs-~n>M$(jitZ_9jMfr6}^g`VCY#HYf3v72*s26&Vbx@kUP92MZmsqMM`9>!iq6k8zqfC*293R^a4neNT1 \ No newline at end of file diff --git a/scripts/.brand_bundle_parts/part-03.b85 b/scripts/.brand_bundle_parts/part-03.b85 new file mode 100644 index 00000000..782c4a5b --- /dev/null +++ b/scripts/.brand_bundle_parts/part-03.b85 @@ -0,0 +1 @@ +uy3o>qgC9o*r=|eFtw%Q=$;Q_5suhRtyG_tm8mPGd{?5ct@9a>e!^uZIG9McTsm!E)Ko)bSGvv_>r%?%ak9HLC@K5~;cu?HVdAwAaAjURA(!V=LhpP|P7fiROQnkXj&16n<1h7AH)T;YC;QG$!chl#|UCT&aBd+_K)LRDj&ny43WoqP8S$dZP-G2y!Udl?z9zf{>e)i&R#3DH5s4cc-$Dpow14W?myuR8(Db_j8JdkJl(1q<79%E~{%OvP-YBjH0#Vhpkn`{&A(p@_tc07fSY}#dM8~WnR^!OiQu&?;Z;lZQnUmd=geu3fPfVOz>=&^Iq+mU(_U%BTGz^u{z3M3H#C{hf9*&MvRoHE9R!7uvR^1#f_3wZCUzt^Z$m+4y^2SS|(kS*yPk|ZwN1kgKhyK-8V9Gj!G(RRZWyA+2b1_9wvnf7x0aMthX!>vCzSVm&F7KF5Gpvocdq_@kWRn#v%kNnV5wuIE!FBbuG2pysbEC{2R(1aiF_UV>>e5a0+{gDUDP+prg#j5+#HGS{Hl-XEPuY^zSFjuEg!3Y)M~;w4MIGBciXnhMGBQeBpUI54zB0_$nA{W`FFmBoXlYfFVv(p;CTA+HxZaybzUS+SMw?o?xmz03nBA;-eyA4bEu8%)^%zRO%CE7w+GW^S%D1W;gco(GWOp_UCmXomR!8UuX6!9Zqox-Ic0^Ai6aQKZ&Rli*ozh!M{BzPx5aD_XQ+t-6si<>p45r-{$E9u9IX;Ps>qe6{fB5``6a}|Pdf5Zld)|riR%7V&(5#O^=A05i9pFXpKI5?kR5Y4$JM9!5;iSe*2Ex)2tpSfPs0kgxgZ++tc%ETkah=z+0VfSB{HwZ;L=zTfY*LBZ&^yP*GcUw7MAEQH7cuYTCB{}L@kCVq97qXvxKA#j}CW^H%pP<~S(Fz|h|xzh}G2HS^W*c@(;$Kjwk9Pb}&jrX^<4@bj;?dG%5?*9Ja2p<{2Q{$~5JlH=Nh0SrZIaD>d^llT9j{1B!#w1DpI-Rf{F4WOJ-2y4kIQ|4m_SrmFVLAp{{q74S27aX;bw{MR_e+;N(oDJc5zEYH6=qz-t6r03M)N#O8qvX`G*EoqG@m~nFjH@hNW#^z=aZT!__O7Zuq#Ge{%?P^&lT*l-%K@w{=!@YStJE_L=TOCX(0gZ901itj@vDmeN}waXYh}dJMcO2rNFAris_T6&-JMD1XBPvDP~>K#=68-XBqf3?N&ANl_+p26L@^?$e!dKX_TBJI-vt+G^QZR;;{rfWi8SRTjN1ckxaU)kv3MBDtZ(oCLWvZb;i!Q~4^YqSY`p(q`U1+Uw+9(MPrqV-oX1b-l2^?cr4yF9wg}F^jRoy_PFA(P{q|&DWCUzN)0sG?Y+Atog_o?I$eJi}lsafI3MI!xhsw|mssw?$L6?9AVBJeH9YV?$0?_y8jZqNy)y^Gs~qLRg?pOr+e3LPATuAi{F@vmIs`27=l<|-P!e{uwWwJt7NJ*-sWgM8Gv#3Z%%PipE}pagL!4{FPr2_Wwq^7Y!1n5_Xs+vg!JiYgG%u2$D)|E`X7R(BTB#?kAklqZ&F*eH*2sb(UCZ=Qt>$Itw&9qVU?iz}oM7$QsW;=>hTn`*N1+Z?y0k8Zg!{W5tZ<>XdBwxhi13ZKC?s=#)a5N&D0l(luF9>@;}0nrlP}A}E>Ht-VPFTMbATz4L$Sv@iYhUiVG+taW1Jk6zRjEapn=WcV6zy0Ca!(n9J=LH9aTK#A=tn+BlB@WZ=1X}xLaUmz2>$s2==8HqT|hLrLSe8-q#O+%dj_t-^qPkUWv$=3!Yhm}L}DDKS1K>0eEh@_uGpHO>1yx%hYo0$e7RQ@m&=qG{doKi?9SP)^(b7v9Fv%Fu-Xp%4nGN_Dsm^fWqv~GR%}bv?qvHUgBTWqEd>}Ll|Eq$sLqKJgrm_RU(Yq$fHF4D_um3mRzjOP?uB@mHh737qHyy#bP(LD(hNJEO^|zRLRPw>5H{Ip*C02%$#8#fBkFiL2S5L&4$Zjc-zvXu(t32AGvS2e#Uk5FkhzJ{|%V${@&i8q##&y$6U%4Cj(+N?VHKE9$#CXj4LjGc{Kd60o(ke)g;g=97No+q77QPmXIj9Trf?DJDJ(INdmLYFV8J^?o(R(5_V%(>?MD~Z)ca=07DO^R8@I`QwX!~sF%}$&fN@rpHWiv@v*9(&@U(mBD~8xUKkoeJFvG3E?)F}|b#MUOuem$i9X;Cv4mjE$9v%eELAV`m1^auWo$(-SZg1^wHFtNmn>&ZY!EUfO2=))hkHQQ~K(dkAsiOOA+Re>U{+qBsE*01u`A)62N>=EE4d~*UH+I3{)%@?+6d|-4Lst91_y8#+SO);l63j_{gg%lRFiESBorB7DxyiE9$%XACj1ra`QPk5Kz0*nszEh5@4VmTp`OigDa47`r$Zff(H$JR6IQwX>(AK>|Y_utZ1;vG=benpiG;*b75Psa0PI&*s2of6C*RM$1K}>t&Su9UAKI6k3u@II7fjUQ}`umuwpM>w^NZPxSxvkL^S^(KXKy}`z~WG>quu5>qqud%dgz>#QK%wTKSQCnOHw+pSC=}JxQzwsQS6u5_KAJkHG|(IwzsM;zH)!Mjm2w<6a*8N1>kwe@Py7oBU}nqzvn{>X3Js7sr=)!O6?>Hw`HKC@C0LVcf}xYx*Y8v^+N)<)N$lmBC8OP)Tk3S^^wG65Q=)nGw31a`o|RI}-8t=)Z#(6D{jNU8?%Pf*VIXI!w_N>lVx{g6SvTBs@GWQxw%Izy!vDJ^6ixY|ErVE8z5vv!di?T);N{|Z92cdzrSRjHA><6axxh!8~DGU`GX-;rRz0mL5`;9>ny>0?$^nJUvs>FR?@ca+py?A_pY&^!`$;c>J!!@o1@{3RRiBGtaA?zR_GI}NW6MfNImzqrb^D3Re;6j6G8}OHzY5mM7;;xM41Anuqw@(nnc+=Sb(&I<*Za?7nP0ab~DgQF7^-VgYvQw%6%4nU(*($NL26dkZ}JSC*5}Etlz2UAM+~AeUu6qYncvLp$^kB({P{T8@Zz7hkG847Q+X!7X{a*j)otyUE#4HTX%#}%^sY>G~9@SwUJ~j9~5Vx#Eh!W6kkxl*t}Xu;U>i0-M}Q{@bHQo)UJ>!HmGy*kV_P5WU>!^DfD{JEtEdYG^M7IH5Hj{nL9n*?8fbe<5p`UJQ#%2;3i5^sThRSL@Q#uR-(lj=;fE)A39g65$;zYs9%CoF2n}yo0LjQ_PYmqY}0iW*y!%r(v8cmex`Ekv?{~7kd4>p-IJ;cohre0$9<&+LRx7{`f24lOeIaH4p1aq_Nzb@A>Mh)`FplWyzFC{WcRdl^LVAg@~QF5jN_x|cs*{Q>A}XVtS@wTs?BO8lnB#(AmOwGjXwT=9g*KXm(eBE9o>mz@WK;ZPhizU52tl?&w_n~`zpR6(Nd5_6e|^Twmn;-6#X-eWSs)H|J4C(V;$0hpf-@fHb6l|*|$obCv*HpwwH)&$R}IAPl%u0C)*iAOEFTX05ZB2mI4`*>ulF*vHB|@ak9#;Qq@%{K83BAF(?dVHS7U2Pdur-sx69WVEiu&5*wEGtGr|7qWg`PtbJC#B#`^_#OLCv_qz&Ak->W|8#S_ZoyN5NLHZO^SSrzt_u|b3v|lzT78Cv`76QCh9w*y0v9^gfeY~80|+WaZM{Zrx*=&_La{p=b!KCtPSy6grGRPl#_^A=rByVP%>W2MR#lzSl&I-V!(c>>+jZBcDMpAS_#leK)U|dE%cydq{sy7c=^j_OLzAiBkWDtUlGgMAJ;cQUQ=V)UP&oec`ys$NSog@!0YO_A55-Ulrxnk*uXA|U9kf$Bbq%UYkMMvBMP+Wa$zfGXz(NU1ZG0Qt=?Ysl_(PY;;tP`6ST)_O&aNe;tDH$Kstt(m$vM{SQYE^2o%VU} \ No newline at end of file diff --git a/scripts/.brand_bundle_parts/part-04.b85 b/scripts/.brand_bundle_parts/part-04.b85 new file mode 100644 index 00000000..d14665a4 --- /dev/null +++ b/scripts/.brand_bundle_parts/part-04.b85 @@ -0,0 +1 @@ +_@VT+RETV~8@EuF^g#-tO8Sv(5nnpmKhE`4cN46Y(#LdVQXNvyXuvV_Qf0DGh>(kG(uI*(KwA4T!IsK4STkLRXnk=5{`!(8RoC}`;t~~atx1x#rMVS(Llpz^e~W=94*@3=eB)!yDwN@JeFKjGRJw{wsSp*{Xw%)t&=4Vszkx}~r?=+8jjmfS!9kfxlXtp@&af^pmFQhjEeqwdd#y;~h(#QgfQvWpTD{|5t9$ZLxGG`#Ln1qB4*}K(gn?lH1RrxeK{wmX8re5p#otgqLU51BCSo}yv0_hyD>PnCnE%HIhi@qIZ7$uHAjrJ?UQlSbm`2X}{}L{_^Jh2XrxjU|jyKeSUV?>%Mw-393xRuU+miP#=!RYb2x3^u<<{mM`NQ1z|dIf#x2c96fpQ5AYqv+<5rK%lGpa_+O%b-($e~`zQ1rXd5qaq!-vO7xU17a^>*AG9UYg@1J-L>sOSG;3qV~p`Zi5N+cqDc^i%L>GvZ*reWyQA2n4h4WzFi4!_^79!=#T5$an58dbNlN}qJi+&pOsMU4T+%>*k;?^4wk5Iv|pu8N*^LUg!2g>kBtV%-^yZQCK6E$h|WN8LX3he?ciSl0G+wo=tg^tew^o}u%WM7&qMlO}@%#Ww3{f|uGj!JJp~5;h4(Gx+ypaY-b($D5|%_l#id)15;tABumm`yMF{W@?r1?{*V}&P{l~wTdTMl}=g-!*=llFx!0O)jWVK%SF{SRQ#rzJHWtgB3KcOVRrKb6iUNnN2SaT7^7)3W`6iX_GfzZV3Pooh4()T3MgtA_EjgyN(hAh11^AGUgiw1vGZ>*XKq@&54lNjM0cxufH!$1MMJ+c1w9qQt7Y%yREK+@<&v?@qo+T^Jlf?jY^3-n3T@jbF?g^e$hfNk%2ZFJNyB-9FiDnZoONYdgRx3PS5F;YM2j)@lpX}`7j1)(Pi#~x;VQw&@$nl%vRxh{=u)W7X9}Utj_J6%Rmyc$6=SdxV_fnksE1q*QAYWRAYgasP)U=n1Mz45MnRs5Z9A9`!9Rr$K3y8W|Weylvvh@G{FaKtpaRbg&fRQ0k!6V^2p@|Rz3$5sD3+{?YZUZ)ghib|vn?Ta#0qu)1%^#?lul1NJ5@+3a9F7Ke@`Eot(VUWv@bmRn@$udo}wCvQl@`jVABBe%)=i+Qqrg?AWjJ3NF%w`_G_1(>OKMN|tlBaI9K^`*-25fV%rSSG?e24DJm}Ck~Si=#U_hi%Nu0_R;1_)o58(r988`-lhFKo=O`dIsj`>o2z@r;aIQ|{r3jFaxe2J2l#0`YJo{Bi)&Of2@5r)zPukJ_`JSQDmyUM+x5*?`syIcES88c1FlS1;kx(9RObjX^o3QsF@LE;&PA{hxse&2vGT5p$`qx!MTAPXX<4DRFC+(;yja`l&c^(A08?1U_vDW^bj)g-iJ-(Ar1MGIs#G&rlRi2ohWDe(~>PKiACx>*g*>-h)n|($FS*%2(%_;bk6ivqVj;T8`Xq=2@h^$P7raLcRW7l^WeCH*c3)bl19@;T&An+y$bwSP(3UPZ@%Up>-dIxx{`0`q1Gx^#GPD%v!1OKrNdR6gRLkeVW0-~8VxUS#@xyUq#O`IhvzKC!euTje40OdziK@s6EOMnj?!C?VRegZAE!qgErrca5n0zsvgovOzl*Kwq_K$sK@pNPrTbm{b(s6_aQlH$j?)g`5dr^DbVXPLglTbJSkK$((745?Xgv94hwm@8E5s+vArHHnHpy*>c-^(c3RkkKQh@?JzQ|Nbxk`l1oN)HKWfj;NROkRf8Lu)lg!5|2pi!6sHffk=K-Wj@XIP=Z5>&A#s036WlBkwWPnw!(fg&Tmn6P8EsLt3;rtZsR0d6OpWYebjvvkRe&8iiQ530)UpcUnp?o9*?oW`wU3-Vd~~<)o1vtotQB0@Hy~xcumvN2;0{PM@swzy%b}y#bk9DtuwyN}C%9utE-RVbN_Fo(cA`z4C1#>h{x;$z6ZVCa`G^|jem0^fEDN#rv+AjmId2U&(MD!qCFd6%PRVGUcqgqNJH3_cq`34#f>#CG(9_2OAeLI97}Y3a3Y+&@@}8!v8HYOnE>I(G}8zjX8LUQmMmOR8Q8fcHZmFiqHG=Aj`Y!s!Tt)l`SKqV$fmJ24*wZ^O!x5z_PSRV~i(i0?!l6Q|dRqLX~@h=`PQ&D(={Qjj74Jx)K=ulHCp;AR#;FMbRT}457e42h!&0?n}&=n+>c}TrX?lRSX*$#?dWxd@5`ndkE4R#Dn>)}k^qVJH?a0HyHn_U5a_`)VKInG~poENOma!HhG{86*kg0tLWt5RwT_$rlKUcpx_*u_%D2tPH#$d{GN(JEW(^SI-Cm`jFbV~3z()A4B*^41x?>swwx_gFLD*zu7MK2n}-uiG!3MzF}jaXR|hdD_+VM7|NgiCm3cxyRfv7b2OhuFJmDSM1cVC!=qD-lI48%ws!hU@I^2Ce1!%^$2U5zq*h?K&KL}JTHs%onJ;q$z1;(FQ%*1BIX!SH~h${r>UVo}{!7$?TPqe(^(A?NyZa4gOjB;jLmv5OUnTLq3=r(PvEOn;kQZnDrT^J{y>c`^7$~=szNQv)<;}eFb9E8($H#n+|5H@^FaSw2}wmcI)z_)n9?z?!I*F!LdpM+z7o-gM~}T0nh0fXb?UMV0A{-}#EjyyGCVD&mO9ENgiftL|ZP$w_AgMi;kt%KnwUD(bNjMtA2oBpzbuM=;Jqwr^Ddvc#zYwnQ;zj|&g1k!6fDQB*G?@yO#Spk`u5M}WZvWN|M$TUH@8HvI_VNkY7rJio@yr%I%#>S2O0YJI#5EiQ~eqg!l*$)-Sqo~99vMk%7ItIY0MAd6B@_v}1jP|OXA#Ru$%J%EjwaBno206KAzMv|f!hsGHbVUly-U7k434bH=6u>5`=cnaR>B!Mj~kWVn0x3|sBm+)>tUbGx!6Xb%Ij1*8^v=fxy3SDO;6fOb!j5qpdgYm6p31GK%32xriZ;v|EwM34HbU}KnFeD{W~7u@`_>FSbB{(*I~9zg+xrZai+&h>Q8ce8-fh^q@yve-m0QXj{Fabf{ceSV&1qr}Hrn!Ql~&u8Zfg~`?p?Gl-=gpOtg|kZ)+o#F8qY0{GxDD}jwW>Rk||128fhr`>G_S7CD820UU!Nuz-Ooia|lzXLaMPiDMnfIbB-zp(3MXml=1=GA3dfO->Ia-nfidV%>R(K3oQIg|suhYiD7C=NUJ7+*ddO}ICIYrUeWAl4wS8Y{8%ks>Cm>R^%WEqy@C^<`n<4;CW21~jlZ95a+%m|Y^6v;PzV^nXMOp1N0hB4wV&1zPX8CL|gn%tP~*{Z^FIS8ce88v|ts^7RD#POF($XcC=RSNVkKoDMs&sABHic~9XY!YYg)1>^U9B2B5+iLN7w4GQzWXLyXX{40Kz*GjlvWi(z^Ym;QEed)XJ8)3K0+lMs3<-6`M?-;}wZ2Vmv6?9!MyA&JmP=_N!^1p_nv|W-B#Y9iF;6jq{4B_k(QG-h^Dim9xJL0M7?e(yFb-6g_;nXqRKR-R2_KfCsVg?%-UOx^Q#StR8*kl)G+0+4ju9KmL#M&5lyiW4PNn9+%9++pq7blpo{hJ#qQ<J6%Mz{j7v=;<5wEjW$rn1IRSB=mmUKG!>5}+bhT}fq|7y4@KHCcZN=XiYtX1C20$;U#r-XU7n{cIXkgXo#LwszUH7gJuO?fGOSc%GB$?S%Jqb2$aMcK4ms%*Knt(*$(#e-RB#inb$J`{H^O+KUXz;H;1+yGXZCX>km1y10iSd1R9H<|!IrksTk#v^i`kkKMj1qH+aow-qhbPc0$v_y%5CN}*r)h4MNoiVX~RkmB8lWxt3aUk#_V`?i7wt4t+8?6M2fMeBkWRp-i3O?w+eOHM$`J<0ySM@4OPSL!+*w%^k7RTAH`yd2m~F?99v0E2r2qyoyzO1{;aDM>N_e&_7wp%~p$VTF|1ps5eJYHO4lAcC?wOVyxaf(0YPIq0mMplz{m+L|$+5)ox#Bh6KOuvwnQrj=)V7D5@wP9oCyibYsWZj8F6$MN-7*(-Os)7*vn`e)1$!;yNL@O0r)`o1Xg>T6xDr1%eaqL@5#%(MOK!yRX&992}L^?wbgy2D#a`+)_&q^#tR~*LrBKr@7vp-x$XTHn)JiOt$&r-LIJpr&#K2u<~VK$wsKG2+mW)8O|89$R{L_B<)BF&mGlLmrBc@63uM6$w$VMbIKbGGo;R`Y&#dDn`K1=3k^Z4@CZ|T%th3l#S1YrBIify@o?NK4`k`-JADxiBgqh~q)FvpZLV4!gVVqq%4HOFLC@mxPLzPmLufERN0DD3j(t6*Iu1i7*EQ4>UI+mZ>JTVRDiGSX>{1F#dr8aXY2Gj2?UJa#7j1`k$WOU{&kz$ob)qLRPWVbZb_9gOT&^^&Z9b>3pemry6#96Xg`&K|oLmtq^#bwf~VK@sI^o5%&EzPydRVxq@2lFI~NjfG3c%1Vh{^%x~_T^zfWqhO6guR!!xO(^Uwqg9Wh{fvT&dz13b@*Mlx1Huh$A*>xm=f>cDns=wqGUm9>JSiGA@m(11rDZGVI+LVa$vMHgf)oxoYgo0cPl?*sEp8~3|tV=THLJ31Wl|KVMa`t;F@;rX4yL`hS)L`$yTKKz+4ciiXcw;3*<`Ky{bhbbjNZL>>cd6Q@?kXp-#pq$f4zApmjPmPI)0UdO9#zG~KVbjkWo(}c=hbsTW1=Y1fcz`tf1&yJmqGdBB#(;aIQZ2fiKF4&gD|`p54S1_!LelPM1(@sDP7LH=(?3yHI3KQ7{y9pg|o6SdRaD=z#;Ch1P*v5$pim-o+g#&-0?snigLSU;mu4Bv>T{HQI^@R`t{8hOp{!9yyA2b-@x<|w3-6fj=-MONWdQmz*CexxE>D|Pb#o|QDPz3@1U8Io=Xo5j4h8eLIi0GTF$FqbQsHPRmXC#sbDCaxAmTgIuo%WhTV}Wti0`ZNsgI#lORrQA*Q$qSRW~fxy5pM6QlW=S9aE*_nkwR?D;=9c$ApQf2TJXxTmMfkM+RI%jx90SyM=>NtX+2LPilW0{cT8yDruI}AFI#fj6MoIr+HCD+`8;o+cmw}wLqg)RFV8-e#3^6^6PRA)1;Uua%Q@-k2oMl6|{3}p!3#st8*(?toga;U^k^NpM)KbXe=q_G^ms!w&esQ!^AH>lYk>Am0_0a9xCH+{b6j)^i}CD*o6obT$}$PR^IE^jQZoQei>&Xr0?;2Oeg9RwOb3hv>Ga;tzO)C-sddvtnc*v?lcm<_30FepaYHDOM)Eu?M>R62{RX$g6;E6(cmZTz68<#oeZeI;pRQNxYoRdFhyJV^^nM^IV@T+V?)!HO(^cJXh4sTeFq*i)VO$C2kibKTua6ZrsC#*hvx9LRcWB{)y+g4RYLp4cg#teROugY}_7*o4CW~P)VAxE#cwke%fijZJl-7t&@`;dOF43@IL&Yvz$&Os1Aw+n4P0vyvxR&|MHR3-BH#b8ph1y#=h)RD1Vlxta@E@$|vwgqCfPZjWum8nhls|Oq`a1T7yv-3-@(?`bG?Z16w3PC69wAX&=v_xYo*B1iTQak5{)q`TrPV#Ah73n7db*})zFbWBziPqXl!kZuGyED}*{qbq7={y>!jriQc+v?^`qD}v**LlD!Ev9exacp5sVtfXH#+dr&YVTB2nJjPV+x>L0M86b`#@roN0X2uG)!wUAV5*F%rII)dQ*)ON}LWxBat)6d3;NqSt+V%e&?YO5)1&H3R&@+OVv(-ZG~v<{QkM}wL93Ocb{1)MW1xuwAw#<-LvD)p8%WA&d>Z;pw#@Kd-ldV?Y?0fi6c)}uk@U3|J3V+JlAaCrnW*TUWz!)!m}7GRRwC57OlmjG`vkY2#8z=SO_Jim|bHR@;b^LFpP3jb3`RjOU4QFFj)%sgA_CSdP%vkMV61N8XcMpVz*4RRbF}$4DVE-nSQd2olGc`bDX43JsT&*cvYK4D;cs-rTP@@O4a$1)*Nf9&=z-yIb4M~`k0EcGOx?t%9@Ip#l1*YNnxrU$IGwCHL<2zU$3t)83n%=X1c)0-FpJ!t@+j!6Dw}b?#JTHd5QL)4FYtndDoOUD&8EjhmC+!F{f1#gx2)M_`&r{kXWUS7I7-k7%KytZ163FHQjUmx*^d9%V@I)d0iZawU9y-#`<+Am+)rCL7wGJyvNpZY~S&tm4{cIxyxmR=!ef`e=^E{8RHW7@BoJR<{V_dv(_2?anXan=nsb@78Y;Y0ks3BMiL8sl_aBktV`JE9Zg38`bL09W{W5sN|_~e=_gFCcU9XR^Z7-C??zp1HcUYQ^|EwmYVJ{81%M$GjlwZJ3nS8F@Y0J0#^ce4X+Fb(luv3;2n@6Q4jt*kEPHbFzk*d)@53IzE1Ged&+FZ~?z`n#X&uUdu1h`~!ToeE|QrD_>Ei2>6nVH_Mj;IJaN$TQ2x2-zFd7tl&$o-Y#EKB_)8y41VAa4f4Y+vSD0W`*rKJnhlGR9(n0x5PZA6{jBCaga1F+uX$Vb-NUEWGd{vx^@GjkQ)izOL=Snix!bIH&7D2?r@4&}KigIhqZw&@1h))oUli25_fJsq$~>$B&`V|kkcJxBn#SsdU=G7BBl^Iv*Hl5vWmNOfVuuT*Z))BvbTpp^LpDvmPB7Q@{gZw;NkZ>kmj>74$P6>k2x$iVKx%T$>%TsQKm1-eS;j$H1BG`UC-9$S9t;!eAdTL2EUvEJQOiZ@aD)zdIIzR5#m5@PEe^lk-rBmk^?YylaPje}K8$HFqHefzxQ#7ud+`zeY0tDm;KBpl6`S8_q4<10Ug(m=_lQ{NO7V!}^#ClFq64ZqE;}Oub2sB(Qj4(-2AUXnBHT_nu^eGL7$iQF@IxuMCuuxh(?9jiBM)~x}+iAi+>C0gf<3v0NTm)~_8lRCBHyj}G1eE|t+_xIO0K5RY>RZi&r+6AQ4)K&8jqt_OTJ-?WwnpKk=9R~3t{(^A$9`~XoMDs>F(obcntQ+Z4u4-$x6y=~*s3*mcWd>7{ip7DJG-C)s1cp>b2pFI1^3+Cg=@xtYW7FG8~&Hx8Ux_^<6suWcj`D1Zh1)DJ?K-YiywL0^}Rj%mclGjWEv>}1tz=9V8)YhBIKPOv#+1Km@BBln?5>{K>A<#I*IQd0S3K)LT|GJDgh`MquYtTBhXY}L4HPnMb5lC0@6Ka)JOnHU=2rJy~(8M7uHCQJb$}*P|*xn1E6FYfBXIk>aIuhO(VOR{Nu-2T>Je_8$NpQah`qu4N4{;nB3mp)^B&}Kvx>gt*tG5_#2NP^!slB6Tk8J#Q2Zj?{r|0aTFuh2lerrx%w)+T&9C%%mK-Fi}3qz#%Sye;L92pnVE#@(F|kCg85C5!2`44qe5_84HE7NIrRiro5BgA*)PKrtAN2otuOfM(nKRWypYV~I)?2^a^OC+X~nKO(RG@mqg;ckB1o1F~NIum=~Ed045D&)sbCXq*g}c*fNOZ>dA!=lq)}qar_aFv&7!ez60a0wxrVWVIv~@N(oCKN*h+HFs(2a?T{a{_4rq3T?qJVOA#buyLM(8HT)PNlvS;$e{%W!j|h`)gqraITYinCD?}JHY_Xb(x3d9o@^xgb~?Xz}wPn1jHhgicl!3KLggZvtRks&fZgR|FH6>!~LhU*Hw%VXj}CHY=+e=esO^7a9*J~6xi~a4Fk%aTz&VcT*aXv+8_a90g$aRPhZbC4*8o&U}JO;!-e<}x|EM}L3<`>h88)})D8^&)J66~#Puz`1Qd*pNV2kV1!dbZ)>rU)k%k?bL0I}!v*{!6+2UhkyWVuD8dInoAsfX*T3UU3JDeF_Z6W`1f&N#f6uhQUnk;;#{s_*?^xGZYYQ>F+5B*nnnL-Z)eSvlkg0y&f7y|m?&JfoE-tHMuPa)btbLD0Jd6{9MbYDvi+%|0x=C`3ByK}?X%_2+<95i=jXhXQ+9`WwKWf_UCUf(^7*H1w~ZHhpx#bANEjV|2+d)8r|uDJu`Bf3ua9Kyel?U@qI6S#Ngy)9pwxix$`D4G8rZcVhdX$9njG?9>!IH>Ba!dp*TR+3zrIeRyH$5phG{w!kgT2y%q~6(V^QM3*1J(UvWV|&M;j2c&(8bmC;_SOT`Q9$h4RF)kX!4yscXr7%Joa7<%|EQQT7tA*{&r1IkG(dG85jj3DDEetw93_4W*Y?U{Uum7VAqS&HXj4bg&eYILZw#P&<8R&`g6-&S+%R|+xs}!_VBC?pr%FGU>+42lZ2`nzfzB;5iwg|oNk_YB0<)LMh_DFj;QY8-9(&jckRbU4@d)5pDgbb-nQbFk-A9*NXTVSVPXRle*5Sz`%()9Ngw0;z~jIoVrCyfKAy!3@W7Zz`t`=x^^-!rnVO)h%RxoDnnpm_C(P{IWz36l%bukzLmClyc`lk-_4IBrCimW0$Rn&E{T-k{6^3WleqzxuUqx&A2hEpCuYn8g05`-TBTT>vda( \ No newline at end of file diff --git a/scripts/.brand_bundle_parts/part-06.b85 b/scripts/.brand_bundle_parts/part-06.b85 new file mode 100644 index 00000000..b5c8ce68 --- /dev/null +++ b/scripts/.brand_bundle_parts/part-06.b85 @@ -0,0 +1 @@ +(^@2XyMiJYhjSq40mK)EQn_xvA#&=Jn8s~jOyLV}?$?^zJGJd++qL@cuB=oKPrbi>T?=$ZBz50f4+3>LbglXrAgeS$-4W_9vc|8t3*L}^g5?fZw8|xX712d_uZ}1p6Ggx*mYDjTeR*VeG14zzJU2P3JH{5;T}f+Y|Cc!Tbj|U*M9?vHb7Z_wr2t5jsWFBCAZnTm)n3Co+@LkS{|9_P>!(gp?Jr8B+MMt_i*%QY`}U}yy6P+LCc*nj2$XdpcVzhbaiM*oMpG7?d-7KYUi1N)lLOiIXgkABo4A1>k?wr#VQ5cCVU4cjex@6ucoZu*S;i~!(R<2#_ScMuOsQ{&J0s^c!Bpsr$3Eicz#1%`=HvygI3eXvist{`5SkiXO;(#3XZ9tX94Y@gT_rnTLb<9SY0dMa-tL^X9b`NTRjeFHIt<(fJ;yfE08H%&suQeWQq%<~-+%f?&2T-4*4Z2{)(dTdDzTurYn5d_c-EQ7P7s?JcP*W_A^bqHxb62{cIj}=Y*!P9KX4LoDDZyzo$P$?!rLTon0o@hWn62>XTs^ws5b3Mewo0SgmGT5ka-50;pC;0nsTb4pe0g6m;~B{ao}q#5_XvIA*duz9$lNvk;aLm2F{J!GJoy^=O@+qa@z(BV#-1WRg^emS|huvZRsZ#J*`)&8J&;zuvl4`(?Lk|I7Z9{v~@J011#FB|GWaot~5_5;y=34$cb)2c>>2*QVWIEd^zTEmJf+T*JuFYCOi^i|R(6c^A(d?_tx-1%w(&DHw-Mys#7lS03bT&ACo~pw^yDh1FSJ#rbM;*whtKtGaYxR<8NKYT{>2%{B+vRRR`P^6)j5L8%9-Qz+6+dgNcY+_B-X%B?8&Z#w>Uw5)oCnwU|uPsr;EOi@x|Goh0Vzmo+OR0S`ETvZ!T4WW#?L5b-efQ1D8EPIu%*MZA`jB_Iy5GCaJpmd~xj;Re`<-8gx!`#oi+0i_rw+)@U-J_}(%(Zi_Cw!xU8?8awj)zyAkzrDm#g2BUb4-P*lb0|1lmr}Ng5;cM!IK~FX5kG=+@I3IS~tqjk+mBECDs#B1ZsV!k*Q^S0UPH28lT;=gM+=^XCbt$@XS08?-xL*JuXhiCQA!oY})-zuc1H4o>pop1h?8Q%RsVvn6I~DU4%V7MwhjW_4oK_0m^5EAC|LmCV8@Qtz(60(Hi61RuO?QNO&Q}M&8pkkOQ<*kaPC<6$S)9LC#lDL^UAbi7%ewZImW+OFYpR|@t))7x8C#_HE#Da{xjD3b(;(ETB~;Yf#vRi&Rgq@B9}kB&^!)K3vGrALPUG`I$eR%*KZ5Vo?zif|%KP=at5Lf=^vWiwt9eW+E|&@+>+4F5jZyrgmGt3v*w?M{2|W|_%54*^3g&ez6Y6?POWnB*)m<{$gp+abm6xQSni&c!3{F{`(McsmQT6Y-!=MhDH>n2aBRm9I2IQ2=i#;TbTYCS5eIS1gT}7M8$G6x0wE89WRq3n0qOVSpuW=RNSNm}q&h1p?k5NG`a!!}=lBe3(R7;9%${0zNXRU2GlGmxEeJzEQg_?82%Z>h(!K`(DjrEn#YS-2|VQ5ao`el_PAr|;BWc&Q1#kCety;546I{4Taro85ssHfQBqQh2~W}UYn!g~EtSsIaPBlTOW1$E;$*sX52)^0UISd;PO>(T^7$YFz>ztSLf6{oG9L$SIO>AHpIR>`UN{}-8RJxd$4$wW8!nooVMHufkeUvsp63(~8x|5hlfkr5lWIq{*@yq$kWugV0ze(e1H8rhjAZeeRbom|uHkCJ*t2D5eClxvJ$NpE&r@-{@Mss?dsCmqdSE6g`f)pi-_AW*fJ=I{e^-EU`#_)<+V^yO&Tuyvf`gp5#TAC625%#NMa{<(Ugr+X#G3+n|FCD4p^IAW*jwzI+EZ$B^_8yEVF93Gh^VYG%WYL8J1gZu%U2MBNJk*rxBv`X-?`GeRBTV=#p|%B<^rf}I;lvhoRLwX=(!Ix{t*_~UE?^tVl>F1yH)0)&N%m!nRa5DT~spTpacVtRaeglN1W-*r5Gv4Q%F)d8LfLhVjXaPo^f3Ur=1|w<1n4Ng66Y=PD$@_){Wz+@>(^R(kdUfX&xFoI6P(2n+~1anqMpEt3A=7k%yZo4Cm#Bl;@R3@`gaP>CawO~T1hOXR^$MUaU|*rtM5g{IikLWtD)Dev-^6hcF^x~N#%f!8fGt>hh~sx+D-^a1nwC^7?7rZCcnW8VBUZn@D*bpBahJ%nP{SA*fJ)m^PR@0_l&@|{x8&HID-8a8iXX>+6s&4x5T0Ij@#vYLmKe8ek<7q6JCvOCo%EGyo_Mm>S+7(5B+z0)oG)#GjQ3)KbdMODv_Do%ClYeg68v_;bCr5Uj*_eOa|3l)UY5|nf92^nNK)2d4Uz%!`Wl|qWBH$*%sB|Wz&c?XQ+`2$t|YH&rZ#R4>>&*qpzdSTDA;pHI1q?8oAiehG%Ex;MT%Jw440^6xM+s7}7^TPgy)TTd5j1B+NtBJT9lG+#2Q)LN<@D@I73OI%MxzHxt})!MWkJ!TZW-?&_mcXc#a(is48E?}{g`B@)X0EVK!VF1fMbNnaQJHwWq`sIA0LtkD2XfA+&xA}T?0O}1fdG;5+R28H6FEi^#1-iU4LlhS08XXozWnZ~IFQ&g<;)RTlfDuhVrRV^056G&I)joJ_=!aM<^)ckee|?)HxH+MnL`@y_eMxJv#Gm;j}H%at-XV%{atS4Xy=V}bliL1Kf;?C4)*(2|Mkw(KHfzHua6aw(uyIF+@me%&+g81x)OxJ9lz*XqB*?B7Qz0sbNs@>_jJQ#0q=}-HGn1W1>D&`IzD{0*WW)rVqk}__K$Y}1^2Au{yud6u+Jj^Tpbl~@P;`7N7lC-lx*9*ec7#N2ZwvT-JQQN;`aN`d&epc3t$xLRDtH<1Rd}8M8EhF9jcG0I+n9QcS01f#6@uxXg>Ds5HGyh?K8mVy_W<)T>g8sN57u+c8~Bz(8B^G-33nT_as5^RhSiMy;2<;};B(iJ7R!`u)HEZ%f{`&d()^rhB~@C|<7g8mW&*%D|xiFZVZL+jOm-{LIU06L5Mt;myc-hH7wYQ=(tWroD7|-XTYFTgt}KWRlH)!ta|8Br6qStFH75Dj2e);q?f#XD1RiXzWj`1t)7dc2ukeKeKJfkQ!lR!c6gKm}YLOCL-NT7&tZ|L2c6Lgwk`)~3{d^?AXq$MXy?a6r8%pC4X1c$FkdX()j1*)dQ*=qumxa+GR`ER3>m|MKD_n|EgEe39);mMsE+$tPt|RWBhFW7t0I7o3{IiLQTwMb=#9tl;W!-|0zCaNp^m8WE>ri?9D}I+|2Im&b+$TUS7`*YNKOI)Cl8t*;qo30b0&Qd%A4Np?1xyaY0Dsc$bp3pt*OUX^izAE=w^2gYj+HS+Z(EuhBhw#7$q?=-|V-a9ddC%k1|n^yBM()pm&C@ddZ(=T^mCFcV`LjfV@Ah|lel9Yna56U0&#%}I_`9_Ro+l$~dYo&V=F(%i*5RIeO4CH|5OhGCP7kk%3Un2}4Bhfa2bI&u`pD4B53j?MtmPnk9jbmky8#^ZwDf5A$?kJr0)DKDY2Z?sE$qQ}(p(zhq#s5TAgVl*7$wV-N$6sl|FPgw96jbH^?>UX>^eLAnh^KiP<)&{iZR}o9rm5SKN`-bc8*2AqC3*Hv6sIZqynpZyps?H{7v;d-YR*moHL&V!sTWA}P$Wn$s4;e5%-cuKpl$Pn_jE2%0ftE`}H(|ue*dOTSFVbJAJ0t4{E3zIvk_LHEf;1sS4o{Pf&Qz@PXF04NDN~(wU;N(8>yuPwX0jZx^MmWvK<|Shc0humtW`0jm^~1JbC@y3ccED93Yk59Z1ZpcwP6YB7wTL5E>1KWXf`BBbWi#o|02%{4;GCz!=FpkERg6hSN~KTC^-3J?_F!&E!0Z+uz`j!~8yX)M#0AqT8bMo+h}ui=3k^aLwQK{Fp^+d3gQ5|8)wzM%o<_|j1wyo_K*`N-SRvHkYJd0^$6G-J7UtcRjGUkR@0bmZsKW-_&6*){t8jiO@4>JOB*D`Sg`op6gb5)jcz>f(MkW<@t!{%(f0CXLrQrRg9&x@^8Y%@=G&KZE`XC9pc~3f@FLSTDm1RsPyx!@5tc_cj)zaUrh>4c3P81E-2KJ62~LolJ*KZgEaziMC)It+*(sgEI1+#8#_Z26T}8ZfgFaNxJ;r-eW(@t{P$M1aZonS6+UzwQ*F;!d$q|j&eGEs6noRg!f)HGY)kbX4ZdMk66V7z>BTD)Rn5CKyp{zD6;&%RLkRZpT7QYPhF976w}bNb-6$>zYq4B^YXQ*EWnTdZ;i|T{;&pSyQi}~R`OBS=6^AHXtHS`cw`v?z^Lj;1;{=(T3|FGgH@B!)e4|0p(Nh&#`93L$E??76>R61r2n};(=zYNlwDvsOM2SN4f!gb?Jc5g#qHi_$V*d`@vi>bvNmEL-=BwN3loh%Co(@Fq)o^usmr5@?FeYR@SvFhz%%FdAe#szAti4b@PBp%#UI(ReI>777$=O_k4K!(<9>hG}uo0rXC4)NaT-QoQ+tGR~$QsRwdH3M+KLfa|A&^Hz&9BjEMB!|bYv7Af(|%E-7{$=yNx7KhV98wZBNrEtEyvhyU54nzkE1;+hgdF(`i*_Te2dqijOhp6N}nH2J8;VVJef)U{}ptBrqTkmiSaRFW8wwRWZ&9$Hi%KpT14SjbEnfSD4}tcB-fv)x$j)AW9|K%C9EpV@mk$!*S0yk>TiGizGgHuWy5%Oed%HVN4mql2%gQ5QxFy*|Pa9=yY^tDuAaBMr{|6%0%R~bq5uOv=h6n<^EbwL8*3n+hr=Rhqr@cH&XKxUk7*qM%+x%M;Y2PSr{S66hn}Afmz9q?TGZ+$b66?s%NN^T=9yz1KaCu^N`0$WA&CexnmsfzXas_;NNJHo6QUy)T5Z%8=1%FfrP*PpQNbXS=sNh?*ATtH2JqS9HY5{V_z_7Vt;5F!d*i-@M{y81CLKOx5f-oLU&RX0k=#rbASvowzj^mNn_Z!ap4Nz4WP(jUI{KQA*RSFgxs;G4+3q^`dZsV~c*EEdHvrpSJ9ipI(Gw=h?iMjY8O*b*7)A4jVZ>cr6sOF|tm8_6_`echdh3#UxBy65CIeHVHcgZmutF7E9gJz`4fLi72jm|W+t{#-a;qryZtL!5$uJ6CB!|`rD0%|3ek)MPnfnBgWsF+mDwqhTH*>9vO8|X7Gf6LW_sMdR$)Gy!X*~P-Rbfep=1W_rNFTKck?QSSR)=Gr59^S0PCTmf}axoFOVlm;*hV>+k5?dM{TNZBZNo%^L*d*{tA%K!+z+f{lwjb1Z9So*Kxq?ZAqfEHVT69Yx7(6{IWsfpoxtOrLziuZq1vgZn@v`B`Da-$OmHam@2;B@~NPcy*Gf{9+*=ww5_`NqfM~Wn@Q%wpP7&Z#QJ;0jLxVo)k4HB@%U#3K$FoLgEdere&yc)@S$l>)(ZY~jWx;mI?^v>6sTc3kwsq3B1|QHQr`r1Dq~!HT-(kEuEr9du8IaWp3M$*;uO}$gY$~*A+*A%t*6)Do4r(9|RLFxhA=5fJ$!p5Uzsj7GAGWed-L}-huG||}>ZwCnSx;iT4YLZhKYUoFM$j^5=M%M{E#)RLC}zL9i184GIMgB7GXME&r{8(n0ml4}8lV8q+QMBhIsToayjGeqGUE1M!b@3gdaFFE}-i$@`Sut4FfB$6qdDdv3%jX(v(8NhgI(1_8ktms$Ay*CH@x6rR-gQzR!&ksRV{~34REle!6S#(BFVyWQ!4VuLF8`k}=*3Y^X7fs>5=_ciG20HC(ST%TGK@dB&zSrfIaEQp>Naw5PNY%sbbbVb%SYDO;rM_vQGd9e37tpxj$i{Hfq+lkREqhb9<_`IYQiBIFxVs0^xRk`9s6Tcwv71WQYWGI*3~{0mwVY3_1PBHm6#@872jU(4!r_!;i4+-60tu?nG#xh3M$KpD|U?}5B$JNj!IPugUo5mR8iG2inZF+?1h_(xc|%k!B6}7VN)UWd;3p!p3)g#veJ~}riJ{kU{(#Na}!;;F3|0+qYa=}u*`-{9OfmSTr~2RJD9TTFc>;2c?b3)edGC-ooVAl-7j&%TQerwDy)<7EfGAbuqcw6iF~JQDE=r(?gv^NHf}b%n+8=;J!Sjk)z0oyex$R-{FtAjHqcEWj?iRkwh}{y9#$^E%Bo{)J6UtxDMi;JB{1lM{qAIT@JU?<|$O5iD7o3``Yfcli&uE>OkBy82g&hXe{H-SyV@Y)iP90OClb04W%r39hitYWBDr*wPW@lxVUsZsUk6E!}Z?oz1nqs4teh;y2%hrNN?ADIJU=?UKGAZrXJqFjgRkAu?T<;N%Ob0Twc*99Ai9N(me@tOi(JSM3*PTrt4G%&Ry09vGvo;4bvL$uq$-Ld7~#N9JE5_6s9Zb^EH!exway7jR~d3dOHY7Y4=*)Kwx_c_@lp478thQZfe~3ewml7l@mtW=XQxqzQ)J@S4=HqRC<`V__why8uSr~4wC3k}KOh38Dxty#=af|86cXDf1%d8{y?o$4wLhk7)kaChW=F$P8OBG|815)ADVK!K{I0cM;GPHJCC_otIgSw+{%Jkrg7M`bfN+!4u7~+}tTS+{V>0p~r?r`46jC8ieGQF7P9TZvU@(?Ej{F2Y`b4xV4S({S^N7$k~Z+hIbM8qOT0Vlpg{;43R6cMc_(4!i8+;@F4J-wui+~cq_B?Dc#FXDCQ@5;PTI5WTBlnD++CZ*%OT}*QEkn^@b{{9O#0XqE6!!7aZA?6vCA6)u?FFq=;OLI`oKO{V*naGU!$m-4K>2>FHKD}t=IQTneH9X43bb)bijC_buA+NZ3hX~@_mml75Y^qrorzZo(8ABdMA^@4R>8345jQ23yr$jmJ;>Dgw#{Pc8_JbcRyzs#eJbK7eBQDQjEuT-vm`06b#YmQVQXGRA;ap&wf`Q5DF7!$9hVUY1tRo)skDF!wht}3OW8!LMpE69HAqP`Rk4ay(#F!$94=(#n$rJ#CgKS*l30y?#h+0<6RYZ>GwHMjlSr0;AUeB`Wsg~a}9S-|Ap4|~h49fNK2vfsIyuj2p7Ki0C0MwmjD3Vnia@*A9`j*#kG;gom2DTz_VdxuYh{L||Q=3x8FBZTl^=z#&m0X^>wSFS*Gz~YKR9hyx{WMx!rsKs{@8!$hVek0W(a*i3qyF(x=VG{Vf8#r=H=Ct%x`yF3m9%;bSa=H@?swmT>I%FEM>?Hfa4WyP?l-7s+jiw2g8P`t_HE#vljUeJ8)XB~wLYDF#Af=vr+a;iZsM2@M(NnbP*+fwEVVQg{1}{c-P9cnoYP@)61#pnNWC}>Pfvz^2>*+`6VLB@Y3y~=lOQ`C#DgGmqsTouaffb@CfM2mXv(7r;9xeN{sPO_`b4}NzC5AJKmf;QnKhk^uOC>GDcDlh`3Thk+VBBmdpbXZ+5K0V>@DaR5DTW$ng5ETj>0gBlR-G>#slxiVaia~g!1f8q|BN4-C_#@QKsfsqU|YLgT#A0AXE?qCM^Wjq4{D$tP!CI*TgfBJ#1sa@Hvb6;(Rn~TbBz;)v~lP9afu&IA0TU%uhXBSm_+N*2H>;sG%EYwo+VwKnau4DXeoq(RA|hL#yfYW=E`6uVeMbOG<#vZ@#mhDa!=ky-eqWbDEjOU<7|gK#%%b`aD{mLtkMc1=cRIba4qx3g;5|*?=hp55-uVg1b0FW1$Zfu~3AhSa_oekN_yhOS+AN5wSea(AEJA{Qi&s*_R8zVrv3l{-?s3@K?cB_|A_!U47^GfBY{w+HIJ%6Hfb0O9bC8Q!qVSO4jE3?fyf2my&_-p2J=~&lTpJ;AWdXAXI23%sUIKX!>$n9>N)vxrAWcdpp(JTi7u!k3pA9Udjrq&RI7LWZBkTugchj?ZIy(m#L+%k+ImK9k)S;pLI+8PVwqQLVUtAS1{kUXsaDtb29&X{F84(xRGDXwlF{{D3;1CiK=pOwuF)70W%a@6S3Phh>oMf=nhqz8idl+`Cr+}ZcNk#XNL+nt;x3r$agq|+M7#_}2yi9Sz^LvJ91eziT3;gy1_`z-#`Ad|41riPk7?#MKTu29#SBp&0n*@ArA<#V_+ZSH2hqt%CAQ*g<_>q#uUc9o!B^B@qy_Us$*`ZGSeq*a>gMF0N0M`ft9$-z^$0dS=BZPnn6CWu#_UVPzP!QGvb-NO34m4N~sILHhxFxV=MEW_}Zwk^A73axnl*{jVw&X{cX%}y~Zn?>3-IO@56Ky8)kyK~G^$Zh{Gz@Pr9?kIlIFIiHnC>>bfu3l%^#eagt{V#Uy1s{iA8tt)5+?g47$P~i_opKoJ%X}b=LdMaE*@n+U5|d|_~hRON=~!m%lXOWm~;oU4WSH}RD)$YmJOVwi|iTMRTJuLC&tcBE-wzC)Ta!mgbnT~iuqP6FfV4nh$kXtHX>@0mn>?Yf}W1kaGE^>3%4g*qziVi1NirK-lip&#=|W`3u^OE0DnVnlGTOh$b&~9E8wMMd$s&d06qi1}^iwTs2#dzSCLaJ?XT9XZJh-}_rp6EBZx&>UjCe)RB~UuK`bqx97Uu0%h7qUv+6VjMFWR-yY$95Ir7RY>@Q4d=`La#g(@H9{Vj4>q-P_?iW0%EkeNJwc6hLVymRz&r%#(tPY!$gPp$sb9oj{7T+qg#UOkL3#wHF9cb@O;_jaw<{q5s}L%a^FxrnsF-*}mXydI?nC@@G3thnV7)Wa$A=L{3LKPFxD|NIYBM-?!P=A2Rrg^hilLjPZY3ByKiK`Ki{bQL)WlgM&iZ?j=d$m?t*DdAS&Ae}wRQ_ZU%ewh-35?`u>mIRjHb+%mx{(7F(?OL$nLG6w~Zwa>D(6wA2bePz;lCI@;9m|iZMC>U%tpM$MFq9F@D7iOYvC9GGrmih4*@5EW5E=;0aAszH#jE&RB)kQ(8Zh^Fh_t<7?0l#w{iOXZp=M_USWsBt`QQ`!q|qP``(2c*)SXScdjVRYC*!CKoNO<~fk)#I`SP9z9d{jnYy*wqPlvEJwnHfD>>=?b;TVt-gnNOH_q>i{9MPVK6dR9Y&-TMTz_{;6;|P8vdq`$}5(9|X_G4Avp8b)W(J^yIU*U{$=~fs}+!gExh7O;VgeSeHy_d%nSy98gx~}A0UKbgn$T|RwZ+o|QbcBfq@cCfxW$&h9$tJ|-vT9b?E>IOvAk^Ay&~(08dtUPGnxi(THE!6@oa=qV(eYzrdr@3%uSu2cs61;8i^~54pe6>jdo3L=H1b;jL_%{5Q=*t@-lc1eP?}r(j;s^On46PM9;BZe=q0)@AdpK2rc}Dk|>JCt`DF0JU_I82sp6oLzUaMT4F2pjTjcn(^_HV#1=E7GVg?0YtMaKd14l7%L&>dlEg&7Bh^B@2i=5u3Uc3ye7QfMx+zgUAj++ujELmP?N1+u)b{r4jV;82_4lERZSO==xE=cCUc0rTy2k;D4KpMjIqi`G8k=4ana6=n)iDVF4T?;ic3l$LYIYa=cuSEkJCM-Zc75Fwl2%%BX8ypMN0_Y4V1hN0OW)d!Q>}CxwKqbk8c0K4v07JFAHtMhN2D_C2DuBvRpNIk|3@Td;y#`L>*?>OR_JI~1_z=LT{ecZu4%UOwz>xO>2gJNbxV1vthoRX)XeWCn2Yy#`v8XixBeC}>xd4DIi~R}vas-e0>#`#5%NL&|*N+{ea_)CL6P@&T`}^B3&rw3O54`IV2J3#+Rr{)htrF6@-OHMsP;KYO#QPxgV(*-#L~N^`ng@tdl7_96KNWU8wFoI5U*?Ma{Wg23e>Vx|9X;0Ozx@PZ8yc#uGX7GUPvnKKf&qdscb&igw(~9FM40wVDVMLlD8l?(2C}OlMK&uD~>XGrCAl%@>XClXm?+}=xMlAJMRXGsr%&b`X=4?ye>H_Z$AQY`Ui$^{Pt!4Y3&ev&o$0~`>lrHC(+%8u)DL}-#@}h*xNbQ$yrF@M#A>Jc=OvaXtT4pMk+j}K2|13{Z;WddA;FMIZ0^E(=yFv$RO99UZtt;@oIJzr*uN$h}@4w#Z|5Q~?lHjiJ1IO3k(+M6U!ITt&tP%o^~qA>qCsnd0Y+lsfK5MMUYPy?AYiVMfg<1=E@;%tD=0Az?#9TFgZNjP!}jy3Xp3Eio>uubI46O*haQpja|~iXJq^rN6~c3p)lGWF`+(}ORUS9x0bcQlSI15=xI)-&>akL&%8tI1}VBg0?QHqMG3z4f)ovKeC@)2V1p-j*_AFVpi-;ur#RSiT?=g|e=qjN;Ma;qirV|(MHx`5Q>>_=?foDasv-z@G!FI)w5h#2<{X773_GgS4T+s1>`*<@Jf0a7)&x`5l@^d=ReuiZsOTp^zgq;Eu#e@AU;=x4oIiD%Q3yTV3Kv+~90?!Zj0vj1Jh@#>GMg=TyU>B>EW3Kb_FIW$KlU$RKR*KEmiq0_<#aqf!)VeOj8O0!5ypv!IZip^^&DAGiv7f>SbQ;`UY?zE2<+v_7(|2!=gzKX<8;DNrrMyHx2Q(`esSTtGYjj2V)9FY>wJh*A=}he-7$yJ#cWJL;Vgkqr{nSTGln?hLReg4gtfY7p{h_EwF(;GuNX3J?JPM$bpdKrA%I0}I7fS{k={y>wv^7+QAT_qD>g)qUO(sHZ9WXa2O~HTTv-!_V-NaL)p4Pm>Cg^ySkqIv_QhF`!4zZs2iQ7t5m*a|8p%EvH0aSnJp)oX;G@PykIsOt0UtvGCk=)}^ORFS`mq`1tPJ7oBs)((ji$gzZT+lJM-m<-D){hz0)IDd{x3x^ivs \ No newline at end of file From 111a8fe0599dcbf2003a1ba8f7bc2389e6846f13 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:31:24 -0700 Subject: [PATCH 21/37] chore: permit intentional Markdown hard breaks in brand gate --- .github/workflows/apply-elenchion-brand.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/apply-elenchion-brand.yml b/.github/workflows/apply-elenchion-brand.yml index d19c8af6..71db6a64 100644 --- a/.github/workflows/apply-elenchion-brand.yml +++ b/.github/workflows/apply-elenchion-brand.yml @@ -53,7 +53,7 @@ jobs: assert item['source_page_url'].startswith('https://') assert item['source_file_sha256'] PY - git diff --check + git diff --check -- . ':(exclude)README.md' - name: Install exact validation environment run: | From 63353ebbe23de02c782f343e7c078c9ca9ef67e3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:32:54 +0000 Subject: [PATCH 22/37] brand: apply Elenchion Apparatus classical-scientific system --- .github/workflows/apply-elenchion-brand.yml | 83 --- README.md | 384 +++++------ THIRD_PARTY_NOTICES.md | 23 + docs/brand/BRAND_SYSTEM.md | 112 ++++ docs/brand/CLAIMS_REGISTER.md | 36 ++ docs/brand/NAMING_ARCHITECTURE.md | 63 ++ docs/brand/README.md | 43 ++ docs/brand/SOURCE_PACKAGE.md | 46 ++ docs/brand/asset-manifest.json | 77 +++ docs/brand/brand-system.json | 52 ++ docs/content-provenance.json | 258 +++++--- docs/index.html | 329 +++++++--- docs/styles.css | 544 +++++++++++++++- docs/system-field.js | 673 ++++++++++++++------ docs/visuals/APPARATUS_ASSETS.md | 29 + docs/visuals/apparatus-frontispiece.svg | 124 ++++ docs/visuals/elenchion-mark.svg | 11 + docs/visuals/visual-provenance.json | 18 +- scripts/.apply_elenchion_bundle.py | 70 -- scripts/.brand_bundle_parts/part-01.b85 | 1 - scripts/.brand_bundle_parts/part-02.b85 | 1 - scripts/.brand_bundle_parts/part-03.b85 | 1 - scripts/.brand_bundle_parts/part-04.b85 | 1 - scripts/.brand_bundle_parts/part-05.b85 | 1 - scripts/.brand_bundle_parts/part-06.b85 | 1 - scripts/.brand_bundle_parts/part-07.b85 | 1 - scripts/.brand_bundle_parts/part-08.b85 | 1 - 27 files changed, 2194 insertions(+), 789 deletions(-) delete mode 100644 .github/workflows/apply-elenchion-brand.yml create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 docs/brand/BRAND_SYSTEM.md create mode 100644 docs/brand/CLAIMS_REGISTER.md create mode 100644 docs/brand/NAMING_ARCHITECTURE.md create mode 100644 docs/brand/README.md create mode 100644 docs/brand/SOURCE_PACKAGE.md create mode 100644 docs/brand/asset-manifest.json create mode 100644 docs/brand/brand-system.json create mode 100644 docs/visuals/APPARATUS_ASSETS.md create mode 100644 docs/visuals/apparatus-frontispiece.svg create mode 100644 docs/visuals/elenchion-mark.svg delete mode 100644 scripts/.apply_elenchion_bundle.py delete mode 100644 scripts/.brand_bundle_parts/part-01.b85 delete mode 100644 scripts/.brand_bundle_parts/part-02.b85 delete mode 100644 scripts/.brand_bundle_parts/part-03.b85 delete mode 100644 scripts/.brand_bundle_parts/part-04.b85 delete mode 100644 scripts/.brand_bundle_parts/part-05.b85 delete mode 100644 scripts/.brand_bundle_parts/part-06.b85 delete mode 100644 scripts/.brand_bundle_parts/part-07.b85 delete mode 100644 scripts/.brand_bundle_parts/part-08.b85 diff --git a/.github/workflows/apply-elenchion-brand.yml b/.github/workflows/apply-elenchion-brand.yml deleted file mode 100644 index 71db6a64..00000000 --- a/.github/workflows/apply-elenchion-brand.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Apply Apparatus brand payload - -on: - push: - branches: - - work/elenchion-apparatus-brand - paths: - - scripts/.apply_elenchion_bundle.py - - .github/workflows/apply-elenchion-brand.yml - -permissions: - contents: write - -concurrency: - group: apply-elenchion-apparatus-brand - cancel-in-progress: false - -jobs: - apply-and-validate: - runs-on: ubuntu-latest - timeout-minutes: 25 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: work/elenchion-apparatus-brand - fetch-depth: 0 - - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.12' - - - name: Apply digest-bound payload - run: python scripts/.apply_elenchion_bundle.py - - - name: Static syntax and rights gates - run: | - node --check docs/system-field.js - python -m json.tool docs/brand/brand-system.json >/dev/null - python -m json.tool docs/brand/asset-manifest.json >/dev/null - python -m json.tool docs/content-provenance.json >/dev/null - python -m json.tool docs/visuals/visual-provenance.json >/dev/null - python - <<'PY' - import json - from pathlib import Path - from xml.etree import ElementTree as ET - for name in ('docs/visuals/apparatus-frontispiece.svg', 'docs/visuals/elenchion-mark.svg'): - ET.parse(name) - items=json.loads(Path('docs/brand/asset-manifest.json').read_text()) - assert len(items)==3 - for item in items: - assert item['commercial_use_allowed'] is True - assert item['review_status']=='APPROVED_PROJECT_ORIGINAL' - assert item['source_page_url'].startswith('https://') - assert item['source_file_sha256'] - PY - git diff --check -- . ':(exclude)README.md' - - - name: Install exact validation environment - run: | - python -m pip install --upgrade pip - python -m pip install --require-hashes -r requirements-lock.txt - python -m playwright install --with-deps chromium - - - name: Run showcase validation in clean copy - run: | - rm -rf /tmp/apparatus-showcase-check - mkdir -p /tmp/apparatus-showcase-check - rsync -a --exclude .git ./ /tmp/apparatus-showcase-check/ - cd /tmp/apparatus-showcase-check - python validation/validate_showcase.py - - - name: Commit reviewed product files - env: - BRANCH: work/elenchion-apparatus-brand - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - test ! -e scripts/.apply_elenchion_bundle.py - test ! -e .github/workflows/apply-elenchion-brand.yml - git status --short - git commit -m "brand: apply Elenchion Apparatus classical-scientific system" - git push origin "HEAD:${BRANCH}" diff --git a/README.md b/README.md index 71ed3c46..738789b2 100644 --- a/README.md +++ b/README.md @@ -1,307 +1,247 @@ -# Evidence-Governed Research Toolkit +

+ Apparatus frontispiece: a calibrated classical profile inside an armillary instrument, with botanical and measurement annotations +

-**Modular research software for evidence-governed AI-assisted reasoning, verification, evaluation, process assurance, and adaptive complementary assistance.** +# Apparatus + +**An evidence-governed agent and research system by Elenchion Systems.** [![Research software validation](https://github.com/Kitahl/The-Gauntlet/actions/workflows/validate.yml/badge.svg)](https://github.com/Kitahl/The-Gauntlet/actions/workflows/validate.yml) +[![Fastpath checkpoint](https://github.com/Kitahl/The-Gauntlet/actions/workflows/fastpath-checkpoint.yml/badge.svg)](https://github.com/Kitahl/The-Gauntlet/actions/workflows/fastpath-checkpoint.yml) [![CodeQL](https://github.com/Kitahl/The-Gauntlet/actions/workflows/codeql.yml/badge.svg)](https://github.com/Kitahl/The-Gauntlet/actions/workflows/codeql.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Version](https://img.shields.io/badge/version-0.5.1-informational.svg)](CHANGELOG.md) -> **Research status:** public research-software toolkit with executable runtime checks, evidence-bearing structural/source validation, and exploratory benchmark pilots. The repository does **not** claim that the complete system improves human reasoning, scientific discovery, or general AI capability in prospective deployment. +> **Research status:** Apparatus is public research software with executable checks, typed evidence receipts, bounded benchmark pilots, and an interim Hermes-derived runtime alpha. It does **not** establish that the complete system improves human reasoning, scientific discovery, or general AI capability in prospective deployment. -**Demo:** https://kitahl.github.io/The-Gauntlet/ +**Website:** [kitahl.github.io/The-Gauntlet](https://kitahl.github.io/The-Gauntlet/) **5-minute evaluator path:** [`docs/EVALUATOR_QUICKSTART.md`](docs/EVALUATOR_QUICKSTART.md) -**Benchmark pilots:** [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) · **earlier blinded receipt:** [`benchmarks/results/2026-08-22-blinded-pilot.json`](benchmarks/results/2026-08-22-blinded-pilot.json) · **BrowseComp four-way receipt:** [`benchmark_runs/2026-08-22/browsecomp_four_way_results.json`](benchmark_runs/2026-08-22/browsecomp_four_way_results.json) -**Runtime setup:** [`docs/RUNTIME_SETUP.md`](docs/RUNTIME_SETUP.md) · **Mirror:** [`docs/MIRROR.md`](docs/MIRROR.md) · **Mirror onboarding (legacy FOIL paths):** [`docs/FOIL_ONBOARDING.md`](docs/FOIL_ONBOARDING.md) · **Deep calibration:** [`docs/FOIL_DEEP_CALIBRATION.md`](docs/FOIL_DEEP_CALIBRATION.md) -**Research statement:** [`RESEARCH.md`](RESEARCH.md) · **Reproducibility:** [`REPRODUCIBILITY.md`](REPRODUCIBILITY.md) · **Roadmap:** [`ROADMAP.md`](ROADMAP.md) +**Architecture:** [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) · **Runtime alpha:** [`docs/engineering/PHASE8_USER_CLI_BOOT.md`](docs/engineering/PHASE8_USER_CLI_BOOT.md) +**Benchmarks:** [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) · **Reproducibility:** [`REPRODUCIBILITY.md`](REPRODUCIBILITY.md) · **Research boundary:** [`RESEARCH.md`](RESEARCH.md) --- -## Why this project exists - -The practical problem was simple: **there are many powerful research, reasoning, search, coding, verification, benchmarking, and review tools, but using them well means repeatedly deciding which one to call, in what order, what evidence it actually produced, and what still has not been established.** The Gauntlet is an attempt to make that easier: one control surface that routes the work to the right specialist method instead of requiring the user to manually orchestrate every tool. - -AI-assisted research can also fail even when the prose is persuasive, multiple agents agree, software tests are green, or a benchmark score is high. This project treats those signals as **evidence with scope**, not as automatic proof. - -The core research question is: - -> **Can a modular, evidence-governed reasoning workflow improve traceability, verification discipline, and independent usefulness in AI-assisted research without confusing confidence, consensus, or passing software checks with scientific validity?** - -The toolkit routes work according to the **epistemic obligation**: what must be proved, searched, executed, measured, independently checked, or left unresolved. - -**Mirror** is the adaptive complement layer. It asks what the current task requires, what the user already covers, and which load-bearing capability is least covered; it then requests the smallest useful complement from the existing Gems/tools. Mirror is the public name. The technical ID, `/foil` command, `tools/foil_*` modules, historical benchmark condition names, and existing FOIL-named files remain stable for backwards compatibility. - -## Exploratory benchmark pilots - -The repository reports positive, null, and mixed/negative results. Earlier blinded pilots compare GPT-5.6 Sol `BASE` with the same underlying model using a frozen **Frontier-Exam FOIL + Mastermind** pre-commit procedure (`FOIL_MM`). A newer BrowseComp ablation separates `BASE`, generic `FOIL`, `FOIL_PROFILE`, and `FOIL_MM`. These are historical technical condition names retained after the public rename to Mirror. Because the conditions were executed in one conversation, items are deterministic **disjoint subsets**; these are exploratory estimates, not official submissions or isolated same-item causal A/B tests. +## 1. Identity -| Evaluation | BASE | Assisted condition | Delta | Status | -|---|---:|---:|---:|---| -| **HLE public text-only subset** | 1/6 · **16.7%** | FOIL_MM 2/6 · **33.3%** | **+16.7 pp** | blinded CI-scored pilot | -| **ARC-AGI-1 evaluation** | 4/6 · **66.7%** | FOIL_MM 5/6 · **83.3%** | **+16.7 pp** | blinded CI-scored pilot | -| **GPQA-Diamond** | 9/12 · **75.0%** | FOIL_MM 9/12 · **75.0%** | **0.0 pp** | blinded CI-scored pilot · **null result** | -| SimpleBench public subset | 3/5 · 60% | FOIL 5/5 · 100% | +40 pp | legacy manual pilot | -| Current-evidence retrieval holdout | 0/5 · 0% | FOIL 5/5 · 100% | +100 pp | custom mechanism holdout; not a standard benchmark | +| Layer | Public name | Meaning | Compatibility boundary | +|---|---|---|---| +| Organization | **Elenchion Systems** | Working company identity for research and engineering instruments that test claims | Naming decision only; no claim of legal incorporation, trademark clearance, or historical lineage | +| Product suite | **Apparatus** | One agent runtime, one evidence-control plane, and a portfolio of specialist instruments | Repository remains `Kitahl/The-Gauntlet`; the installed command remains `gauntlet` | +| Product principle | **Evidence before authority** | Models and tools may produce observations; claim-native methods and receipts govern factual warrant | Existing task, obligation, receipt, verdict, and release semantics are unchanged | +| Visual language | **Scholarly Antiquarian Framing** | Classical, natural-history, and instrument imagery paired with modern evidence panels | Brand language is never evidence for a technical or scientific claim | -**BrowseComp four-way exploratory ablation:** +The previous public names remain technical aliases so existing commands, state, receipts, tests, files, and citations continue to resolve. The migration is presentation-first rather than a breaking code rename. See [`docs/brand/NAMING_ARCHITECTURE.md`](docs/brand/NAMING_ARCHITECTURE.md). -| Condition | Correct / n | Exact-normalized accuracy | -|---|---:|---:| -| **BASE** | 1/2 | **50%** | -| **FOIL** | 2/2 | **100%** | -| **FOIL_PROFILE** | 1/2 | **50%** | -| **FOIL_MM** | 0/2 | **0%** | +## 2. What Apparatus does -The BrowseComp result is **not** evidence that generic FOIL/Mirror is generally superior or that Mastermind is generally harmful: there are only two different scored items per condition, several complete pre-commit blocks were retired for contamination or execution-integrity reasons, and the exploratory scorer uses normalized exact string match rather than the official BrowseComp LLM judge. +Apparatus treats a claim like a specimen under examination: -**Do not combine these rows into a single headline accuracy.** Samples are small and the evaluations measure different constructs. Null, negative, and mixed outcomes are retained because the research question is whether mechanisms help, not whether every benchmark can be made to show an improvement. Several math/error-localization pilots were also discarded when BASE saturated at or near 100%, rather than being used as non-discriminating evidence. +1. **Frame** the goal and create explicit load-bearing obligations. +2. **Route** each obligation to the method that can actually establish it. +3. **Execute** models, tools, search, code, and verifiers through a bounded runtime. +4. **Record** observations and evidence as scoped, integrity-checked receipts. +5. **Challenge** the result through process assurance and independent review. +6. **Release** only the conclusion supported by the latest valid evidence state. -Methodology, exclusions, sources, reproduction commands, and validity boundaries: **[`docs/BENCHMARKS.md`](docs/BENCHMARKS.md)**. +The system is intentionally asymmetric. A model may propose a route, call a tool, or draft an answer. It may not convert its own activity into proof or release its own claim. -## Architecture +## 3. System architecture ```mermaid -flowchart LR - U[Researcher] --> O[Research Orchestrator] - O --> F[Formal Reasoning] - O --> D[Research Discovery] - O --> S[Method Synthesis] - O --> E[Engineering Verification] - O --> B[Evaluation & Benchmarking] - O --> P[Process Assurance] - O --> A[Mirror: Adaptive Reasoning Complement] - P --> O - A --> O - F --> O - D --> O - S --> O - E --> O - B --> O - O --> R[Supported result + explicit unresolved state] +flowchart TD + U[Researcher / artifact] --> M[Mercury Runtime\nprovider · tools · MCP · context · sessions] + M --> O[ToolObservation\noperational output only] + U --> X[Axis\ntask framing and obligations] + X --> P[Parallax\nadaptive complement and advisory routing] + P --> I{Claim-native instrument} + O --> I + I --> R[Canonical receipt\nverdict · scope · evidence references] + R --> A[Aegis\nprocess and integrity assurance] + A --> C[Conclave / Obsidian\nindependent review and adversarial attack] + C --> X + X --> G{Release gate} + G -->|supported| S[CLEARED / scoped result] + G -->|defect| E[ISSUE] + G -->|insufficient evidence| Q[UNKNOWN] + G -->|missing capability| N[UNAVAILABLE] ``` -Full architecture and evidence flow: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). +The governing path remains: -## Research modules - -Professional display names are used for the research portfolio. Existing technical IDs and slash-command aliases are retained for backwards compatibility. - -| Research module | Technical ID / alias | Responsibility | -|---|---|---| -| **Research Orchestrator** | `soul`, `/soul` | Frame, decompose, route, integrate, audit, release | -| **Formal Reasoning** | `mathbot`, `/mind` | Proof, logic, probability/statistics, counterexamples, formalization | -| **Research Discovery** | `scoutbot`, `/space` | Literature, prior art, existing software, cross-domain terminology | -| **Method Synthesis** | `novelbot`, `/reality` | New mechanisms only after known methods fail a named constraint | -| **Engineering Verification** | `codebot`, `/power` | Architecture, implementation, integration, execution, software verification | -| **Evaluation & Benchmarking** | `benchbot`, `/time` | Baselines, capability measurement, ceilings, cost, stop/go | -| **Process Assurance Framework** | `infinity-gauntlet`, `/gauntlet` | Frame/process audit, stale-state checks, inherited-number checks, false-green defense | -| **Decision Preflight Protocol** | `meditate` | Grounding before consequential decisions and after failures | -| **Evidence Review Panel** | `council-of-elders`, `/council` | Selective independent evidence/method review with matched control | -| **Mirror — Adaptive Reasoning Complement** | `foil`, `/foil` | User/task-specific missing-capability support, multi-stage calibration, and independent-transfer tracking | - -Every `skills//` directory contains **`SKILL.md` only**. Hooks, executable helpers, state policy, profiles, and benchmark harnesses deliberately live elsewhere. +```text +runtime tool execution +→ ToolObservation +→ claim-native instrument or verifier +→ canonical Receipt +→ Axis release gate +``` -## Executable runtime +## 4. Mercury Runtime — Hermes-derived operational layer -Version 0.2.0 introduced the portable runtime. Version 0.3.0 added adaptive real-work deep calibration. Version 0.4.0 added a reproducible structured calibration layer for previously unknown users. Version 0.5.0 hardens release security, privacy, reproducibility, dependency identity, and cross-platform CI without changing the technical `foil` subsystem's Layer 1 / Layer 2A / Layer 2B architecture. +**Mercury** is the public name for the interim runtime contained in `gauntlet_host/`. It vendors the exact pinned MIT-licensed Hermes Agent source and runs it as a Gauntlet-owned isolated subprocess rather than as a separately installed product. -**Version 0.5.1** is a research-repair release. It replaces the technical `foil` subsystem's non-monotone competence count rule with a Beta-posterior estimator carrying evidence tiers and a recency weight (`tools/foil_evidence.py`, characterized in [`docs/FOIL_EVIDENCE_ESTIMATOR.md`](docs/FOIL_EVIDENCE_ESTIMATOR.md)); makes the assistance ladder, execution-ownership axis, and gap vocabulary generated contracts that fail a test on drift; states honestly that the frozen-run tool budget is a tamper-evident ledger enforced only under the PreToolUse broker and advisory everywhere else; replaces a lock that was not a lock with real kernel byte-range locks; ports the V2 routing kernel into `tools/foil_policy.py`, where the routing regime comes from task properties and a benchmark name is receipt metadata only; and makes the language model itself a configured capability via provider-neutral adapters. It closes no efficacy question — retrieval and personalization quality remain `NOT_MEASURED`. Full defect disposition (D1–D11), the not-adopted list, and the corrected vNext evidence boundary are in [`CHANGELOG.md`](CHANGELOG.md). +### Included mechanisms -- `.claude/settings.json` — shareable Claude Code hooks using `${CLAUDE_PROJECT_DIR}`; -- `.gauntlet.json` — configurable governing files, audit budgets, optional evidence-ledger policy; -- `tools/gauntlet_monitor.py` — stale governing-state detection; -- `tools/gauntlet_boundary.py` — Stop-hook `frame` / `costume` boundary checks; -- `tools/gauntlet_hook.py` — Pre/Post tool integration; -- `tools/verify_ledger.py` — optional generic evidence-ledger commit gate; -- `tools/openrouter_bot.py`, `tools/blackgem_runtime.py`, `tools/snap.py` — optional model-backed independent review; -- `tools/foil_profile.py` / `tools/foil_hook.py` — Mirror's persistent profiles and prompt-time domain/facet relevance adaptation; -- `tools/foil_assessment.py` — Layer 1 blank cold-start domain questionnaire; -- `tools/foil_layer2.py` — Layer 2A structured cross-cutting stranger calibration; -- `tools/foil_calibration.py` — Layer 2B transfer/adversarial/real-work deep calibration; -- `tools/foil_domains.py` — expanded non-diagnostic domain-relevance recognition. +- provider selection and OpenAI-compatible model access; +- dynamic tool registration and tool execution; +- MCP integration inherited from the pinned runtime; +- context and session handling; +- operational memory and skill mechanisms with write approval; +- retries, interruption lifecycle, and delegation primitives; +- typed JSONL parent/worker communication; +- repository-bound task identity; +- `gauntlet` one-shot and `gauntlet chat` entry points; +- observation recording followed by the parent-owned release gate. -Runtime state is written under gitignored `.egrt/state/`, not `.git/`. Model credentials are environment-only. No private workstation path or project-specific keystore is required. +### Verified checkpoint -## Mirror profiles and multi-stage calibration +The recorded FAST-P8 checkpoint reports **8/8 bounded boot checks passed**: the command started, the isolated worker started, the model responded, a runtime tool executed, canonical task status was read, an observation-only record was written, the Soul release gate ran, and an unresolved task was not reported as cleared. -Mirror contains no built-in profile for any individual. A first hooked session creates a **blank local `default` profile** when needed; named profiles support multiple users on one installation. The implementation retains the `foil` technical ID and existing FOIL-named paths for compatibility. +### Current boundary -Profiles are stored outside the repository by default and record evidence metadata rather than raw prompts. Topic or facet mentions can change routing relevance without changing competence classification. +Mercury remains an interim pinned-runtime alpha. The checkpoint did not qualify paid external providers, automatic claim-native execution, autonomous replanning, task release, profile-based complements, dynamic tool narrowing, cross-platform operation, or behavioral/cost benefit. Runtime, model, tool, plugin, memory, and session state remain **observation-only** with respect to factual authority. -### Layer 1 — broad cold start +Inspect the frozen source, attribution, and checkpoint: -The onboarding screen includes: +- [`third_party/HERMES_SOURCE_LEDGER.md`](third_party/HERMES_SOURCE_LEDGER.md) +- [`vendor/HERMES_SNAPSHOT.json`](vendor/HERMES_SNAPSHOT.json) +- [`docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md`](docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md) +- [`docs/engineering/HERMES_FAST_P8_CHECKPOINT.json`](docs/engineering/HERMES_FAST_P8_CHECKPOINT.json) -- 20 generated objective probes across quantitative reasoning, formal reasoning, probability/statistics, causal inference, software engineering, systems/reliability, research/evidence literacy, scientific method, security/privacy, and planning/decision-making; -- context/goals, work-style preferences, self-estimates, and confidence calibration; -- open design/UX, creativity, and explanation tasks; -- dynamic setup/usage domains, including arbitrary custom domains. +## 5. The ten core instruments -### Layer 2A — structured cross-cutting calibration +| Public instrument | Existing technical ID / command | Responsibility | Returns | +|---|---|---|---| +| **Axis** | `soul`, `/soul` | Frame goals, create obligations, route work, integrate receipts, govern release | Supported result or explicit unresolved state | +| **Canon** | `mathbot`, `/mind` | Formalize and test mathematical, logical, probabilistic, and specification claims | Proof, counterexample, measured result, or unresolved obligation | +| **Atlas** | `scoutbot`, `/space` | Search literature, standards, prior art, repositories, and current sources | Source set, nearest established class, differentiator, and search limits | +| **Crucible** | `novelbot`, `/reality` | Construct a new mechanism only after a named constraint defeats established methods | Candidate mechanism, assumptions, failure modes, negative control, verifier plan | +| **Forge** | `codebot`, `/power` | Implement and verify software through real entry points and defect classes | Executed checks, output hashes, coverage, and untested limits | +| **Chronometer** | `benchbot`, `/time` | Design matched comparisons, baselines, ablations, uncertainty, and stop/go rules | Decision-relevant estimate with exclusions and uncertainty | +| **Aegis** | `infinity-gauntlet`, `/gauntlet` | Detect stale state, false greens, inherited numbers, scope errors, and process defects | Assurance findings and integrity events; never cosmetic approval | +| **Sextant** | `meditate` | Establish facts, assumptions, unknowns, options, blockers, and value of more computation | Decision preflight state and bounded next action | +| **Conclave** | `council-of-elders`, `/council` | Run independent seats, commitment/reveal, cross-critique, and controlled synthesis | Review receipt with preserved disagreement and scope | +| **Parallax** | `foil`, `/foil` | Identify the least-covered capability for this user and task, then request the smallest useful complement | Advisory route, complement, verifier requirements, and stop signal | -The stranger-facing second screen adds: +Every `skills//` directory retains `SKILL.md` as the public reasoning contract. Executable state, hooks, receipts, profiles, and verifiers remain outside those skill directories. -- 24 objective micro-scenarios in standard mode; -- two observations across 12 cross-cutting reasoning facets; -- a 12-item short screening mode that cannot classify a facet from one response; -- confidence calibration and self-estimates kept separate from observed performance; -- open design, mechanism-diversity/creativity, and explanation tasks that remain rubric-reviewed. +## 6. Additional and candidate systems -The objective facets include formalization precision, decomposition/systems reasoning, error detection, evidence discipline, causal/quantitative reasoning, implementation/execution, planning/prioritization, metacognitive calibration, transfer/adaptation, verifier/tool selection, and uncertainty management. +### Obsidian — adversarial examination -### Layer 2B — adaptive real-work calibration +**Obsidian** is the public name for Black Gem. It freezes a candidate and attack rubric, runs independently provenanced breaker seats, performs off-diagonal critique, records participation, and can raise an `ISSUE`. It structurally cannot produce `CLEARED`; failure to find a break is not proof of correctness. See [`docs/specs/BLACKGEM_ENGINEERING_SPEC.md`](docs/specs/BLACKGEM_ENGINEERING_SPEC.md). -The saved profile then drives a profile-specific plan containing: +### Strategist Candidate — mechanism planning -- changed-representation discriminators for uncertain/gap hypotheses; -- harder transfer probes for apparent strengths; -- adversarial/error-detection checks; -- real-work/artifact samples; -- design and creative production; -- explanation/teach-back; -- verifier/tool-selection probes; -- confidence-before-feedback; -- domain-specific follow-up. +The archived mechanism-planner candidate contains bounded minimum successful-repair selection over a declared finite repair universe. Its recorded hardening and inherited checks apply only to that archived candidate; it is **not** promoted into Apparatus authority or runtime by the archive. See [`research/postbench-candidate2/README.md`](research/postbench-candidate2/README.md). -Open-ended outcomes only count as verified when an appropriate rubric, artifact, proof, execution, or independent reviewer supports the result. A perfect Layer 2A screen alone cannot satisfy the deep-profile real-work coverage gates. +### Formal Plane Candidate — mathematical execution hardening -The personalizer is an **experimental onboarding/calibration system**, not an IQ, personality, clinical, diagnostic, aptitude, or employment test. See [`research/FOIL_PERSONALIZATION_BASIS.md`](research/FOIL_PERSONALIZATION_BASIS.md). +The archived formal-plane candidate strengthens trusted-base minimality, isolated qualification, staged dependencies, process cleanup, and deterministic numeric thread limits. It remains an engineering candidate and does not replace Canon or change the release gate. See [`research/postbench-candidate2/README.md`](research/postbench-candidate2/README.md). -## What is currently supported by evidence +## 7. Evidence ledger -| Claim | Evidence status | Where to inspect | +| Statement | Current status | Evidence path | |---|---|---| -| Process Assurance hooks/tools are portable, config-driven, and state-isolated | release-gated source/runtime checks | `validation/RUNTIME_FOIL_MASTERMIND_AUDIT.md`, `tests/` | -| Public skill directories contain `SKILL.md` only and private-lineage regressions are tested | release-gated checks | `tests/test_skill_layout.py`, `tests/test_private_leaks.py` | -| Mirror Layer 1 saved-profile/questionnaire mechanics enforce conservative initial classifications | release-gated tests | `tests/test_runtime_tools.py`, `tests/test_foil_assessment.py` | -| Mirror Layer 2A has blank-session, answer-isolation, assistance, confidence, and no-false-deep regressions | release-gated tests | `tests/test_foil_layer2.py` | -| Mirror Layer 2B mechanics enforce transfer breadth, independent verification, duplicate protection, and multi-domain maturity gates | release-gated tests | `tests/test_foil_calibration.py` | -| Mirror structured-calibration falsification history is preserved | audit record | `validation/FOIL_LAYER2_MASTERMIND_AUDIT.md` | -| Mirror/FOIL research-integration structure/source/regression checks passed the recorded validator | **94/94 PASS** | `validation/FOIL_RESEARCH_INTEGRATION_VALIDATION.json` | -| Mirror's frozen behavioral-contract cases are represented in the specification | **18/18 PASS-SPEC** | `validation/FOIL_RESEARCH_INTEGRATION_BEHAVIORAL_CONTRACT_VALIDATION.json` | -| HLE/ARC/GPQA/BrowseComp pilot score receipts exist under blinded question-generation/scoring harnesses | exploratory benchmark evidence | `docs/BENCHMARKS.md`, `benchmarks/results/2026-08-22-blinded-pilot.json`, `benchmark_runs/2026-08-22/browsecomp_four_way_results.json` | -| Public claims have a machine-readable provenance map | implemented | `docs/content-provenance.json` | -| Mirror improves independent human reasoning in deployment | **not established** | planned in `ROADMAP.md` | +| The ten core contracts and portable runtime checks exist | **Implemented / mechanically checked** | [`validation/`](validation/) · [`tests/`](tests/) | +| The Hermes-derived alpha completes its bounded boot route without a false clear | **8/8 checkpoint checks passed** | [`docs/engineering/HERMES_FAST_P8_CHECKPOINT.json`](docs/engineering/HERMES_FAST_P8_CHECKPOINT.json) | +| Runtime observations cannot directly create canonical receipts or release tasks | **Architecture invariant with executable checks** | [`docs/engineering/PHASE8_USER_CLI_BOOT.md`](docs/engineering/PHASE8_USER_CLI_BOOT.md) · [`.github/phase8_verify.py`](.github/phase8_verify.py) | +| Parallax profile, onboarding, calibration, and routing mechanics exist | **Mechanically checked; efficacy open** | [`research/FOIL_RESEARCH_BASIS.md`](research/FOIL_RESEARCH_BASIS.md) · [`validation/`](validation/) | +| Exploratory benchmark receipts include positive, null, and negative/mixed outcomes | **Exploratory, small-sample evidence** | [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) | +| Apparatus improves independent human reasoning or scientific discovery in deployment | **Not established** | [`RESEARCH.md`](RESEARCH.md) · [`ROADMAP.md`](ROADMAP.md) | +| Elenchion Systems and Apparatus are cleared corporate/product marks | **Unverified; no legal clearance performed** | [`docs/brand/CLAIMS_REGISTER.md`](docs/brand/CLAIMS_REGISTER.md) | -`PASS-SPEC` means the specification contains the required decision behavior; it is not a behavioral execution result. Benchmark pilots measure model-output accuracy under particular benchmark protocols; they are not evidence of human learning efficacy. +The repository does not combine unlike benchmark rows into a single headline score. Passing source checks, green CI, model agreement, or persuasive design are evidence with bounded scope—not automatic scientific validity. -## Quick evaluation +## 8. Quick evaluation -### 1. Clone and create an isolated environment +### 8.1 Clone with the pinned runtime ```bash -git clone https://github.com/Kitahl/The-Gauntlet.git +git clone --recurse-submodules https://github.com/Kitahl/The-Gauntlet.git cd The-Gauntlet -python -m venv .venv ``` -Activate the environment for your shell, then install the exact hash-locked development + runtime environment: +For the combined runtime-and-brand working branch: ```bash -python -m pip install --upgrade pip -python -m pip install --require-hashes -r requirements-lock.txt -python -m playwright install chromium +git checkout work/elenchion-apparatus-brand +git submodule update --init --recursive ``` -### 2. Run the reproducible public checks +### 8.2 Create an isolated environment ```bash -ruff check validation tools tests benchmarks/harness -python -m unittest discover -s tests -v -python validation/validate_soul_gauntlet_public.py -python validation/validate_showcase.py -python -m compileall -q validation tools tests benchmarks/harness +python -m venv .venv +python -m pip install --upgrade pip +python -m pip install --require-hashes -r requirements-lock.txt +python -m playwright install chromium ``` -For interpretation and evidence boundaries, read [`REPRODUCIBILITY.md`](REPRODUCIBILITY.md). - -### 3. Optional Mirror stranger calibration - -The commands retain their `foil_*` filenames for backwards compatibility: +Activate the environment using the command appropriate for your shell, then inspect the entry point: ```bash -python tools/foil_assessment.py start --out foil_assessment.json --responses foil_responses.json +python -m pip install -e . +gauntlet --help +gauntlet chat --help ``` -Complete and apply Layer 1 to a saved profile, then run the structured Layer 2A screen: +### 8.3 Run bounded validation ```bash -python tools/foil_layer2.py start --profile default --mode standard \ - --out foil_layer2.json --responses foil_layer2_responses.json -python tools/foil_layer2.py score foil_layer2.json foil_layer2_responses.json \ - --profile default --out foil_layer2_report.json +python -m unittest discover -s tests -v +python validation/validate_showcase.py +python .github/phase8_verify.py ``` -Then generate the profile-specific Layer 2B real-work/transfer plan: +The FAST-P8 harness uses a deterministic local OpenAI-compatible endpoint. Passing it reproduces the bounded alpha boot contract; it does not establish external-provider or behavioral efficacy. -```bash -python tools/foil_calibration.py start --profile default --out foil_deep_calibration.json -python tools/foil_calibration.py status --profile default -``` +## 9. Brand and website system -Full instructions: [`docs/FOIL_ONBOARDING.md`](docs/FOIL_ONBOARDING.md) and [`docs/FOIL_DEEP_CALIBRATION.md`](docs/FOIL_DEEP_CALIBRATION.md). +The visual system applies the supplied classical-scientific package as a restrained interface: -## Research methodology +- parchment, ink, slate, bronze, oxide, and verdigris tokens; +- editorial serif, technical sans, and receipt-mono typography stacks; +- original procedural armillary, botanical, geometric, engineering, and calibration motifs; +- clean modern evidence panels separated from archival imagery; +- explicit source, status, and boundary labels; +- no fake seals, founding dates, patents, accession numbers, museum endorsement, or antique-looking evidence receipts. -The repository separates: +Implementation and provenance: -1. **Generation** — candidate reasoning, methods, code, hypotheses. -2. **Evidence acquisition** — primary sources, formal derivations, executable observations, benchmarks. -3. **Verification** — a verifier matched to the exact claim and failure mode. -4. **Assurance** — process/frame audits that attack what ordinary candidate review can miss. -5. **Evaluation** — strong baselines, matched budgets, ablations, uncertainty, and negative results. -6. **Human learning** — assisted performance kept distinct from later independent ownership and transfer. +- [`docs/brand/README.md`](docs/brand/README.md) +- [`docs/brand/BRAND_SYSTEM.md`](docs/brand/BRAND_SYSTEM.md) +- [`docs/brand/NAMING_ARCHITECTURE.md`](docs/brand/NAMING_ARCHITECTURE.md) +- [`docs/brand/CLAIMS_REGISTER.md`](docs/brand/CLAIMS_REGISTER.md) +- [`docs/brand/asset-manifest.json`](docs/brand/asset-manifest.json) +- [`docs/content-provenance.json`](docs/content-provenance.json) -Planned behavioral comparisons include strong direct AI, static rules, adaptive Mirror (technical/historical `FOIL` identifiers), Layer 1-only vs Layer 1 + Layer 2A vs full Layer 2B, module ablations, native verification vs same-model critique, and Evidence Review Panel vs matched-evidence direct control. See [`RESEARCH.md`](RESEARCH.md). +## 10. Compatibility map -## Repository structure +The public rename does not alter the following stable interfaces: ```text -. -├── skills/ # specification-only modules: SKILL.md per directory -├── tools/ # portable runtime helpers -├── benchmarks/ # blinded benchmark protocols, harnesses, permanent receipts -├── .claude/settings.json # project hook wiring -├── .gauntlet.json # Process Assurance runtime policy -├── research/ # research basis and source records -├── validation/ # deterministic/specification evidence -├── tests/ # runtime, privacy, layout, questionnaire/calibration regressions -├── docs/ # architecture, benchmark, runtime/onboarding docs, public showcase -├── .github/ # CI, CodeQL, benchmark workflow, Dependabot, issue/PR forms -├── RESEARCH.md # question, method, baselines, ablations -├── REPRODUCIBILITY.md # exact reproduction/evidence protocol -├── ROADMAP.md # evidence-first research roadmap -├── CITATION.cff # GitHub/software citation metadata -├── CHANGELOG.md # release history -├── CONTRIBUTING.md # contribution/research mechanism standards -├── SECURITY.md # vulnerability reporting -└── LICENSE # MIT license +repository: Kitahl/The-Gauntlet +command: gauntlet +runtime: gauntlet_host +state: .egrt/state and ~/.gauntlet/runtime +skills: soul, mathbot, scoutbot, novelbot, codebot, + benchbot, infinity-gauntlet, meditate, + council-of-elders, foil +commands: /soul /mind /space /reality /power /time + /gauntlet /council /foil +verdicts: CLEARED | ISSUE | UNKNOWN | UNAVAILABLE ``` -## Research integrity principles - -- User authority governs voluntary goals and actions; evidence governs factual warrant. -- A citation must support the exact claim and scope being relied on. -- A green test suite certifies only the properties it actually observes. -- Multi-agent agreement is not independent verification by itself. -- Novelty and absence claims are scoped to searched evidence. -- Negative results and failed mechanisms are retained when they change the credible search space. -- Behavioral efficacy is not inferred from specification correctness. -- Benchmark improvements are not generalized beyond their exact protocol and sample. -- User-profile relevance is not competence evidence; one miss never creates a permanent weakness. -- A deep profile requires evidence breadth; repeated success in one narrow task family is insufficient. -- A structured questionnaire may accelerate cold start but does not replace real-work and transfer evidence. - -## Citation - -GitHub exposes citation information from [`CITATION.cff`](CITATION.cff). Cite the exact release or commit used. A DOI will be added after the first evidence-bearing stable release is archived. - -## Contributing and governance - -See [`CONTRIBUTING.md`](CONTRIBUTING.md), [`GOVERNANCE.md`](GOVERNANCE.md), [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md), and [`SECURITY.md`](SECURITY.md). +Brand aliases may be removed or changed later only through an explicit migration with tests, redirects, and receipt compatibility review. -Bug reports, research-mechanism proposals, and independent reproductions have separate structured issue forms so evidence is captured consistently. +## 11. Governance, security, and citation -## License +- [`SECURITY.md`](SECURITY.md) +- [`GOVERNANCE.md`](GOVERNANCE.md) +- [`CONTRIBUTING.md`](CONTRIBUTING.md) +- [`CITATION.cff`](CITATION.cff) +- [`REPRODUCIBILITY.md`](REPRODUCIBILITY.md) +- [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) -MIT License. See [`LICENSE`](LICENSE). +Code is released under the [`MIT License`](LICENSE). The pinned Hermes Agent source retains its upstream MIT notice under [`third_party/HERMES_LICENSE.txt`](third_party/HERMES_LICENSE.txt). Brand names and original project artwork are presented as project identity; the MIT software license does not by itself grant trademark rights. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 00000000..1f9e25b0 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,23 @@ +# Third-Party Notices + +## 1. Hermes Agent source snapshot + +This branch includes an exact pinned source snapshot of the upstream Hermes Agent project as the internal runtime source under `vendor/hermes-agent/`. + +| Field | Record | +|---|---| +| Upstream project | `NousResearch/hermes-agent` | +| Pinned commit | `5fc308a70719a83cccdbba4c0e39c23f5a8239d5` | +| Local source identity | `vendor/HERMES_SNAPSHOT.json` | +| Adoption ledger | `third_party/HERMES_SOURCE_LEDGER.md` | +| License notice | `third_party/HERMES_LICENSE.txt` | + +The upstream MIT license and copyright notice must be retained in copies or substantial portions of the upstream software. Elenchion Systems, Apparatus, Mercury Runtime, and the other public aliases in this branch do not imply endorsement by Nous Research or the Hermes Agent maintainers. + +## 2. Brand and visual assets + +The initial Apparatus frontispiece, calibration mark, and local Canvas2D system field are project-authored assets recorded in `docs/brand/asset-manifest.json`. No museum-hosted or third-party historical image is committed by the branding change. + +## 3. Working-name boundary + +`Elenchion Systems`, `Apparatus`, `Mercury Runtime`, and the instrument aliases are working project identities. This notice does not assert corporate-name, domain, or trademark clearance. diff --git a/docs/brand/BRAND_SYSTEM.md b/docs/brand/BRAND_SYSTEM.md new file mode 100644 index 00000000..a40e8396 --- /dev/null +++ b/docs/brand/BRAND_SYSTEM.md @@ -0,0 +1,112 @@ +# Apparatus Visual and Marketing System + +## 1. North star + +**Museum study room × scientific instrument bench × modern evidence console.** + +The historical layer supplies cultural depth and a visual language of observation, classification, geometry, construction, and calibration. The modern layer supplies explicit state, source, uncertainty, and authority boundaries. + +Recommended composition ratio, recorded as a design decision rather than an empirical claim: + +- `65%` classical / archival / natural-history / instrument language; +- `25%` contemporary technical and evidence interface; +- `10%` annotation, inspection, and boundary marks. + +## 2. Palette + +| Token | Value | Use | +|---|---|---| +| `paper` | `#F2EBDD` | primary background | +| `paper_deep` | `#E2D5BF` | secondary archival plane | +| `ink` | `#171714` | primary text and structural line | +| `slate` | `#3D4546` | technical text and secondary information | +| `bronze` | `#77634C` | quiet archival rule and calibration line | +| `oxide` | `#8B3F2F` | issue, inspection, boundary, amendment, decisive emphasis | +| `verdigris` | `#476B63` | observation, taxonomy, supported/pass state | +| `white` | `#FCFAF4` | clean evidence surface | + +`oxide` must not be used as a decorative brand color with no meaning. + +## 3. Typography + +| Layer | Preferred | Repository-safe fallback | Use | +|---|---|---|---| +| Editorial | Source Serif 4 | Georgia, Times New Roman, serif | product name, major statements, section headings | +| Technical | IBM Plex Sans | system UI, Segoe UI, sans-serif | body, navigation, explanations | +| Receipt | IBM Plex Mono | ui-monospace, SFMono-Regular, Menlo, monospace | statuses, hashes, paths, commands, labels | + +No font files are committed by this brand change. The static site uses system-safe fallbacks and makes no network request for fonts. + +## 4. Image grammar + +| Instrument | Visual family | Motif | +|---|---|---| +| Axis | geometry / navigation | armillary rings, axes, obligation routes | +| Canon | geometry | compass construction, Euclidean figures, proof points | +| Atlas | botany / taxonomy | specimen stem, leaves, taxon labels, source scale | +| Crucible | morphology / engineering | transformations, mechanism delta, falsifier sequence | +| Forge | engineering | exploded gear train, source/build/entry/verify alignment | +| Chronometer | metrology | calibrated dial, baseline points, decision hand | +| Aegis | inspection | gauge, boundary marks, integrity ledger | +| Sextant | navigation | triangulation and value-of-computation bearing | +| Conclave | comparative specimen board | independent seats, reveal, off-diagonal critique | +| Parallax | optics | paired lenses, ray trace, gap between perspectives | +| Mercury Runtime | instrument cabinet | provider/tool/session mechanisms behind a hard authority boundary | +| Obsidian | fracture / stress plate | attack axes, break triples, participation marks | + +## 5. Evidence boundary + +Historical styling stops at factual claim tables. Evidence surfaces use: + +- white or near-white background; +- sans and mono typography; +- explicit sample size or test count where available; +- explicit status vocabulary; +- direct repository source path; +- visible unresolved state; +- no antiqued certificate, seal, wax stamp, patent plate, or faux accession identifier. + +## 6. Approved labels + +`PLATE` · `SPECIMEN` · `METHOD` · `INSTRUMENT` · `RECEIPT` · `STATUS` · `SOURCE` · `BOUNDARY` + +Numbering such as `PLATE 01` is a layout index only. It must not resemble a scientific receipt or catalog identifier unless it is tied to a real project record. + +## 7. Prohibited cues + +- jeweled superhero glove or derivative franchise imagery; +- Roman eagle, military imperial emblem, laurel authority seal, or fake academic crest; +- fake founding date or “centuries of knowledge” claim; +- fake museum, university, government, or standards-body endorsement; +- fake patent, accession, manuscript, specimen, or receipt ID; +- generated art described as historical or archival; +- historical image used without item-level rights and source evidence; +- benchmark result placed on a visual without its sample, boundary, and source; +- “ancient wisdom meets AI” language; +- antique styling used to make an unsupported result appear established. + +## 8. Motion and interaction + +- use slow line construction, instrument rotation, or specimen annotation only; +- evidence-state changes are immediate rather than theatrical; +- no dust, magic particles, neon circuitry, glowing brain, or “awakening” effect; +- respect `prefers-reduced-motion` and preserve all information without animation; +- keep all navigation and controls at least 24×24 CSS pixels; the implemented site targets 44-pixel controls. + +## 9. Accessibility + +- informative illustrations require descriptive alt text; +- decorative marks use empty alt text; +- status is always text plus color, never color alone; +- content order remains useful with CSS, animation, or images disabled; +- source and boundary labels remain readable on small screens; +- canvas art is decorative and marked `aria-hidden`; +- local-only CSS and JavaScript avoid third-party tracking and availability dependencies. + +## 10. Implemented assets + +| Asset | Type | Source | Boundary | +|---|---|---|---| +| `docs/visuals/apparatus-frontispiece.svg` | original SVG | project-authored for this branch | Brand image only; not evidence; no external artwork embedded or traced | +| `docs/visuals/elenchion-mark.svg` | original SVG | project-authored for this branch | Brand mark only; no legal trademark claim | +| `docs/system-field.js` | original Canvas2D visualization | architecture and skill contracts | Conceptual system visualization, not a live execution trace | diff --git a/docs/brand/CLAIMS_REGISTER.md b/docs/brand/CLAIMS_REGISTER.md new file mode 100644 index 00000000..f5acf751 --- /dev/null +++ b/docs/brand/CLAIMS_REGISTER.md @@ -0,0 +1,36 @@ +# Brand and Product Claims Register + +## Status vocabulary + +| Status | Meaning | +|---|---| +| `VERIFIED_REPOSITORY_FACT` | Directly supported by a committed source, test, receipt, manifest, or checkpoint | +| `DESIGN_DECISION` | Chosen name, visual rule, copy, or information architecture; not an empirical finding | +| `EXPLORATORY_EVIDENCE` | A bounded pilot or study exists but does not support a general claim | +| `UNVERIFIED` | No sufficient evidence or clearance is recorded | +| `PROHIBITED` | Must not appear in public copy because it is false, misleading, or outside scope | + +## Registered claims + +| ID | Claim | Status | Evidence / action | +|---|---|---|---| +| `BR-001` | Elenchion Systems is the working public organization identity for this project. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | +| `BR-002` | Apparatus is the working public name of the integrated tool suite. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | +| `BR-003` | The repository and installed command remain `Kitahl/The-Gauntlet` and `gauntlet`. | `VERIFIED_REPOSITORY_FACT` | repository metadata; `pyproject.toml`; `gauntlet_host/cli.py` | +| `BR-004` | The runtime vendors pinned Hermes Agent source and uses an isolated parent/worker boundary. | `VERIFIED_REPOSITORY_FACT` | `vendor/HERMES_SNAPSHOT.json`; `third_party/HERMES_SOURCE_LEDGER.md`; runtime engineering plan | +| `BR-005` | The FAST-P8 record reports 8/8 bounded boot checks passed. | `VERIFIED_REPOSITORY_FACT` | `docs/engineering/HERMES_FAST_P8_CHECKPOINT.json` | +| `BR-006` | FAST-P8 establishes external-provider, autonomous release, cross-platform, routing-benefit, or cost-benefit performance. | `PROHIBITED` | Checkpoint `known_limitations` explicitly withholds these conclusions | +| `BR-007` | Runtime outputs are observations and cannot directly create canonical receipts or release tasks. | `VERIFIED_REPOSITORY_FACT` | runtime engineering plan; Phase-8 document and harness | +| `BR-008` | The ten public instrument names map to existing stable skills and commands. | `DESIGN_DECISION` plus `VERIFIED_REPOSITORY_FACT` compatibility map | `NAMING_ARCHITECTURE.md`; existing skill paths | +| `BR-009` | Obsidian can raise an issue and can never clear a claim. | `VERIFIED_REPOSITORY_FACT` | `docs/specs/BLACKGEM_ENGINEERING_SPEC.md` | +| `BR-010` | Strategist and Formal Plane are archived engineering candidates, not promoted runtime authority. | `VERIFIED_REPOSITORY_FACT` | `research/postbench-candidate2/README.md` | +| `BR-011` | Exploratory benchmark receipts include positive, null, and mixed/negative outcomes. | `EXPLORATORY_EVIDENCE` | `docs/BENCHMARKS.md` and linked receipts | +| `BR-012` | Apparatus improves independent human reasoning, scientific discovery, or general AI capability in deployment. | `UNVERIFIED` | `RESEARCH.md` states this is not established | +| `BR-013` | Classical-scientific styling proves rigor, correctness, or historical legitimacy. | `PROHIBITED` | Brand evidence-boundary rule | +| `BR-014` | Elenchion Systems or Apparatus has completed corporate-name, trademark, domain, or legal clearance. | `UNVERIFIED` | Professional legal clearance required before registration or commercial launch | +| `BR-015` | The committed frontispiece and calibration mark are original project-authored vectors with no external artwork embedded or traced. | `VERIFIED_REPOSITORY_FACT` | source SVG metadata; `asset-manifest.json`; reproducible SHA-256 hashes | +| `BR-016` | Scholarly Antiquarian Framing is an established scientific or marketing term. | `PROHIBITED` | It is a coined internal label from the supplied design package | + +## Public-copy rule + +Every factual claim must resolve to a repository path or an external source ledger. Statements that lack evidence remain explicitly marked `UNVERIFIED`; visual prestige is never accepted as a substitute. diff --git a/docs/brand/NAMING_ARCHITECTURE.md b/docs/brand/NAMING_ARCHITECTURE.md new file mode 100644 index 00000000..2d318737 --- /dev/null +++ b/docs/brand/NAMING_ARCHITECTURE.md @@ -0,0 +1,63 @@ +# Naming Architecture + +## 1. Decision + +| Level | New public identity | Status | Existing identity retained for compatibility | +|---|---|---|---| +| Organization | **Elenchion Systems** | `DESIGN_DECISION` | Independent project ownership remains unchanged | +| Product suite | **Apparatus** | `DESIGN_DECISION` | Repository: `Kitahl/The-Gauntlet`; CLI: `gauntlet` | +| Runtime | **Mercury Runtime** | `DESIGN_DECISION` | `gauntlet_host`; pinned Hermes Agent runtime source | +| Principle | **Evidence before authority** | `DESIGN_DECISION` | Existing authority/evidence separation | +| Visual method | **Scholarly Antiquarian Framing** | coined internal label | No claim that this is an established academic term | + +“Elenchion” is a coined brand form selected to suggest rigorous examination. This document does not claim a legally protected mark, available domain, incorporated company, or authoritative classical-language derivation. + +## 2. Public module map + +| New public name | Existing public/technical name | Stable ID / command | Feature definition | Authority ceiling | +|---|---|---|---|---| +| **Axis** | Soul / Research Orchestrator | `soul`, `/soul` | Goal hashing, task creation, typed obligations, deterministic ownership, receipt integration, release gate | Release authority within existing rules | +| **Canon** | Mind / Formal Reasoning | `mathbot`, `/mind` | Formal objects, assumptions, proof obligations, negations, exact arithmetic, optional solver checks, counterexamples | Claim-scoped formal receipt | +| **Atlas** | Space / Research Discovery | `scoutbot`, `/space` | Bounded search plans, literature and software discovery, identity deduplication, saturation, source assessment | Claim-scoped discovery receipt | +| **Crucible** | Reality / Method Synthesis | `novelbot`, `/reality` | Verified gap, mechanism delta, assumptions, failure modes, negative control, transfer, ablation, verifier plan | Candidate mechanism only until verified | +| **Forge** | Power / Engineering Verification | `codebot`, `/power` | Typed verification plans, bounded execution, known verifier families, output hashes, defect-class coverage | Claim-scoped engineering receipt | +| **Chronometer** | Time / Evaluation & Benchmarking | `benchbot`, `/time` | Frozen arms, matched baselines, exclusions, contamination handling, uncertainty, multiplicity, decision consequence | Evaluation receipt only | +| **Aegis** | Gauntlet / Process Assurance | `infinity-gauntlet`, `/gauntlet` | Stale-state, false-green, inherited-number, frame/costume, event, and ledger-integrity checks | May identify issues; cannot replace claim-native evidence | +| **Sextant** | Meditate / Decision Preflight | `meditate` | Facts, assumptions, unknowns, options, blocker, action, value of computation | Decision preparation only | +| **Conclave** | Council / Evidence Review Panel | `council-of-elders`, `/council` | Frozen artifact/budget, independent seats, commitment/reveal, skeptic seat, cross-critique, controlled synthesis | Review receipt; no automatic factual warrant outside scope | +| **Parallax** | Mirror / adaptive complement; technical `foil` | `foil`, `/foil` | Conservative user/task evidence, capability-gap routing, calibrated assistance, transfer tracking | Advisory/adaptation only | + +## 3. Additional systems + +| Public name | Existing technical identity | Included feature set | Status | +|---|---|---|---| +| **Mercury Runtime** | `gauntlet_host`, pinned Hermes Agent source | Provider access, tools, MCP, context, sessions, memory, skills, retry, interruption, delegation, typed IPC, CLI | Interim pinned-runtime alpha; observation-only authority | +| **Obsidian** | Black Gem / `blackgem` | Independent breaker seats, frozen rubric, participation accounting, injection canary, cross-critique, break triples | Adversarial instrument; may raise `ISSUE`; can never return `CLEARED` | +| **Strategist Candidate** | archived mechanism-planner candidate | Exact minimum successful repair over a declared finite repair universe and hardening checks | Engineering candidate only; not promoted | +| **Formal Plane Candidate** | archived Math Foundry candidate | Trusted-base minimality and isolated qualification hardening | Engineering candidate only; not promoted | + +## 4. Compatibility policy + +The rename is intentionally non-breaking: + +- do not rename Python packages or source modules; +- do not rename skill directories or slash commands; +- do not change task, obligation, receipt, evidence-reference, or verdict schemas; +- do not rewrite historical benchmark condition names; +- do not rewrite hashes, source ledgers, checkpoint records, or citation paths; +- do not make the public brand an authority-bearing field; +- do not infer that a new public name changes a component’s validation state. + +A future code-level migration requires an explicit specification, redirects/aliases, tests, receipt compatibility review, and a release note. + +## 5. Source basis + +| Feature area | Repository source | +|---|---| +| Core typed architecture and module features | `docs/VNEXT_IMPLEMENTATION_REPORT.md`; `docs/ARCHITECTURE.md`; `docs/specs/` | +| Hermes-derived runtime architecture | `docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md` | +| Runtime CLI and authority boundary | `docs/engineering/PHASE8_USER_CLI_BOOT.md` | +| Runtime checkpoint and limitations | `docs/engineering/HERMES_FAST_P8_CHECKPOINT.json` | +| Obsidian/Black Gem semantics | `docs/specs/BLACKGEM_ENGINEERING_SPEC.md` | +| Candidate status and scoped validation | `research/postbench-candidate2/README.md` | +| Visual and marketing system | `SOURCE_PACKAGE.md` and supplied package identified there | diff --git a/docs/brand/README.md b/docs/brand/README.md new file mode 100644 index 00000000..d1ccb3a7 --- /dev/null +++ b/docs/brand/README.md @@ -0,0 +1,43 @@ +# Apparatus Brand System + +**Organization:** Elenchion Systems +**Product suite:** Apparatus +**Product principle:** Evidence before authority +**Brand edition:** `apparatus-brand-01` +**Status:** working public identity; technical compatibility preserved; legal name/trademark clearance not performed + +## File index + +| File | Purpose | +|---|---| +| [`NAMING_ARCHITECTURE.md`](NAMING_ARCHITECTURE.md) | Public names, technical aliases, migration boundary, feature mapping | +| [`BRAND_SYSTEM.md`](BRAND_SYSTEM.md) | Visual system, typography, palette, imagery, layout, accessibility, prohibited cues | +| [`CLAIMS_REGISTER.md`](CLAIMS_REGISTER.md) | Verified, design-decision, unverified, and prohibited claims | +| [`SOURCE_PACKAGE.md`](SOURCE_PACKAGE.md) | Identity of the supplied design package and what was adopted | +| [`brand-system.json`](brand-system.json) | Machine-readable names, tokens, module map, and status vocabulary | +| [`asset-manifest.json`](asset-manifest.json) | Rights, source, creator, hashes, and allowed use for committed brand assets | +| [`../content-provenance.json`](../content-provenance.json) | Machine-readable public-content claim map | + +## Controlling rules + +1. Classical, archaeological, botanical, zoological, and technical-engraving cues are product identity only. +2. Evidence tables use clean modern panels with explicit state and source paths. +3. No fake founding year, academic seal, patent, accession number, museum endorsement, or antique evidence receipt. +4. Public names may coexist with technical aliases; source identifiers change only through an explicit compatibility migration. +5. `oxide` indicates inspection, issue, boundary, amendment, or decisive emphasis—not generic decoration. +6. Generated or project-authored art must be labeled as such and must not be presented as a historical artifact. + +## Acceptance state + +- [x] README redesigned. +- [x] GitHub Pages homepage redesigned. +- [x] Company and product hierarchy established. +- [x] Ten core modules renamed publicly with compatibility aliases. +- [x] Hermes-derived runtime, adversarial review, and research candidates included with bounded status. +- [x] Original frontispiece and calibration mark created. +- [x] Asset and content provenance recorded. +- [x] No external image binary introduced. +- [ ] Legal/trademark/domain clearance. +- [ ] Repository rename. +- [ ] GitHub organization rename. +- [ ] Production social-preview PNG export and repository-setting update. diff --git a/docs/brand/SOURCE_PACKAGE.md b/docs/brand/SOURCE_PACKAGE.md new file mode 100644 index 00000000..a6406022 --- /dev/null +++ b/docs/brand/SOURCE_PACKAGE.md @@ -0,0 +1,46 @@ +# Supplied Design Package Identity + +## Package record + +| Field | Value | +|---|---| +| Supplied filename | `GAUNTLET_CLASSICAL_SCIENTIFIC_BRAND_PACKAGE_V1.zip` | +| Byte length | `42,438` | +| SHA-256 | `2232a3b18ee2c1ffeb2cf670f6018dc7129d4668db18ed8eef0ae767de2f22c5` | +| Package role | Non-authority design and marketing specification supplied by the project owner | +| Imported image binaries | `0` | +| External museum images committed by this change | `0` | + +The package included research notes, a visual system, GitHub and website specifications, an asset-library plan, copy and campaign systems, image prompts, rights/provenance rules, implementation and measurement plans, design tokens, a demo, and an asset-manifest validator. + +## Adopted decisions + +- museum study room × scientific instrument bench × modern evidence console; +- parchment/ink/slate/bronze/oxide/verdigris palette; +- editorial serif + technical sans + receipt mono hierarchy; +- classical profile, botany, geometry, engineering, optics, and calibration motifs; +- “Evidence before authority” primary line; +- clean evidence surfaces separated from archival styling; +- item-level asset provenance and rights manifest; +- prohibition on fake heritage, endorsement, seals, patents, accession IDs, and scientific veneer; +- responsive, local-only static implementation with reduced-motion support. + +## Deliberate adaptations + +- The old product identity was replaced by Elenchion Systems / Apparatus. +- The five “Stones/Gems” became claim-native Instruments. +- Soul, Gauntlet, Council, Meditate, and Mirror received new public instrument names while technical aliases remain. +- The Hermes-derived host is described as Mercury Runtime and given an explicit alpha/observation-only boundary. +- Black Gem is presented as Obsidian. +- The archived mechanism-planner and formal-plane packages are presented as candidates, not shipped authority. +- Original SVG and Canvas2D artwork replaced external historical imagery for the initial implementation, avoiding unresolved item-level rights and provenance. + +## Not adopted yet + +- museum-hosted source images; +- generated raster campaign art; +- self-hosted fonts; +- GitHub repository social-preview setting; +- legal or trademark clearance; +- organization or repository rename; +- marketing claims based on unqualified benchmarks. diff --git a/docs/brand/asset-manifest.json b/docs/brand/asset-manifest.json new file mode 100644 index 00000000..526dae82 --- /dev/null +++ b/docs/brand/asset-manifest.json @@ -0,0 +1,77 @@ +[ + { + "asset_id": "APPARATUS-ASSET-001", + "title": "Apparatus Classical-Scientific Frontispiece", + "creator": "Project-authored with AI-assisted vector generation under project-owner direction", + "date": "2026-09-03", + "source_institution": "Kitahl/The-Gauntlet project", + "source_object_id": "docs/visuals/apparatus-frontispiece.svg", + "source_page_url": "https://github.com/Kitahl/The-Gauntlet/blob/work/elenchion-apparatus-brand/docs/visuals/apparatus-frontispiece.svg", + "download_url": "https://raw.githubusercontent.com/Kitahl/The-Gauntlet/work/elenchion-apparatus-brand/docs/visuals/apparatus-frontispiece.svg", + "rights_statement": "Original project vector created for this branch; no external artwork is embedded or traced. Commercial project use is approved by the project owner. No third-party endorsement is implied.", + "license_id": "PROJECT-ORIGINAL", + "commercial_use_allowed": true, + "rights_checked_on": "2026-09-03", + "source_file_sha256": "4b91a1cc0fd8b713aca27a5fbd3555562b2392a729ab4efc7c461661bb1d14a8", + "transformations": [], + "production_files": ["docs/visuals/apparatus-frontispiece.svg"], + "credit_line": "Apparatus frontispiece — Kitahl/The-Gauntlet project, 2026", + "endorsement_disclaimer_required": true, + "review_status": "APPROVED_PROJECT_ORIGINAL", + "asset_type": "svg-frontispiece", + "use": ["README hero", "GitHub social-preview source", "website brand reference"], + "alt_text": "Parchment scientific frontispiece with an original measured classical profile, armillary rings, a botanical specimen, and the Apparatus identity.", + "evidence_boundary": "Brand image only. It is not an execution trace, receipt, benchmark result, historical artifact, or proof of product efficacy.", + "external_sources": [] + }, + { + "asset_id": "APPARATUS-ASSET-002", + "title": "Elenchion Systems Calibration Mark", + "creator": "Project-authored with AI-assisted vector generation under project-owner direction", + "date": "2026-09-03", + "source_institution": "Kitahl/The-Gauntlet project", + "source_object_id": "docs/visuals/elenchion-mark.svg", + "source_page_url": "https://github.com/Kitahl/The-Gauntlet/blob/work/elenchion-apparatus-brand/docs/visuals/elenchion-mark.svg", + "download_url": "https://raw.githubusercontent.com/Kitahl/The-Gauntlet/work/elenchion-apparatus-brand/docs/visuals/elenchion-mark.svg", + "rights_statement": "Original project vector created for this branch; no external source artwork is embedded. Commercial project use is approved by the project owner. No trademark registration or third-party endorsement is claimed.", + "license_id": "PROJECT-ORIGINAL", + "commercial_use_allowed": true, + "rights_checked_on": "2026-09-03", + "source_file_sha256": "655878c5c97c2bba9328e5031cc692ec4034aa92d89b7749ea693f20f45e3b1c", + "transformations": [], + "production_files": ["docs/visuals/elenchion-mark.svg"], + "credit_line": "Elenchion Systems calibration mark — Kitahl/The-Gauntlet project, 2026", + "endorsement_disclaimer_required": true, + "review_status": "APPROVED_PROJECT_ORIGINAL", + "asset_type": "svg-brand-mark", + "use": ["website brand mark", "documentation identity"], + "alt_text": "Circular calibration mark formed by instrument arcs, a central axis, and one oxide registration tick.", + "evidence_boundary": "Working brand mark only. It does not establish legal trademark clearance, institutional status, or product efficacy.", + "external_sources": [] + }, + { + "asset_id": "APPARATUS-ASSET-003", + "title": "Apparatus Semantic Instrument Field", + "creator": "Project-authored with AI-assisted Canvas2D generation under project-owner direction", + "date": "2026-09-03", + "source_institution": "Kitahl/The-Gauntlet project", + "source_object_id": "docs/system-field.js", + "source_page_url": "https://github.com/Kitahl/The-Gauntlet/blob/work/elenchion-apparatus-brand/docs/system-field.js", + "download_url": "https://raw.githubusercontent.com/Kitahl/The-Gauntlet/work/elenchion-apparatus-brand/docs/system-field.js", + "rights_statement": "Original project Canvas2D implementation derived from committed architecture and skill contracts. Commercial project use is approved by the project owner. No external image or JavaScript runtime is embedded.", + "license_id": "MIT-PROJECT-CODE", + "commercial_use_allowed": true, + "rights_checked_on": "2026-09-03", + "source_file_sha256": "0b415e0771cc24c4d95ec4d6c87a2be1e0a65d3fbe210402431238cb4a5ba68f", + "transformations": [], + "production_files": ["docs/system-field.js"], + "credit_line": "Apparatus semantic instrument field — Kitahl/The-Gauntlet project, 2026", + "endorsement_disclaimer_required": true, + "review_status": "APPROVED_PROJECT_ORIGINAL", + "asset_type": "canvas2d-visualization", + "use": ["GitHub Pages interactive background"], + "alt_text": "Decorative procedural scientific instrument field that changes among armillary, botanical, geometric, engineering, and calibration motifs.", + "evidence_boundary": "Conceptual architecture illustration only. It is not a live system monitor, receipt, benchmark result, or authority surface.", + "external_sources": [] + } +] diff --git a/docs/brand/brand-system.json b/docs/brand/brand-system.json new file mode 100644 index 00000000..77c40967 --- /dev/null +++ b/docs/brand/brand-system.json @@ -0,0 +1,52 @@ +{ + "schema": "apparatus.brand-system.v1", + "brand_edition": "apparatus-brand-01", + "organization": { + "public_name": "Elenchion Systems", + "status": "DESIGN_DECISION", + "legal_clearance": "UNVERIFIED" + }, + "product": { + "public_name": "Apparatus", + "descriptor": "evidence-governed agent and research system", + "principle": "Evidence before authority", + "repository_compatibility_name": "The Gauntlet", + "cli": "gauntlet" + }, + "palette": { + "paper": "#F2EBDD", + "paper_deep": "#E2D5BF", + "ink": "#171714", + "slate": "#3D4546", + "bronze": "#77634C", + "oxide": "#8B3F2F", + "verdigris": "#476B63", + "white": "#FCFAF4" + }, + "typography": { + "editorial": ["Source Serif 4", "Georgia", "Times New Roman", "serif"], + "technical": ["IBM Plex Sans", "system-ui", "Segoe UI", "sans-serif"], + "receipt": ["IBM Plex Mono", "ui-monospace", "SFMono-Regular", "monospace"] + }, + "core_instruments": [ + {"public_name":"Axis","technical_id":"soul","command":"/soul","role":"orchestration and release"}, + {"public_name":"Canon","technical_id":"mathbot","command":"/mind","role":"formal reasoning"}, + {"public_name":"Atlas","technical_id":"scoutbot","command":"/space","role":"research discovery"}, + {"public_name":"Crucible","technical_id":"novelbot","command":"/reality","role":"method synthesis"}, + {"public_name":"Forge","technical_id":"codebot","command":"/power","role":"engineering verification"}, + {"public_name":"Chronometer","technical_id":"benchbot","command":"/time","role":"evaluation and benchmarking"}, + {"public_name":"Aegis","technical_id":"infinity-gauntlet","command":"/gauntlet","role":"process assurance"}, + {"public_name":"Sextant","technical_id":"meditate","command":"/meditate","role":"decision preflight"}, + {"public_name":"Conclave","technical_id":"council-of-elders","command":"/council","role":"independent evidence review"}, + {"public_name":"Parallax","technical_id":"foil","command":"/foil","role":"adaptive reasoning complement"} + ], + "additional_systems": [ + {"public_name":"Mercury Runtime","technical_id":"gauntlet_host","status":"INTERIM_ALPHA_OBSERVATION_ONLY"}, + {"public_name":"Obsidian","technical_id":"blackgem","status":"ADVERSARIAL_NEVER_CLEARS"}, + {"public_name":"Strategist Candidate","technical_id":"postbench mechanism-planner candidate","status":"ENGINEERING_CANDIDATE_NOT_PROMOTED"}, + {"public_name":"Formal Plane Candidate","technical_id":"postbench Math Foundry candidate","status":"ENGINEERING_CANDIDATE_NOT_PROMOTED"} + ], + "status_vocabulary": ["VERIFIED_REPOSITORY_FACT", "DESIGN_DECISION", "EXPLORATORY_EVIDENCE", "UNVERIFIED", "PROHIBITED"], + "evidence_boundary": "Brand imagery and historical cues never add evidential weight to technical or scientific claims.", + "source_package_sha256": "2232a3b18ee2c1ffeb2cf670f6018dc7129d4668db18ed8eef0ae767de2f22c5" +} diff --git a/docs/content-provenance.json b/docs/content-provenance.json index 04f961a5..0990e662 100644 --- a/docs/content-provenance.json +++ b/docs/content-provenance.json @@ -1,49 +1,40 @@ { - "schema": "egrt/showcase-provenance-1", + "schema": "egrt/showcase-provenance-2", + "brand_edition": "elenchion-apparatus-1", + "generated_on": "2026-09-03", "claims": [ { - "id": "architecture-modular-control", - "page_section": "Architecture", - "claim": "The toolkit uses a portable Research Orchestrator control plane plus bounded specialist research modules rather than one undifferentiated prompt.", + "id": "public-identity-working-decision", + "page_section": "Identity", + "claim": "Elenchion Systems and Apparatus are working public identities adopted by this branch; no corporate-name, trademark, domain, or legal clearance is claimed.", + "status": "DESIGN_DECISION_WITH_UNVERIFIED_LEGAL_CLEARANCE", "sources": [ - "docs/ARCHITECTURE.md", - "skills/soul/SKILL.md", - "skills/mathbot/SKILL.md", - "skills/scoutbot/SKILL.md", - "skills/novelbot/SKILL.md", - "skills/codebot/SKILL.md", - "skills/benchbot/SKILL.md" + "docs/brand/NAMING_ARCHITECTURE.md", + "docs/brand/CLAIMS_REGISTER.md" ] }, { - "id": "professional-naming-compatibility", - "page_section": "Modules", - "claim": "Professional display names are used for the research portfolio while technical IDs and slash-command aliases are retained for backwards compatibility.", - "sources": ["docs/ARCHITECTURE.md", "GOVERNANCE.md"] - }, - { - "id": "foil-complement", - "page_section": "FOIL", - "claim": "FOIL models competing explanations for observed performance and supplies task-relevant complementary methods without treating a single error as a stable weakness.", - "sources": ["skills/foil/SKILL.md", "research/FOIL_RESEARCH_BASIS.md"] - }, - { - "id": "independent-transfer-target", - "page_section": "FOIL", - "claim": "FOIL separates immediate assisted task success from later independent competence and transfer.", - "sources": ["skills/foil/SKILL.md", "research/FOIL_RESEARCH_BASIS.md", "RESEARCH.md"] - }, - { - "id": "authority-evidence-split", - "page_section": "Evidence", - "claim": "Human authority governs voluntary goals and actions while evidence/proof determine factual warrant.", - "sources": ["skills/foil/SKILL.md", "skills/soul/SKILL.md", "RESEARCH.md"] + "id": "technical-compatibility-preserved", + "page_section": "Compatibility", + "claim": "The public rename preserves the repository, gauntlet command, technical skill IDs, slash-command aliases, state paths, verdict vocabulary, receipt semantics, and release authority.", + "status": "VERIFIED_REPOSITORY_FACT", + "sources": [ + "docs/brand/NAMING_ARCHITECTURE.md", + "pyproject.toml", + "gauntlet_host/cli.py", + "docs/ARCHITECTURE.md", + "tools/egrt_types.py", + "tools/soul_runtime.py" + ] }, { - "id": "module-count", - "page_section": "Hero", - "claim": "This repository contains ten research-method modules.", + "id": "apparatus-modular-control", + "page_section": "System", + "claim": "Apparatus publicly groups one orchestrator, five claim-native research instruments, four control/review instruments, and additional bounded systems while retaining the existing implementation boundaries.", + "status": "DESIGN_DECISION_MAPPED_TO_REPOSITORY_FACTS", "sources": [ + "docs/brand/NAMING_ARCHITECTURE.md", + "docs/ARCHITECTURE.md", "skills/soul/SKILL.md", "skills/mathbot/SKILL.md", "skills/scoutbot/SKILL.md", @@ -57,72 +48,177 @@ ] }, { - "id": "foil-structural-validation", - "page_section": "Evidence", - "claim": "The recorded FOIL structural/source/regression validator reports 94 of 94 checks passed.", - "sources": ["validation/FOIL_RESEARCH_INTEGRATION_VALIDATION.json"] + "id": "mercury-pinned-runtime", + "page_section": "Mercury Runtime", + "claim": "The Mercury public layer corresponds to the Gauntlet-owned host around an exact pinned Hermes Agent source snapshot, isolated through parent/worker process and JSONL boundaries.", + "status": "VERIFIED_REPOSITORY_FACT", + "sources": [ + "vendor/HERMES_SNAPSHOT.json", + "third_party/HERMES_SOURCE_LEDGER.md", + "third_party/HERMES_LICENSE.txt", + "docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md", + "gauntlet_host/launcher.py", + "gauntlet_host/worker_main.py", + "gauntlet_host/ipc.py" + ] }, { - "id": "foil-contract-coverage", - "page_section": "Evidence", - "claim": "The recorded FOIL behavioral-contract specification artifact reports 18 of 18 cases represented as PASS-SPEC.", - "sources": ["validation/FOIL_RESEARCH_INTEGRATION_BEHAVIORAL_CONTRACT_VALIDATION.json"] + "id": "mercury-checkpoint-eight", + "page_section": "Mercury Runtime", + "claim": "The recorded FAST-P8 checkpoint reports eight of eight bounded alpha boot checks passed.", + "status": "VERIFIED_REPOSITORY_FACT", + "sources": [ + "docs/engineering/HERMES_FAST_P8_CHECKPOINT.json", + ".github/phase8_verify.py", + "docs/engineering/PHASE8_USER_CLI_BOOT.md" + ] }, { - "id": "validation-boundary", - "page_section": "Evidence", - "claim": "Mechanical and specification validation do not establish behavioral efficacy of the complete system.", - "sources": ["RESEARCH.md", "REPRODUCIBILITY.md", "validation/FOIL_RESEARCH_INTEGRATION_3_LOOP_REPORT.md"] + "id": "runtime-authority-ceiling", + "page_section": "Authority Boundary", + "claim": "Runtime, model, provider, tool, plugin, skill, memory, session, scheduler, and subagent outputs remain observations and cannot directly create canonical receipts, clear obligations, or release tasks.", + "status": "VERIFIED_REPOSITORY_FACT", + "sources": [ + "docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md", + "docs/engineering/PHASE8_USER_CLI_BOOT.md", + "docs/engineering/HERMES_FAST_P8_CHECKPOINT.json", + "gauntlet_host/observation_bridge.py", + "gauntlet_host/finalizer.py", + "tools/soul_runtime.py" + ] + }, + { + "id": "mercury-limitations-visible", + "page_section": "Mercury Runtime", + "claim": "FAST-P8 did not establish paid external-provider operation, autonomous claim-native execution, autonomous replanning, task release, dynamic tool narrowing, profile complement benefit, cross-platform qualification, or behavioral and cost efficacy.", + "status": "VERIFIED_NEGATIVE_BOUNDARY", + "sources": [ + "docs/engineering/HERMES_FAST_P8_CHECKPOINT.json", + "docs/engineering/PHASE8_USER_CLI_BOOT.md" + ] + }, + { + "id": "parallax-adaptation-only", + "page_section": "Parallax", + "claim": "Parallax is the public name for the existing Mirror/FOIL adaptive complement layer; its route is advisory and cannot clear proof, discovery, engineering, evaluation, or release obligations.", + "status": "VERIFIED_REPOSITORY_FACT_PLUS_PUBLIC_ALIAS", + "sources": [ + "docs/brand/NAMING_ARCHITECTURE.md", + "skills/foil/SKILL.md", + "docs/FOIL_AUTHORITY_KERNEL.md", + "docs/specs/FOIL_INTEGRATION_SPEC.md", + "docs/engineering/PHASE7_FOIL_ADVISORY_ROUTE.md" + ] + }, + { + "id": "conclave-independent-review", + "page_section": "Conclave", + "claim": "Conclave maps to the Council protocol for independent seats, commitment/reveal, cross-critique, disagreement preservation, and controlled synthesis.", + "status": "VERIFIED_REPOSITORY_FACT_PLUS_PUBLIC_ALIAS", + "sources": [ + "docs/brand/NAMING_ARCHITECTURE.md", + "skills/council-of-elders/SKILL.md", + "docs/specs/COUNCIL_ENGINEERING_SPEC.md", + "tools/council_runtime.py" + ] }, { - "id": "portable-process-assurance", - "page_section": "Modules", - "claim": "The public Process Assurance Framework does not require private project hooks or hard-coded local runtime paths and treats missing optional machinery as unavailable.", - "sources": ["skills/infinity-gauntlet/SKILL.md", "validation/SOUL_GAUNTLET_PUBLIC_AUDIT.md"] + "id": "obsidian-never-clears", + "page_section": "Additional Systems", + "claim": "Obsidian maps to Black Gem adversarial examination; it may raise ISSUE and structurally has no CLEARED verdict path.", + "status": "VERIFIED_REPOSITORY_FACT_PLUS_PUBLIC_ALIAS", + "sources": [ + "docs/brand/NAMING_ARCHITECTURE.md", + "docs/specs/BLACKGEM_ENGINEERING_SPEC.md", + "tools/blackgem_runtime.py" + ] }, { - "id": "research-question-open", - "page_section": "Research Dossier", - "claim": "Prospective behavioral efficacy of the complete workflow remains an open research question with planned matched-baseline and ablation studies.", - "sources": ["RESEARCH.md", "ROADMAP.md"] + "id": "candidate-archive-no-promotion", + "page_section": "Candidate Systems", + "claim": "The Strategist and Formal Plane public labels refer to archived Mastermind and Math Foundry engineering candidates; their archive does not promote them into Apparatus runtime or evidential authority.", + "status": "VERIFIED_REPOSITORY_FACT_PLUS_PUBLIC_ALIAS", + "sources": [ + "docs/brand/NAMING_ARCHITECTURE.md", + "research/postbench-candidate2/README.md" + ] }, { - "id": "research-dossier-output-index", - "page_section": "Research Dossier", - "claim": "The homepage indexes the current research question, implemented architecture, FOIL basis, mechanical/specification receipts, exploratory benchmark pilots, prospective behavioral evaluation, and external reproduction as distinct research objects with explicit states.", + "id": "benchmark-scope", + "page_section": "Evidence", + "claim": "The repository preserves positive, null, mixed, and excluded exploratory results and does not support a single general efficacy headline from the heterogeneous pilots.", + "status": "EXPLORATORY_EVIDENCE_BOUNDARY", + "sources": [ + "docs/BENCHMARKS.md", + "benchmarks/results/2026-08-22-blinded-pilot.json", + "benchmark_runs/2026-08-22/browsecomp_four_way_results.json", + "RESEARCH.md" + ] + }, + { + "id": "complete-system-efficacy-open", + "page_section": "Evidence", + "claim": "Independent human-reasoning, scientific-discovery, and general deployment benefit of the complete system remain unestablished.", + "status": "UNVERIFIED", "sources": [ "RESEARCH.md", - "ROADMAP.md", - "docs/ARCHITECTURE.md", - "research/FOIL_RESEARCH_BASIS.md", - "validation/FOIL_RESEARCH_INTEGRATION_VALIDATION.json", - "validation/FOIL_RESEARCH_INTEGRATION_BEHAVIORAL_CONTRACT_VALIDATION.json", - "docs/BENCHMARKS.md" + "ROADMAP.md" ] }, { - "id": "research-site-benchmark", - "page_section": "Research Dossier", - "claim": "The research-site benchmark compares public research institutes, research indexes, open-science catalogs, and mature research-software repositories to identify transferable information-architecture mechanisms without treating the set as an objective global ranking.", - "sources": ["docs/RESEARCH_WEBSITE_BENCHMARK_2026-08-22.md"] + "id": "brand-package-source", + "page_section": "Brand System", + "claim": "The branch applies the supplied classical-scientific design package recorded by filename, byte length, and SHA-256, with no museum image binary imported.", + "status": "VERIFIED_LOCAL_PACKAGE_RECORD", + "sources": [ + "docs/brand/SOURCE_PACKAGE.md", + "docs/brand/BRAND_SYSTEM.md" + ] }, { - "id": "citation-license", - "page_section": "Release", - "claim": "The repository provides machine-readable citation metadata and an MIT open-source license.", - "sources": ["CITATION.cff", "LICENSE"] + "id": "project-original-assets", + "page_section": "Brand System", + "claim": "The committed frontispiece, calibration mark, and procedural system field are project-authored assets with file hashes and explicit non-evidence boundaries.", + "status": "VERIFIED_REPOSITORY_FACT", + "sources": [ + "docs/brand/asset-manifest.json", + "docs/visuals/apparatus-frontispiece.svg", + "docs/visuals/elenchion-mark.svg", + "docs/system-field.js", + "docs/visuals/visual-provenance.json" + ] }, { - "id": "homepage-trust-center", - "page_section": "Research Software Trust Center", - "claim": "The public homepage exposes direct paths to security, governance, contribution, citation, changelog, and roadmap artifacts.", - "sources": ["SECURITY.md", "GOVERNANCE.md", "CONTRIBUTING.md", "CITATION.cff", "CHANGELOG.md", "ROADMAP.md"] + "id": "historical-style-not-evidence", + "page_section": "Brand System", + "claim": "Classical and scientific visual language is treated as identity and navigation, never as proof of correctness, maturity, heritage, endorsement, or efficacy.", + "status": "DESIGN_AND_CLAIMS_GUARDRAIL", + "sources": [ + "docs/brand/BRAND_SYSTEM.md", + "docs/brand/CLAIMS_REGISTER.md", + "docs/brand/asset-manifest.json" + ] }, { - "id": "homepage-reproducibility-path", - "page_section": "Reproducibility", - "claim": "The public homepage exposes the documented mechanical validation commands and separates their interpretation from behavioral-efficacy claims.", - "sources": ["REPRODUCIBILITY.md", "validation/validate_soul_gauntlet_public.py", "validation/validate_showcase.py"] + "id": "trust-center-visible", + "page_section": "Source and Governance", + "claim": "The public site exposes direct paths to architecture, runtime evidence, benchmark boundaries, research status, reproducibility, security, governance, contribution, citation, roadmap, source adoption, and brand provenance records.", + "status": "VERIFIED_REPOSITORY_FACT", + "sources": [ + "docs/index.html", + "docs/ARCHITECTURE.md", + "docs/engineering/HERMES_FAST_P8_CHECKPOINT.json", + "docs/BENCHMARKS.md", + "RESEARCH.md", + "REPRODUCIBILITY.md", + "SECURITY.md", + "GOVERNANCE.md", + "CONTRIBUTING.md", + "CITATION.cff", + "ROADMAP.md", + "third_party/HERMES_SOURCE_LEDGER.md", + "docs/brand/README.md" + ] } ] } diff --git a/docs/index.html b/docs/index.html index a8098439..c186228a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,36 +3,39 @@ - - - + + + - - + + - - The Gauntlet — Research & Engineering Toolkit + + Apparatus — Elenchion Systems +
- The Gauntlet + + + ELENCHION SYSTEMSAPPARATUS + @@ -43,175 +46,289 @@
-

RESEARCH & ENGINEERING SYSTEMS

-

The
Gauntlet

-

10 tools I built for research, engineering, verification, and review.

-

Give the system a professional task. It identifies what must actually be established, selects the method that owns each obligation, executes the check, and keeps the result scoped to the evidence.

+

ELENCHION SYSTEMS / RESEARCH INSTRUMENT 01

+

Apparatus

+

Evidence before authority.

+

An evidence-governed agent and research system that combines a Hermes-derived operational runtime with specialist methods, typed receipts, process assurance, adaptive complement, and independent review.

+
+
RUNTIMEInterim alpha8 / 8 bounded boot checks recorded
+
AUTHORITYSeparatedRuntime output remains observation-only
+
EFFICACYNot establishedProspective deployment remains unproven
+
-
-
-

What I built.

-

Five specialist research methods. Five control and review systems. They can run independently or as one routed workflow.

+
+
+
+

METHOD / CONTROL MODEL

+

A research system built like an inspection bench.

+
+

Models, tools, and agents can move the work. They cannot certify their own output. Apparatus separates operational activity from factual warrant and release authority.

-
-
-

Research Gems

- MindFormal reasoning, proof, probability, counterexamples. - SpaceLiterature, prior art, repositories, standards. - RealityMethod synthesis after a verified constraint gap. - PowerImplementation, execution, integration, verification. - TimeBaselines, ablations, uncertainty, stop/go. -
-
-

Control & review

- SoulResearch orchestration and obligation routing. - MirrorAdaptive reasoning complement for the current task. - GauntletProcess assurance and false-green detection. - MeditateDecision preflight before consequential work. - CouncilStructured independent review. -
+ +
+
01

Frame

Axis translates a goal into explicit tasks, claims, and load-bearing obligations.

+
02

Route

Parallax identifies the least-covered capability; Axis selects the claim-native instrument.

+
03

Observe

Mercury runs models, tools, MCP services, sessions, memory, and delegated operational work.

+
04

Verify

Canon, Atlas, Crucible, Forge, or Chronometer returns evidence with an explicit scope.

+
05

Challenge

Aegis, Conclave, and Obsidian attack false greens, weak independence, and hidden defects.

+
06

Release

Axis reads the latest integrity-valid receipts and returns CLEARED, ISSUE, UNKNOWN, or UNAVAILABLE.

-

Professional workflows.

-

The route changes with the job. The background is the same system view: task, active methods, evidence returning, release.

+

ROUTE PLATES / FIVE EXAMPLES

+

The route changes with the obligation.

+

No single model or method owns every question. Apparatus selects a bounded chain according to what must be proved, searched, built, measured, reviewed, or left unresolved.

-
Research method

Is this actually new, buildable, and better?

Space Reality Power Time

-
Formal claim

Can this be proved or refuted?

Mind Council

-
Software release

The tests are green. What could still be wrong?

Power Gauntlet Council

-
Stalled project

What is the next experiment worth running?

Meditate Soul Time

-
Adaptive work

What part of this task is least covered?

Mirror Relevant Gem

+
Novel mechanism

Is this new, buildable, and decision-relevant?

Atlas Crucible Forge Chronometer

+
Formal claim

Can the statement be proved or broken?

Canon Conclave

+
Software release

The tests are green. What can still be false?

Forge Aegis Obsidian

+
Consequential decision

Which uncertainty is worth resolving first?

Sextant Axis Chronometer

+
Adaptive assistance

Which capability is least covered for this task?

Parallax Relevant instrument

+
+
+
+ +
+
+
+

APPARATUS / OPERATIONAL LAYER

+

Mercury Runtime

+

Hermes-quality operational mechanisms, contained behind a Gauntlet-owned authority boundary.

+

Mercury is the public name for the interim runtime in gauntlet_host/. It runs the exact pinned MIT-licensed Hermes Agent source as an isolated internal worker. The parent process owns task identity, observation binding, finalization, and the release gate.

+ +
+
+
CAPABILITY LEDGERSTATUS / ALPHA
+
+
Provider access
Included through pinned runtime
+
Tools + MCP
Included operationally
+
Context + sessions
Included operationally
+
Memory + skills
Approval-gated context only
+
Retry + delegation
Inherited mechanisms
+
Task identity
Explicit and repository-bound
+
Evidence authority
Excluded from runtime
+
Release authority
Parent-owned Axis gate
+
+
BOUNDARYNo automatic claim-native execution, autonomous replanning, task release, external-provider qualification, or demonstrated cost benefit.
-
-
-

The five Gems.

-

Each one is a different evidence-producing mechanism. As you move through them, the system field reconfigures into that Gem and runs its actual conceptual workflow.

+
+
+
+

THE FIVE CLAIM-NATIVE INSTRUMENTS

+

Different evidence requires different machinery.

+
+

Each instrument owns a distinct obligation class. The visual field changes from geometry to botany, morphology, engineering, and metrology while the evidence panel stays modern and explicit.

-
+
-

MIND

+
INSTRUMENT 01mathbot / /mind
+

CANON

Formal Reasoning

-

Turn a claim into explicit objects, assumptions, a proof obligation, and a claim-native verifier.

-
Use
Theorems, derivations, formal specifications, probability, exact counterexamples.
Returns
Proof, measured result, cited theorem, counterexample, or unresolved state.
- Inspect Mind ↗ +

Translate a claim into explicit objects, assumptions, a proof obligation, a negation, and a verifier that speaks the claim’s native language.

+
Use
Theorems, derivations, exact arithmetic, formal specifications, probability, counterexamples.
Returns
Proof, measured result, cited theorem, counterexample, or an unresolved obligation.
+ Inspect Canon’s technical contract ↗
-
+
-

SPACE

+
INSTRUMENT 02scoutbot / /space
+

ATLAS

Research Discovery

-

Search the evidence landscape before novelty, current-state, or reuse claims are made.

-
Use
Literature, prior art, current technical facts, standards, reusable software.
Returns
Verified source set, nearest established class, concrete differentiator, search limits.
- Inspect Space ↗ +

Map the evidence landscape before novelty, current-state, reuse, or attribution claims are allowed to harden.

+
Use
Literature, standards, prior art, repositories, current technical facts, reusable software.
Returns
Source set, nearest established class, concrete differentiator, provenance, and search limits.
+ Inspect Atlas’s technical contract ↗
-
+
-

REALITY

+
INSTRUMENT 03novelbot / /reality
+

CRUCIBLE

Method Synthesis

-

Only invent after the nearest known methods fail a specific, named constraint.

-
Use
New algorithms, mechanisms, or research methods after a verified gap.
Returns
Candidate mechanism, assumptions, invariants, failure modes, verifier plan.
- Inspect Reality ↗ +

Invent only after the nearest established methods fail a specific, named constraint and the delta can be tested.

+
Use
New algorithms, mechanisms, protocols, or research methods after a verified gap.
Returns
Candidate mechanism, assumptions, invariants, failure modes, negative control, ablation, and verifier plan.
+ Inspect Crucible’s technical contract ↗
-
+
-

POWER

+
INSTRUMENT 04codebot / /power
+

FORGE

Engineering Verification

-

Move software claims through the real implementation and the real entrypoint.

-
Use
Implementation, debugging, integration, regression, executable verification.
Returns
Executed checks, defect-class coverage, output hashes, untested limits.
- Inspect Power ↗ +

Move software claims through the real source boundary, real implementation, real entry point, and the defect classes that could falsify them.

+
Use
Architecture, implementation, debugging, integration, regression, executable verification.
Returns
Executed checks, defect-class coverage, output hashes, environment, and untested limits.
+ Inspect Forge’s technical contract ↗
-
+
-

TIME

+
INSTRUMENT 05benchbot / /time
+

CHRONOMETER

Evaluation & Benchmarking

-

Make candidate and baseline compete under the comparison that actually answers the decision.

-
Use
Experiment design, matched baselines, ablations, cost analysis, stop/go.
Returns
Matched comparison, uncertainty, failures, exclusions, decision consequence.
- Inspect Time ↗ +

Make candidate and baseline compete under the comparison, exclusions, uncertainty model, and stopping rule that answer the actual decision.

+
Use
Experiment design, matched baselines, ablations, contamination control, cost, stop/go.
Returns
Matched estimate, uncertainty, failures, exclusions, and the bounded decision consequence.
+ Inspect Chronometer’s technical contract ↗
-

One control model.

-

Frame the task. Create load-bearing obligations. Route each one to the method that owns it. Keep the receipt. Release only what the evidence supports.

+

AUTHORITY PATH / ONE CONTROL MODEL

+

Operational intelligence. Evidential restraint.

+

Mercury can execute. Parallax can advise. The instruments can generate claim-scoped receipts. Aegis and the review systems can identify defects. Only Axis governs final release.

-

TaskObligationsSelected methodsReceiptsScoped result

+

TaskObligationsRuntime observationsClaim-native receiptsAssuranceScoped release

+
Non-negotiable invariant

A model may select or propose an action. It may not declare that its own action established truth.

-

Complete toolkit.

-

The `SKILL.md` is the reasoning contract. The linked Python runtime supplies execution, state, receipts, hooks, or gates where that tool has mechanical work to do.

+
+

DIRECTORY / TEN CORE CONTRACTS

+

Complete instrument catalogue.

+

Public display names changed. Technical IDs, slash commands, skill paths, runtimes, receipts, and compatibility contracts did not.

+
Download repository
-
Soul · Research Orchestrator/soulRoute complex work across the toolkit.

Professional use

Complex research or engineering tasks that require several methods without manually coordinating each step.

How it works

Frames the goal, creates load-bearing obligations, selects the minimum specialist routes, integrates receipts, and governs release.

+
Axis · Orchestration & Release/soulFrame complex work and govern its evidence state.

Professional use

Multi-stage research and engineering tasks that need explicit ownership, integration, and a fail-closed release decision.

How it works

Hashes the goal, creates typed obligations, routes the minimum specialist chain, accepts only valid latest receipts, and returns a scoped verdict.

+ +
Canon · Formal Reasoning/mindProof, logic, probability, and counterexamples.

Professional use

Theorem checking, mathematical derivations, formal specifications, probabilistic reasoning, and exact counterexample search.

How it works

Defines objects and assumptions, states the claim and negation, selects a native verifier, attacks edge cases, and reports the verified scope.

-
Mind · Formal Reasoning/mindProof, logic, probability, formalization.

Professional use

Theorem checking, mathematical derivations, formal specifications, probabilistic reasoning, and counterexample search.

How it works

Defines objects and assumptions, states the exact claim and negation, chooses a claim-native verifier, searches edge cases, and reports scope.

+
Atlas · Research Discovery/spaceLiterature, prior art, repositories, and standards.

Professional use

Literature reviews, prior-art searches, technical due diligence, current-source verification, and reusable implementation discovery.

How it works

Translates mechanisms into search terms, expands across neighboring fields, inspects primary sources and repositories, deduplicates identities, and records limits.

-
Space · Research Discovery/spaceLiterature, prior art, repositories, standards.

Professional use

Literature reviews, prior-art checks, technical due diligence, current-source verification, and reusable implementation discovery.

How it works

Translates the mechanism into search terms, expands across neighboring fields, inspects primary sources and repositories, and records source limits.

+
Crucible · Method Synthesis/realityConstruct mechanisms after a verified constraint gap.

Professional use

New methods, algorithms, protocols, and architectures when established approaches fail a named, evidence-backed constraint.

How it works

Requires the prior-art delta, binds the mechanism to the gap, enumerates assumptions and failures, and specifies controls, ablations, and verification.

-
Reality · Method Synthesis/realityNew mechanisms after a verified gap.

Professional use

Designing a new method when established approaches have been checked and fail a named requirement.

How it works

Turns the gap into constraints, generates candidates that alter different assumptions, states invariants and failure modes, then rechecks prior art.

+
Forge · Engineering Verification/powerArchitecture, implementation, execution, and regression.

Professional use

Building, integrating, debugging, and qualifying software against its real source, environment, entry points, and defect classes.

How it works

Creates a typed verification plan, executes bounded commands without shell interpolation, records hashes and environment, and separates checked from untested behavior.

-
Power · Engineering Verification/powerImplementation, debugging, integration, execution.

Professional use

Building or reviewing software, debugging failures, integration work, release checks, and verifying executable claims.

How it works

Inspects the real repository, maps edits to requirements, implements the smallest complete change, runs targeted and regression checks, and exercises the real entrypoint.

+
Chronometer · Evaluation & Benchmarking/timeBaselines, ablations, uncertainty, cost, and stop/go.

Professional use

Matched comparisons, experimental design, benchmark qualification, contamination handling, uncertainty analysis, and cost-aware decisions.

How it works

Freezes arms and exclusions, chooses a matched estimator, preserves null and negative results, corrects multiplicity where required, and binds results to a decision.

-
Time · Evaluation & Benchmarking/timeBaselines, ablations, uncertainty, stop/go.

Professional use

Experimental design, capability comparisons, matched baselines, ablations, cost analysis, and stop/go decisions.

How it works

Defines the construct and endpoint, selects a strong baseline, matches relevant budgets, preserves failures, reports uncertainty, and applies stop/go criteria.

+
Aegis · Process Assurance/gauntletFalse-green, stale-state, scope, and integrity defense.

Professional use

Auditing a research or engineering process when passing tests, repeated claims, inherited numbers, or stale authority may conceal a defect.

How it works

Records typed events, checks governing-state freshness and ledger integrity, distinguishes framing from cosmetic process, and raises bounded findings.

-
Gauntlet · Process Assurance/gauntletAudit the process behind a result.

Professional use

Repeated failures, stale assumptions, inherited numbers, false-green tests, or a result that appears cleared too easily.

How it works

Runs the smallest relevant assurance operation across framing, derivation, boundary, refresh, and out-of-band checks.

+
Sextant · Decision Preflight/meditateFacts, assumptions, unknowns, blockers, and next action.

Professional use

Consequential choices, post-failure resets, and stalled projects where premature execution would harden an unsupported direction.

How it works

Separates facts from assumptions, records unknowns and options, identifies the binding blocker, and estimates whether more computation is worth its cost.

-
Meditate · Decision Preflightorchestrator-invokedGround consequential work before acting.

Professional use

High-cost decisions, unstable task frames, repeated failure, or moments when execution is moving faster than the evidence state.

How it works

STILL → GROUND → ORIENT → WEIGH → RELEASE: identify authoritative state, separate facts from assumptions, choose one high-value next action.

+
Conclave · Evidence Review Panel/councilIndependent commitments, critique, and synthesis.

Professional use

High-stakes review where one model, one reviewer, or untracked consensus would provide weak independence.

How it works

Freezes the artifact and budget, assigns independent seats including a skeptic, commits before reveal, cross-critiques off diagonal, and preserves disagreement.

-
Council · Evidence Review Panel/councilStructured independent review.

Professional use

Important artifacts that benefit from independent correctness, measurement, implementation, novelty, or adversarial review.

How it works

Creates distinct review seats, freezes first-pass conclusions before reveal, cross-critiques them, normalizes scope, and compares against a direct control.

+
Parallax · Adaptive Reasoning Complement/foilFind the task capability least covered by the user.

Professional use

Adaptive work where assistance should complement the user’s demonstrated coverage instead of replacing judgment or repeating existing strengths.

How it works

Uses conservative evidence tiers, task facets, structured calibration, and transfer history to request the smallest useful complement while remaining advisory.

+
-
Mirror · Adaptive Reasoning Complement/foilFind the smallest missing complement for the task.

Professional use

Work where the right next method depends on what the current task and available evidence already cover.

How it works

Classifies the task first, keeps competing gap hypotheses, uses a minimal discriminator when needed, supplies the smallest useful complement, then verifies transfer.

+
+

Implementation directory — executable helpers, state policy, profiles, adapters, and validation live outside the public skill contracts.

+ Inspect all Python implementation files ↗ +
+
+
+ +
+
+
+

ADDITIONAL SYSTEMS / DECLARED STATUS

Useful machinery without inflated authority.

+

These components extend execution, attack, or research exploration. Their status is stated separately from the ten core contracts.

+
+
+
OPERATIONAL

Mercury Runtime

Provider, tool, MCP, context, session, memory, skill, retry, and delegation mechanisms behind an observation-only boundary.

gauntlet_host/
+
ADVERSARIAL

Obsidian

Multi-seat attack, participation accounting, cross-critique, and structured break triples. It can raise an issue and can never clear a claim.

blackgem
+
CANDIDATE

Strategist

Archived mechanism-planning hardening with exact minimum successful repair over a declared finite repair universe. Not promoted into runtime authority.

postbench candidate 2
+
CANDIDATE

Formal Plane

Archived mathematical execution hardening for trusted-base minimality, process isolation, cleanup, and deterministic qualification.

postbench candidate 2
+
+ Inspect the candidate archive and its scoped validation record ↗ +
+
+ +
+
+
+

RECEIPT TABLE / CLAIM SCOPE

What the repository can support today.

+

The archival treatment stops here. Evidence is shown on a clean modern panel with explicit scope, source paths, and unresolved boundaries.

+
+
+
ClaimStateInspection path
+
Bounded Mercury alpha boot route works without a false clear8 / 8 RECORDEDCheckpoint ↗
+
Core contracts, state, receipts, hooks, and validators existMECHANICALLY CHECKEDValidation ↗
+
Exploratory benchmark receipts preserve positive, null, and mixed outcomesEXPLORATORYBoundaries ↗
+
Complete workflow improves independent human reasoning or discovery in deploymentNOT ESTABLISHEDResearch statement ↗
+
Elenchion Systems / Apparatus naming has legal or trademark clearanceUNVERIFIEDClaims register ↗
+
+

Design boundary: classical sculpture, botany, zoology, engraving, and instrument cues may affect perceived prestige or scientific seriousness. They do not add evidential weight to any row above.

+
+
+ +
+
+

LEGACY INDEX / NON-BREAKING MIGRATION

Old coordinates remain readable.

The public identity changed without rewriting the underlying contracts. Existing citations, commands, and receipts remain stable.

+
+
AxisSoul · Research Orchestrator/soul
+
CanonMind · Formal Reasoning/mind
+
AtlasSpace · Research Discovery/space
+
CrucibleReality · Method Synthesis/reality
+
ForgePower · Engineering Verification/power
+
ChronometerTime · Evaluation & Benchmarking/time
+
AegisGauntlet · Process Assurance/gauntlet
+
SextantMeditate · Decision Preflight/meditate
+
ConclaveCouncil · Evidence Review Panel/council
+
ParallaxMirror · Adaptive Reasoning Complement/foil
-
-

Inspect the work.

Source, runtime contracts, reproducibility, security, governance, and research notes remain directly accessible.

- +
+

SOURCE / GOVERNANCE / PROVENANCE

Inspect the machinery, not the mood.

+

The site is a frontispiece. The repository remains the authority for source, validation, security, governance, reproducibility, claims, and visual provenance.

+
+
-
The GauntletResearch & engineering toolkit · MIT · Showcase R19Source ↗
+
+ +
+ diff --git a/docs/styles.css b/docs/styles.css index 5ba3256d..54a5d2d0 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -1,33 +1,531 @@ -:root{ - color-scheme:dark; - --bg:#050608;--bg-soft:#080a0d;--text:#f4f5f7;--muted:#a5adb8;--dim:#657180;--line:#1c232c;--line2:#35404c;--accent:#ff735f; - --serif:"Iowan Old Style","Palatino Linotype","Book Antiqua",Palatino,Georgia,serif; - --sans:"Helvetica Neue",Helvetica,Arial,sans-serif;--mono:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace; +:root { + --paper: #F2EBDD; + --paper-deep: #E2D5BF; + --ink: #171714; + --slate: #3D4546; + --bronze: #77634C; + --oxide: #8B3F2F; + --verdigris: #476B63; + --white: #FCFAF4; + --rule: rgba(119, 99, 76, 0.48); + --rule-soft: rgba(119, 99, 76, 0.22); + --paper-glass: rgba(242, 235, 221, 0.91); + --serif: Georgia, "Times New Roman", serif; + --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + --shell: min(1180px, calc(100vw - 48px)); + --shadow-line: 0 1px 0 rgba(255,255,255,.48) inset; } -*{box-sizing:border-box} -html{background:var(--bg);scroll-behavior:smooth;scroll-padding-top:72px} -body{margin:0;background:var(--bg);color:var(--text);font-family:var(--sans);font-size:16px;line-height:1.6;text-rendering:optimizeLegibility;overflow-x:hidden} -a{color:inherit}.shell{width:min(1180px,calc(100% - 48px));margin-inline:auto}.skip-link{position:fixed;z-index:300;left:16px;top:-90px;background:#fff;color:#000;padding:12px 16px;font-weight:800}.skip-link:focus{top:16px}a:focus-visible,summary:focus-visible{outline:3px solid var(--accent);outline-offset:4px} -.system-field{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;background:#030405}.system-field canvas{position:absolute;inset:0;width:100%;height:100%}.system-field-vignette{position:absolute;inset:0;background:radial-gradient(ellipse at 72% 45%,transparent 0 30%,rgba(5,6,8,.08) 48%,rgba(5,6,8,.76) 82%,#050608 100%),linear-gradient(90deg,#050608 0 24%,rgba(5,6,8,.78) 40%,rgba(5,6,8,.05) 66%,rgba(5,6,8,.28) 100%)}.system-scan{position:absolute;left:0;right:0;height:1px;top:-5%;background:rgba(255,255,255,.12);box-shadow:0 0 14px rgba(255,115,95,.08);animation:scan 26s linear infinite}.system-caption{position:absolute;right:max(24px,calc((100vw - 1180px)/2));bottom:22px;display:flex;gap:14px;align-items:center;color:rgba(188,198,209,.45);font:700 9px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase}.system-caption strong{color:rgba(255,115,95,.65)} -@keyframes scan{0%{top:-4%}100%{top:104%}} +* { box-sizing: border-box; } +html { scroll-behavior: auto; overflow-x: clip; background: var(--paper); } +body { + margin: 0; + min-width: 320px; + color: var(--ink); + background: + linear-gradient(rgba(119,99,76,.027) 1px, transparent 1px), + linear-gradient(90deg, rgba(119,99,76,.022) 1px, transparent 1px), + radial-gradient(circle at 25% 8%, rgba(252,250,244,.76), transparent 34rem), + var(--paper); + background-size: 24px 24px, 24px 24px, auto, auto; + font-family: var(--sans); + line-height: 1.55; + text-rendering: optimizeLegibility; + overflow-x: hidden; +} -.topbar{position:sticky;top:0;z-index:100;display:flex;align-items:center;justify-content:space-between;gap:24px;padding:12px max(24px,calc((100vw - 1180px)/2));background:rgba(5,6,8,.82);border-bottom:1px solid rgba(255,255,255,.06);backdrop-filter:blur(16px)}.brand{text-decoration:none;font:850 13px/1 var(--sans);letter-spacing:.02em;text-transform:uppercase}.site-nav{display:flex;align-items:center;gap:5px}.site-nav a{min-height:42px;display:inline-flex;align-items:center;padding:0 9px;text-decoration:none;font:800 9px/1 var(--sans);letter-spacing:.06em;text-transform:uppercase;color:#d6dce4}.site-nav a:hover{text-decoration:underline;text-underline-offset:5px}.site-nav .repo-link{margin-left:8px;border:1px solid var(--line2);padding-inline:14px} +body::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + opacity: .23; + z-index: -1; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E"); + mix-blend-mode: multiply; +} -main,.footer{position:relative;z-index:2}.scene-section{position:relative}.hero{min-height:calc(100vh - 66px);display:grid;grid-template-columns:minmax(0,610px) 1fr;align-items:center;padding-top:72px;padding-bottom:72px}.hero-copy{position:relative;z-index:5;padding:28px 0;min-width:0}.eyebrow{margin:0 0 20px;color:var(--accent);font:800 10px/1 var(--mono);letter-spacing:.16em}.hero h1{margin:0;font:850 clamp(72px,8.3vw,126px)/.79 var(--sans);letter-spacing:-.07em;text-transform:uppercase}.hero-deck{margin:30px 0 0;max-width:610px;font:400 clamp(30px,3.35vw,46px)/1.04 var(--serif);letter-spacing:-.04em}.hero-summary{margin:24px 0 0;max-width:585px;color:#c0c8d1;font-size:15px;line-height:1.75}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:30px}.button{min-height:46px;display:inline-flex;align-items:center;justify-content:center;padding:0 17px;border:1px solid var(--line2);text-decoration:none;font:800 10px/1 var(--sans);letter-spacing:.05em;text-transform:uppercase;background:rgba(5,6,8,.42);backdrop-filter:blur(8px)}.button.primary{background:#f0f2f5;color:#06080b;border-color:#f0f2f5}.button:hover{border-color:var(--accent)} +a { color: inherit; } +img, canvas { max-width: 100%; } +button, input, textarea, select { font: inherit; } +code { font-family: var(--mono); } -.section{padding:112px 0;border-top:1px solid rgba(255,255,255,.055)}.section-heading{display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,.72fr);gap:38px 72px;align-items:end;margin-bottom:52px}.section-heading h2{margin:0;font:400 clamp(48px,5.2vw,72px)/.95 var(--serif);letter-spacing:-.05em}.section-heading p{margin:0;max-width:560px;color:var(--muted);font-size:14px;line-height:1.72} -.inventory{display:grid;grid-template-columns:1fr 1fr;gap:72px}.inventory h3{margin:0 0 12px;padding-bottom:10px;border-bottom:1px solid var(--line2);color:#8994a1;font:800 10px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase}.inventory-row{display:grid;grid-template-columns:minmax(145px,.48fr) 1fr;gap:20px;align-items:center;min-height:76px;border-bottom:1px solid var(--line);text-decoration:none}.inventory-row strong{font:500 24px/1.08 var(--serif)}.inventory-row span{color:var(--muted);font-size:12px;line-height:1.55}.inventory-row:hover strong{color:#fff;text-decoration:underline;text-underline-offset:5px} +.skip-link { + position: fixed; + top: 10px; + left: 10px; + z-index: 1000; + padding: 12px 16px; + color: var(--white); + background: var(--ink); + transform: translateY(-140%); + transition: transform .16s ease; +} +.skip-link:focus { transform: translateY(0); } -.workflow-section{background:rgba(5,6,8,.68);backdrop-filter:blur(5px)}.workflow-list{border-top:1px solid var(--line2)}.workflow-list article{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,.65fr);gap:48px;align-items:center;padding:32px 0;border-bottom:1px solid var(--line)}.workflow-list span{display:block;margin-bottom:8px;color:var(--accent);font:800 9px/1 var(--mono);letter-spacing:.11em;text-transform:uppercase}.workflow-list h3{margin:0;font:500 clamp(24px,2.55vw,36px)/1.1 var(--serif);letter-spacing:-.025em}.route{margin:0;color:#e2e6ea;font:700 13px/1.8 var(--mono);letter-spacing:.025em}.route i{padding:0 8px;color:#6d7886;font-style:normal} +:focus-visible { + outline: 3px solid var(--oxide); + outline-offset: 4px; +} + +.shell { width: var(--shell); margin-inline: auto; } + +.system-field { + position: fixed; + inset: 0; + z-index: -2; + overflow: hidden; + pointer-events: none; +} +#system-field-canvas { width: 100%; height: 100%; display: block; opacity: .46; } +.system-field-vignette { + position: absolute; + inset: 0; + background: + linear-gradient(90deg, var(--paper) 0%, rgba(242,235,221,.92) 36%, rgba(242,235,221,.35) 68%, rgba(242,235,221,.86) 100%), + linear-gradient(180deg, var(--paper) 0%, transparent 20%, transparent 76%, var(--paper) 100%); +} +.system-caption { + position: fixed; + right: 18px; + bottom: 16px; + display: flex; + gap: 10px; + align-items: baseline; + max-width: calc(100vw - 36px); + padding: 8px 10px; + border: 1px solid var(--rule); + background: rgba(252,250,244,.78); + color: var(--slate); + font: 9px/1.2 var(--mono); + letter-spacing: .11em; + text-transform: uppercase; + backdrop-filter: blur(3px); +} +.system-caption strong { color: var(--oxide); } -.gems-intro{min-height:60vh;display:flex;align-items:center}.gems-intro .section-heading{width:100%;margin-bottom:0}.gem-scene{min-height:92vh;display:flex;align-items:center;border-top:1px solid rgba(255,255,255,.045)}.gem-scene::before{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(5,6,8,.98) 0%,rgba(5,6,8,.91) 31%,rgba(5,6,8,.48) 48%,rgba(5,6,8,.04) 76%);pointer-events:none}.gem-copy{position:relative;z-index:4;margin-top:0}.gem-copy>*{max-width:460px}.gem-name{margin:0 0 15px;color:var(--gem-accent,var(--accent));font:800 11px/1 var(--mono);letter-spacing:.18em}.gem-copy h2{margin:0;font:400 clamp(48px,5.4vw,78px)/.94 var(--serif);letter-spacing:-.055em}.gem-lede{margin:22px 0 0;color:#c5ccd5;font-size:15px;line-height:1.75}.gem-copy dl{margin:30px 0 0;border-top:1px solid var(--line2)}.gem-copy dl div{display:grid;grid-template-columns:72px 1fr;gap:18px;padding:14px 0;border-bottom:1px solid var(--line)}.gem-copy dt{color:#74808e;font:800 9px/1.5 var(--mono);letter-spacing:.06em;text-transform:uppercase}.gem-copy dd{margin:0;color:var(--muted);font-size:13px;line-height:1.6}.gem-copy>a{display:inline-flex;margin-top:22px;font:800 10px/1 var(--sans);text-transform:uppercase;text-underline-offset:5px}.gem-scene[data-scene="mind"]{--gem-accent:#dec167}.gem-scene[data-scene="space"]{--gem-accent:#80aef7}.gem-scene[data-scene="reality"]{--gem-accent:#df86cf}.gem-scene[data-scene="power"]{--gem-accent:#58cedc}.gem-scene[data-scene="time"]{--gem-accent:#84cb96} +.topbar { + position: sticky; + top: 0; + z-index: 50; + min-height: 72px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 24px; + padding: 10px max(24px, calc((100vw - 1180px) / 2)); + border-bottom: 1px solid var(--rule); + background: var(--paper-glass); + backdrop-filter: blur(12px); +} +.brand { + display: inline-flex; + align-items: center; + gap: 10px; + min-width: 210px; + text-decoration: none; +} +.brand img { width: 34px; height: 34px; border: 1px solid var(--rule); } +.brand span { display: flex; flex-direction: column; } +.brand small { + color: var(--oxide); + font: 700 9px/1.2 var(--sans); + letter-spacing: .18em; +} +.brand strong { + font: 600 17px/1.1 var(--serif); + letter-spacing: .04em; +} +.site-nav { display: flex; align-items: center; justify-content: flex-end; gap: 2px; } +.site-nav a { + min-height: 44px; + display: inline-flex; + align-items: center; + padding: 0 12px; + text-decoration: none; + color: var(--slate); + font-size: 13px; + border-bottom: 2px solid transparent; +} +.site-nav a:hover { color: var(--ink); border-bottom-color: var(--bronze); } +.site-nav .repo-link { margin-left: 8px; border: 1px solid var(--rule); background: var(--white); } +.site-nav .repo-link:hover { border-color: var(--ink); } -#system{min-height:78vh;display:flex;flex-direction:column;justify-content:center}.system-heading{margin-bottom:58px}.system-line{margin:0;padding:40px 0;border-top:1px solid var(--line2);border-bottom:1px solid var(--line2);display:flex;align-items:center;justify-content:space-between;gap:12px;font:500 clamp(22px,2.5vw,36px)/1.25 var(--serif);letter-spacing:-.025em}.system-line span{white-space:nowrap}.system-line i{color:var(--accent);font-style:normal} +.hero { + min-height: calc(100svh - 72px); + display: grid; + grid-template-columns: minmax(0, 1.08fr) minmax(270px, .48fr); + align-items: center; + gap: clamp(40px, 8vw, 110px); + padding-block: clamp(84px, 13vh, 150px) clamp(74px, 11vh, 120px); +} +.hero-copy { position: relative; max-width: 800px; } +.eyebrow, .plate-label { + margin: 0 0 18px; + color: var(--oxide); + font: 700 11px/1.3 var(--sans); + letter-spacing: .18em; + text-transform: uppercase; +} +.hero h1 { + margin: 0; + font: 500 clamp(76px, 12.2vw, 166px)/.78 var(--serif); + letter-spacing: -.07em; +} +.hero-deck { + margin: 34px 0 14px; + font: 400 clamp(28px, 4.2vw, 54px)/1.02 var(--serif); + letter-spacing: -.035em; +} +.hero-summary { + max-width: 730px; + margin: 0; + color: var(--slate); + font-size: clamp(17px, 1.7vw, 21px); + line-height: 1.6; +} +.hero-actions, .runtime-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 32px; +} +.button, .download-all { + min-width: 24px; + min-height: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 11px 16px; + border: 1px solid var(--ink); + border-radius: 2px; + text-decoration: none; + font: 700 12px/1.2 var(--sans); + letter-spacing: .045em; +} +.button.primary { color: var(--white); background: var(--ink); } +.button.primary:hover { background: var(--oxide); border-color: var(--oxide); } +.button.secondary { color: var(--ink); background: var(--white); } +.button.secondary:hover { background: var(--paper-deep); } +.button.tertiary { color: var(--slate); border-color: var(--rule); background: transparent; } +.button.tertiary:hover { color: var(--ink); border-color: var(--ink); } +.hero-status { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 44px; + border-top: 1px solid var(--rule); + border-bottom: 1px solid var(--rule); +} +.hero-status div { min-width: 0; padding: 17px 18px 18px 0; } +.hero-status div + div { padding-left: 18px; border-left: 1px solid var(--rule); } +.hero-status span, .hero-status small { display: block; } +.hero-status span { color: var(--oxide); font: 700 9px/1.2 var(--mono); letter-spacing: .14em; } +.hero-status strong { display: block; margin-top: 6px; font: 600 16px/1.2 var(--serif); } +.hero-status small { margin-top: 5px; color: var(--slate); font-size: 11px; line-height: 1.35; } +.hero-plate { + align-self: end; + margin-bottom: 52px; + padding: 22px; + border: 1px solid var(--rule); + background: rgba(252,250,244,.76); + box-shadow: var(--shadow-line); +} +.hero-plate p { margin: 0 0 26px; color: var(--oxide); font: 700 10px/1.2 var(--mono); letter-spacing: .15em; } +.hero-plate strong { display: block; font: 500 29px/1.08 var(--serif); } +.hero-plate span { display: block; margin-top: 14px; color: var(--slate); font-size: 14px; } +.hero-plate small { display: block; margin-top: 28px; padding-top: 12px; border-top: 1px solid var(--rule); color: var(--oxide); font: 10px/1.45 var(--mono); } -.toolkit-section,.source-section{background:rgba(5,6,8,.95);backdrop-filter:blur(14px)}.tools-heading{grid-template-columns:minmax(0,1fr) minmax(300px,.72fr)}.download-all{grid-column:2;justify-self:start;min-height:42px;display:inline-flex;align-items:center;font:800 10px/1 var(--sans);text-transform:uppercase;text-underline-offset:5px}.tool-directory{border-top:1px solid var(--line2)}.tool-entry{scroll-margin-top:82px;border-bottom:1px solid var(--line)}.tool-entry summary{list-style:none;cursor:pointer;display:grid;grid-template-columns:minmax(260px,.85fr) 150px minmax(280px,1.15fr) 24px;gap:18px;align-items:center;min-height:78px;padding:12px 0}.tool-entry summary::-webkit-details-marker{display:none}.tool-entry summary strong{font:500 23px/1.05 var(--serif)}.tool-entry summary code{color:#e4e8ed;font:700 10px/1.4 var(--mono)}.tool-entry summary span{color:var(--dim);font-size:11px;line-height:1.5}.tool-entry summary::after{content:"+";justify-self:end;color:var(--dim);font:300 22px/1 var(--sans)}.tool-entry[open] summary::after{content:"−"}.tool-entry[open] summary{border-bottom:1px solid var(--line)}.tool-body{display:grid;grid-template-columns:1fr 1fr .65fr;gap:34px;padding:25px 0 30px}.tool-body h3{margin:0 0 9px;color:var(--accent);font:800 9px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase}.tool-body p{margin:0;color:var(--muted);font-size:13px;line-height:1.67}.tool-links{display:flex;flex-direction:column;gap:10px}.tool-links a{width:fit-content;font:800 10px/1.25 var(--mono);text-underline-offset:5px}.implementation-link{margin:28px 0 0}.implementation-link a{font:800 10px/1 var(--sans);text-transform:uppercase;text-underline-offset:5px} -.source-index{display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--line2)}.source-index a{min-height:86px;display:grid;align-content:center;gap:5px;padding:12px 22px 12px 0;border-bottom:1px solid var(--line);text-decoration:none}.source-index a:nth-child(even){padding-left:24px;border-left:1px solid var(--line)}.source-index b{font:500 20px/1.1 var(--serif)}.source-index span{color:var(--dim);font:650 10px/1.4 var(--mono);text-transform:uppercase}.source-index a:hover b{text-decoration:underline;text-underline-offset:5px}.footer{min-height:116px;display:grid;grid-template-columns:1fr auto auto;gap:24px;align-items:center;border-top:1px solid var(--line2);color:var(--dim);font:700 9px/1.4 var(--mono);text-transform:uppercase}.footer strong{color:#e8ebef;font:600 18px/1 var(--serif);text-transform:none}.footer a{color:#e8ebef;font:800 10px/1 var(--sans)} +.section { padding-block: clamp(86px, 12vw, 152px); } +.section-heading { max-width: 880px; margin-bottom: 48px; } +.section-heading h2 { + margin: 0; + font: 500 clamp(43px, 6.3vw, 78px)/.99 var(--serif); + letter-spacing: -.045em; +} +.section-heading > p:not(.plate-label), .split-heading > p { + margin: 22px 0 0; + max-width: 740px; + color: var(--slate); + font-size: 18px; + line-height: 1.65; +} +.split-heading { + max-width: none; + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(260px, .65fr); + gap: clamp(40px, 8vw, 110px); + align-items: end; +} +.split-heading > p { margin: 0 0 4px; } -@media(max-width:900px){.system-field-vignette{background:linear-gradient(180deg,rgba(5,6,8,.32),rgba(5,6,8,.68)),radial-gradient(ellipse at 65% 52%,transparent 0 20%,rgba(5,6,8,.8) 88%)}.system-caption{display:none}.hero{grid-template-columns:1fr;min-height:95vh}.section-heading{grid-template-columns:1fr;gap:20px}.inventory{grid-template-columns:1fr;gap:46px}.workflow-list article{grid-template-columns:1fr;gap:15px}.gem-scene::before{background:linear-gradient(180deg,rgba(5,6,8,.42) 0%,rgba(5,6,8,.82) 55%,rgba(5,6,8,.98) 100%)}.gem-scene{align-items:flex-end;padding-bottom:72px}.tool-entry summary{grid-template-columns:1fr auto;gap:6px 18px;padding:17px 0}.tool-entry summary code{grid-column:1}.tool-entry summary span{grid-column:1}.tool-entry summary::after{grid-column:2;grid-row:1}.tool-body{grid-template-columns:1fr}.tools-heading{grid-template-columns:1fr}.download-all{grid-column:1}} -@media(max-width:720px){html{scroll-padding-top:126px}.shell{width:min(calc(100% - 32px),1180px)}.topbar{padding:10px 16px 7px;flex-wrap:wrap;gap:7px 12px}.site-nav{width:100%;order:2;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:1px solid var(--line);padding-top:4px}.site-nav a{justify-content:center;font-size:8.5px;padding-inline:3px}.site-nav .repo-link{margin-left:0}.hero{padding-top:46px}.hero h1{font-size:clamp(58px,17.5vw,76px)}.hero-deck{font-size:clamp(30px,9vw,40px)}.hero-actions{display:grid}.button{width:100%}.section{padding:82px 0}.section-heading h2{font-size:48px}.inventory-row{grid-template-columns:1fr;gap:5px;padding:13px 0}.workflow-list article{padding:27px 0}.workflow-list h3{font-size:30px}.gem-scene{min-height:88vh}.gem-copy h2{font-size:52px}.gem-copy dl div{grid-template-columns:62px 1fr}.system-line{font-size:25px;flex-wrap:wrap;justify-content:flex-start;gap:7px 10px}.system-line span{white-space:normal}.source-index{grid-template-columns:1fr}.source-index a,.source-index a:nth-child(even){padding:12px 0;border-left:0}.footer{grid-template-columns:1fr;align-items:start;padding:28px 0}.tool-entry{scroll-margin-top:130px}} -@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}.system-scan{animation:none}} +.inspection-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + border-top: 1px solid var(--rule); + border-left: 1px solid var(--rule); + background: rgba(252,250,244,.37); +} +.inspection-grid article { + min-width: 0; + min-height: 225px; + padding: 24px; + border-right: 1px solid var(--rule); + border-bottom: 1px solid var(--rule); +} +.inspection-grid span { color: var(--oxide); font: 700 11px/1 var(--mono); } +.inspection-grid h3 { margin: 38px 0 10px; font: 500 29px/1.1 var(--serif); } +.inspection-grid p { margin: 0; color: var(--slate); font-size: 14px; } + +.workflow-section { border-block: 1px solid var(--rule); background: rgba(226,213,191,.44); } +.workflow-list { border-top: 1px solid var(--ink); } +.workflow-list article { + min-width: 0; + display: grid; + grid-template-columns: minmax(0, 1.4fr) minmax(260px, .8fr); + align-items: center; + gap: 36px; + padding: 26px 0; + border-bottom: 1px solid var(--rule); +} +.workflow-list span { color: var(--oxide); font: 700 10px/1.2 var(--mono); letter-spacing: .14em; text-transform: uppercase; } +.workflow-list h3 { margin: 7px 0 0; font: 500 clamp(23px, 2.7vw, 35px)/1.12 var(--serif); } +.workflow-list .route { margin: 0; color: var(--slate); font: 600 13px/1.6 var(--mono); text-align: right; } +.workflow-list i { color: var(--oxide); font-style: normal; } + +.runtime-section { padding-block: clamp(90px, 12vw, 150px); } +.runtime-grid { + display: grid; + grid-template-columns: minmax(0, .95fr) minmax(360px, .75fr); + gap: clamp(45px, 8vw, 110px); + align-items: start; +} +.runtime-copy h2 { margin: 0; font: 500 clamp(56px, 7.5vw, 96px)/.92 var(--serif); letter-spacing: -.05em; } +.large-copy { margin: 24px 0 18px; font: 400 clamp(25px, 3.2vw, 39px)/1.13 var(--serif); } +.runtime-copy > p:last-of-type { color: var(--slate); font-size: 17px; } +.runtime-ledger { border: 1px solid var(--ink); background: var(--white); } +.ledger-head { display: flex; justify-content: space-between; gap: 16px; padding: 14px 16px; color: var(--white); background: var(--ink); font: 700 10px/1.2 var(--mono); letter-spacing: .12em; } +.ledger-head code { color: var(--paper-deep); } +.runtime-ledger dl { margin: 0; } +.runtime-ledger dl div { display: grid; grid-template-columns: 1fr 1.25fr; gap: 16px; padding: 12px 16px; border-bottom: 1px solid var(--rule-soft); } +.runtime-ledger dt { color: var(--slate); font-size: 12px; } +.runtime-ledger dd { margin: 0; font: 600 12px/1.4 var(--mono); } +.ledger-warning { padding: 16px; border-top: 2px solid var(--oxide); background: rgba(139,63,47,.055); } +.ledger-warning strong { display: block; color: var(--oxide); font: 700 10px/1 var(--mono); letter-spacing: .14em; } +.ledger-warning span { display: block; margin-top: 7px; color: var(--slate); font-size: 12px; } + +.gems-intro { padding-bottom: 70px; } +.gem-scene { + overflow: hidden; + position: relative; + min-height: 82svh; + display: flex; + align-items: center; + padding-block: clamp(100px, 15vw, 190px); + border-top: 1px solid var(--rule); +} +.gem-scene::before { + content: ""; + position: absolute; + left: max(24px, calc((100vw - 1180px) / 2)); + top: 50%; + width: min(34vw, 390px); + aspect-ratio: 1; + border: 1px solid var(--rule-soft); + border-radius: 50%; + transform: translateY(-50%); + pointer-events: none; +} +.gem-copy { min-width: 0; padding-left: min(47vw, 555px); } +.instrument-index { display: flex; flex-wrap: wrap; gap: 10px 20px; align-items: baseline; margin-bottom: 36px; } +.instrument-index span { color: var(--oxide); font: 700 10px/1.2 var(--mono); letter-spacing: .15em; } +.instrument-index code { color: var(--slate); font-size: 11px; } +.gem-name { margin: 0; color: var(--bronze); font: 700 12px/1.2 var(--sans); letter-spacing: .22em; } +.gem-copy h2 { margin: 9px 0 22px; font: 500 clamp(49px, 7vw, 86px)/.94 var(--serif); letter-spacing: -.05em; } +.gem-lede { margin: 0; max-width: 740px; font: 400 clamp(23px, 3vw, 38px)/1.18 var(--serif); } +.gem-copy dl { max-width: 770px; margin: 38px 0 28px; border-top: 1px solid var(--rule); } +.gem-copy dl div { display: grid; grid-template-columns: 95px 1fr; gap: 24px; padding: 15px 0; border-bottom: 1px solid var(--rule); } +.gem-copy dt { color: var(--oxide); font: 700 10px/1.4 var(--mono); letter-spacing: .12em; text-transform: uppercase; } +.gem-copy dd { margin: 0; color: var(--slate); } +.gem-copy > a { min-height: 44px; display: inline-flex; align-items: center; color: var(--ink); font-weight: 700; text-underline-offset: 5px; } + +.system-heading { max-width: 960px; } +.system-line { + display: flex; + flex-wrap: wrap; + gap: 9px 13px; + align-items: center; + padding: 25px; + border-block: 1px solid var(--ink); + background: rgba(252,250,244,.55); + font: 600 13px/1.5 var(--mono); +} +.system-line i { color: var(--oxide); font-style: normal; } +.authority-note { max-width: 740px; margin: 30px 0 0 auto; padding-left: 20px; border-left: 3px solid var(--oxide); } +.authority-note strong { color: var(--oxide); font: 700 11px/1.2 var(--mono); letter-spacing: .1em; text-transform: uppercase; } +.authority-note p { margin: 6px 0 0; font: 400 24px/1.25 var(--serif); } + +.toolkit-section { border-top: 1px solid var(--rule); background: rgba(226,213,191,.29); } +.tools-heading { max-width: none; display: flex; justify-content: space-between; gap: 40px; align-items: end; } +.tools-heading > div { max-width: 780px; } +.download-all { flex: 0 0 auto; background: var(--white); } +.tool-directory { border-top: 1px solid var(--ink); } +.tool-entry { border-bottom: 1px solid var(--rule); } +.tool-entry summary { + min-height: 76px; + display: grid; + grid-template-columns: minmax(240px, .8fr) 100px minmax(260px, 1.2fr); + align-items: center; + gap: 24px; + padding: 15px 8px; + cursor: pointer; + list-style: none; +} +.tool-entry summary::-webkit-details-marker { display: none; } +.tool-entry summary::after { content: "+"; justify-self: end; grid-column: 4; color: var(--oxide); font: 28px/1 var(--serif); } +.tool-entry[open] summary::after { content: "−"; } +.tool-entry summary strong { font: 500 21px/1.2 var(--serif); } +.tool-entry summary code { color: var(--oxide); font-size: 12px; } +.tool-entry summary span { color: var(--slate); font-size: 13px; } +.tool-body { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 28px 48px; + padding: 8px 8px 32px; +} +.tool-body h3 { margin: 0 0 7px; color: var(--oxide); font: 700 10px/1.2 var(--mono); letter-spacing: .12em; text-transform: uppercase; } +.tool-body p { margin: 0; color: var(--slate); font-size: 14px; } +.tool-links { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 8px; } +.tool-links a { min-width: 76px; min-height: 38px; display: inline-flex; align-items: center; justify-content: center; padding: 7px 10px; border: 1px solid var(--rule); background: var(--white); text-decoration: none; font: 700 11px/1.2 var(--mono); } +.tool-links a:hover { border-color: var(--ink); } +.implementation-index { display: flex; justify-content: space-between; gap: 30px; align-items: center; margin-top: 36px; padding: 20px; border: 1px solid var(--rule); background: var(--white); } +.implementation-index p { margin: 0; color: var(--slate); } +.implementation-index a, .text-link { min-height: 44px; display: inline-flex; align-items: center; font-weight: 700; text-underline-offset: 5px; } + +.extended-section { border-top: 1px solid var(--rule); } +.extended-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border-top: 1px solid var(--rule); border-left: 1px solid var(--rule); } +.extended-grid article { min-width: 0; min-height: 245px; padding: 25px; border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); background: rgba(252,250,244,.48); } +.extended-grid article > span { color: var(--oxide); font: 700 10px/1.2 var(--mono); letter-spacing: .14em; } +.extended-grid h3 { margin: 45px 0 12px; font: 500 32px/1.1 var(--serif); } +.extended-grid p { margin: 0 0 18px; color: var(--slate); } +.extended-grid code { color: var(--bronze); font-size: 11px; } +.extended-section .text-link { margin-top: 22px; } + +.evidence-section { border-block: 1px solid var(--rule); background: var(--white); } +.evidence-table { border: 1px solid var(--ink); } +.evidence-row { min-width: 0; display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(150px, .48fr) minmax(150px, .42fr); align-items: center; gap: 20px; min-height: 72px; padding: 13px 16px; border-bottom: 1px solid var(--rule-soft); } +.evidence-row:last-child { border-bottom: 0; } +.evidence-head { min-height: 42px; color: var(--white); background: var(--ink); font: 700 10px/1.2 var(--mono); letter-spacing: .13em; text-transform: uppercase; } +.evidence-row > span { color: var(--slate); font-size: 13px; } +.evidence-row strong { font: 700 10px/1.3 var(--mono); letter-spacing: .08em; } +.evidence-row a { min-height: 44px; display: inline-flex; align-items: center; justify-content: flex-end; font-weight: 700; font-size: 12px; } +.state-pass { color: var(--verdigris); } +.state-caution, .state-open { color: var(--oxide); } +.evidence-boundary { max-width: 850px; margin: 26px 0 0; padding-left: 18px; border-left: 3px solid var(--oxide); color: var(--slate); font-size: 14px; } +.evidence-boundary strong { color: var(--oxide); } + +.compatibility-section { background: rgba(226,213,191,.27); } +.compatibility-table { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border-top: 1px solid var(--ink); border-left: 1px solid var(--rule); } +.compatibility-table > div { min-width: 0; display: grid; grid-template-columns: 120px minmax(0, 1fr) 90px; gap: 15px; align-items: center; min-height: 66px; padding: 12px 15px; border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); } +.compatibility-table strong { font: 500 19px/1.2 var(--serif); } +.compatibility-table span { color: var(--slate); font-size: 12px; } +.compatibility-table code { justify-self: end; color: var(--oxide); font-size: 11px; } + +.source-section { padding-bottom: 110px; } +.source-index { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border-top: 1px solid var(--ink); border-left: 1px solid var(--rule); } +.source-index a { min-width: 0; min-height: 88px; display: flex; flex-direction: column; justify-content: center; padding: 17px 20px; border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); text-decoration: none; background: rgba(252,250,244,.46); } +.source-index a:hover { background: var(--paper-deep); } +.source-index strong { font: 500 21px/1.2 var(--serif); } +.source-index span { margin-top: 5px; color: var(--slate); font: 10px/1.3 var(--mono); overflow-wrap: anywhere; } + +.footer { border-top: 1px solid var(--ink); background: var(--paper-deep); } +.footer-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 30px; padding-block: 36px 56px; } +.footer-grid strong, .footer-grid span { display: block; } +.footer-grid strong { color: var(--oxide); font: 700 10px/1.2 var(--mono); letter-spacing: .13em; } +.footer-grid span { margin-top: 7px; color: var(--slate); font-size: 12px; } + +@media (max-width: 1040px) { + .topbar { padding-inline: 20px; } + .hero { grid-template-columns: 1fr; } + .hero-plate { max-width: 430px; margin: 0 0 20px auto; } + .inspection-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .runtime-grid { grid-template-columns: 1fr; } + .runtime-ledger { max-width: 720px; } + .gem-scene::before { opacity: .45; left: 24px; } + .gem-copy { padding-left: min(35vw, 340px); } + .tool-entry summary { grid-template-columns: minmax(230px, .8fr) 90px minmax(190px, 1fr) 24px; } +} + +@media (max-width: 760px) { + :root { --shell: min(100% - 30px, 680px); } + .system-caption { display: none; } + .topbar { min-height: 64px; flex-wrap: wrap; row-gap: 4px; padding-block: 8px; } + .brand { min-width: 0; flex: 1 1 auto; } + .site-nav { order: 3; width: 100%; justify-content: flex-start; overflow-x: auto; overscroll-behavior-inline: contain; scrollbar-width: none; } + .site-nav::-webkit-scrollbar { display: none; } + .site-nav a { flex: 0 0 auto; } + .brand small { display: none; } + .brand strong { font-size: 15px; } + .site-nav .repo-link { margin-left: 0; padding-inline: 10px; } + .hero { min-height: auto; gap: 38px; padding-block: 82px 74px; } + .hero h1 { font-size: clamp(64px, 21vw, 102px); } + .hero-deck { margin-top: 27px; } + .hero-status { grid-template-columns: 1fr; } + .hero-status div { padding: 14px 0; } + .hero-status div + div { padding-left: 0; border-left: 0; border-top: 1px solid var(--rule); } + .hero-plate { margin: 0; } + .split-heading { grid-template-columns: 1fr; gap: 22px; } + .split-heading > p { margin: 0; } + .inspection-grid { grid-template-columns: 1fr; } + .inspection-grid article { min-height: 180px; } + .workflow-list article { grid-template-columns: 1fr; gap: 13px; } + .workflow-list .route { text-align: left; } + .runtime-ledger dl div { grid-template-columns: 1fr; gap: 3px; } + .gem-scene { min-height: auto; padding-block: 105px; } + .gem-scene::before { width: 245px; top: 88px; left: auto; right: -84px; transform: none; opacity: .35; } + .gem-copy { padding-left: 0; } + .gem-lede { max-width: 90%; } + .gem-copy dl div { grid-template-columns: 72px 1fr; gap: 13px; } + .tools-heading { display: grid; align-items: start; } + .download-all { justify-self: start; } + .tool-entry summary { grid-template-columns: minmax(0, 1fr) 65px 22px; gap: 10px; padding-block: 16px; } + .tool-entry summary span { grid-column: 1 / -1; grid-row: 2; } + .tool-entry summary::after { grid-column: 3; grid-row: 1; } + .tool-body { grid-template-columns: 1fr; gap: 20px; } + .tool-links { grid-column: 1; } + .implementation-index { align-items: flex-start; flex-direction: column; } + .extended-grid { grid-template-columns: 1fr; } + .evidence-row { grid-template-columns: 1fr; gap: 7px; padding-block: 16px; } + .evidence-head { display: none; } + .evidence-row a { justify-content: flex-start; } + .compatibility-table, .source-index { grid-template-columns: 1fr; } + .compatibility-table > div { grid-template-columns: 96px minmax(0, 1fr) 74px; } + .footer-grid { grid-template-columns: 1fr; } +} + +@media (max-width: 430px) { + .hero-actions, .runtime-actions { display: grid; } + .button { width: 100%; } + .section-heading h2 { font-size: 42px; } + .gem-copy h2 { font-size: 47px; } + .authority-note p { font-size: 21px; } + .compatibility-table > div { grid-template-columns: 1fr auto; } + .compatibility-table span { grid-column: 1 / -1; grid-row: 2; } +} + +@media (prefers-reduced-motion: reduce) { + html { scroll-behavior: auto; } + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + #system-field-canvas { opacity: .28; } +} + +@media print { + .topbar, .system-field, .hero-actions, .runtime-actions { display: none !important; } + body { background: #fff; color: #000; } + .section, .gem-scene { min-height: auto; padding-block: 36px; break-inside: avoid; } + a { text-decoration: none; } +} diff --git a/docs/system-field.js b/docs/system-field.js index 33694eec..eac196e7 100644 --- a/docs/system-field.js +++ b/docs/system-field.js @@ -1,249 +1,540 @@ (() => { - 'use strict'; + "use strict"; - const canvas = document.getElementById('system-field-canvas'); - const caption = document.getElementById('system-caption'); + const canvas = document.getElementById("system-field-canvas"); if (!canvas) return; - const ctx = canvas.getContext('2d', { alpha: true }); - const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches; - const DPR_CAP = 1.5; - const COLORS = { - base: '#8290a0', accent: '#ff735f', mind: '#dec167', space: '#80aef7', reality: '#df86cf', power: '#58cedc', time: '#84cb96', bad: '#e46b5b', good: '#82d79a' + + const ctx = canvas.getContext("2d", { alpha: true }); + if (!ctx) return; + + const caption = document.getElementById("system-caption"); + const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); + let reducedMotion = motionQuery.matches; + let width = 0; + let height = 0; + let dpr = 1; + let frame = 0; + let time = 0; + let activeScene = document.body.dataset.scene || "hero"; + + const palette = { + paper: "#F2EBDD", + ink: "#171714", + slate: "#3D4546", + bronze: "#77634C", + oxide: "#8B3F2F", + verdigris: "#476B63", + white: "#FCFAF4" }; - let W = 0, H = 0, dpr = 1, active = 'hero', previous = 'hero', switchedAt = performance.now(); - let pointerX = 0, pointerY = 0; - let far = [], webNodes = [], webEdges = []; - const sceneLabels = { - hero: ['LIVE SYSTEM VIEW','TASK → OBLIGATION → METHOD → RECEIPT'], - overview: ['SYSTEM MAP','FIVE SPECIALISTS · FIVE CONTROL / REVIEW TOOLS'], - workflow: ['ROUTING TRACE','ONLY LOAD-BEARING METHODS ACTIVATE'], - gems: ['SPECIALIST LAYER','THE FIELD RECONFIGURES AROUND THE CLAIM'], - mind: ['MIND / FORMAL REASONING','CLAIM → OBLIGATION → PROOF / COUNTEREXAMPLE'], - space: ['SPACE / RESEARCH DISCOVERY','QUERY → SOURCES → SCOPED FINDING'], - reality: ['REALITY / METHOD SYNTHESIS','KNOWN METHODS → GAP → CANDIDATE → RECHECK'], - power: ['POWER / ENGINEERING VERIFICATION','SOURCE → BUILD → TEST → RUNTIME → RECEIPT'], - time: ['TIME / EVALUATION','CANDIDATE ∥ BASELINE → MATCHED CELLS → STOP / GO'], - system: ['CONTROL MODEL','TASK → OBLIGATIONS → METHODS → RECEIPTS → RESULT'], - quiet: ['SOURCE / IMPLEMENTATION','DETAIL VIEW'] + const sceneCaption = { + hero: ["PLATE / ACTIVE SYSTEM", "FRAME → ROUTE → OBSERVE → VERIFY → RELEASE"], + overview: ["METHOD / CONTROL MODEL", "SIX STAGES · ONE EVIDENCE STATE"], + workflow: ["ROUTE / OBLIGATION", "THE METHOD CHANGES WITH THE CLAIM"], + gems: ["INDEX / INSTRUMENTS", "RUNTIME + TEN CORE CONTRACTS"], + mind: ["INSTRUMENT 01 / CANON", "FORMALIZE → NEGATE → VERIFY"], + space: ["INSTRUMENT 02 / ATLAS", "SEARCH → IDENTIFY → BOUND"], + reality: ["INSTRUMENT 03 / CRUCIBLE", "GAP → MECHANISM → FALSIFIER"], + power: ["INSTRUMENT 04 / FORGE", "SOURCE → ENTRYPOINT → EXECUTION"], + time: ["INSTRUMENT 05 / CHRONOMETER", "BASELINE → MEASURE → DECIDE"], + system: ["AUTHORITY / RELEASE PATH", "OBSERVATION ≠ RECEIPT ≠ RELEASE"], + quiet: ["ARCHIVE / SOURCE", "INSPECT THE MACHINERY, NOT THE MOOD"] }; - function rand(a,b){ return a + Math.random() * (b-a); } - function hexToRgb(hex){ const n=parseInt(hex.slice(1),16); return [(n>>16)&255,(n>>8)&255,n&255]; } - function rgba(hex,a){ const [r,g,b]=hexToRgb(hex); return `rgba(${r},${g},${b},${a})`; } - function lerp(a,b,t){ return a + (b-a)*t; } - function ease(t){ return t*t*(3-2*t); } - function clamp(v,a,b){ return Math.max(a,Math.min(b,v)); } - - // Derived from the uploaded Nexus Rift menu's cosmic-web/orbital formation idea, - // but implemented locally in Canvas2D so the public site has no remote runtime dependency. - function buildFarField(){ - far = []; - const count = W < 700 ? 220 : 520; - for(let i=0;i> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`; + } + + function setStroke(color = palette.bronze, alpha = 0.42, lineWidth = 1) { + ctx.strokeStyle = rgba(color, alpha); + ctx.lineWidth = lineWidth; + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + } + + function line(x1, y1, x2, y2, color = palette.bronze, alpha = 0.42, lineWidth = 1) { + setStroke(color, alpha, lineWidth); + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + } + + function circle(x, y, radius, color = palette.bronze, alpha = 0.42, lineWidth = 1, fill = null) { + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + if (fill) { + ctx.fillStyle = fill; + ctx.fill(); } + setStroke(color, alpha, lineWidth); + ctx.stroke(); } - function buildWeb(){ - webNodes=[]; webEdges=[]; - const anchors = W < 700 ? 14 : 22; - for(let i=0;ia[0]-b[0]); - ranked.slice(0, i%3===0?2:1).forEach(([,j])=>webEdges.push([i,j])); + function ellipse(x, y, rx, ry, rotation = 0, color = palette.bronze, alpha = 0.42, lineWidth = 1) { + setStroke(color, alpha, lineWidth); + ctx.beginPath(); + ctx.ellipse(x, y, rx, ry, rotation, 0, Math.PI * 2); + ctx.stroke(); + } + + function label(value, x, y, align = "left", color = palette.slate, alpha = 0.52, size = 10) { + ctx.save(); + ctx.font = `600 ${size}px ui-monospace, SFMono-Regular, Menlo, monospace`; + ctx.textAlign = align; + ctx.textBaseline = "middle"; + ctx.fillStyle = rgba(color, alpha); + ctx.fillText(value, x, y); + ctx.restore(); + } + + function dot(x, y, radius = 3, color = palette.oxide, alpha = 0.62) { + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fillStyle = rgba(color, alpha); + ctx.fill(); + } + + function ticks(x, y, radius, count = 36, length = 6, color = palette.bronze, alpha = 0.33) { + for (let i = 0; i < count; i += 1) { + const angle = (i / count) * Math.PI * 2; + const major = i % 6 === 0; + const inner = radius - (major ? length * 1.65 : length); + line( + x + Math.cos(angle) * inner, + y + Math.sin(angle) * inner, + x + Math.cos(angle) * radius, + y + Math.sin(angle) * radius, + major ? palette.oxide : color, + major ? alpha + 0.14 : alpha, + major ? 1.2 : 0.7 + ); } } - function resize(){ - dpr=Math.min(devicePixelRatio||1,DPR_CAP); W=innerWidth; H=innerHeight; - canvas.width=Math.floor(W*dpr); canvas.height=Math.floor(H*dpr); canvas.style.width=W+'px'; canvas.style.height=H+'px'; - ctx.setTransform(dpr,0,0,dpr,0,0); buildFarField(); buildWeb(); + function crosshair(x, y, radius) { + line(x - radius, y, x + radius, y, palette.oxide, 0.4, 0.8); + line(x, y - radius, x, y + radius, palette.oxide, 0.4, 0.8); + circle(x, y, 4, palette.oxide, 0.62, 1.1); } - function diamond(x,y,r,color,alpha=1,fill=true){ - ctx.save();ctx.translate(x,y);ctx.rotate(Math.PI/4);ctx.globalAlpha=alpha; - if(fill){ctx.fillStyle=color;ctx.fillRect(-r,-r,r*2,r*2);}else{ctx.strokeStyle=color;ctx.lineWidth=1;ctx.strokeRect(-r,-r,r*2,r*2);}ctx.restore(); + function paperGrid(step = 42, alpha = 0.065) { + for (let x = step / 2; x < width; x += step) line(x, 0, x, height, palette.bronze, alpha, 0.6); + for (let y = step / 2; y < height; y += step) line(0, y, width, y, palette.bronze, alpha, 0.6); } - function line(a,b,color,alpha=1,width=1){ctx.beginPath();ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);ctx.strokeStyle=rgba(color,alpha);ctx.lineWidth=width;ctx.stroke();} - function label(text,x,y,color='#b6c0cb',alpha=.8,align='center',size=10){ctx.font=`700 ${size}px ${getComputedStyle(document.documentElement).getPropertyValue('--mono') || 'monospace'}`;ctx.textAlign=align;ctx.textBaseline='middle';ctx.fillStyle=rgba(color,alpha);ctx.fillText(text,x,y);} - function drawFar(t,intensity){ - const driftX=Math.sin(t*.00006)*16 + pointerX*5, driftY=Math.cos(t*.00005)*8 + pointerY*4; - far.forEach((p,i)=>{ - const tw=.55+.45*Math.sin(t*.0007*p.speed+p.phase); - const perspective=.55+.45*p.z; - const x=(p.x*W)+driftX*(1-p.z), y=(p.y*H)+driftY*(1-p.z); - const a=intensity*(.05+.12*tw)*(1-p.z*.32); - diamond(x,y,p.s*perspective,'#c7d0da',a,true); - }); - ctx.save();ctx.globalCompositeOperation='lighter'; - webEdges.forEach(([i,j],k)=>{ - const a=webNodes[i],b=webNodes[j]; - const ax=a.x*W+driftX*(1-a.z),ay=a.y*H+driftY*(1-a.z),bx=b.x*W+driftX*(1-b.z),by=b.y*H+driftY*(1-b.z); - const pulse=.5+.5*Math.sin(t*.00035+k*.7); line([ax,ay],[bx,by],COLORS.base,intensity*(.025+.035*pulse),.65); - }); - ctx.restore(); + function rightCenter(scale = 1) { + const compact = width < 850; + return { + x: compact ? width * 0.73 : width * 0.79, + y: height * 0.49, + r: Math.min(width, height) * (compact ? 0.24 : 0.28) * scale + }; } - function gemGeometry(cx,cy,s){ - const p=[ - [cx,cy-s],[cx-s*.64,cy-s*.55],[cx-s,cy],[cx-s*.58,cy+s*.68],[cx,cy+s],[cx+s*.58,cy+s*.68],[cx+s,cy],[cx+s*.64,cy-s*.55], - [cx,cy-s*.42],[cx-s*.38,cy],[cx,cy+s*.42],[cx+s*.38,cy],[cx,cy] - ]; - const e=[[0,1],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,0],[0,8],[1,8],[7,8],[2,9],[3,10],[4,10],[5,10],[6,11],[8,12],[9,12],[10,12],[11,12],[8,9],[9,10],[10,11],[11,8]]; - return {p,e}; + function armillary(x, y, radius, phase = 0) { + circle(x, y, radius, palette.ink, 0.34, 1.15); + circle(x, y, radius * 0.72, palette.bronze, 0.32, 0.8); + ellipse(x, y, radius, radius * 0.38, -0.22 + phase, palette.ink, 0.31, 0.9); + ellipse(x, y, radius, radius * 0.38, 0.54 - phase * 0.7, palette.bronze, 0.34, 0.9); + ellipse(x, y, radius * 0.39, radius, 0.16 + phase * 0.4, palette.ink, 0.29, 0.9); + ellipse(x, y, radius * 0.62, radius, 1.06 - phase * 0.3, palette.bronze, 0.27, 0.8); + ticks(x, y, radius + 9, 48, 7); + crosshair(x, y, 11); + line(x, y + radius, x, y + radius + 36, palette.ink, 0.36, 1.2); + line(x - radius * 0.24, y + radius + 36, x + radius * 0.24, y + radius + 36, palette.ink, 0.36, 1.2); } - function drawGem(cx,cy,s,color,t,alpha=1){ - const {p,e}=gemGeometry(cx,cy,s); ctx.save();ctx.globalCompositeOperation='lighter'; - e.forEach((edge,k)=>{const pulse=.45+.55*Math.sin(t*.0011-k*.29);line(p[edge[0]],p[edge[1]],color,alpha*(.24+.19*pulse),1);}); - p.forEach((q,i)=>diamond(q[0],q[1],i===12?4:2.3,color,alpha*(i===12?.95:.55),true));ctx.restore(); return {p,e}; + function profile(x, y, scale) { + ctx.save(); + ctx.translate(x, y); + ctx.scale(scale, scale); + setStroke(palette.ink, 0.31, 1.2 / scale); + ctx.beginPath(); + ctx.moveTo(-30, 78); + ctx.bezierCurveTo(-27, 44, -18, 23, 6, 10); + ctx.bezierCurveTo(-7, -6, -12, -26, -8, -45); + ctx.bezierCurveTo(-3, -70, 17, -86, 42, -82); + ctx.bezierCurveTo(57, -80, 69, -72, 76, -61); + ctx.bezierCurveTo(67, -56, 63, -48, 66, -40); + ctx.bezierCurveTo(78, -37, 85, -29, 87, -19); + ctx.bezierCurveTo(84, -12, 79, -8, 71, -6); + ctx.bezierCurveTo(75, 1, 74, 8, 69, 13); + ctx.bezierCurveTo(64, 19, 56, 21, 47, 22); + ctx.bezierCurveTo(44, 38, 35, 51, 20, 60); + ctx.bezierCurveTo(37, 67, 51, 82, 57, 101); + ctx.stroke(); + line(-18, -47, 57, -47, palette.oxide, 0.24, 0.7 / scale); + line(-18, -7, 73, -7, palette.oxide, 0.24, 0.7 / scale); + line(20, -78, 20, 90, palette.oxide, 0.22, 0.7 / scale); + ctx.restore(); } - function travellingPulse(a,b,t,offset,color,alpha=1){ - const u=(t*.00018+offset)%1, x=lerp(a[0],b[0],u),y=lerp(a[1],b[1],u);diamond(x,y,3,color,alpha,true); + function leaf(ctxX, ctxY, angle, length, side) { + const dx = Math.cos(angle); + const dy = Math.sin(angle); + const px = -dy; + const py = dx; + const widthLeaf = length * 0.33 * side; + ctx.beginPath(); + ctx.moveTo(ctxX, ctxY); + ctx.bezierCurveTo( + ctxX + dx * length * 0.35 + px * widthLeaf, + ctxY + dy * length * 0.35 + py * widthLeaf, + ctxX + dx * length * 0.77 + px * widthLeaf * 0.5, + ctxY + dy * length * 0.77 + py * widthLeaf * 0.5, + ctxX + dx * length, + ctxY + dy * length + ); + ctx.bezierCurveTo( + ctxX + dx * length * 0.75 - px * widthLeaf * 0.42, + ctxY + dy * length * 0.75 - py * widthLeaf * 0.42, + ctxX + dx * length * 0.3 - px * widthLeaf * 0.55, + ctxY + dy * length * 0.3 - py * widthLeaf * 0.55, + ctxX, + ctxY + ); + setStroke(palette.verdigris, 0.33, 0.9); + ctx.stroke(); + line(ctxX, ctxY, ctxX + dx * length, ctxY + dy * length, palette.verdigris, 0.25, 0.65); } - function stagePos(){ - if(W<800) return {cx:W*.64,cy:H*.36,s:Math.min(W,H)*.23}; - return {cx:W*.73,cy:H*.49,s:Math.min(W,H)*.27}; + function botanical(x, y, heightStem) { + const sway = reducedMotion ? 0 : Math.sin(time * 0.00035) * 0.035; + setStroke(palette.verdigris, 0.34, 1.2); + ctx.beginPath(); + ctx.moveTo(x, y + heightStem * 0.48); + ctx.bezierCurveTo(x + 18, y + heightStem * 0.12, x - 12, y - heightStem * 0.16, x + 24, y - heightStem * 0.5); + ctx.stroke(); + const points = [0.31, 0.15, -0.02, -0.19, -0.34]; + points.forEach((offset, index) => { + const py = y + heightStem * offset; + const px = x + (0.28 - offset) * 22; + const direction = index % 2 === 0 ? Math.PI + 0.25 + sway : -0.25 + sway; + leaf(px, py, direction, heightStem * (0.19 - index * 0.008), index % 2 === 0 ? 1 : -1); + }); + dot(x + 24, y - heightStem * 0.5, 2.4, palette.verdigris, 0.46); } - function drawTaskRoute(t,color=COLORS.accent,alpha=.8){ - const {cx,cy,s}=stagePos(); - const inP=[W<800?W*.12:W*.48,cy],out=[W<800?W*.92:W*.94,cy]; - line(inP,[cx-s,cy],color,alpha*.5,1.2);line([cx+s,cy],out,color,alpha*.5,1.2); - diamond(inP[0],inP[1],4,color,alpha,true);diamond(out[0],out[1],4,COLORS.good,alpha,true); - if(W>760){label('TASK',inP[0],inP[1]-18,'#cbd3dc',alpha*.7);label('RECEIPT',out[0],out[1]-18,'#cbd3dc',alpha*.7);} - travellingPulse(inP,[cx-s,cy],t,.05,color,alpha);travellingPulse([cx+s,cy],out,t,.45,COLORS.good,alpha); - return {cx,cy,s,inP,out}; + function polygon(points, color = palette.ink, alpha = 0.34, lineWidth = 1, close = true) { + if (!points.length) return; + setStroke(color, alpha, lineWidth); + ctx.beginPath(); + ctx.moveTo(points[0][0], points[0][1]); + for (let i = 1; i < points.length; i += 1) ctx.lineTo(points[i][0], points[i][1]); + if (close) ctx.closePath(); + ctx.stroke(); } - function drawHero(t,transition){ - const {cx,cy,s}=stagePos(); const reveal=clamp((t%10500)/6500,0,1); const r=ease(reveal); - // reveal front borrowed from Nexus Rift's progressive constellation reveal, now tied to a task graph. - drawGem(cx,cy,s,COLORS.accent,t,.65*r); - const task=[W*.48,cy], obligations=[[cx-s*.98,cy-s*.48],[cx-s*1.08,cy],[cx-s*.98,cy+s*.48]], receipts=[[cx+s*.9,cy-s*.48],[cx+s*1.04,cy],[cx+s*.9,cy+s*.48]]; - if(W>800){ - obligations.forEach((o,i)=>{line(task,o,COLORS.accent,.25*r,1);diamond(o[0],o[1],3,COLORS.base,.55*r);line(o,receipts[i],COLORS.accent,.22*r,1);diamond(receipts[i][0],receipts[i][1],3,COLORS.good,.65*r);}); - diamond(task[0],task[1],5,'#e8edf2',.85*r);label('TASK',task[0],task[1]-20,'#dfe5ec',.7*r); - label('SPACE',cx,cy-s*.45,COLORS.space,.75*r);label('POWER',cx,cy,COLORS.power,.75*r);label('COUNCIL',cx,cy+s*.45,COLORS.reality,.65*r); + function gear(x, y, radius, teeth, rotation = 0) { + const points = []; + const total = teeth * 4; + for (let i = 0; i < total; i += 1) { + const angle = rotation + (i / total) * Math.PI * 2; + const phase = i % 4; + const rr = phase === 1 || phase === 2 ? radius * 1.13 : radius; + points.push([x + Math.cos(angle) * rr, y + Math.sin(angle) * rr]); } - const front=lerp(W*.42,W*.97,r);ctx.fillStyle=rgba('#c7d7ea',.08*(1-r));ctx.fillRect(front,0,1,H); + polygon(points, palette.ink, 0.34, 0.9, true); + circle(x, y, radius * 0.42, palette.bronze, 0.34, 0.8); + circle(x, y, radius * 0.12, palette.oxide, 0.48, 0.9); + } + + function drawHero() { + paperGrid(48, 0.05); + const { x, y, r } = rightCenter(0.98); + const drift = reducedMotion ? 0 : Math.sin(time * 0.00015) * 0.03; + armillary(x, y, r, drift); + profile(x - r * 0.05, y + r * 0.08, r / 145); + botanical(x - r * 1.22, y + r * 0.1, r * 1.32); + label("SPECIMEN / CLAIM", x - r * 1.48, y - r * 0.8, "left", palette.verdigris, 0.43, 9); + label("CALIBRATION / AUTHORITY", x + r * 0.25, y + r * 0.78, "left", palette.oxide, 0.44, 9); } - function drawOverview(t){ - const baseX=W<800?W*.2:W*.53, step=W<800?W*.145:W*.09, y=H*.5, s=Math.min(W,H)*(W<800?.08:.105); - [COLORS.mind,COLORS.space,COLORS.reality,COLORS.power,COLORS.time].forEach((c,i)=>{const yy=W<800?H*(.25+i*.12):y;const xx=W<800?W*.72:baseX+i*step;drawGem(xx,yy,s,c,t,.27);}); + function drawOverview() { + paperGrid(54, 0.045); + const { x, y, r } = rightCenter(0.94); + const cols = 3; + const rows = 2; + const gapX = r * 0.78; + const gapY = r * 0.66; + const startX = x - gapX; + const startY = y - gapY * 0.5; + let index = 0; + for (let row = 0; row < rows; row += 1) { + for (let col = 0; col < cols; col += 1) { + const px = startX + col * gapX; + const py = startY + row * gapY; + circle(px, py, r * 0.2, index === 4 ? palette.oxide : palette.bronze, 0.35, 1); + crosshair(px, py, r * 0.045); + label(String(index + 1).padStart(2, "0"), px, py + r * 0.29, "center", palette.slate, 0.46, 9); + if (index > 0) { + const prevCol = (index - 1) % cols; + const prevRow = Math.floor((index - 1) / cols); + line(startX + prevCol * gapX, startY + prevRow * gapY, px, py, palette.bronze, 0.19, 0.7); + } + index += 1; + } + } + label("FRAME / ROUTE / OBSERVE / VERIFY / CHALLENGE / RELEASE", x, y + r * 0.82, "center", palette.oxide, 0.4, 9); } - function drawWorkflow(t){ - const y=H*.52, xs=W<800?[W*.17,W*.38,W*.59,W*.8]:[W*.47,W*.61,W*.75,W*.89];const names=['SPACE','REALITY','POWER','TIME'], cols=[COLORS.space,COLORS.reality,COLORS.power,COLORS.time]; - for(let i=0;i800)label(names[i],xs[i],y,cols[i],.75);if(i { + const yLine = y + p[1] * r; + const x1 = x + p[0] * r; + const x2 = x + p[2] * r; + line(x1, yLine, x2, yLine, index === 2 ? palette.oxide : palette.bronze, 0.36, index === 2 ? 1.4 : 0.9); + for (let j = 0; j < 4; j += 1) { + const px = x1 + ((j + 0.5) / 4) * (x2 - x1); + dot(px, yLine, j === 3 ? 3.2 : 2.1, j === 3 ? palette.oxide : palette.bronze, 0.5); + } + label(`ROUTE ${String(index + 1).padStart(2, "0")}`, x1, yLine - 15, "left", palette.slate, 0.42, 8); + }); + line(x - r * 0.06, y - r * 0.75, x - r * 0.06, y + r * 0.76, palette.oxide, 0.24, 0.8); } - function drawMind(t){ - const st=drawTaskRoute(t,COLORS.mind,.9);drawGem(st.cx,st.cy,st.s,COLORS.mind,t,.78); - const top=[st.cx+st.s*1.08,st.cy-st.s*.52], bot=[st.cx+st.s*1.08,st.cy+st.s*.52], core=[st.cx,st.cy]; - line(core,top,COLORS.mind,.42,1.2);line(core,bot,COLORS.bad,.35,1.1);diamond(top[0],top[1],4,COLORS.good,.9);diamond(bot[0],bot[1],4,COLORS.bad,.8); - if(W>760){label('PROOF',top[0],top[1]-18,COLORS.good,.85);label('COUNTEREXAMPLE',bot[0],bot[1]+18,COLORS.bad,.8);label('PROOF OBLIGATION',core[0],core[1],COLORS.mind,.8);} + function drawGems() { + paperGrid(56, 0.045); + const { x, y, r } = rightCenter(0.96); + const names = ["CANON", "ATLAS", "CRUCIBLE", "FORGE", "CHRONOMETER"]; + names.forEach((name, index) => { + const angle = -Math.PI / 2 + index * (Math.PI * 2 / 5); + const px = x + Math.cos(angle) * r * 0.7; + const py = y + Math.sin(angle) * r * 0.7; + circle(px, py, r * 0.19, index === 2 ? palette.oxide : palette.bronze, 0.36, 1); + line(x, y, px, py, palette.bronze, 0.19, 0.75); + dot(px, py, 3, index === 2 ? palette.oxide : palette.verdigris, 0.55); + label(name, px, py + r * 0.27, "center", palette.slate, 0.42, 8); + }); + circle(x, y, r * 0.23, palette.ink, 0.28, 1); + crosshair(x, y, 12); + label("AXIS", x, y + 29, "center", palette.oxide, 0.45, 9); } - function drawSpace(t){ - const st=drawTaskRoute(t,COLORS.space,.9);drawGem(st.cx,st.cy,st.s,COLORS.space,t,.76); - const targets=[[st.cx+st.s*.45,st.cy-st.s*1.15],[st.cx+st.s*1.05,st.cy-st.s*.38],[st.cx+st.s*.45,st.cy+st.s*1.15]],names=['PAPERS','REPOS','STANDARDS']; - targets.forEach((p,i)=>{line([st.cx,st.cy],p,COLORS.space,.35,1);diamond(p[0],p[1],4,COLORS.space,.78);travellingPulse([st.cx,st.cy],p,t,i*.22,COLORS.space,.85);if(W>760)label(names[i],p[0],p[1]+(i===2?17:-17),'#c9d8ee',.72);}); + function drawMind() { + paperGrid(44, 0.045); + const { x, y, r } = rightCenter(1.0); + const points = gemGeometry.mind.map(([gx, gy]) => [x + gx * r, y + gy * r]); + polygon(points, palette.ink, 0.39, 1.2, true); + const [a, b, c] = points; + circle(a[0], a[1], Math.hypot(b[0] - a[0], b[1] - a[1]), palette.bronze, 0.25, 0.8); + circle(c[0], c[1], Math.hypot(b[0] - c[0], b[1] - c[1]), palette.bronze, 0.25, 0.8); + line(b[0], b[1], x, y, palette.oxide, 0.34, 1); + line(x, y, a[0], a[1], palette.bronze, 0.27, 0.8); + line(x, y, c[0], c[1], palette.bronze, 0.27, 0.8); + [a, b, c, [x, y]].forEach((p, index) => { + dot(p[0], p[1], 3, index === 3 ? palette.oxide : palette.ink, 0.55); + label(["A", "B", "C", "Q.E.D."][index], p[0] + 10, p[1] - 12, "left", index === 3 ? palette.oxide : palette.slate, 0.5, 9); + }); + ticks(x, y, r * 0.31, 24, 5, palette.bronze, 0.23); } - function drawReality(t){ - const st=drawTaskRoute(t,COLORS.reality,.9);drawGem(st.cx,st.cy,st.s,COLORS.reality,t,.76); - const gap=[st.cx-st.s*.1,st.cy],a=[st.cx+st.s*.6,st.cy-st.s*.48],b=[st.cx+st.s*.6,st.cy+st.s*.48]; - line(gap,a,COLORS.reality,.42,1);line(gap,b,COLORS.reality,.32,1);diamond(a[0],a[1],4,COLORS.good,.82);diamond(b[0],b[1],4,COLORS.bad,.55); - if(W>760){label('NAMED GAP',gap[0],gap[1],COLORS.reality,.8);label('CANDIDATE',a[0],a[1]-18,COLORS.good,.8);label('KILLED',b[0],b[1]+18,COLORS.bad,.65);} + function drawSpace() { + paperGrid(52, 0.04); + const { x, y, r } = rightCenter(0.98); + botanical(x, y + r * 0.02, r * 1.65); + const points = gemGeometry.space.map(([gx, gy]) => [x + gx * r, y + gy * r]); + points.forEach((point, index) => { + circle(point[0], point[1], r * 0.08, palette.verdigris, 0.3, 0.8); + dot(point[0], point[1], 2.3, palette.verdigris, 0.46); + label(`TAXON ${String(index + 1).padStart(2, "0")}`, point[0] + (point[0] < x ? -12 : 12), point[1] - 12, point[0] < x ? "right" : "left", palette.slate, 0.42, 8); + }); + line(x - r * 0.95, y + r * 0.78, x + r * 0.98, y + r * 0.78, palette.bronze, 0.29, 0.8); + for (let i = 0; i <= 20; i += 1) { + const tx = x - r * 0.95 + (i / 20) * r * 1.93; + line(tx, y + r * 0.78, tx, y + r * (i % 5 === 0 ? 0.72 : 0.75), palette.oxide, 0.28, 0.7); + } } - function drawPower(t){ - const st=drawTaskRoute(t,COLORS.power,.9);drawGem(st.cx,st.cy,st.s,COLORS.power,t,.76); - const ys=st.cy, xs=[st.cx-st.s*.62,st.cx-st.s*.28,st.cx+.05*st.s,st.cx+.38*st.s,st.cx+.7*st.s]; - const names=['SOURCE','BUILD','TEST','RUN','REGRESS']; - xs.forEach((x,i)=>{if(i760)label(names[i],x,ys-18,'#cce8eb',.65, 'center', 9);if(i760)label('FAILURE CLASS',fail[0],fail[1]+18,COLORS.bad,.65); + function drawReality() { + paperGrid(48, 0.04); + const { x, y, r } = rightCenter(0.98); + const points = gemGeometry.reality.map(([gx, gy]) => [x + gx * r, y + gy * r]); + points.forEach((point, index) => { + const radius = r * (0.17 + index * 0.025); + const sides = 3 + index; + const shape = []; + for (let j = 0; j < sides; j += 1) { + const angle = -Math.PI / 2 + (j / sides) * Math.PI * 2; + shape.push([point[0] + Math.cos(angle) * radius, point[1] + Math.sin(angle) * radius]); + } + polygon(shape, index === 2 ? palette.oxide : palette.ink, 0.34, index === 2 ? 1.3 : 0.9, true); + dot(point[0], point[1], 2.7, index === 2 ? palette.oxide : palette.bronze, 0.52); + if (index < points.length - 1) { + line(point[0] + radius, point[1], points[index + 1][0] - r * (0.17 + (index + 1) * 0.025), points[index + 1][1], palette.oxide, 0.29, 1); + } + label(["KNOWN", "GAP", "CANDIDATE", "FALSIFIER"][index], point[0], point[1] + radius + 18, "center", index === 1 ? palette.oxide : palette.slate, 0.46, 8); + }); } - function drawTime(t){ - const st=drawTaskRoute(t,COLORS.time,.9);drawGem(st.cx,st.cy,st.s,COLORS.time,t,.72); - const y1=st.cy-st.s*.34,y2=st.cy+st.s*.34,xs=[st.cx-st.s*.62,st.cx-st.s*.18,st.cx+st.s*.26,st.cx+st.s*.68]; - xs.forEach((x,i)=>{if(i760){label('CANDIDATE',xs[0],y1-18,COLORS.time,.75);label('BASELINE',xs[0],y2+18,'#aeb7c1',.65);label('COMPARE',xs[3],st.cy,'#dce4de',.75);} + function drawPower() { + paperGrid(46, 0.045); + const { x, y, r } = rightCenter(0.98); + const points = gemGeometry.power.map(([gx, gy]) => [x + gx * r, y + gy * r]); + const rotation = reducedMotion ? 0 : time * 0.00005; + points.forEach((point, index) => { + gear(point[0], point[1], r * (0.16 + index * 0.012), 8 + index, rotation * (index % 2 ? -1 : 1)); + label(["SOURCE", "BUILD", "ENTRY", "VERIFY"][index], point[0], y + r * 0.44, "center", palette.slate, 0.45, 8); + if (index < points.length - 1) line(point[0] + r * 0.18, point[1], points[index + 1][0] - r * 0.18, points[index + 1][1], palette.oxide, 0.26, 0.8); + }); + line(x - r * 0.95, y - r * 0.48, x + r * 0.95, y - r * 0.48, palette.bronze, 0.25, 0.8); + label("EXPLODED VERIFICATION PLATE", x, y - r * 0.55, "center", palette.oxide, 0.42, 8); } - function drawSystem(t){ - const y=W<800?H*.28:H*.24,xs=W<800?[W*.12,W*.31,W*.5,W*.69,W*.88]:[W*.53,W*.63,W*.73,W*.83,W*.93]; - xs.forEach((x,i)=>{if(i [x + gx * r * 0.55, y + gy * r * 0.55]); + polygon(points, palette.verdigris, 0.32, 0.9, true); + points.forEach((point, index) => { + dot(point[0], point[1], 3, index === 0 ? palette.oxide : palette.verdigris, 0.56); + label(String(index + 1), point[0], point[1] - 13, "center", palette.slate, 0.44, 8); + }); + const handAngle = -Math.PI / 2 + (reducedMotion ? 0.68 : (time * 0.00008) % (Math.PI * 2)); + line(x, y, x + Math.cos(handAngle) * r * 0.58, y + Math.sin(handAngle) * r * 0.58, palette.oxide, 0.47, 1.4); + circle(x, y, 7, palette.oxide, 0.52, 1.1, rgba(palette.paper, 0.7)); + label("BASELINE", x - r * 0.62, y + r * 0.92, "left", palette.slate, 0.42, 8); + label("DECISION", x + r * 0.62, y + r * 0.92, "right", palette.oxide, 0.44, 8); } - function drawScene(t){ - const intensity=active==='quiet'?.18:(active==='hero'?.78:(active==='overview'?.28:.5));drawFar(t,intensity); - const reveal=reduced?1:ease(clamp((t-switchedAt)/720,0,1)); - ctx.save();ctx.globalAlpha=reveal; - switch(active){ - case 'hero': drawHero(t); break; case 'overview': drawOverview(t); break; case 'workflow': drawWorkflow(t); break; case 'gems': drawOverview(t); break; - case 'mind': drawMind(t); break; case 'space': drawSpace(t); break; case 'reality': drawReality(t); break; case 'power': drawPower(t); break; case 'time': drawTime(t); break; - case 'system': drawSystem(t); break; case 'quiet': break; + function drawSystem() { + paperGrid(48, 0.045); + const { x, y, r } = rightCenter(1.02); + const layers = [ + { radius: r * 0.2, label: "AXIS", color: palette.oxide }, + { radius: r * 0.43, label: "RECEIPTS", color: palette.verdigris }, + { radius: r * 0.68, label: "INSTRUMENTS", color: palette.bronze }, + { radius: r * 0.91, label: "RUNTIME", color: palette.ink } + ]; + layers.forEach((layer, index) => { + circle(x, y, layer.radius, layer.color, 0.3 + index * 0.02, index === 0 ? 1.3 : 0.8); + label(layer.label, x, y - layer.radius - 12, "center", layer.color, 0.45, 8); + }); + for (let i = 0; i < 10; i += 1) { + const angle = -Math.PI / 2 + i * Math.PI * 2 / 10; + const px = x + Math.cos(angle) * r * 0.68; + const py = y + Math.sin(angle) * r * 0.68; + line(x, y, px, py, palette.bronze, 0.18, 0.7); + dot(px, py, 2.7, i % 3 === 0 ? palette.oxide : palette.bronze, 0.5); } + crosshair(x, y, 13); + label("OBSERVATION ≠ RECEIPT ≠ RELEASE", x, y + r * 1.02, "center", palette.oxide, 0.45, 9); + } + + function drawQuiet() { + paperGrid(58, 0.035); + const { x, y, r } = rightCenter(0.9); + circle(x, y, r * 0.7, palette.bronze, 0.16, 0.8); + ticks(x, y, r * 0.7, 32, 5, palette.bronze, 0.16); + line(x - r, y, x + r, y, palette.bronze, 0.13, 0.7); + line(x, y - r, x, y + r, palette.bronze, 0.13, 0.7); + label("ARCHIVE / SOURCE / PROVENANCE", x, y + r * 0.88, "center", palette.slate, 0.28, 8); + } + + const drawers = { + hero: drawHero, + overview: drawOverview, + workflow: drawWorkflow, + gems: drawGems, + mind: drawMind, + space: drawSpace, + reality: drawReality, + power: drawPower, + time: drawTime, + system: drawSystem, + quiet: drawQuiet + }; + + function clear() { + ctx.clearRect(0, 0, width, height); + } + + function draw() { + clear(); + ctx.save(); + const drawer = drawers[activeScene] || drawQuiet; + drawer(); ctx.restore(); } - function frame(t){ - ctx.clearRect(0,0,W,H);ctx.fillStyle='#030405';ctx.fillRect(0,0,W,H);drawScene(reduced?3500:t);if(!reduced)requestAnimationFrame(frame); + function animate(timestamp) { + time = timestamp; + draw(); + if (!reducedMotion) frame = window.requestAnimationFrame(animate); } - function setScene(scene){ - if(scene===active) return; previous=active;active=scene;switchedAt=performance.now();document.body.dataset.scene=scene; - if(caption && sceneLabels[scene]){caption.innerHTML=`${sceneLabels[scene][0]}${sceneLabels[scene][1]}`;} - const section=document.querySelector(`.scene-section[data-scene="${scene}"]`);const accent=section?.dataset.accent||'';if(accent)document.documentElement.style.setProperty('--live-accent',accent);else document.documentElement.style.removeProperty('--live-accent'); - if(reduced){ctx.clearRect(0,0,W,H);ctx.fillStyle='#030405';ctx.fillRect(0,0,W,H);drawScene(3500);} + function setScene(scene) { + if (!drawers[scene] || scene === activeScene) return; + activeScene = scene; + document.body.dataset.scene = scene; + const copy = sceneCaption[scene] || sceneCaption.quiet; + if (caption) caption.innerHTML = `${copy[0]}${copy[1]}`; + if (reducedMotion) draw(); } - const sections=[...document.querySelectorAll('.scene-section[data-scene]')]; - let sceneRAF=0; - function resolveScene(){ - sceneRAF=0; - const targetY=H*.48; - let best=null,bestDist=Infinity; - for(const section of sections){ - const r=section.getBoundingClientRect(); - if(r.bottom<=0 || r.top>=H) continue; - const visibleTop=Math.max(0,r.top),visibleBottom=Math.min(H,r.bottom); - if(visibleBottom<=visibleTop) continue; - const center=(visibleTop+visibleBottom)*.5; - const dist=Math.abs(center-targetY); - if(distobserver.observe(s)); - addEventListener('scroll',queueScene,{passive:true}); - addEventListener('resize',()=>{resize();queueScene();},{passive:true}); - addEventListener('pointermove',e=>{pointerX=(e.clientX/W-.5);pointerY=(e.clientY/H-.5);},{passive:true}); + const sections = Array.from(document.querySelectorAll(".scene-section[data-scene]")); + const observer = new IntersectionObserver((entries) => { + const candidates = entries + .filter((entry) => entry.isIntersecting) + .sort((a, b) => b.intersectionRatio - a.intersectionRatio); + if (candidates.length) setScene(candidates[0].target.dataset.scene || "quiet"); + }, { + root: null, + rootMargin: "-34% 0px -42% 0px", + threshold: [0, 0.08, 0.2, 0.45, 0.7] + }); + + sections.forEach((section) => observer.observe(section)); + + function handleMotion(event) { + reducedMotion = event.matches; + if (frame) window.cancelAnimationFrame(frame); + frame = 0; + if (reducedMotion) draw(); + else frame = window.requestAnimationFrame(animate); + } + + if (typeof motionQuery.addEventListener === "function") motionQuery.addEventListener("change", handleMotion); + else if (typeof motionQuery.addListener === "function") motionQuery.addListener(handleMotion); + + window.addEventListener("resize", resize, { passive: true }); resize(); - if(reduced){drawScene(3500);}else requestAnimationFrame(frame); + if (reducedMotion) draw(); + else frame = window.requestAnimationFrame(animate); })(); diff --git a/docs/visuals/APPARATUS_ASSETS.md b/docs/visuals/APPARATUS_ASSETS.md new file mode 100644 index 00000000..cf879652 --- /dev/null +++ b/docs/visuals/APPARATUS_ASSETS.md @@ -0,0 +1,29 @@ +# Apparatus Visual Assets + +## 1. Inventory + +| File | Role | SHA-256 | Source | Evidence boundary | +|---|---|---|---|---| +| `apparatus-frontispiece.svg` | README and social-preview frontispiece | `4b91a1cc0fd8b713aca27a5fbd3555562b2392a729ab4efc7c461661bb1d14a8` | Original project vector | Brand image only; not a historical object, execution trace, receipt, or efficacy claim | +| `elenchion-mark.svg` | Working organization/product mark | `655878c5c97c2bba9328e5031cc692ec4034aa92d89b7749ea693f20f45e3b1c` | Original project vector | No legal trademark clearance, institutional status, or endorsement claimed | +| `../system-field.js` | Procedural website field | `0b415e0771cc24c4d95ec4d6c87a2be1e0a65d3fbe210402431238cb4a5ba68f` | Original local Canvas2D code | Conceptual module visualization; not a live system monitor | + +Full rights, authorship, use, alt-text, and boundary records are in [`../brand/asset-manifest.json`](../brand/asset-manifest.json). + +## 2. Alt text + +### Frontispiece + +Parchment scientific frontispiece with an original measured classical profile inside armillary rings, a botanical specimen at left, and the words “Elenchion Systems — Apparatus — Evidence before authority.” + +### Calibration mark + +Circular calibration mark formed by two instrument arcs, a central vertical axis, side ticks, and one oxide registration mark. + +### Procedural field + +The Canvas field is decorative and `aria-hidden`. Every concept represented by its movement also appears as text in the page. + +## 3. Source boundary + +No museum-hosted image, third-party historical illustration, external font, CDN library, or raster generation is committed in this first application. Any future historical source asset must pass item-level provenance and commercial-use review before entering production. diff --git a/docs/visuals/apparatus-frontispiece.svg b/docs/visuals/apparatus-frontispiece.svg new file mode 100644 index 00000000..81e3ec53 --- /dev/null +++ b/docs/visuals/apparatus-frontispiece.svg @@ -0,0 +1,124 @@ + + Apparatus by Elenchion Systems + A parchment-toned scientific frontispiece. A measured classical profile sits inside an armillary instrument beside a botanical specimen, while restrained typography identifies Apparatus as an evidence-governed agent and research system. + + Original vector artwork for Kitahl/The-Gauntlet. Created for the Apparatus brand edition. No external artwork is embedded or traced. Design image only; not evidence. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FRONTISPIECE / BRAND EDITION 01 + ORIGINAL VECTOR · DESIGN PLATE · NOT EVIDENCE + 1280 × 640 + + + + + + + + + + + + + + + + + + SPECIMEN / ADAPTATION + OBSERVE · CLASSIFY · COMPARE + + + + + + + + + + + + + + + + + + + + + + + + + + + + AB0 + + + + + + + + + + + ELENCHION SYSTEMS + Apparatus + + Evidence before authority. + An evidence-governed agent and research system. + + + RUNTIME / ALPHA + + RECEIPTS / TYPED + + LICENSE / MIT + + + FRAME + ROUTE + VERIFY + REVIEW + RELEASE + + + + + + + BOUNDARY + Classical-scientific imagery is identity, not proof. + + diff --git a/docs/visuals/elenchion-mark.svg b/docs/visuals/elenchion-mark.svg new file mode 100644 index 00000000..74720778 --- /dev/null +++ b/docs/visuals/elenchion-mark.svg @@ -0,0 +1,11 @@ + + Elenchion Systems calibration mark + A restrained circular calibration mark formed from two instrument arcs, a vertical axis, and one oxide registration tick. + Original vector mark for Elenchion Systems. No external source artwork. + + + + + + + diff --git a/docs/visuals/visual-provenance.json b/docs/visuals/visual-provenance.json index bfbf0876..88d7abbc 100644 --- a/docs/visuals/visual-provenance.json +++ b/docs/visuals/visual-provenance.json @@ -2,22 +2,30 @@ "schema": "egrt/visual-provenance-1", "showcase_revision": 19, "research_software_version": "0.5.1", + "brand_edition": "elenchion-apparatus-1", "visuals": [ { - "id": "system-field", + "id": "apparatus-semantic-instrument-field", "file": "docs/system-field.js", - "purpose": "Render one persistent semantic research-system field that reconfigures around the active professional workflow and the five specialist Gems.", + "purpose": "Render one local procedural field that translates Apparatus architecture into armillary, botanical, geometric, engineering, metrology, optics, and review motifs as the reader moves through the public system explanation.", "sources": [ "docs/ARCHITECTURE.md", + "docs/brand/NAMING_ARCHITECTURE.md", + "docs/brand/BRAND_SYSTEM.md", "skills/soul/SKILL.md", "skills/mathbot/SKILL.md", "skills/scoutbot/SKILL.md", "skills/novelbot/SKILL.md", "skills/codebot/SKILL.md", - "skills/benchbot/SKILL.md" + "skills/benchbot/SKILL.md", + "skills/infinity-gauntlet/SKILL.md", + "skills/meditate/SKILL.md", + "skills/council-of-elders/SKILL.md", + "skills/foil/SKILL.md" ], - "design_reference_boundary": "The constellation reveal, orbital/web formation language, sparse beam motion, and slow camera-like drift are adapted from an earlier cinematic prototype owned by the project author. The public implementation is rewritten as local Canvas2D and imports no external visual asset or runtime.", - "boundary": "Conceptual visualization of module contracts and routing semantics. It is not an execution trace, benchmark result, or behavioral-efficacy claim." + "source_file_sha256": "0b415e0771cc24c4d95ec4d6c87a2be1e0a65d3fbe210402431238cb4a5ba68f", + "design_reference_boundary": "Original local Canvas2D implementation following the supplied classical-scientific brand package. It embeds no external image, font, library, or historical artwork. Separate original SVG assets are recorded in docs/brand/asset-manifest.json.", + "boundary": "Conceptual visualization of public module contracts and routing semantics. It is not a live execution trace, scientific figure, benchmark result, receipt, institutional endorsement, or behavioral-efficacy claim." } ] } diff --git a/scripts/.apply_elenchion_bundle.py b/scripts/.apply_elenchion_bundle.py deleted file mode 100644 index ffd47f33..00000000 --- a/scripts/.apply_elenchion_bundle.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -"""Apply a digest-bound one-shot Apparatus brand payload to this branch.""" -from __future__ import annotations - -import base64 -import hashlib -import json -from pathlib import Path -import shutil -import zlib - -ROOT = Path(__file__).resolve().parents[1] -PARTS = ROOT / "scripts/.brand_bundle_parts" -BUNDLE_SHA256 = '0f4abac8ee309861c95888b03df15eebe5fee7cc1f62a60e527d17d6f83688b0' -EXPECTED_HASHES = { - "README.md": "ec35581d7d4dd7a6ee7aa0a7cbc811e2e884d08d9b6ed823cc394a581f4252ee", - "THIRD_PARTY_NOTICES.md": "bba84db1c6078897874f0fdb2dc538811661d02e6df915e20eb289492b10511d", - "docs/brand/BRAND_SYSTEM.md": "571a2e247f15b3a7291887291bc7d15e385cccf944f60c5f868a80e3a9b0aaae", - "docs/brand/CLAIMS_REGISTER.md": "dadafda52d0d21e6dcb75c93d10450b0a159a020c8cc111ab306cd0cd0836e6e", - "docs/brand/NAMING_ARCHITECTURE.md": "29e4855ff76f829ba5a98593d4a5670010830b6cd97c65a152825e9532555f2b", - "docs/brand/README.md": "19e155a2a44abf4d002fd6a62068ee829e52ce78011df82587a30d5afef2ad75", - "docs/brand/SOURCE_PACKAGE.md": "73340d1d7f50da427d8ec182c92abbdcfe83407f63623dda98f23f276693eafd", - "docs/brand/asset-manifest.json": "bd8c3bf34f9360952fad2a6082bf656d608356921343236be0da3bc1942026a6", - "docs/brand/brand-system.json": "51d962a687e20adf532a03f14817a7532cad208a584eeadd79139b27b0f5acb5", - "docs/content-provenance.json": "93c6ee235351342fc30d74166c3f761be1a801415e66262b6377cdbb7493aae3", - "docs/index.html": "549a89dd615666a2fc5f41fdf71a8e700aedc4daa5b1741df4b007028e38b453", - "docs/styles.css": "78ff473528b793f566379608e8d03a0a68a45556f791a3ac8183e5d9dbe9d63a", - "docs/system-field.js": "0b415e0771cc24c4d95ec4d6c87a2be1e0a65d3fbe210402431238cb4a5ba68f", - "docs/visuals/APPARATUS_ASSETS.md": "00f45acdd69dca4aa1260421bbd093ce1a2396e8415932302ed2216ebfe4962f", - "docs/visuals/apparatus-frontispiece.svg": "4b91a1cc0fd8b713aca27a5fbd3555562b2392a729ab4efc7c461661bb1d14a8", - "docs/visuals/elenchion-mark.svg": "655878c5c97c2bba9328e5031cc692ec4034aa92d89b7749ea693f20f45e3b1c", - "docs/visuals/visual-provenance.json": "2f2bd950af99342203ae3ebfd67cf2bca5acd75e4b0a2ed5922cd18e9d9f84dd" -} - - -def main() -> int: - encoded = "".join(path.read_text(encoding="ascii") for path in sorted(PARTS.glob("part-*.b85"))) - raw = zlib.decompress(base64.b85decode(encoded.encode("ascii"))) - if hashlib.sha256(raw).hexdigest() != BUNDLE_SHA256: - raise SystemExit("bundle digest mismatch") - payload = json.loads(raw.decode("utf-8")) - if set(payload) != set(EXPECTED_HASHES): - raise SystemExit("bundle path set mismatch") - for relative, content in payload.items(): - path = (ROOT / relative).resolve() - if ROOT.resolve() not in path.parents: - raise SystemExit(f"unsafe output path: {relative}") - digest = hashlib.sha256(content.encode("utf-8")).hexdigest() - if digest != EXPECTED_HASHES[relative]: - raise SystemExit(f"content digest mismatch: {relative}") - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8", newline="\n") - - for relative in ( - "docs/brand/brand-system.json", - "docs/brand/asset-manifest.json", - "docs/content-provenance.json", - "docs/visuals/visual-provenance.json", - ): - json.loads((ROOT / relative).read_text(encoding="utf-8")) - - shutil.rmtree(PARTS) - (ROOT / "scripts/.apply_elenchion_bundle.py").unlink(missing_ok=True) - (ROOT / ".github/workflows/apply-elenchion-brand.yml").unlink(missing_ok=True) - print(json.dumps({"status": "APPLIED", "files": len(payload), "bundle_sha256": BUNDLE_SHA256}, indent=2)) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/.brand_bundle_parts/part-01.b85 b/scripts/.brand_bundle_parts/part-01.b85 deleted file mode 100644 index bc153667..00000000 --- a/scripts/.brand_bundle_parts/part-01.b85 +++ /dev/null @@ -1 +0,0 @@ -c-rK>>2lj>dMJD=h^OXsyF-u?wOV%jE1IHhX0(Nrd#0=1C4nSJVhj=-0Fx4}oy?Vd&-QKrNKtLsN$Q(a+!P7C>$9)FKIwH@$ETh8Z1m*l$%};-#L;B_{re}waGr$uMi&MAYBC=R`7o<*b33+7&s&Z0OD(mVVvPnR=TSk1eQ@~JmS@?cJ{1@niIDAC&^8i&S8DQga;jYX_$pUI-GjhU6zM4Z*b>zVt9B8Q+a**E~|g{9gg+WKm63wPbcI2Hb_J71~xsSh5h{PG|v~=QKK<|y(|ayVKQs{AE{+nPq&*Ly}1Vh?K<2Fe@jN{}Mj-Lk!>vyxbF$hKzI`XHFG~!cQ`s*Oe7Xcjda2gIjERtxRf5!M@8O=ANJ>KG149@##C!_G6Pd;PxAq?=B`0-Y2#^VK^M1X1;V8m(n(hLg-SI?$V7>}}gl<>?AdDMp|{Xx7`OF8MbJ7@h45BEcuW)5^!j&#FM`CIk9`nDg<$4NRPs0?BmwEebq_NH@k{st(+Qv~Oi-gn;_c+L@Hj=uZOppO@2-eNh3qoIW)3hW78!;j%`nFj+vZ@Q_h=H+(_Kre-G@ZvCx7CHPdNS5;v{4_w^ng!_xZxO`_{(@KtWFbQG6wSQ1VLA&le*}Nr!1F1bdo%;W5ih1e-RtJwCxMn^NCK{xQubUA~0QlKiyJep5xUN($?3`Aqt;V8<6NF45JUXpr19l{i-P^$|>Z4nHjILeV+AvMbuA%MV5=#9cfoZKOGsxzheV>rm7Jj6r%=>vgIU4RL$$p#{GmunwuJoP*rch8?i^CfI31gu^LIV=#dgXXy=^4!NtO|hdIz%Ys^-!{kD7AX|3%13Fnh&9>P`5Cyk4@t$ye5>RrFnyK0|wuU?&>+l$=bQCaXS1wxrM)2q%|`|W9~_lLeUrt_s*Y6zD&S`H(=RUV?(x#*oAziW42bx*pNKk^XeFZB?D4g}P|$25KQ4y8z35xS-?V1NDVN~b;ak%qz0&o68=nrpy_g#sh!E!%&e*_AK<0HNVa{zBylLxfJU_4@{bu|{x&IIKE1(DxJvkV433ugG#C>%yekGy0a-#z!{2|wW_V=oRTfLYOen52s&r9}eSNP|&GRACsyyaCV-Ld0p50l1Sy5W^CHZPTv33%bJG%a#DSv`0<0Xm989P!v*6BS@D9CXkcH_D%S_Mt{QY@Aq-z$!D5FdSASTfs3rF4L?oKH5n2vF#`e&TZPjf!bGt2CBR@XpTJSjet#g5(}N3`Ea_fhxBcNX0m(4F^IGuUUzR}{0mXiu5>U~O+e)yk0h$2C3SysOO^jMzD%^_<`F$59Hv4L?p{pMc6BnR^4^jzSbh$h~plb*~?Wip&o1oEm4X)9%@us}(F_-7`p)$<`wl@S1b{&loFt-83~@Lh0v{J}CsFRjNigyUKyGlu2C6eyViO1HCBSr1#t488n&0t=aa=|sCW1peJ;ED+T_cl3C>reGSRE3TaI_iWY>g2XZkfnzjSieI6~GQ)3gkH$I!%_7Dc*}nPeRRL)LmL(2vl?ggW9;xBwN940N^!DKq#w`gf&6h!E$wIa399o!|~cvq*Z{Ua&!PL%Q)BT0Ln59GngO)y)2;73FSZ%3g;uDwlHsa3&ZX6@`w)L`hX3Ha5f*tOJWV#a)A^VWkUWf4v3eK>S8uPXi!&}rDVE{b~9%Q7sd{M?>=1?O=4%yhlLlC$u~>+99WH^miQx7A{Tzkd2oVBA6lMnS;k12$|3T^ye$4SB&>KAc>}Q_ll*T*%=@Q;JH$VjA0nDVLL3nUm#~;jLg4EQlYm$Uaj#c`&+(GGeeWF^ni9T+j&JU)we-GX(#y{M0!=#ea0^kKRx1V4g6|6_AJ7g=Nv9Aco)vOK};8DSlGR@6kxDpIYIBcC2xMFhCH?V1~TaqPN_^@QkecVg@Pmxj=0ms7^Ubeyy|2seJ;%Xy+K_XKEm*{j>eT2=+Y8f#3hs?f2hxoZsM>mt){`$no?kee(YKr*~(6I6MF2ne*^00vJOkqID+_4B^Sv53TM=>(vPk4bZP}ngtJhf$S3^ARP&gAmF~AD>V=yTl7u@)A`^3S|+9j3Pd(iSi~;#V8}XX9)=;X0Y7I+OXO;VlbRah`?q<$E#_Q<&A41S~mblNy;MvPee&Fn&S&-T!`7wFp?#DEs~A1WSI&Phk1ac=EyDrPsy-8P&1qn#0j$MWy^sAdq-@*ce0SUb{OATGB1mwxzNJjd0iy_$j4?O%7RfgBaZKTns{Y!;$&f=RhYz?Y$U?%jnibt7s3Z8XHRb_3W|dY3H4wHBhX4F^niWYvv7vgi=O=u#j&+r7DI1wLN8e0wu6}7Pmy_{Tt_#bE*G@8I0BTs1D%LP1K4{gLI|h{vj~BkkAdwj|I|M}I{_>L%9WFa3QiOS$|VB0K7mwgFh0o@B-d;8j~=m<6VRLdY?|=a_4giZ2!4cDP{KwF9k0U@M3B^ibb@#dS`{!j!|8d20$w(fX-N5jadc!^D;2rmVd1COt^TEdacDh4TT62^4;?lRjm;@RW=<9=m^uSI`0l$SWh)}33;-4>WsPx9!MmKG^CJTwP|rnzW7kl!DAOkV!4MWYU$(G;T7_gKz!&HCY561~@!semgb|Q%c-DThjGco51e|Nr*>Wx-zg#80j`9ni2AaDw)~myjKH&B;#XHc}1pHpnG^L_iRyjrqzhI5S>#$CL1aceB3yK>6E@@sM$bEzn=IjX|97o+{nI~wT9J*v$BPu^JPiCk?!7e~AV~ZVaPtKvHd<*^{$XLM^Ap&M_$if^{+2oc67^X><`7mIP7E*(?BnuA0X#jc~$R~{<5LUERhGUT76_Ky;?bk$lT*S*s1aC6B*BshFQmfMVzWc6t(Lnr(5Fi`@y+f5k>D^kxr>pADY>D+X)~JX`)-*@8P5xp~Qe+FnP_6NaVwt2y8@GrUBaf_*M0Z{ykc{ad$!V3KX^d!<=;(-}Us#MG1ri?%iM*hhC{8B)0jR}*#8EiKpTb4@aR*5y~eElrb8UYLiFkNl;Wv)-gjJRvmnbM)El14b0&(W3>SQ286I{vP~0=0*MM>)>|X$u+9(4b%=&D6mF43ayEQH4lZ{vH37};n8$yGN+Rp-nlN5K;b@Wgg;?6j|lpO@O?}$DkoT2)}$4nO@HlLApj)@vQ4Pdv3Q-~kIL}WXJwG5vw+ycxAj4*^_2nOE2PjY)hI0~(m7qRf&ITk$u-*>ha*_&KDE)*l1NYI=#wu6>N~1-cJ-k7>`&?f%YotTInhR~j7nO87C%t576CP!5P>>Fn6UBZq_QMzjFgkqs7Iy&Rjbp{j5HoAUSfU)U0TBKsL>kKC(-6)r`pZ%PS1=Csz3grdq&JJ$PeKL_VFwH|WPeVPSdOABz>C$m##brpS2iME|2T=5GtmdcQc_jMVl@P+ocn}J2>Hfc%c?Z2Wtkxf5$t?Vt&uN{|66j(u3ogm!2^omDebAX6X@(IT(}ZwxC4qKv|uuE}`DHW}uO=%s?WhZMu4-E-GslDmGcc^PT}5xF91ZRCaGH_*^LMp+Rr1W0-2IAu_b$r`UtTJ1l)>6~75&fav-I-MT6U-}oFwk0j!|KwAxA@pY&22KmkVX;0*MEQL}?v4KHS|9C9p71A`+pPWvj5m6kNhUAwyyj0ZKwggFuyf?2;B|mt}|g(pTjj0MS;?24I~Nwhn_7kK|i;kFwE->FASrgACzUl|TKa`6YA75YGVx-MAu;5iu1(Nm1J!=>7X`@tbWNUW{E0t2_4L{bCUUYP>4>e2TxgEfPIOEV9^eBI02T?!m_W9ITZ}=(4se&gU~4X`n-E`rQ)%!UbXt&egSE#pO9^@fp*$l=i?&ANG6otOn9Td*od7T9%=9Z^`NXSuaMGqOZz|C@c`o5a7-YOVNA6&PvD{)HgPLY#{ZlSDU^34o;ao+XaGK^C_Y5ZYPgo1Zi#W<|6QpC>V`n&~&JolKotg!C@@zlUDb%53up3+rLx>)3v`pHE}@3(_&Z^UYcQi%Wu_g{)5FV!Qfd--J1fTXn`Wr4mn)9>e05(MSh|Qw2kqW(^4@aK;rFPuD7T>DefL@0W_vq7Yk}(qx82=hhkIE~=6Gu*j4>m#toxaJNpmF$Ny?k%EAW_8kQEJs2gd4G(%6MQ+ug&}cJugax3guEhl*`2gV=VF8)engt>$8Q*^Igr>N4rHmM|Zq6KpfoNrevB>;|!-DBS*`b-dqdgW^h~Nn>O14g`4czmLra=E(_ptIV*m50O=kq1_>r9?vdpXu}pABD~`t5U+4kYY7eIG!>|kM3v_AfZ=HMDArpy062k#jObY5qPL^mzE~aEtDO36*2_K56p{>FraOszZGG@`Ih3Z-p|S8Novbhp+#acz=IgB*pRC5Xq3V}tPn$JnX=DMt{Js)Ba~eUD7;g01t2y?vEDPSi9Em_d00L6ReH+f=dr8IYw$kop|v;1Q?3QC!DCCHC5^4(5I=cjCWZe>03m(S0fL&h2}6)Tc?`@QP*_Dm9#>_*8By$Ts=|yVYI<%cm5G99S(GE`L2Dqr+$GH3(1Af6(#$ZrUH9IQUz>AOGL+;ZdjJMDKpolj)_B}nt;{0EFmIBN^qJAcbF?enwCUia0zOP~V$RN))-T=%kd**!n2565GC$of;B*fhzBpT2zC?HylTw0f66UY(s^cH14%EUSMYB*xrK&%{lRBt#$4^QY_6?xiOQkJl#0ks~2QF{p}8RQ?b&4J{XFeGFz8(}?H~qsbJNp)N89imcU9F4hB2mGHs28Wh89ap6Cs%n7fe+)w(Gq*8Ejm3lzO(9xE0k=l1G5txQ@4eoGDVLS%)4Q)Cm1&X9;r#P4Q#J=us#fE=&L+^;nA=JoL<)wp&7Ao2J2kzU@soFbmMk+BFYgShlB;+_?9s#*_Qu1Vt;678>tOp?aJVxZ4o8DQushrecb*NKJLA3Ju(|VWv?uSMkmM{|(1~)NytM&QVs9BG63aO2lJaCYaT0YWlUhrw1WWGXP*&~5S-LFt}w>?9v&|&9JlJt&*nGwLsxuFTjVM2`5%36{1P@m*2J4*Drbj9eL*C&L36t4N&+_M$Yrf!+i$;IwcAf)Jc_J~vos2aHsjwH>E&5xE@gWvI*NkyngDP@wnT0keVYOY!`6mjXNFiuD&>Au!Snb4jAN>KW|itcBHCCis1j2QqO>i_4v2^BjYF)pilQfKe+bE`!!Z+4csgva3_$Cd5%FGO*4<5F1I_=~;t%C4-+gtFSsbo@9#w>l-T6reRwMC{b0uZe8kon^!8XSNZ{6LXIz!0;r(ieEW2o@aHcKy58&CaXiWBiPX131MX7=DIPc;MZG{a5=t_}Rh5JotzKe(2*-;js<$U)oWy*SF{el$&h7ZoQ^MW_!YM7*8E%@A#Z8heJ6dVeDf0Dw6Ll1aMJJi$O$^!-@O;8L5Z3Ka_|PYzj_ofz+D!8k-$ZHz#+0zws(cC^h*hp_h1c;@ZG7jMEOokT&+yNr;NpM|$x4@f*l63{cBREm+u^nVuoIZ65CBp54`01|YiQ1BnSqBV?xBKqpoyNJV&UO&K4X~nMJb!)JYNfOd0G~fAZ&OtJ|!~F9bod=K9FPMwZm%YKFOj_fgqQUQzw~Wa2M~XE}@fyGOd*Ngm2PsNO^Eg3Ajy!=2BJ&zr%2ebiy8k6Wk8{P@*v3~6a={pA6X%Juhs@>rB2kSL&nK#mlc1{yZLVZI=3{WLlJ%ll@PW9nHxF4KQo(d+u*a!2<=U-#T@pP2c3`Oz;?T9D+J(uEgIN^cQAvXw&$xn|QAXY;&8vuH-{^>ItE?1SKvAXth(~2bWm7DHTn`6;SfhPnwLi-WEU>d2#(+tHXya(IOeJ0g7^+16^3pKSn*bxnN*WD~)OdR;T^*0JhT)1gI0!u<_g2%I))1p#d4Upr4I|7z$e99yPH<#9$mJ6XIrB_YM6{Rc*sM_)=K;sia*$$%w=^Hh_!J2ZBe>%s94E+RCm3KsR&uqt#$Xx6qXzk08WI^zwZ-@%Y0V~98J8VmSSYUglU*8Y6rtT(1trTd)(j_~28tt5{4mmCO@Q&jf+a>wyPVR>ZP#iR1=DcMF1+S|N{&$!h;P~~OhLG46|>`SRcQd;o66u7KoRn(ZC28IxcEi_34(>x$pXkzuJfq4W(1jnGiw;(lH?&1v5=T6stlMl;qzZcIwH{x$v@LR*J)Gmhj4gChS?~nfly@}Od^3PLM+~L6GSmCsPGzxcin*2I6x-Aqq1qz4j~XCfyAGl04bj+L@{$l`CPPduw0tqFiy;ZIgMcYIo(!)@!ARt|IB8vWt}(3R8iPr@r=2tY>$G$E_WYPXchCBly?5}_CI8*)v^(7k`J;c?x_sA{&twtd&#%tkogKG&KaxrKjC@0M=SwV5lzBX}x3ztZI&Off3Hs;}6+ey*3o5XOEREq+m;+U?C$>f0{#kS{-WFIdKCX|kZ_GyxdXD|{4R^zUa#{__dRwkj`IbAK5P#L0TjLDbi38jJQd&e(_PyhXI{~N1DE>kiEjXlg{AoBY6zx^M^FY_{+B5EoWmrUZE0H&RGI(g_3C@ZF-#i+#YEWSj&RIH5=$4gZ+Aye@_f<9cGCBPCuAW1P>GO8oCtYJau0M@N0KwPv7MXrImme<=pZzCb`+Yi05CD>BDT|9IWud+Kd;nOVzetjS=B*Im_Dl+;Uc$!oSZ>wZ2Sh|YnMylL&D{`=IwIG2U)m{&qLK5Y;)#wkFtF@6M+(d3UaB*P@rrCU7)hypu7H|)9b$Oc8+1@i}QZ>^1S!s)$3OKlJY!NB)n7pE3j-7%5se2vsGbGxj$;`_SYf*Jmqnx-+gm-bqr!>zk7a0naP;RHW$N?aA33p*2n|Kbi*Rq_T?~;I(B499+l}jf4Vq1@3m-Wogcc#oih|w0SR@S996evsRum)6u%BYx3nVVzs@|*(X7A3_G2*~yiC0m8iH<@?F-MjMiO0C;oxknA>R$2~r%C`8=8yGk+2Mu<0uszHSW+1?9ElPwP{_<;ww%#{sVqUAtx15Q;D)2`qM1;n?lBi^#4Q=9(VAGpPFk%bOs{(W77F!Map00Ds2xYVk;E>2>J&63oEK_P_o^Z0dPO{zXDz2m*KF5Fu2=^7%*02kDIV&I&av{20mAHA*C$sqad9gIrFpe-k#9sccUcT5TRN<;MF(8y+o^gbNj*E8794Q`u??%?T?4NJuoPVwtZ=26dp#k3Mp4zDj&Xwvusz}lL5=HlKI;=(L2g*DM4H-$o?lbG{1v76u~2%gO(noKPz0PN@rdv)vkOXQ_WlkeubnfpsO|#m6s1a;zdb|iew-q%1q;;nbO~9Bf8-!TQ83t1P)&A7C||RYVKF9i4WW8&ErWy`a#h${dWKulRzoLsnjCIAqgwO>O7)Wxup8pU2Kms|inh0?6pP7r#sxTuvb+U9Zr+ZhvX{xEca^{V{J^hTb&KkOQib_AD#jX-@s?yCk>Lr@nHD&E}7MbP-iD$p1I=@ws-z!N_P9_=~N;8%UqC(<=7N2PC#BSSFNd?N>BAWs6AJtkbNxxLW9N-H}s0{BaNFYn)+8UyDR&rfi>)EZAhhQdiz|$bpNwuR=kc?JqoOqc+PF%$ag0?Vbqa5$WNud+N!AKP(E9X`l86O6I*dD$)pQmX-`u6`l25r_YduzW=u6H~03%e{1iY`0(1Y=VU^eJJ4ytAS`~SW0Nq~rvkFNIh)0YiOG54nxum;HdQZm293nlsGJ!k<#SCkpL7KTN>kOub-9q8T|5%XO0H!`N?fM|4s#=Ntn9d}n+$kqFhHe0tOO5|X)TFB*PO#GPOA`Me#pE|@f>4dfWF!!;C-=f(-c}$U+WUjlf|2|0fBWAWRL~4=Qwxq1p<W;L_a-qt)C{bmJ`%F2Wm}W_Op6PccW^bHoJW+{S>+$qe95#4QB}?6ECZ;Mm7;H&J?oH1F#H7gseU`Bc1sGq4RK*Xgk5S|>;bBX=;Xp8oiw3hbB^jycYr{k}pJOq2;;BhmS+VgAb~->FV``<6Hp6jdR3X?Y;OJ(DYJAs5H@i-9%`HERn9P3~FC(&ABF%DkQc12@#_^Q$ij;)i)0GC+7I=h(|2ZsISCi66_NL~c{>aG;fIs<^YPzW!Y{YY7(k>)}-YxZ*DZ7;~#nQPHjy{%BdB;8{NtvrU)^!qA)_#1YQttT;)C)0?OP7Kaf1sp~SQ4;i5%BgBxuB}8LjkPvS?nfQi7->uWb7T8LJoyZIaMEfD(lNTW%|^&3cRf>38`y0Pl827MAE~`IC6v&nIWWjVX3rZRkcV?1w+~%tH(#>B9IrH%;t40osL&OR9v*WPUIya(~bS8T%a=OShbAFpkWqGp>9<8s8A1*YZm%M%yeFqOXYuL^r6MtA5;vzSGkE{Q)4j6<<0`93zUD&1L5kTMs-~n>M$(jitZ_9jMfr6}^g`VCY#HYf3v72*s26&Vbx@kUP92MZmsqMM`9>!iq6k8zqfC*293R^a4neNT1 \ No newline at end of file diff --git a/scripts/.brand_bundle_parts/part-03.b85 b/scripts/.brand_bundle_parts/part-03.b85 deleted file mode 100644 index 782c4a5b..00000000 --- a/scripts/.brand_bundle_parts/part-03.b85 +++ /dev/null @@ -1 +0,0 @@ -uy3o>qgC9o*r=|eFtw%Q=$;Q_5suhRtyG_tm8mPGd{?5ct@9a>e!^uZIG9McTsm!E)Ko)bSGvv_>r%?%ak9HLC@K5~;cu?HVdAwAaAjURA(!V=LhpP|P7fiROQnkXj&16n<1h7AH)T;YC;QG$!chl#|UCT&aBd+_K)LRDj&ny43WoqP8S$dZP-G2y!Udl?z9zf{>e)i&R#3DH5s4cc-$Dpow14W?myuR8(Db_j8JdkJl(1q<79%E~{%OvP-YBjH0#Vhpkn`{&A(p@_tc07fSY}#dM8~WnR^!OiQu&?;Z;lZQnUmd=geu3fPfVOz>=&^Iq+mU(_U%BTGz^u{z3M3H#C{hf9*&MvRoHE9R!7uvR^1#f_3wZCUzt^Z$m+4y^2SS|(kS*yPk|ZwN1kgKhyK-8V9Gj!G(RRZWyA+2b1_9wvnf7x0aMthX!>vCzSVm&F7KF5Gpvocdq_@kWRn#v%kNnV5wuIE!FBbuG2pysbEC{2R(1aiF_UV>>e5a0+{gDUDP+prg#j5+#HGS{Hl-XEPuY^zSFjuEg!3Y)M~;w4MIGBciXnhMGBQeBpUI54zB0_$nA{W`FFmBoXlYfFVv(p;CTA+HxZaybzUS+SMw?o?xmz03nBA;-eyA4bEu8%)^%zRO%CE7w+GW^S%D1W;gco(GWOp_UCmXomR!8UuX6!9Zqox-Ic0^Ai6aQKZ&Rli*ozh!M{BzPx5aD_XQ+t-6si<>p45r-{$E9u9IX;Ps>qe6{fB5``6a}|Pdf5Zld)|riR%7V&(5#O^=A05i9pFXpKI5?kR5Y4$JM9!5;iSe*2Ex)2tpSfPs0kgxgZ++tc%ETkah=z+0VfSB{HwZ;L=zTfY*LBZ&^yP*GcUw7MAEQH7cuYTCB{}L@kCVq97qXvxKA#j}CW^H%pP<~S(Fz|h|xzh}G2HS^W*c@(;$Kjwk9Pb}&jrX^<4@bj;?dG%5?*9Ja2p<{2Q{$~5JlH=Nh0SrZIaD>d^llT9j{1B!#w1DpI-Rf{F4WOJ-2y4kIQ|4m_SrmFVLAp{{q74S27aX;bw{MR_e+;N(oDJc5zEYH6=qz-t6r03M)N#O8qvX`G*EoqG@m~nFjH@hNW#^z=aZT!__O7Zuq#Ge{%?P^&lT*l-%K@w{=!@YStJE_L=TOCX(0gZ901itj@vDmeN}waXYh}dJMcO2rNFAris_T6&-JMD1XBPvDP~>K#=68-XBqf3?N&ANl_+p26L@^?$e!dKX_TBJI-vt+G^QZR;;{rfWi8SRTjN1ckxaU)kv3MBDtZ(oCLWvZb;i!Q~4^YqSY`p(q`U1+Uw+9(MPrqV-oX1b-l2^?cr4yF9wg}F^jRoy_PFA(P{q|&DWCUzN)0sG?Y+Atog_o?I$eJi}lsafI3MI!xhsw|mssw?$L6?9AVBJeH9YV?$0?_y8jZqNy)y^Gs~qLRg?pOr+e3LPATuAi{F@vmIs`27=l<|-P!e{uwWwJt7NJ*-sWgM8Gv#3Z%%PipE}pagL!4{FPr2_Wwq^7Y!1n5_Xs+vg!JiYgG%u2$D)|E`X7R(BTB#?kAklqZ&F*eH*2sb(UCZ=Qt>$Itw&9qVU?iz}oM7$QsW;=>hTn`*N1+Z?y0k8Zg!{W5tZ<>XdBwxhi13ZKC?s=#)a5N&D0l(luF9>@;}0nrlP}A}E>Ht-VPFTMbATz4L$Sv@iYhUiVG+taW1Jk6zRjEapn=WcV6zy0Ca!(n9J=LH9aTK#A=tn+BlB@WZ=1X}xLaUmz2>$s2==8HqT|hLrLSe8-q#O+%dj_t-^qPkUWv$=3!Yhm}L}DDKS1K>0eEh@_uGpHO>1yx%hYo0$e7RQ@m&=qG{doKi?9SP)^(b7v9Fv%Fu-Xp%4nGN_Dsm^fWqv~GR%}bv?qvHUgBTWqEd>}Ll|Eq$sLqKJgrm_RU(Yq$fHF4D_um3mRzjOP?uB@mHh737qHyy#bP(LD(hNJEO^|zRLRPw>5H{Ip*C02%$#8#fBkFiL2S5L&4$Zjc-zvXu(t32AGvS2e#Uk5FkhzJ{|%V${@&i8q##&y$6U%4Cj(+N?VHKE9$#CXj4LjGc{Kd60o(ke)g;g=97No+q77QPmXIj9Trf?DJDJ(INdmLYFV8J^?o(R(5_V%(>?MD~Z)ca=07DO^R8@I`QwX!~sF%}$&fN@rpHWiv@v*9(&@U(mBD~8xUKkoeJFvG3E?)F}|b#MUOuem$i9X;Cv4mjE$9v%eELAV`m1^auWo$(-SZg1^wHFtNmn>&ZY!EUfO2=))hkHQQ~K(dkAsiOOA+Re>U{+qBsE*01u`A)62N>=EE4d~*UH+I3{)%@?+6d|-4Lst91_y8#+SO);l63j_{gg%lRFiESBorB7DxyiE9$%XACj1ra`QPk5Kz0*nszEh5@4VmTp`OigDa47`r$Zff(H$JR6IQwX>(AK>|Y_utZ1;vG=benpiG;*b75Psa0PI&*s2of6C*RM$1K}>t&Su9UAKI6k3u@II7fjUQ}`umuwpM>w^NZPxSxvkL^S^(KXKy}`z~WG>quu5>qqud%dgz>#QK%wTKSQCnOHw+pSC=}JxQzwsQS6u5_KAJkHG|(IwzsM;zH)!Mjm2w<6a*8N1>kwe@Py7oBU}nqzvn{>X3Js7sr=)!O6?>Hw`HKC@C0LVcf}xYx*Y8v^+N)<)N$lmBC8OP)Tk3S^^wG65Q=)nGw31a`o|RI}-8t=)Z#(6D{jNU8?%Pf*VIXI!w_N>lVx{g6SvTBs@GWQxw%Izy!vDJ^6ixY|ErVE8z5vv!di?T);N{|Z92cdzrSRjHA><6axxh!8~DGU`GX-;rRz0mL5`;9>ny>0?$^nJUvs>FR?@ca+py?A_pY&^!`$;c>J!!@o1@{3RRiBGtaA?zR_GI}NW6MfNImzqrb^D3Re;6j6G8}OHzY5mM7;;xM41Anuqw@(nnc+=Sb(&I<*Za?7nP0ab~DgQF7^-VgYvQw%6%4nU(*($NL26dkZ}JSC*5}Etlz2UAM+~AeUu6qYncvLp$^kB({P{T8@Zz7hkG847Q+X!7X{a*j)otyUE#4HTX%#}%^sY>G~9@SwUJ~j9~5Vx#Eh!W6kkxl*t}Xu;U>i0-M}Q{@bHQo)UJ>!HmGy*kV_P5WU>!^DfD{JEtEdYG^M7IH5Hj{nL9n*?8fbe<5p`UJQ#%2;3i5^sThRSL@Q#uR-(lj=;fE)A39g65$;zYs9%CoF2n}yo0LjQ_PYmqY}0iW*y!%r(v8cmex`Ekv?{~7kd4>p-IJ;cohre0$9<&+LRx7{`f24lOeIaH4p1aq_Nzb@A>Mh)`FplWyzFC{WcRdl^LVAg@~QF5jN_x|cs*{Q>A}XVtS@wTs?BO8lnB#(AmOwGjXwT=9g*KXm(eBE9o>mz@WK;ZPhizU52tl?&w_n~`zpR6(Nd5_6e|^Twmn;-6#X-eWSs)H|J4C(V;$0hpf-@fHb6l|*|$obCv*HpwwH)&$R}IAPl%u0C)*iAOEFTX05ZB2mI4`*>ulF*vHB|@ak9#;Qq@%{K83BAF(?dVHS7U2Pdur-sx69WVEiu&5*wEGtGr|7qWg`PtbJC#B#`^_#OLCv_qz&Ak->W|8#S_ZoyN5NLHZO^SSrzt_u|b3v|lzT78Cv`76QCh9w*y0v9^gfeY~80|+WaZM{Zrx*=&_La{p=b!KCtPSy6grGRPl#_^A=rByVP%>W2MR#lzSl&I-V!(c>>+jZBcDMpAS_#leK)U|dE%cydq{sy7c=^j_OLzAiBkWDtUlGgMAJ;cQUQ=V)UP&oec`ys$NSog@!0YO_A55-Ulrxnk*uXA|U9kf$Bbq%UYkMMvBMP+Wa$zfGXz(NU1ZG0Qt=?Ysl_(PY;;tP`6ST)_O&aNe;tDH$Kstt(m$vM{SQYE^2o%VU} \ No newline at end of file diff --git a/scripts/.brand_bundle_parts/part-04.b85 b/scripts/.brand_bundle_parts/part-04.b85 deleted file mode 100644 index d14665a4..00000000 --- a/scripts/.brand_bundle_parts/part-04.b85 +++ /dev/null @@ -1 +0,0 @@ -_@VT+RETV~8@EuF^g#-tO8Sv(5nnpmKhE`4cN46Y(#LdVQXNvyXuvV_Qf0DGh>(kG(uI*(KwA4T!IsK4STkLRXnk=5{`!(8RoC}`;t~~atx1x#rMVS(Llpz^e~W=94*@3=eB)!yDwN@JeFKjGRJw{wsSp*{Xw%)t&=4Vszkx}~r?=+8jjmfS!9kfxlXtp@&af^pmFQhjEeqwdd#y;~h(#QgfQvWpTD{|5t9$ZLxGG`#Ln1qB4*}K(gn?lH1RrxeK{wmX8re5p#otgqLU51BCSo}yv0_hyD>PnCnE%HIhi@qIZ7$uHAjrJ?UQlSbm`2X}{}L{_^Jh2XrxjU|jyKeSUV?>%Mw-393xRuU+miP#=!RYb2x3^u<<{mM`NQ1z|dIf#x2c96fpQ5AYqv+<5rK%lGpa_+O%b-($e~`zQ1rXd5qaq!-vO7xU17a^>*AG9UYg@1J-L>sOSG;3qV~p`Zi5N+cqDc^i%L>GvZ*reWyQA2n4h4WzFi4!_^79!=#T5$an58dbNlN}qJi+&pOsMU4T+%>*k;?^4wk5Iv|pu8N*^LUg!2g>kBtV%-^yZQCK6E$h|WN8LX3he?ciSl0G+wo=tg^tew^o}u%WM7&qMlO}@%#Ww3{f|uGj!JJp~5;h4(Gx+ypaY-b($D5|%_l#id)15;tABumm`yMF{W@?r1?{*V}&P{l~wTdTMl}=g-!*=llFx!0O)jWVK%SF{SRQ#rzJHWtgB3KcOVRrKb6iUNnN2SaT7^7)3W`6iX_GfzZV3Pooh4()T3MgtA_EjgyN(hAh11^AGUgiw1vGZ>*XKq@&54lNjM0cxufH!$1MMJ+c1w9qQt7Y%yREK+@<&v?@qo+T^Jlf?jY^3-n3T@jbF?g^e$hfNk%2ZFJNyB-9FiDnZoONYdgRx3PS5F;YM2j)@lpX}`7j1)(Pi#~x;VQw&@$nl%vRxh{=u)W7X9}Utj_J6%Rmyc$6=SdxV_fnksE1q*QAYWRAYgasP)U=n1Mz45MnRs5Z9A9`!9Rr$K3y8W|Weylvvh@G{FaKtpaRbg&fRQ0k!6V^2p@|Rz3$5sD3+{?YZUZ)ghib|vn?Ta#0qu)1%^#?lul1NJ5@+3a9F7Ke@`Eot(VUWv@bmRn@$udo}wCvQl@`jVABBe%)=i+Qqrg?AWjJ3NF%w`_G_1(>OKMN|tlBaI9K^`*-25fV%rSSG?e24DJm}Ck~Si=#U_hi%Nu0_R;1_)o58(r988`-lhFKo=O`dIsj`>o2z@r;aIQ|{r3jFaxe2J2l#0`YJo{Bi)&Of2@5r)zPukJ_`JSQDmyUM+x5*?`syIcES88c1FlS1;kx(9RObjX^o3QsF@LE;&PA{hxse&2vGT5p$`qx!MTAPXX<4DRFC+(;yja`l&c^(A08?1U_vDW^bj)g-iJ-(Ar1MGIs#G&rlRi2ohWDe(~>PKiACx>*g*>-h)n|($FS*%2(%_;bk6ivqVj;T8`Xq=2@h^$P7raLcRW7l^WeCH*c3)bl19@;T&An+y$bwSP(3UPZ@%Up>-dIxx{`0`q1Gx^#GPD%v!1OKrNdR6gRLkeVW0-~8VxUS#@xyUq#O`IhvzKC!euTje40OdziK@s6EOMnj?!C?VRegZAE!qgErrca5n0zsvgovOzl*Kwq_K$sK@pNPrTbm{b(s6_aQlH$j?)g`5dr^DbVXPLglTbJSkK$((745?Xgv94hwm@8E5s+vArHHnHpy*>c-^(c3RkkKQh@?JzQ|Nbxk`l1oN)HKWfj;NROkRf8Lu)lg!5|2pi!6sHffk=K-Wj@XIP=Z5>&A#s036WlBkwWPnw!(fg&Tmn6P8EsLt3;rtZsR0d6OpWYebjvvkRe&8iiQ530)UpcUnp?o9*?oW`wU3-Vd~~<)o1vtotQB0@Hy~xcumvN2;0{PM@swzy%b}y#bk9DtuwyN}C%9utE-RVbN_Fo(cA`z4C1#>h{x;$z6ZVCa`G^|jem0^fEDN#rv+AjmId2U&(MD!qCFd6%PRVGUcqgqNJH3_cq`34#f>#CG(9_2OAeLI97}Y3a3Y+&@@}8!v8HYOnE>I(G}8zjX8LUQmMmOR8Q8fcHZmFiqHG=Aj`Y!s!Tt)l`SKqV$fmJ24*wZ^O!x5z_PSRV~i(i0?!l6Q|dRqLX~@h=`PQ&D(={Qjj74Jx)K=ulHCp;AR#;FMbRT}457e42h!&0?n}&=n+>c}TrX?lRSX*$#?dWxd@5`ndkE4R#Dn>)}k^qVJH?a0HyHn_U5a_`)VKInG~poENOma!HhG{86*kg0tLWt5RwT_$rlKUcpx_*u_%D2tPH#$d{GN(JEW(^SI-Cm`jFbV~3z()A4B*^41x?>swwx_gFLD*zu7MK2n}-uiG!3MzF}jaXR|hdD_+VM7|NgiCm3cxyRfv7b2OhuFJmDSM1cVC!=qD-lI48%ws!hU@I^2Ce1!%^$2U5zq*h?K&KL}JTHs%onJ;q$z1;(FQ%*1BIX!SH~h${r>UVo}{!7$?TPqe(^(A?NyZa4gOjB;jLmv5OUnTLq3=r(PvEOn;kQZnDrT^J{y>c`^7$~=szNQv)<;}eFb9E8($H#n+|5H@^FaSw2}wmcI)z_)n9?z?!I*F!LdpM+z7o-gM~}T0nh0fXb?UMV0A{-}#EjyyGCVD&mO9ENgiftL|ZP$w_AgMi;kt%KnwUD(bNjMtA2oBpzbuM=;Jqwr^Ddvc#zYwnQ;zj|&g1k!6fDQB*G?@yO#Spk`u5M}WZvWN|M$TUH@8HvI_VNkY7rJio@yr%I%#>S2O0YJI#5EiQ~eqg!l*$)-Sqo~99vMk%7ItIY0MAd6B@_v}1jP|OXA#Ru$%J%EjwaBno206KAzMv|f!hsGHbVUly-U7k434bH=6u>5`=cnaR>B!Mj~kWVn0x3|sBm+)>tUbGx!6Xb%Ij1*8^v=fxy3SDO;6fOb!j5qpdgYm6p31GK%32xriZ;v|EwM34HbU}KnFeD{W~7u@`_>FSbB{(*I~9zg+xrZai+&h>Q8ce8-fh^q@yve-m0QXj{Fabf{ceSV&1qr}Hrn!Ql~&u8Zfg~`?p?Gl-=gpOtg|kZ)+o#F8qY0{GxDD}jwW>Rk||128fhr`>G_S7CD820UU!Nuz-Ooia|lzXLaMPiDMnfIbB-zp(3MXml=1=GA3dfO->Ia-nfidV%>R(K3oQIg|suhYiD7C=NUJ7+*ddO}ICIYrUeWAl4wS8Y{8%ks>Cm>R^%WEqy@C^<`n<4;CW21~jlZ95a+%m|Y^6v;PzV^nXMOp1N0hB4wV&1zPX8CL|gn%tP~*{Z^FIS8ce88v|ts^7RD#POF($XcC=RSNVkKoDMs&sABHic~9XY!YYg)1>^U9B2B5+iLN7w4GQzWXLyXX{40Kz*GjlvWi(z^Ym;QEed)XJ8)3K0+lMs3<-6`M?-;}wZ2Vmv6?9!MyA&JmP=_N!^1p_nv|W-B#Y9iF;6jq{4B_k(QG-h^Dim9xJL0M7?e(yFb-6g_;nXqRKR-R2_KfCsVg?%-UOx^Q#StR8*kl)G+0+4ju9KmL#M&5lyiW4PNn9+%9++pq7blpo{hJ#qQ<J6%Mz{j7v=;<5wEjW$rn1IRSB=mmUKG!>5}+bhT}fq|7y4@KHCcZN=XiYtX1C20$;U#r-XU7n{cIXkgXo#LwszUH7gJuO?fGOSc%GB$?S%Jqb2$aMcK4ms%*Knt(*$(#e-RB#inb$J`{H^O+KUXz;H;1+yGXZCX>km1y10iSd1R9H<|!IrksTk#v^i`kkKMj1qH+aow-qhbPc0$v_y%5CN}*r)h4MNoiVX~RkmB8lWxt3aUk#_V`?i7wt4t+8?6M2fMeBkWRp-i3O?w+eOHM$`J<0ySM@4OPSL!+*w%^k7RTAH`yd2m~F?99v0E2r2qyoyzO1{;aDM>N_e&_7wp%~p$VTF|1ps5eJYHO4lAcC?wOVyxaf(0YPIq0mMplz{m+L|$+5)ox#Bh6KOuvwnQrj=)V7D5@wP9oCyibYsWZj8F6$MN-7*(-Os)7*vn`e)1$!;yNL@O0r)`o1Xg>T6xDr1%eaqL@5#%(MOK!yRX&992}L^?wbgy2D#a`+)_&q^#tR~*LrBKr@7vp-x$XTHn)JiOt$&r-LIJpr&#K2u<~VK$wsKG2+mW)8O|89$R{L_B<)BF&mGlLmrBc@63uM6$w$VMbIKbGGo;R`Y&#dDn`K1=3k^Z4@CZ|T%th3l#S1YrBIify@o?NK4`k`-JADxiBgqh~q)FvpZLV4!gVVqq%4HOFLC@mxPLzPmLufERN0DD3j(t6*Iu1i7*EQ4>UI+mZ>JTVRDiGSX>{1F#dr8aXY2Gj2?UJa#7j1`k$WOU{&kz$ob)qLRPWVbZb_9gOT&^^&Z9b>3pemry6#96Xg`&K|oLmtq^#bwf~VK@sI^o5%&EzPydRVxq@2lFI~NjfG3c%1Vh{^%x~_T^zfWqhO6guR!!xO(^Uwqg9Wh{fvT&dz13b@*Mlx1Huh$A*>xm=f>cDns=wqGUm9>JSiGA@m(11rDZGVI+LVa$vMHgf)oxoYgo0cPl?*sEp8~3|tV=THLJ31Wl|KVMa`t;F@;rX4yL`hS)L`$yTKKz+4ciiXcw;3*<`Ky{bhbbjNZL>>cd6Q@?kXp-#pq$f4zApmjPmPI)0UdO9#zG~KVbjkWo(}c=hbsTW1=Y1fcz`tf1&yJmqGdBB#(;aIQZ2fiKF4&gD|`p54S1_!LelPM1(@sDP7LH=(?3yHI3KQ7{y9pg|o6SdRaD=z#;Ch1P*v5$pim-o+g#&-0?snigLSU;mu4Bv>T{HQI^@R`t{8hOp{!9yyA2b-@x<|w3-6fj=-MONWdQmz*CexxE>D|Pb#o|QDPz3@1U8Io=Xo5j4h8eLIi0GTF$FqbQsHPRmXC#sbDCaxAmTgIuo%WhTV}Wti0`ZNsgI#lORrQA*Q$qSRW~fxy5pM6QlW=S9aE*_nkwR?D;=9c$ApQf2TJXxTmMfkM+RI%jx90SyM=>NtX+2LPilW0{cT8yDruI}AFI#fj6MoIr+HCD+`8;o+cmw}wLqg)RFV8-e#3^6^6PRA)1;Uua%Q@-k2oMl6|{3}p!3#st8*(?toga;U^k^NpM)KbXe=q_G^ms!w&esQ!^AH>lYk>Am0_0a9xCH+{b6j)^i}CD*o6obT$}$PR^IE^jQZoQei>&Xr0?;2Oeg9RwOb3hv>Ga;tzO)C-sddvtnc*v?lcm<_30FepaYHDOM)Eu?M>R62{RX$g6;E6(cmZTz68<#oeZeI;pRQNxYoRdFhyJV^^nM^IV@T+V?)!HO(^cJXh4sTeFq*i)VO$C2kibKTua6ZrsC#*hvx9LRcWB{)y+g4RYLp4cg#teROugY}_7*o4CW~P)VAxE#cwke%fijZJl-7t&@`;dOF43@IL&Yvz$&Os1Aw+n4P0vyvxR&|MHR3-BH#b8ph1y#=h)RD1Vlxta@E@$|vwgqCfPZjWum8nhls|Oq`a1T7yv-3-@(?`bG?Z16w3PC69wAX&=v_xYo*B1iTQak5{)q`TrPV#Ah73n7db*})zFbWBziPqXl!kZuGyED}*{qbq7={y>!jriQc+v?^`qD}v**LlD!Ev9exacp5sVtfXH#+dr&YVTB2nJjPV+x>L0M86b`#@roN0X2uG)!wUAV5*F%rII)dQ*)ON}LWxBat)6d3;NqSt+V%e&?YO5)1&H3R&@+OVv(-ZG~v<{QkM}wL93Ocb{1)MW1xuwAw#<-LvD)p8%WA&d>Z;pw#@Kd-ldV?Y?0fi6c)}uk@U3|J3V+JlAaCrnW*TUWz!)!m}7GRRwC57OlmjG`vkY2#8z=SO_Jim|bHR@;b^LFpP3jb3`RjOU4QFFj)%sgA_CSdP%vkMV61N8XcMpVz*4RRbF}$4DVE-nSQd2olGc`bDX43JsT&*cvYK4D;cs-rTP@@O4a$1)*Nf9&=z-yIb4M~`k0EcGOx?t%9@Ip#l1*YNnxrU$IGwCHL<2zU$3t)83n%=X1c)0-FpJ!t@+j!6Dw}b?#JTHd5QL)4FYtndDoOUD&8EjhmC+!F{f1#gx2)M_`&r{kXWUS7I7-k7%KytZ163FHQjUmx*^d9%V@I)d0iZawU9y-#`<+Am+)rCL7wGJyvNpZY~S&tm4{cIxyxmR=!ef`e=^E{8RHW7@BoJR<{V_dv(_2?anXan=nsb@78Y;Y0ks3BMiL8sl_aBktV`JE9Zg38`bL09W{W5sN|_~e=_gFCcU9XR^Z7-C??zp1HcUYQ^|EwmYVJ{81%M$GjlwZJ3nS8F@Y0J0#^ce4X+Fb(luv3;2n@6Q4jt*kEPHbFzk*d)@53IzE1Ged&+FZ~?z`n#X&uUdu1h`~!ToeE|QrD_>Ei2>6nVH_Mj;IJaN$TQ2x2-zFd7tl&$o-Y#EKB_)8y41VAa4f4Y+vSD0W`*rKJnhlGR9(n0x5PZA6{jBCaga1F+uX$Vb-NUEWGd{vx^@GjkQ)izOL=Snix!bIH&7D2?r@4&}KigIhqZw&@1h))oUli25_fJsq$~>$B&`V|kkcJxBn#SsdU=G7BBl^Iv*Hl5vWmNOfVuuT*Z))BvbTpp^LpDvmPB7Q@{gZw;NkZ>kmj>74$P6>k2x$iVKx%T$>%TsQKm1-eS;j$H1BG`UC-9$S9t;!eAdTL2EUvEJQOiZ@aD)zdIIzR5#m5@PEe^lk-rBmk^?YylaPje}K8$HFqHefzxQ#7ud+`zeY0tDm;KBpl6`S8_q4<10Ug(m=_lQ{NO7V!}^#ClFq64ZqE;}Oub2sB(Qj4(-2AUXnBHT_nu^eGL7$iQF@IxuMCuuxh(?9jiBM)~x}+iAi+>C0gf<3v0NTm)~_8lRCBHyj}G1eE|t+_xIO0K5RY>RZi&r+6AQ4)K&8jqt_OTJ-?WwnpKk=9R~3t{(^A$9`~XoMDs>F(obcntQ+Z4u4-$x6y=~*s3*mcWd>7{ip7DJG-C)s1cp>b2pFI1^3+Cg=@xtYW7FG8~&Hx8Ux_^<6suWcj`D1Zh1)DJ?K-YiywL0^}Rj%mclGjWEv>}1tz=9V8)YhBIKPOv#+1Km@BBln?5>{K>A<#I*IQd0S3K)LT|GJDgh`MquYtTBhXY}L4HPnMb5lC0@6Ka)JOnHU=2rJy~(8M7uHCQJb$}*P|*xn1E6FYfBXIk>aIuhO(VOR{Nu-2T>Je_8$NpQah`qu4N4{;nB3mp)^B&}Kvx>gt*tG5_#2NP^!slB6Tk8J#Q2Zj?{r|0aTFuh2lerrx%w)+T&9C%%mK-Fi}3qz#%Sye;L92pnVE#@(F|kCg85C5!2`44qe5_84HE7NIrRiro5BgA*)PKrtAN2otuOfM(nKRWypYV~I)?2^a^OC+X~nKO(RG@mqg;ckB1o1F~NIum=~Ed045D&)sbCXq*g}c*fNOZ>dA!=lq)}qar_aFv&7!ez60a0wxrVWVIv~@N(oCKN*h+HFs(2a?T{a{_4rq3T?qJVOA#buyLM(8HT)PNlvS;$e{%W!j|h`)gqraITYinCD?}JHY_Xb(x3d9o@^xgb~?Xz}wPn1jHhgicl!3KLggZvtRks&fZgR|FH6>!~LhU*Hw%VXj}CHY=+e=esO^7a9*J~6xi~a4Fk%aTz&VcT*aXv+8_a90g$aRPhZbC4*8o&U}JO;!-e<}x|EM}L3<`>h88)})D8^&)J66~#Puz`1Qd*pNV2kV1!dbZ)>rU)k%k?bL0I}!v*{!6+2UhkyWVuD8dInoAsfX*T3UU3JDeF_Z6W`1f&N#f6uhQUnk;;#{s_*?^xGZYYQ>F+5B*nnnL-Z)eSvlkg0y&f7y|m?&JfoE-tHMuPa)btbLD0Jd6{9MbYDvi+%|0x=C`3ByK}?X%_2+<95i=jXhXQ+9`WwKWf_UCUf(^7*H1w~ZHhpx#bANEjV|2+d)8r|uDJu`Bf3ua9Kyel?U@qI6S#Ngy)9pwxix$`D4G8rZcVhdX$9njG?9>!IH>Ba!dp*TR+3zrIeRyH$5phG{w!kgT2y%q~6(V^QM3*1J(UvWV|&M;j2c&(8bmC;_SOT`Q9$h4RF)kX!4yscXr7%Joa7<%|EQQT7tA*{&r1IkG(dG85jj3DDEetw93_4W*Y?U{Uum7VAqS&HXj4bg&eYILZw#P&<8R&`g6-&S+%R|+xs}!_VBC?pr%FGU>+42lZ2`nzfzB;5iwg|oNk_YB0<)LMh_DFj;QY8-9(&jckRbU4@d)5pDgbb-nQbFk-A9*NXTVSVPXRle*5Sz`%()9Ngw0;z~jIoVrCyfKAy!3@W7Zz`t`=x^^-!rnVO)h%RxoDnnpm_C(P{IWz36l%bukzLmClyc`lk-_4IBrCimW0$Rn&E{T-k{6^3WleqzxuUqx&A2hEpCuYn8g05`-TBTT>vda( \ No newline at end of file diff --git a/scripts/.brand_bundle_parts/part-06.b85 b/scripts/.brand_bundle_parts/part-06.b85 deleted file mode 100644 index b5c8ce68..00000000 --- a/scripts/.brand_bundle_parts/part-06.b85 +++ /dev/null @@ -1 +0,0 @@ -(^@2XyMiJYhjSq40mK)EQn_xvA#&=Jn8s~jOyLV}?$?^zJGJd++qL@cuB=oKPrbi>T?=$ZBz50f4+3>LbglXrAgeS$-4W_9vc|8t3*L}^g5?fZw8|xX712d_uZ}1p6Ggx*mYDjTeR*VeG14zzJU2P3JH{5;T}f+Y|Cc!Tbj|U*M9?vHb7Z_wr2t5jsWFBCAZnTm)n3Co+@LkS{|9_P>!(gp?Jr8B+MMt_i*%QY`}U}yy6P+LCc*nj2$XdpcVzhbaiM*oMpG7?d-7KYUi1N)lLOiIXgkABo4A1>k?wr#VQ5cCVU4cjex@6ucoZu*S;i~!(R<2#_ScMuOsQ{&J0s^c!Bpsr$3Eicz#1%`=HvygI3eXvist{`5SkiXO;(#3XZ9tX94Y@gT_rnTLb<9SY0dMa-tL^X9b`NTRjeFHIt<(fJ;yfE08H%&suQeWQq%<~-+%f?&2T-4*4Z2{)(dTdDzTurYn5d_c-EQ7P7s?JcP*W_A^bqHxb62{cIj}=Y*!P9KX4LoDDZyzo$P$?!rLTon0o@hWn62>XTs^ws5b3Mewo0SgmGT5ka-50;pC;0nsTb4pe0g6m;~B{ao}q#5_XvIA*duz9$lNvk;aLm2F{J!GJoy^=O@+qa@z(BV#-1WRg^emS|huvZRsZ#J*`)&8J&;zuvl4`(?Lk|I7Z9{v~@J011#FB|GWaot~5_5;y=34$cb)2c>>2*QVWIEd^zTEmJf+T*JuFYCOi^i|R(6c^A(d?_tx-1%w(&DHw-Mys#7lS03bT&ACo~pw^yDh1FSJ#rbM;*whtKtGaYxR<8NKYT{>2%{B+vRRR`P^6)j5L8%9-Qz+6+dgNcY+_B-X%B?8&Z#w>Uw5)oCnwU|uPsr;EOi@x|Goh0Vzmo+OR0S`ETvZ!T4WW#?L5b-efQ1D8EPIu%*MZA`jB_Iy5GCaJpmd~xj;Re`<-8gx!`#oi+0i_rw+)@U-J_}(%(Zi_Cw!xU8?8awj)zyAkzrDm#g2BUb4-P*lb0|1lmr}Ng5;cM!IK~FX5kG=+@I3IS~tqjk+mBECDs#B1ZsV!k*Q^S0UPH28lT;=gM+=^XCbt$@XS08?-xL*JuXhiCQA!oY})-zuc1H4o>pop1h?8Q%RsVvn6I~DU4%V7MwhjW_4oK_0m^5EAC|LmCV8@Qtz(60(Hi61RuO?QNO&Q}M&8pkkOQ<*kaPC<6$S)9LC#lDL^UAbi7%ewZImW+OFYpR|@t))7x8C#_HE#Da{xjD3b(;(ETB~;Yf#vRi&Rgq@B9}kB&^!)K3vGrALPUG`I$eR%*KZ5Vo?zif|%KP=at5Lf=^vWiwt9eW+E|&@+>+4F5jZyrgmGt3v*w?M{2|W|_%54*^3g&ez6Y6?POWnB*)m<{$gp+abm6xQSni&c!3{F{`(McsmQT6Y-!=MhDH>n2aBRm9I2IQ2=i#;TbTYCS5eIS1gT}7M8$G6x0wE89WRq3n0qOVSpuW=RNSNm}q&h1p?k5NG`a!!}=lBe3(R7;9%${0zNXRU2GlGmxEeJzEQg_?82%Z>h(!K`(DjrEn#YS-2|VQ5ao`el_PAr|;BWc&Q1#kCety;546I{4Taro85ssHfQBqQh2~W}UYn!g~EtSsIaPBlTOW1$E;$*sX52)^0UISd;PO>(T^7$YFz>ztSLf6{oG9L$SIO>AHpIR>`UN{}-8RJxd$4$wW8!nooVMHufkeUvsp63(~8x|5hlfkr5lWIq{*@yq$kWugV0ze(e1H8rhjAZeeRbom|uHkCJ*t2D5eClxvJ$NpE&r@-{@Mss?dsCmqdSE6g`f)pi-_AW*fJ=I{e^-EU`#_)<+V^yO&Tuyvf`gp5#TAC625%#NMa{<(Ugr+X#G3+n|FCD4p^IAW*jwzI+EZ$B^_8yEVF93Gh^VYG%WYL8J1gZu%U2MBNJk*rxBv`X-?`GeRBTV=#p|%B<^rf}I;lvhoRLwX=(!Ix{t*_~UE?^tVl>F1yH)0)&N%m!nRa5DT~spTpacVtRaeglN1W-*r5Gv4Q%F)d8LfLhVjXaPo^f3Ur=1|w<1n4Ng66Y=PD$@_){Wz+@>(^R(kdUfX&xFoI6P(2n+~1anqMpEt3A=7k%yZo4Cm#Bl;@R3@`gaP>CawO~T1hOXR^$MUaU|*rtM5g{IikLWtD)Dev-^6hcF^x~N#%f!8fGt>hh~sx+D-^a1nwC^7?7rZCcnW8VBUZn@D*bpBahJ%nP{SA*fJ)m^PR@0_l&@|{x8&HID-8a8iXX>+6s&4x5T0Ij@#vYLmKe8ek<7q6JCvOCo%EGyo_Mm>S+7(5B+z0)oG)#GjQ3)KbdMODv_Do%ClYeg68v_;bCr5Uj*_eOa|3l)UY5|nf92^nNK)2d4Uz%!`Wl|qWBH$*%sB|Wz&c?XQ+`2$t|YH&rZ#R4>>&*qpzdSTDA;pHI1q?8oAiehG%Ex;MT%Jw440^6xM+s7}7^TPgy)TTd5j1B+NtBJT9lG+#2Q)LN<@D@I73OI%MxzHxt})!MWkJ!TZW-?&_mcXc#a(is48E?}{g`B@)X0EVK!VF1fMbNnaQJHwWq`sIA0LtkD2XfA+&xA}T?0O}1fdG;5+R28H6FEi^#1-iU4LlhS08XXozWnZ~IFQ&g<;)RTlfDuhVrRV^056G&I)joJ_=!aM<^)ckee|?)HxH+MnL`@y_eMxJv#Gm;j}H%at-XV%{atS4Xy=V}bliL1Kf;?C4)*(2|Mkw(KHfzHua6aw(uyIF+@me%&+g81x)OxJ9lz*XqB*?B7Qz0sbNs@>_jJQ#0q=}-HGn1W1>D&`IzD{0*WW)rVqk}__K$Y}1^2Au{yud6u+Jj^Tpbl~@P;`7N7lC-lx*9*ec7#N2ZwvT-JQQN;`aN`d&epc3t$xLRDtH<1Rd}8M8EhF9jcG0I+n9QcS01f#6@uxXg>Ds5HGyh?K8mVy_W<)T>g8sN57u+c8~Bz(8B^G-33nT_as5^RhSiMy;2<;};B(iJ7R!`u)HEZ%f{`&d()^rhB~@C|<7g8mW&*%D|xiFZVZL+jOm-{LIU06L5Mt;myc-hH7wYQ=(tWroD7|-XTYFTgt}KWRlH)!ta|8Br6qStFH75Dj2e);q?f#XD1RiXzWj`1t)7dc2ukeKeKJfkQ!lR!c6gKm}YLOCL-NT7&tZ|L2c6Lgwk`)~3{d^?AXq$MXy?a6r8%pC4X1c$FkdX()j1*)dQ*=qumxa+GR`ER3>m|MKD_n|EgEe39);mMsE+$tPt|RWBhFW7t0I7o3{IiLQTwMb=#9tl;W!-|0zCaNp^m8WE>ri?9D}I+|2Im&b+$TUS7`*YNKOI)Cl8t*;qo30b0&Qd%A4Np?1xyaY0Dsc$bp3pt*OUX^izAE=w^2gYj+HS+Z(EuhBhw#7$q?=-|V-a9ddC%k1|n^yBM()pm&C@ddZ(=T^mCFcV`LjfV@Ah|lel9Yna56U0&#%}I_`9_Ro+l$~dYo&V=F(%i*5RIeO4CH|5OhGCP7kk%3Un2}4Bhfa2bI&u`pD4B53j?MtmPnk9jbmky8#^ZwDf5A$?kJr0)DKDY2Z?sE$qQ}(p(zhq#s5TAgVl*7$wV-N$6sl|FPgw96jbH^?>UX>^eLAnh^KiP<)&{iZR}o9rm5SKN`-bc8*2AqC3*Hv6sIZqynpZyps?H{7v;d-YR*moHL&V!sTWA}P$Wn$s4;e5%-cuKpl$Pn_jE2%0ftE`}H(|ue*dOTSFVbJAJ0t4{E3zIvk_LHEf;1sS4o{Pf&Qz@PXF04NDN~(wU;N(8>yuPwX0jZx^MmWvK<|Shc0humtW`0jm^~1JbC@y3ccED93Yk59Z1ZpcwP6YB7wTL5E>1KWXf`BBbWi#o|02%{4;GCz!=FpkERg6hSN~KTC^-3J?_F!&E!0Z+uz`j!~8yX)M#0AqT8bMo+h}ui=3k^aLwQK{Fp^+d3gQ5|8)wzM%o<_|j1wyo_K*`N-SRvHkYJd0^$6G-J7UtcRjGUkR@0bmZsKW-_&6*){t8jiO@4>JOB*D`Sg`op6gb5)jcz>f(MkW<@t!{%(f0CXLrQrRg9&x@^8Y%@=G&KZE`XC9pc~3f@FLSTDm1RsPyx!@5tc_cj)zaUrh>4c3P81E-2KJ62~LolJ*KZgEaziMC)It+*(sgEI1+#8#_Z26T}8ZfgFaNxJ;r-eW(@t{P$M1aZonS6+UzwQ*F;!d$q|j&eGEs6noRg!f)HGY)kbX4ZdMk66V7z>BTD)Rn5CKyp{zD6;&%RLkRZpT7QYPhF976w}bNb-6$>zYq4B^YXQ*EWnTdZ;i|T{;&pSyQi}~R`OBS=6^AHXtHS`cw`v?z^Lj;1;{=(T3|FGgH@B!)e4|0p(Nh&#`93L$E??76>R61r2n};(=zYNlwDvsOM2SN4f!gb?Jc5g#qHi_$V*d`@vi>bvNmEL-=BwN3loh%Co(@Fq)o^usmr5@?FeYR@SvFhz%%FdAe#szAti4b@PBp%#UI(ReI>777$=O_k4K!(<9>hG}uo0rXC4)NaT-QoQ+tGR~$QsRwdH3M+KLfa|A&^Hz&9BjEMB!|bYv7Af(|%E-7{$=yNx7KhV98wZBNrEtEyvhyU54nzkE1;+hgdF(`i*_Te2dqijOhp6N}nH2J8;VVJef)U{}ptBrqTkmiSaRFW8wwRWZ&9$Hi%KpT14SjbEnfSD4}tcB-fv)x$j)AW9|K%C9EpV@mk$!*S0yk>TiGizGgHuWy5%Oed%HVN4mql2%gQ5QxFy*|Pa9=yY^tDuAaBMr{|6%0%R~bq5uOv=h6n<^EbwL8*3n+hr=Rhqr@cH&XKxUk7*qM%+x%M;Y2PSr{S66hn}Afmz9q?TGZ+$b66?s%NN^T=9yz1KaCu^N`0$WA&CexnmsfzXas_;NNJHo6QUy)T5Z%8=1%FfrP*PpQNbXS=sNh?*ATtH2JqS9HY5{V_z_7Vt;5F!d*i-@M{y81CLKOx5f-oLU&RX0k=#rbASvowzj^mNn_Z!ap4Nz4WP(jUI{KQA*RSFgxs;G4+3q^`dZsV~c*EEdHvrpSJ9ipI(Gw=h?iMjY8O*b*7)A4jVZ>cr6sOF|tm8_6_`echdh3#UxBy65CIeHVHcgZmutF7E9gJz`4fLi72jm|W+t{#-a;qryZtL!5$uJ6CB!|`rD0%|3ek)MPnfnBgWsF+mDwqhTH*>9vO8|X7Gf6LW_sMdR$)Gy!X*~P-Rbfep=1W_rNFTKck?QSSR)=Gr59^S0PCTmf}axoFOVlm;*hV>+k5?dM{TNZBZNo%^L*d*{tA%K!+z+f{lwjb1Z9So*Kxq?ZAqfEHVT69Yx7(6{IWsfpoxtOrLziuZq1vgZn@v`B`Da-$OmHam@2;B@~NPcy*Gf{9+*=ww5_`NqfM~Wn@Q%wpP7&Z#QJ;0jLxVo)k4HB@%U#3K$FoLgEdere&yc)@S$l>)(ZY~jWx;mI?^v>6sTc3kwsq3B1|QHQr`r1Dq~!HT-(kEuEr9du8IaWp3M$*;uO}$gY$~*A+*A%t*6)Do4r(9|RLFxhA=5fJ$!p5Uzsj7GAGWed-L}-huG||}>ZwCnSx;iT4YLZhKYUoFM$j^5=M%M{E#)RLC}zL9i184GIMgB7GXME&r{8(n0ml4}8lV8q+QMBhIsToayjGeqGUE1M!b@3gdaFFE}-i$@`Sut4FfB$6qdDdv3%jX(v(8NhgI(1_8ktms$Ay*CH@x6rR-gQzR!&ksRV{~34REle!6S#(BFVyWQ!4VuLF8`k}=*3Y^X7fs>5=_ciG20HC(ST%TGK@dB&zSrfIaEQp>Naw5PNY%sbbbVb%SYDO;rM_vQGd9e37tpxj$i{Hfq+lkREqhb9<_`IYQiBIFxVs0^xRk`9s6Tcwv71WQYWGI*3~{0mwVY3_1PBHm6#@872jU(4!r_!;i4+-60tu?nG#xh3M$KpD|U?}5B$JNj!IPugUo5mR8iG2inZF+?1h_(xc|%k!B6}7VN)UWd;3p!p3)g#veJ~}riJ{kU{(#Na}!;;F3|0+qYa=}u*`-{9OfmSTr~2RJD9TTFc>;2c?b3)edGC-ooVAl-7j&%TQerwDy)<7EfGAbuqcw6iF~JQDE=r(?gv^NHf}b%n+8=;J!Sjk)z0oyex$R-{FtAjHqcEWj?iRkwh}{y9#$^E%Bo{)J6UtxDMi;JB{1lM{qAIT@JU?<|$O5iD7o3``Yfcli&uE>OkBy82g&hXe{H-SyV@Y)iP90OClb04W%r39hitYWBDr*wPW@lxVUsZsUk6E!}Z?oz1nqs4teh;y2%hrNN?ADIJU=?UKGAZrXJqFjgRkAu?T<;N%Ob0Twc*99Ai9N(me@tOi(JSM3*PTrt4G%&Ry09vGvo;4bvL$uq$-Ld7~#N9JE5_6s9Zb^EH!exway7jR~d3dOHY7Y4=*)Kwx_c_@lp478thQZfe~3ewml7l@mtW=XQxqzQ)J@S4=HqRC<`V__why8uSr~4wC3k}KOh38Dxty#=af|86cXDf1%d8{y?o$4wLhk7)kaChW=F$P8OBG|815)ADVK!K{I0cM;GPHJCC_otIgSw+{%Jkrg7M`bfN+!4u7~+}tTS+{V>0p~r?r`46jC8ieGQF7P9TZvU@(?Ej{F2Y`b4xV4S({S^N7$k~Z+hIbM8qOT0Vlpg{;43R6cMc_(4!i8+;@F4J-wui+~cq_B?Dc#FXDCQ@5;PTI5WTBlnD++CZ*%OT}*QEkn^@b{{9O#0XqE6!!7aZA?6vCA6)u?FFq=;OLI`oKO{V*naGU!$m-4K>2>FHKD}t=IQTneH9X43bb)bijC_buA+NZ3hX~@_mml75Y^qrorzZo(8ABdMA^@4R>8345jQ23yr$jmJ;>Dgw#{Pc8_JbcRyzs#eJbK7eBQDQjEuT-vm`06b#YmQVQXGRA;ap&wf`Q5DF7!$9hVUY1tRo)skDF!wht}3OW8!LMpE69HAqP`Rk4ay(#F!$94=(#n$rJ#CgKS*l30y?#h+0<6RYZ>GwHMjlSr0;AUeB`Wsg~a}9S-|Ap4|~h49fNK2vfsIyuj2p7Ki0C0MwmjD3Vnia@*A9`j*#kG;gom2DTz_VdxuYh{L||Q=3x8FBZTl^=z#&m0X^>wSFS*Gz~YKR9hyx{WMx!rsKs{@8!$hVek0W(a*i3qyF(x=VG{Vf8#r=H=Ct%x`yF3m9%;bSa=H@?swmT>I%FEM>?Hfa4WyP?l-7s+jiw2g8P`t_HE#vljUeJ8)XB~wLYDF#Af=vr+a;iZsM2@M(NnbP*+fwEVVQg{1}{c-P9cnoYP@)61#pnNWC}>Pfvz^2>*+`6VLB@Y3y~=lOQ`C#DgGmqsTouaffb@CfM2mXv(7r;9xeN{sPO_`b4}NzC5AJKmf;QnKhk^uOC>GDcDlh`3Thk+VBBmdpbXZ+5K0V>@DaR5DTW$ng5ETj>0gBlR-G>#slxiVaia~g!1f8q|BN4-C_#@QKsfsqU|YLgT#A0AXE?qCM^Wjq4{D$tP!CI*TgfBJ#1sa@Hvb6;(Rn~TbBz;)v~lP9afu&IA0TU%uhXBSm_+N*2H>;sG%EYwo+VwKnau4DXeoq(RA|hL#yfYW=E`6uVeMbOG<#vZ@#mhDa!=ky-eqWbDEjOU<7|gK#%%b`aD{mLtkMc1=cRIba4qx3g;5|*?=hp55-uVg1b0FW1$Zfu~3AhSa_oekN_yhOS+AN5wSea(AEJA{Qi&s*_R8zVrv3l{-?s3@K?cB_|A_!U47^GfBY{w+HIJ%6Hfb0O9bC8Q!qVSO4jE3?fyf2my&_-p2J=~&lTpJ;AWdXAXI23%sUIKX!>$n9>N)vxrAWcdpp(JTi7u!k3pA9Udjrq&RI7LWZBkTugchj?ZIy(m#L+%k+ImK9k)S;pLI+8PVwqQLVUtAS1{kUXsaDtb29&X{F84(xRGDXwlF{{D3;1CiK=pOwuF)70W%a@6S3Phh>oMf=nhqz8idl+`Cr+}ZcNk#XNL+nt;x3r$agq|+M7#_}2yi9Sz^LvJ91eziT3;gy1_`z-#`Ad|41riPk7?#MKTu29#SBp&0n*@ArA<#V_+ZSH2hqt%CAQ*g<_>q#uUc9o!B^B@qy_Us$*`ZGSeq*a>gMF0N0M`ft9$-z^$0dS=BZPnn6CWu#_UVPzP!QGvb-NO34m4N~sILHhxFxV=MEW_}Zwk^A73axnl*{jVw&X{cX%}y~Zn?>3-IO@56Ky8)kyK~G^$Zh{Gz@Pr9?kIlIFIiHnC>>bfu3l%^#eagt{V#Uy1s{iA8tt)5+?g47$P~i_opKoJ%X}b=LdMaE*@n+U5|d|_~hRON=~!m%lXOWm~;oU4WSH}RD)$YmJOVwi|iTMRTJuLC&tcBE-wzC)Ta!mgbnT~iuqP6FfV4nh$kXtHX>@0mn>?Yf}W1kaGE^>3%4g*qziVi1NirK-lip&#=|W`3u^OE0DnVnlGTOh$b&~9E8wMMd$s&d06qi1}^iwTs2#dzSCLaJ?XT9XZJh-}_rp6EBZx&>UjCe)RB~UuK`bqx97Uu0%h7qUv+6VjMFWR-yY$95Ir7RY>@Q4d=`La#g(@H9{Vj4>q-P_?iW0%EkeNJwc6hLVymRz&r%#(tPY!$gPp$sb9oj{7T+qg#UOkL3#wHF9cb@O;_jaw<{q5s}L%a^FxrnsF-*}mXydI?nC@@G3thnV7)Wa$A=L{3LKPFxD|NIYBM-?!P=A2Rrg^hilLjPZY3ByKiK`Ki{bQL)WlgM&iZ?j=d$m?t*DdAS&Ae}wRQ_ZU%ewh-35?`u>mIRjHb+%mx{(7F(?OL$nLG6w~Zwa>D(6wA2bePz;lCI@;9m|iZMC>U%tpM$MFq9F@D7iOYvC9GGrmih4*@5EW5E=;0aAszH#jE&RB)kQ(8Zh^Fh_t<7?0l#w{iOXZp=M_USWsBt`QQ`!q|qP``(2c*)SXScdjVRYC*!CKoNO<~fk)#I`SP9z9d{jnYy*wqPlvEJwnHfD>>=?b;TVt-gnNOH_q>i{9MPVK6dR9Y&-TMTz_{;6;|P8vdq`$}5(9|X_G4Avp8b)W(J^yIU*U{$=~fs}+!gExh7O;VgeSeHy_d%nSy98gx~}A0UKbgn$T|RwZ+o|QbcBfq@cCfxW$&h9$tJ|-vT9b?E>IOvAk^Ay&~(08dtUPGnxi(THE!6@oa=qV(eYzrdr@3%uSu2cs61;8i^~54pe6>jdo3L=H1b;jL_%{5Q=*t@-lc1eP?}r(j;s^On46PM9;BZe=q0)@AdpK2rc}Dk|>JCt`DF0JU_I82sp6oLzUaMT4F2pjTjcn(^_HV#1=E7GVg?0YtMaKd14l7%L&>dlEg&7Bh^B@2i=5u3Uc3ye7QfMx+zgUAj++ujELmP?N1+u)b{r4jV;82_4lERZSO==xE=cCUc0rTy2k;D4KpMjIqi`G8k=4ana6=n)iDVF4T?;ic3l$LYIYa=cuSEkJCM-Zc75Fwl2%%BX8ypMN0_Y4V1hN0OW)d!Q>}CxwKqbk8c0K4v07JFAHtMhN2D_C2DuBvRpNIk|3@Td;y#`L>*?>OR_JI~1_z=LT{ecZu4%UOwz>xO>2gJNbxV1vthoRX)XeWCn2Yy#`v8XixBeC}>xd4DIi~R}vas-e0>#`#5%NL&|*N+{ea_)CL6P@&T`}^B3&rw3O54`IV2J3#+Rr{)htrF6@-OHMsP;KYO#QPxgV(*-#L~N^`ng@tdl7_96KNWU8wFoI5U*?Ma{Wg23e>Vx|9X;0Ozx@PZ8yc#uGX7GUPvnKKf&qdscb&igw(~9FM40wVDVMLlD8l?(2C}OlMK&uD~>XGrCAl%@>XClXm?+}=xMlAJMRXGsr%&b`X=4?ye>H_Z$AQY`Ui$^{Pt!4Y3&ev&o$0~`>lrHC(+%8u)DL}-#@}h*xNbQ$yrF@M#A>Jc=OvaXtT4pMk+j}K2|13{Z;WddA;FMIZ0^E(=yFv$RO99UZtt;@oIJzr*uN$h}@4w#Z|5Q~?lHjiJ1IO3k(+M6U!ITt&tP%o^~qA>qCsnd0Y+lsfK5MMUYPy?AYiVMfg<1=E@;%tD=0Az?#9TFgZNjP!}jy3Xp3Eio>uubI46O*haQpja|~iXJq^rN6~c3p)lGWF`+(}ORUS9x0bcQlSI15=xI)-&>akL&%8tI1}VBg0?QHqMG3z4f)ovKeC@)2V1p-j*_AFVpi-;ur#RSiT?=g|e=qjN;Ma;qirV|(MHx`5Q>>_=?foDasv-z@G!FI)w5h#2<{X773_GgS4T+s1>`*<@Jf0a7)&x`5l@^d=ReuiZsOTp^zgq;Eu#e@AU;=x4oIiD%Q3yTV3Kv+~90?!Zj0vj1Jh@#>GMg=TyU>B>EW3Kb_FIW$KlU$RKR*KEmiq0_<#aqf!)VeOj8O0!5ypv!IZip^^&DAGiv7f>SbQ;`UY?zE2<+v_7(|2!=gzKX<8;DNrrMyHx2Q(`esSTtGYjj2V)9FY>wJh*A=}he-7$yJ#cWJL;Vgkqr{nSTGln?hLReg4gtfY7p{h_EwF(;GuNX3J?JPM$bpdKrA%I0}I7fS{k={y>wv^7+QAT_qD>g)qUO(sHZ9WXa2O~HTTv-!_V-NaL)p4Pm>Cg^ySkqIv_QhF`!4zZs2iQ7t5m*a|8p%EvH0aSnJp)oX;G@PykIsOt0UtvGCk=)}^ORFS`mq`1tPJ7oBs)((ji$gzZT+lJM-m<-D){hz0)IDd{x3x^ivs \ No newline at end of file From 0cb491e5a5b4453eacb6d3d1bd90600f65e015f9 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:39:32 -0700 Subject: [PATCH 23/37] validation: recognize Apparatus public identity --- validation/validate_soul_gauntlet_public.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/validation/validate_soul_gauntlet_public.py b/validation/validate_soul_gauntlet_public.py index df10e961..b317b601 100644 --- a/validation/validate_soul_gauntlet_public.py +++ b/validation/validate_soul_gauntlet_public.py @@ -115,14 +115,14 @@ def need(path: str) -> str: if forbidden in foil: fail(f"person-specific Mirror/legacy-foil prior leaked into public skill: {forbidden}") -# Public identity changed from FOIL to Mirror; the legacy technical namespace must -# remain explicit so profiles, hooks, benchmarks, and old links do not silently break. +# Apparatus is the public product identity. Existing technical names must remain +# explicit so commands, profiles, hooks, benchmarks, receipts, and old links do not break. for token in ( - "Evidence-Governed Research Toolkit", "Research Orchestrator", - "Process Assurance Framework", "Mirror — Adaptive Reasoning Complement", + "Elenchion Systems", "Apparatus", "Mercury Runtime", + "Research Orchestrator", "Process Assurance Framework", "Mirror / FOIL", ): if token not in readme: - fail(f"README missing professional public terminology: {token}") + fail(f"README missing Apparatus public or compatibility terminology: {token}") for token in ( "Mirror — Adaptive Reasoning Complement", "technical skill name: `foil`", "slash command: `/foil`", "runtime modules: `tools/foil_*`", @@ -144,5 +144,6 @@ def need(path: str) -> str: print("PASS: per-component engineering specifications present") print("PASS: SKILL.md-only module directories preserved") print("PASS: Mastermind absent from runtime imports") +print("PASS: Apparatus public identity + legacy technical compatibility contract") print("PASS: Mirror public identity + legacy foil compatibility contract") print("PASS: public Mirror/legacy-foil skill contains no embedded user profile") From 3ba5edf415543519f4a398cceba8bff98529a8c6 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:39:42 -0700 Subject: [PATCH 24/37] security: pin two non-secret estimator false positives --- .gitleaksignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitleaksignore diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 00000000..58aa857e --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,6 @@ +# Exact historical false-positive fingerprints. +# The detected value is the documented non-secret local estimator identifier +# `utf8-bytes-div-4-ceiling.v1`, not a credential. Keep exclusions commit-, +# path-, rule-, and line-specific; do not disable the generic-api-key rule. +b793e6fe393614ed1d6987744baabc4da03d58dc:.github/token000_verify.py:generic-api-key:371 +b793e6fe393614ed1d6987744baabc4da03d58dc:gauntlet_host/gauntlet_plugin.py:generic-api-key:296 From 0221cc118630011ff0c5de8ee1443d2fe34c3e2c Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:43:18 -0700 Subject: [PATCH 25/37] validation: check renamed README aliases directly --- validation/validate_soul_gauntlet_public.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/validation/validate_soul_gauntlet_public.py b/validation/validate_soul_gauntlet_public.py index b317b601..03f00c92 100644 --- a/validation/validate_soul_gauntlet_public.py +++ b/validation/validate_soul_gauntlet_public.py @@ -115,11 +115,14 @@ def need(path: str) -> str: if forbidden in foil: fail(f"person-specific Mirror/legacy-foil prior leaked into public skill: {forbidden}") -# Apparatus is the public product identity. Existing technical names must remain -# explicit so commands, profiles, hooks, benchmarks, receipts, and old links do not break. +# Apparatus is the public product identity. Require both the new instrument names +# and the stable technical aliases rather than treating superseded public labels +# as the only acceptable README wording. for token in ( "Elenchion Systems", "Apparatus", "Mercury Runtime", - "Research Orchestrator", "Process Assurance Framework", "Mirror / FOIL", + "**Axis**", "`soul`, `/soul`", + "**Aegis**", "`infinity-gauntlet`, `/gauntlet`", + "**Parallax**", "`foil`, `/foil`", ): if token not in readme: fail(f"README missing Apparatus public or compatibility terminology: {token}") From a77c7272306d11706ed29a8fe8ba036a63159eea Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:00:33 -0700 Subject: [PATCH 26/37] brand: adopt Lattice Prism and gemstone names --- docs/brand/NAMING_ARCHITECTURE.md | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/brand/NAMING_ARCHITECTURE.md b/docs/brand/NAMING_ARCHITECTURE.md index 2d318737..8bd271f3 100644 --- a/docs/brand/NAMING_ARCHITECTURE.md +++ b/docs/brand/NAMING_ARCHITECTURE.md @@ -4,37 +4,37 @@ | Level | New public identity | Status | Existing identity retained for compatibility | |---|---|---|---| -| Organization | **Elenchion Systems** | `DESIGN_DECISION` | Independent project ownership remains unchanged | -| Product suite | **Apparatus** | `DESIGN_DECISION` | Repository: `Kitahl/The-Gauntlet`; CLI: `gauntlet` | -| Runtime | **Mercury Runtime** | `DESIGN_DECISION` | `gauntlet_host`; pinned Hermes Agent runtime source | +| Organization | **Lattice** | `DESIGN_DECISION` | Independent project ownership remains unchanged | +| Product suite | **Prism** | `DESIGN_DECISION` | Repository: `Kitahl/The-Gauntlet`; CLI: `gauntlet` | +| Runtime | **Quartz** | `DESIGN_DECISION` | `gauntlet_host`; pinned Hermes Agent runtime source | | Principle | **Evidence before authority** | `DESIGN_DECISION` | Existing authority/evidence separation | | Visual method | **Scholarly Antiquarian Framing** | coined internal label | No claim that this is an established academic term | -“Elenchion” is a coined brand form selected to suggest rigorous examination. This document does not claim a legally protected mark, available domain, incorporated company, or authoritative classical-language derivation. +**Lattice** is selected because it is a short scientific term for an ordered structural arrangement. **Prism** is selected because it is a short scientific instrument/object term associated with separating a complex input into inspectable components. These are working public identities only; this document does not claim legal incorporation, trademark availability, domain availability, or institutional endorsement. ## 2. Public module map | New public name | Existing public/technical name | Stable ID / command | Feature definition | Authority ceiling | |---|---|---|---|---| -| **Axis** | Soul / Research Orchestrator | `soul`, `/soul` | Goal hashing, task creation, typed obligations, deterministic ownership, receipt integration, release gate | Release authority within existing rules | -| **Canon** | Mind / Formal Reasoning | `mathbot`, `/mind` | Formal objects, assumptions, proof obligations, negations, exact arithmetic, optional solver checks, counterexamples | Claim-scoped formal receipt | -| **Atlas** | Space / Research Discovery | `scoutbot`, `/space` | Bounded search plans, literature and software discovery, identity deduplication, saturation, source assessment | Claim-scoped discovery receipt | -| **Crucible** | Reality / Method Synthesis | `novelbot`, `/reality` | Verified gap, mechanism delta, assumptions, failure modes, negative control, transfer, ablation, verifier plan | Candidate mechanism only until verified | -| **Forge** | Power / Engineering Verification | `codebot`, `/power` | Typed verification plans, bounded execution, known verifier families, output hashes, defect-class coverage | Claim-scoped engineering receipt | -| **Chronometer** | Time / Evaluation & Benchmarking | `benchbot`, `/time` | Frozen arms, matched baselines, exclusions, contamination handling, uncertainty, multiplicity, decision consequence | Evaluation receipt only | -| **Aegis** | Gauntlet / Process Assurance | `infinity-gauntlet`, `/gauntlet` | Stale-state, false-green, inherited-number, frame/costume, event, and ledger-integrity checks | May identify issues; cannot replace claim-native evidence | -| **Sextant** | Meditate / Decision Preflight | `meditate` | Facts, assumptions, unknowns, options, blocker, action, value of computation | Decision preparation only | -| **Conclave** | Council / Evidence Review Panel | `council-of-elders`, `/council` | Frozen artifact/budget, independent seats, commitment/reveal, skeptic seat, cross-critique, controlled synthesis | Review receipt; no automatic factual warrant outside scope | -| **Parallax** | Mirror / adaptive complement; technical `foil` | `foil`, `/foil` | Conservative user/task evidence, capability-gap routing, calibrated assistance, transfer tracking | Advisory/adaptation only | +| **Diamond** | Soul / Research Orchestrator | `soul`, `/soul` | Goal hashing, task creation, typed obligations, deterministic ownership, receipt integration, release gate | Release authority within existing rules | +| **Sapphire** | Mind / Formal Reasoning | `mathbot`, `/mind` | Formal objects, assumptions, proof obligations, negations, exact arithmetic, optional solver checks, counterexamples | Claim-scoped formal receipt | +| **Emerald** | Space / Research Discovery | `scoutbot`, `/space` | Bounded search plans, literature and software discovery, identity deduplication, saturation, source assessment | Claim-scoped discovery receipt | +| **Ruby** | Reality / Method Synthesis | `novelbot`, `/reality` | Verified gap, mechanism delta, assumptions, failure modes, negative control, transfer, ablation, verifier plan | Candidate mechanism only until verified | +| **Garnet** | Power / Engineering Verification | `codebot`, `/power` | Typed verification plans, bounded execution, known verifier families, output hashes, defect-class coverage | Claim-scoped engineering receipt | +| **Topaz** | Time / Evaluation & Benchmarking | `benchbot`, `/time` | Frozen arms, matched baselines, exclusions, contamination handling, uncertainty, multiplicity, decision consequence | Evaluation receipt only | +| **Onyx** | Gauntlet / Process Assurance | `infinity-gauntlet`, `/gauntlet` | Stale-state, false-green, inherited-number, frame/costume, event, and ledger-integrity checks | May identify issues; cannot replace claim-native evidence | +| **Citrine** | Meditate / Decision Preflight | `meditate` | Facts, assumptions, unknowns, options, blocker, action, value of computation | Decision preparation only | +| **Amethyst** | Council / Evidence Review Panel | `council-of-elders`, `/council` | Frozen artifact/budget, independent seats, commitment/reveal, skeptic seat, cross-critique, controlled synthesis | Review receipt; no automatic factual warrant outside scope | +| **Opal** | Mirror / adaptive complement; technical `foil` | `foil`, `/foil` | Conservative user/task evidence, capability-gap routing, calibrated assistance, transfer tracking | Advisory/adaptation only | ## 3. Additional systems | Public name | Existing technical identity | Included feature set | Status | |---|---|---|---| -| **Mercury Runtime** | `gauntlet_host`, pinned Hermes Agent source | Provider access, tools, MCP, context, sessions, memory, skills, retry, interruption, delegation, typed IPC, CLI | Interim pinned-runtime alpha; observation-only authority | +| **Quartz** | `gauntlet_host`, pinned Hermes Agent source | Provider access, tools, MCP, context, sessions, memory, skills, retry, interruption, delegation, typed IPC, CLI | Interim pinned-runtime alpha; observation-only authority | | **Obsidian** | Black Gem / `blackgem` | Independent breaker seats, frozen rubric, participation accounting, injection canary, cross-critique, break triples | Adversarial instrument; may raise `ISSUE`; can never return `CLEARED` | -| **Strategist Candidate** | archived mechanism-planner candidate | Exact minimum successful repair over a declared finite repair universe and hardening checks | Engineering candidate only; not promoted | -| **Formal Plane Candidate** | archived Math Foundry candidate | Trusted-base minimality and isolated qualification hardening | Engineering candidate only; not promoted | +| **Moonstone Candidate** | archived mechanism-planner candidate | Exact minimum successful repair over a declared finite repair universe and hardening checks | Engineering candidate only; not promoted | +| **Zircon Candidate** | archived Math Foundry candidate | Trusted-base minimality and isolated qualification hardening | Engineering candidate only; not promoted | ## 4. Compatibility policy From 0656d700aeac6580a6f11538824ae884a830920b Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:00:57 -0700 Subject: [PATCH 27/37] brand: update claims register for Lattice Prism --- docs/brand/CLAIMS_REGISTER.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/brand/CLAIMS_REGISTER.md b/docs/brand/CLAIMS_REGISTER.md index f5acf751..863cdb5a 100644 --- a/docs/brand/CLAIMS_REGISTER.md +++ b/docs/brand/CLAIMS_REGISTER.md @@ -14,20 +14,20 @@ | ID | Claim | Status | Evidence / action | |---|---|---|---| -| `BR-001` | Elenchion Systems is the working public organization identity for this project. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | -| `BR-002` | Apparatus is the working public name of the integrated tool suite. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | +| `BR-001` | Lattice is the working public organization identity for this project. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | +| `BR-002` | Prism is the working public name of the integrated tool suite. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | | `BR-003` | The repository and installed command remain `Kitahl/The-Gauntlet` and `gauntlet`. | `VERIFIED_REPOSITORY_FACT` | repository metadata; `pyproject.toml`; `gauntlet_host/cli.py` | | `BR-004` | The runtime vendors pinned Hermes Agent source and uses an isolated parent/worker boundary. | `VERIFIED_REPOSITORY_FACT` | `vendor/HERMES_SNAPSHOT.json`; `third_party/HERMES_SOURCE_LEDGER.md`; runtime engineering plan | | `BR-005` | The FAST-P8 record reports 8/8 bounded boot checks passed. | `VERIFIED_REPOSITORY_FACT` | `docs/engineering/HERMES_FAST_P8_CHECKPOINT.json` | | `BR-006` | FAST-P8 establishes external-provider, autonomous release, cross-platform, routing-benefit, or cost-benefit performance. | `PROHIBITED` | Checkpoint `known_limitations` explicitly withholds these conclusions | | `BR-007` | Runtime outputs are observations and cannot directly create canonical receipts or release tasks. | `VERIFIED_REPOSITORY_FACT` | runtime engineering plan; Phase-8 document and harness | -| `BR-008` | The ten public instrument names map to existing stable skills and commands. | `DESIGN_DECISION` plus `VERIFIED_REPOSITORY_FACT` compatibility map | `NAMING_ARCHITECTURE.md`; existing skill paths | +| `BR-008` | The ten gemstone public instrument names map to existing stable skills and commands. | `DESIGN_DECISION` plus `VERIFIED_REPOSITORY_FACT` compatibility map | `NAMING_ARCHITECTURE.md`; existing skill paths | | `BR-009` | Obsidian can raise an issue and can never clear a claim. | `VERIFIED_REPOSITORY_FACT` | `docs/specs/BLACKGEM_ENGINEERING_SPEC.md` | -| `BR-010` | Strategist and Formal Plane are archived engineering candidates, not promoted runtime authority. | `VERIFIED_REPOSITORY_FACT` | `research/postbench-candidate2/README.md` | +| `BR-010` | Moonstone Candidate and Zircon Candidate are archived engineering candidates, not promoted runtime authority. | `VERIFIED_REPOSITORY_FACT` | `research/postbench-candidate2/README.md` | | `BR-011` | Exploratory benchmark receipts include positive, null, and mixed/negative outcomes. | `EXPLORATORY_EVIDENCE` | `docs/BENCHMARKS.md` and linked receipts | -| `BR-012` | Apparatus improves independent human reasoning, scientific discovery, or general AI capability in deployment. | `UNVERIFIED` | `RESEARCH.md` states this is not established | +| `BR-012` | Prism improves independent human reasoning, scientific discovery, or general AI capability in deployment. | `UNVERIFIED` | `RESEARCH.md` states this is not established | | `BR-013` | Classical-scientific styling proves rigor, correctness, or historical legitimacy. | `PROHIBITED` | Brand evidence-boundary rule | -| `BR-014` | Elenchion Systems or Apparatus has completed corporate-name, trademark, domain, or legal clearance. | `UNVERIFIED` | Professional legal clearance required before registration or commercial launch | +| `BR-014` | Lattice or Prism has completed corporate-name, trademark, domain, or legal clearance. | `UNVERIFIED` | Professional legal clearance required before registration or commercial launch | | `BR-015` | The committed frontispiece and calibration mark are original project-authored vectors with no external artwork embedded or traced. | `VERIFIED_REPOSITORY_FACT` | source SVG metadata; `asset-manifest.json`; reproducible SHA-256 hashes | | `BR-016` | Scholarly Antiquarian Framing is an established scientific or marketing term. | `PROHIBITED` | It is a coined internal label from the supplied design package | From eb936a9c0acdc199df91bb8674772cccb4ad74d1 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:01:44 -0700 Subject: [PATCH 28/37] brand: present Lattice Prism gemstone system --- README.md | 95 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 738789b2..7c99f6c5 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@

- Apparatus frontispiece: a calibrated classical profile inside an armillary instrument, with botanical and measurement annotations + Prism frontispiece: a calibrated classical profile inside an armillary instrument, with botanical and measurement annotations

-# Apparatus +# Prism -**An evidence-governed agent and research system by Elenchion Systems.** +**An evidence-governed agent and research system by Lattice.** [![Research software validation](https://github.com/Kitahl/The-Gauntlet/actions/workflows/validate.yml/badge.svg)](https://github.com/Kitahl/The-Gauntlet/actions/workflows/validate.yml) [![Fastpath checkpoint](https://github.com/Kitahl/The-Gauntlet/actions/workflows/fastpath-checkpoint.yml/badge.svg)](https://github.com/Kitahl/The-Gauntlet/actions/workflows/fastpath-checkpoint.yml) @@ -12,7 +12,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Version](https://img.shields.io/badge/version-0.5.1-informational.svg)](CHANGELOG.md) -> **Research status:** Apparatus is public research software with executable checks, typed evidence receipts, bounded benchmark pilots, and an interim Hermes-derived runtime alpha. It does **not** establish that the complete system improves human reasoning, scientific discovery, or general AI capability in prospective deployment. +> **Research status:** Prism is public research software with executable checks, typed evidence receipts, bounded benchmark pilots, and an interim Hermes-derived runtime alpha. It does **not** establish that the complete system improves human reasoning, scientific discovery, or general AI capability in prospective deployment. **Website:** [kitahl.github.io/The-Gauntlet](https://kitahl.github.io/The-Gauntlet/) **5-minute evaluator path:** [`docs/EVALUATOR_QUICKSTART.md`](docs/EVALUATOR_QUICKSTART.md) @@ -25,16 +25,17 @@ | Layer | Public name | Meaning | Compatibility boundary | |---|---|---|---| -| Organization | **Elenchion Systems** | Working company identity for research and engineering instruments that test claims | Naming decision only; no claim of legal incorporation, trademark clearance, or historical lineage | -| Product suite | **Apparatus** | One agent runtime, one evidence-control plane, and a portfolio of specialist instruments | Repository remains `Kitahl/The-Gauntlet`; the installed command remains `gauntlet` | +| Organization | **Lattice** | One-word scientific identity: an ordered structure for research and engineering instruments | Naming decision only; no claim of legal incorporation, trademark clearance, or historical lineage | +| Product suite | **Prism** | One runtime, one evidence-control plane, and a portfolio of specialist gemstone instruments | Repository remains `Kitahl/The-Gauntlet`; the installed command remains `gauntlet` | +| Runtime | **Quartz** | Hermes-derived operational layer behind the evidence boundary | Technical runtime remains `gauntlet_host` | | Product principle | **Evidence before authority** | Models and tools may produce observations; claim-native methods and receipts govern factual warrant | Existing task, obligation, receipt, verdict, and release semantics are unchanged | -| Visual language | **Scholarly Antiquarian Framing** | Classical, natural-history, and instrument imagery paired with modern evidence panels | Brand language is never evidence for a technical or scientific claim | +| Visual language | **Scholarly Antiquarian Framing** | Classical, natural-history, mineralogical, and instrument imagery paired with modern evidence panels | Brand language is never evidence for a technical or scientific claim | -The previous public names remain technical aliases so existing commands, state, receipts, tests, files, and citations continue to resolve. The migration is presentation-first rather than a breaking code rename. See [`docs/brand/NAMING_ARCHITECTURE.md`](docs/brand/NAMING_ARCHITECTURE.md). +The gemstone names are public aliases. Existing commands, state, receipts, tests, files, and citations continue to resolve through the stable technical names. See [`docs/brand/NAMING_ARCHITECTURE.md`](docs/brand/NAMING_ARCHITECTURE.md). -## 2. What Apparatus does +## 2. What Prism does -Apparatus treats a claim like a specimen under examination: +Prism treats a claim like a specimen under examination: 1. **Frame** the goal and create explicit load-bearing obligations. 2. **Route** each obligation to the method that can actually establish it. @@ -49,21 +50,21 @@ The system is intentionally asymmetric. A model may propose a route, call a tool ```mermaid flowchart TD - U[Researcher / artifact] --> M[Mercury Runtime\nprovider · tools · MCP · context · sessions] - M --> O[ToolObservation\noperational output only] - U --> X[Axis\ntask framing and obligations] - X --> P[Parallax\nadaptive complement and advisory routing] - P --> I{Claim-native instrument} + U[Researcher / artifact] --> Q[Quartz\nprovider · tools · MCP · context · sessions] + Q --> O[ToolObservation\noperational output only] + U --> D[Diamond\ntask framing and obligations] + D --> P[Opal\nadaptive complement and advisory routing] + P --> I{Claim-native gemstone instrument} O --> I I --> R[Canonical receipt\nverdict · scope · evidence references] - R --> A[Aegis\nprocess and integrity assurance] - A --> C[Conclave / Obsidian\nindependent review and adversarial attack] - C --> X - X --> G{Release gate} + R --> N[Onyx\nprocess and integrity assurance] + N --> A[Amethyst / Obsidian\nindependent review and adversarial attack] + A --> D + D --> G{Release gate} G -->|supported| S[CLEARED / scoped result] G -->|defect| E[ISSUE] - G -->|insufficient evidence| Q[UNKNOWN] - G -->|missing capability| N[UNAVAILABLE] + G -->|insufficient evidence| X[UNKNOWN] + G -->|missing capability| V[UNAVAILABLE] ``` The governing path remains: @@ -73,12 +74,12 @@ runtime tool execution → ToolObservation → claim-native instrument or verifier → canonical Receipt -→ Axis release gate +→ Diamond release gate ``` -## 4. Mercury Runtime — Hermes-derived operational layer +## 4. Quartz — Hermes-derived operational layer -**Mercury** is the public name for the interim runtime contained in `gauntlet_host/`. It vendors the exact pinned MIT-licensed Hermes Agent source and runs it as a Gauntlet-owned isolated subprocess rather than as a separately installed product. +**Quartz** is the public name for the interim runtime contained in `gauntlet_host/`. It vendors the exact pinned MIT-licensed Hermes Agent source and runs it as a Gauntlet-owned isolated subprocess rather than as a separately installed product. ### Included mechanisms @@ -99,7 +100,7 @@ The recorded FAST-P8 checkpoint reports **8/8 bounded boot checks passed**: the ### Current boundary -Mercury remains an interim pinned-runtime alpha. The checkpoint did not qualify paid external providers, automatic claim-native execution, autonomous replanning, task release, profile-based complements, dynamic tool narrowing, cross-platform operation, or behavioral/cost benefit. Runtime, model, tool, plugin, memory, and session state remain **observation-only** with respect to factual authority. +Quartz remains an interim pinned-runtime alpha. The checkpoint did not qualify paid external providers, automatic claim-native execution, autonomous replanning, task release, profile-based complements, dynamic tool narrowing, cross-platform operation, or behavioral/cost benefit. Runtime, model, tool, plugin, memory, and session state remain **observation-only** with respect to factual authority. Inspect the frozen source, attribution, and checkpoint: @@ -108,20 +109,20 @@ Inspect the frozen source, attribution, and checkpoint: - [`docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md`](docs/engineering/GAUNTLET_FAST_BUILD_HERMES_INTERNAL_RUNTIME_2026-08-28.md) - [`docs/engineering/HERMES_FAST_P8_CHECKPOINT.json`](docs/engineering/HERMES_FAST_P8_CHECKPOINT.json) -## 5. The ten core instruments +## 5. The ten gemstone instruments | Public instrument | Existing technical ID / command | Responsibility | Returns | |---|---|---|---| -| **Axis** | `soul`, `/soul` | Frame goals, create obligations, route work, integrate receipts, govern release | Supported result or explicit unresolved state | -| **Canon** | `mathbot`, `/mind` | Formalize and test mathematical, logical, probabilistic, and specification claims | Proof, counterexample, measured result, or unresolved obligation | -| **Atlas** | `scoutbot`, `/space` | Search literature, standards, prior art, repositories, and current sources | Source set, nearest established class, differentiator, and search limits | -| **Crucible** | `novelbot`, `/reality` | Construct a new mechanism only after a named constraint defeats established methods | Candidate mechanism, assumptions, failure modes, negative control, verifier plan | -| **Forge** | `codebot`, `/power` | Implement and verify software through real entry points and defect classes | Executed checks, output hashes, coverage, and untested limits | -| **Chronometer** | `benchbot`, `/time` | Design matched comparisons, baselines, ablations, uncertainty, and stop/go rules | Decision-relevant estimate with exclusions and uncertainty | -| **Aegis** | `infinity-gauntlet`, `/gauntlet` | Detect stale state, false greens, inherited numbers, scope errors, and process defects | Assurance findings and integrity events; never cosmetic approval | -| **Sextant** | `meditate` | Establish facts, assumptions, unknowns, options, blockers, and value of more computation | Decision preflight state and bounded next action | -| **Conclave** | `council-of-elders`, `/council` | Run independent seats, commitment/reveal, cross-critique, and controlled synthesis | Review receipt with preserved disagreement and scope | -| **Parallax** | `foil`, `/foil` | Identify the least-covered capability for this user and task, then request the smallest useful complement | Advisory route, complement, verifier requirements, and stop signal | +| **Diamond** | `soul`, `/soul` | Frame goals, create obligations, route work, integrate receipts, govern release | Supported result or explicit unresolved state | +| **Sapphire** | `mathbot`, `/mind` | Formalize and test mathematical, logical, probabilistic, and specification claims | Proof, counterexample, measured result, or unresolved obligation | +| **Emerald** | `scoutbot`, `/space` | Search literature, standards, prior art, repositories, and current sources | Source set, nearest established class, differentiator, and search limits | +| **Ruby** | `novelbot`, `/reality` | Construct a new mechanism only after a named constraint defeats established methods | Candidate mechanism, assumptions, failure modes, negative control, verifier plan | +| **Garnet** | `codebot`, `/power` | Implement and verify software through real entry points and defect classes | Executed checks, output hashes, coverage, and untested limits | +| **Topaz** | `benchbot`, `/time` | Design matched comparisons, baselines, ablations, uncertainty, and stop/go rules | Decision-relevant estimate with exclusions and uncertainty | +| **Onyx** | `infinity-gauntlet`, `/gauntlet` | Detect stale state, false greens, inherited numbers, scope errors, and process defects | Assurance findings and integrity events; never cosmetic approval | +| **Citrine** | `meditate` | Establish facts, assumptions, unknowns, options, blockers, and value of more computation | Decision preflight state and bounded next action | +| **Amethyst** | `council-of-elders`, `/council` | Run independent seats, commitment/reveal, cross-critique, and controlled synthesis | Review receipt with preserved disagreement and scope | +| **Opal** | `foil`, `/foil` | Identify the least-covered capability for this user and task, then request the smallest useful complement | Advisory route, complement, verifier requirements, and stop signal | Every `skills//` directory retains `SKILL.md` as the public reasoning contract. Executable state, hooks, receipts, profiles, and verifiers remain outside those skill directories. @@ -131,13 +132,13 @@ Every `skills//` directory retains `SKILL.md` as the public reason **Obsidian** is the public name for Black Gem. It freezes a candidate and attack rubric, runs independently provenanced breaker seats, performs off-diagonal critique, records participation, and can raise an `ISSUE`. It structurally cannot produce `CLEARED`; failure to find a break is not proof of correctness. See [`docs/specs/BLACKGEM_ENGINEERING_SPEC.md`](docs/specs/BLACKGEM_ENGINEERING_SPEC.md). -### Strategist Candidate — mechanism planning +### Moonstone Candidate — mechanism planning -The archived mechanism-planner candidate contains bounded minimum successful-repair selection over a declared finite repair universe. Its recorded hardening and inherited checks apply only to that archived candidate; it is **not** promoted into Apparatus authority or runtime by the archive. See [`research/postbench-candidate2/README.md`](research/postbench-candidate2/README.md). +The archived mechanism-planner candidate contains bounded minimum successful-repair selection over a declared finite repair universe. Its recorded hardening and inherited checks apply only to that archived candidate; it is **not** promoted into Prism authority or runtime by the archive. See [`research/postbench-candidate2/README.md`](research/postbench-candidate2/README.md). -### Formal Plane Candidate — mathematical execution hardening +### Zircon Candidate — mathematical execution hardening -The archived formal-plane candidate strengthens trusted-base minimality, isolated qualification, staged dependencies, process cleanup, and deterministic numeric thread limits. It remains an engineering candidate and does not replace Canon or change the release gate. See [`research/postbench-candidate2/README.md`](research/postbench-candidate2/README.md). +The archived formal-plane candidate strengthens trusted-base minimality, isolated qualification, staged dependencies, process cleanup, and deterministic numeric thread limits. It remains an engineering candidate and does not replace Sapphire or change the release gate. See [`research/postbench-candidate2/README.md`](research/postbench-candidate2/README.md). ## 7. Evidence ledger @@ -146,10 +147,10 @@ The archived formal-plane candidate strengthens trusted-base minimality, isolate | The ten core contracts and portable runtime checks exist | **Implemented / mechanically checked** | [`validation/`](validation/) · [`tests/`](tests/) | | The Hermes-derived alpha completes its bounded boot route without a false clear | **8/8 checkpoint checks passed** | [`docs/engineering/HERMES_FAST_P8_CHECKPOINT.json`](docs/engineering/HERMES_FAST_P8_CHECKPOINT.json) | | Runtime observations cannot directly create canonical receipts or release tasks | **Architecture invariant with executable checks** | [`docs/engineering/PHASE8_USER_CLI_BOOT.md`](docs/engineering/PHASE8_USER_CLI_BOOT.md) · [`.github/phase8_verify.py`](.github/phase8_verify.py) | -| Parallax profile, onboarding, calibration, and routing mechanics exist | **Mechanically checked; efficacy open** | [`research/FOIL_RESEARCH_BASIS.md`](research/FOIL_RESEARCH_BASIS.md) · [`validation/`](validation/) | +| Opal profile, onboarding, calibration, and routing mechanics exist | **Mechanically checked; efficacy open** | [`research/FOIL_RESEARCH_BASIS.md`](research/FOIL_RESEARCH_BASIS.md) · [`validation/`](validation/) | | Exploratory benchmark receipts include positive, null, and negative/mixed outcomes | **Exploratory, small-sample evidence** | [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) | -| Apparatus improves independent human reasoning or scientific discovery in deployment | **Not established** | [`RESEARCH.md`](RESEARCH.md) · [`ROADMAP.md`](ROADMAP.md) | -| Elenchion Systems and Apparatus are cleared corporate/product marks | **Unverified; no legal clearance performed** | [`docs/brand/CLAIMS_REGISTER.md`](docs/brand/CLAIMS_REGISTER.md) | +| Prism improves independent human reasoning or scientific discovery in deployment | **Not established** | [`RESEARCH.md`](RESEARCH.md) · [`ROADMAP.md`](ROADMAP.md) | +| Lattice and Prism are cleared corporate/product marks | **Unverified; no legal clearance performed** | [`docs/brand/CLAIMS_REGISTER.md`](docs/brand/CLAIMS_REGISTER.md) | The repository does not combine unlike benchmark rows into a single headline score. Passing source checks, green CI, model agreement, or persuasive design are evidence with bounded scope—not automatic scientific validity. @@ -198,11 +199,11 @@ The FAST-P8 harness uses a deterministic local OpenAI-compatible endpoint. Passi ## 9. Brand and website system -The visual system applies the supplied classical-scientific package as a restrained interface: +The Prism visual system applies the supplied classical-scientific package as a restrained mineralogical research interface: -- parchment, ink, slate, bronze, oxide, and verdigris tokens; +- parchment, ink, slate, bronze, oxide, verdigris, and gemstone accents; - editorial serif, technical sans, and receipt-mono typography stacks; -- original procedural armillary, botanical, geometric, engineering, and calibration motifs; +- original procedural armillary, botanical, geometric, engineering, calibration, and crystalline motifs; - clean modern evidence panels separated from archival imagery; - explicit source, status, and boundary labels; - no fake seals, founding dates, patents, accession numbers, museum endorsement, or antique-looking evidence receipts. @@ -233,7 +234,7 @@ commands: /soul /mind /space /reality /power /time verdicts: CLEARED | ISSUE | UNKNOWN | UNAVAILABLE ``` -Brand aliases may be removed or changed later only through an explicit migration with tests, redirects, and receipt compatibility review. +Public gemstone aliases may be changed later only through an explicit migration with tests, redirects, and receipt compatibility review. ## 11. Governance, security, and citation From 4b08b7763ee42ce21c822481adfc82c48d7f2b63 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:02:16 -0700 Subject: [PATCH 29/37] brand: encode Prism gemstone naming system --- docs/brand/brand-system.json | 41 ++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/docs/brand/brand-system.json b/docs/brand/brand-system.json index 77c40967..427797bf 100644 --- a/docs/brand/brand-system.json +++ b/docs/brand/brand-system.json @@ -1,18 +1,23 @@ { - "schema": "apparatus.brand-system.v1", - "brand_edition": "apparatus-brand-01", + "schema": "prism.brand-system.v1", + "brand_edition": "prism-brand-01", "organization": { - "public_name": "Elenchion Systems", + "public_name": "Lattice", "status": "DESIGN_DECISION", "legal_clearance": "UNVERIFIED" }, "product": { - "public_name": "Apparatus", + "public_name": "Prism", "descriptor": "evidence-governed agent and research system", "principle": "Evidence before authority", "repository_compatibility_name": "The Gauntlet", "cli": "gauntlet" }, + "runtime": { + "public_name": "Quartz", + "technical_id": "gauntlet_host", + "status": "INTERIM_ALPHA_OBSERVATION_ONLY" + }, "palette": { "paper": "#F2EBDD", "paper_deep": "#E2D5BF", @@ -29,24 +34,24 @@ "receipt": ["IBM Plex Mono", "ui-monospace", "SFMono-Regular", "monospace"] }, "core_instruments": [ - {"public_name":"Axis","technical_id":"soul","command":"/soul","role":"orchestration and release"}, - {"public_name":"Canon","technical_id":"mathbot","command":"/mind","role":"formal reasoning"}, - {"public_name":"Atlas","technical_id":"scoutbot","command":"/space","role":"research discovery"}, - {"public_name":"Crucible","technical_id":"novelbot","command":"/reality","role":"method synthesis"}, - {"public_name":"Forge","technical_id":"codebot","command":"/power","role":"engineering verification"}, - {"public_name":"Chronometer","technical_id":"benchbot","command":"/time","role":"evaluation and benchmarking"}, - {"public_name":"Aegis","technical_id":"infinity-gauntlet","command":"/gauntlet","role":"process assurance"}, - {"public_name":"Sextant","technical_id":"meditate","command":"/meditate","role":"decision preflight"}, - {"public_name":"Conclave","technical_id":"council-of-elders","command":"/council","role":"independent evidence review"}, - {"public_name":"Parallax","technical_id":"foil","command":"/foil","role":"adaptive reasoning complement"} + {"public_name":"Diamond","technical_id":"soul","command":"/soul","role":"orchestration and release"}, + {"public_name":"Sapphire","technical_id":"mathbot","command":"/mind","role":"formal reasoning"}, + {"public_name":"Emerald","technical_id":"scoutbot","command":"/space","role":"research discovery"}, + {"public_name":"Ruby","technical_id":"novelbot","command":"/reality","role":"method synthesis"}, + {"public_name":"Garnet","technical_id":"codebot","command":"/power","role":"engineering verification"}, + {"public_name":"Topaz","technical_id":"benchbot","command":"/time","role":"evaluation and benchmarking"}, + {"public_name":"Onyx","technical_id":"infinity-gauntlet","command":"/gauntlet","role":"process assurance"}, + {"public_name":"Citrine","technical_id":"meditate","command":"/meditate","role":"decision preflight"}, + {"public_name":"Amethyst","technical_id":"council-of-elders","command":"/council","role":"independent evidence review"}, + {"public_name":"Opal","technical_id":"foil","command":"/foil","role":"adaptive reasoning complement"} ], "additional_systems": [ - {"public_name":"Mercury Runtime","technical_id":"gauntlet_host","status":"INTERIM_ALPHA_OBSERVATION_ONLY"}, + {"public_name":"Quartz","technical_id":"gauntlet_host","status":"INTERIM_ALPHA_OBSERVATION_ONLY"}, {"public_name":"Obsidian","technical_id":"blackgem","status":"ADVERSARIAL_NEVER_CLEARS"}, - {"public_name":"Strategist Candidate","technical_id":"postbench mechanism-planner candidate","status":"ENGINEERING_CANDIDATE_NOT_PROMOTED"}, - {"public_name":"Formal Plane Candidate","technical_id":"postbench Math Foundry candidate","status":"ENGINEERING_CANDIDATE_NOT_PROMOTED"} + {"public_name":"Moonstone Candidate","technical_id":"postbench mechanism-planner candidate","status":"ENGINEERING_CANDIDATE_NOT_PROMOTED"}, + {"public_name":"Zircon Candidate","technical_id":"postbench Math Foundry candidate","status":"ENGINEERING_CANDIDATE_NOT_PROMOTED"} ], "status_vocabulary": ["VERIFIED_REPOSITORY_FACT", "DESIGN_DECISION", "EXPLORATORY_EVIDENCE", "UNVERIFIED", "PROHIBITED"], - "evidence_boundary": "Brand imagery and historical cues never add evidential weight to technical or scientific claims.", + "evidence_boundary": "Brand imagery, gemstone names, and historical cues never add evidential weight to technical or scientific claims.", "source_package_sha256": "2232a3b18ee2c1ffeb2cf670f6018dc7129d4668db18ed8eef0ae767de2f22c5" } From 6a39469ac277f072711b499a466df6acf6bad7be Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:02:59 -0700 Subject: [PATCH 30/37] chore: stage Prism gemstone rebrand applicator --- scripts/.apply_prism_gemstone_rebrand.py | 161 +++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 scripts/.apply_prism_gemstone_rebrand.py diff --git a/scripts/.apply_prism_gemstone_rebrand.py b/scripts/.apply_prism_gemstone_rebrand.py new file mode 100644 index 00000000..591a6c83 --- /dev/null +++ b/scripts/.apply_prism_gemstone_rebrand.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import hashlib +import json +import re +import shutil +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +TARGETS = [ + "README.md", + "THIRD_PARTY_NOTICES.md", + "docs/index.html", + "docs/system-field.js", + "docs/brand/BRAND_SYSTEM.md", + "docs/brand/CLAIMS_REGISTER.md", + "docs/brand/NAMING_ARCHITECTURE.md", + "docs/brand/README.md", + "docs/brand/SOURCE_PACKAGE.md", + "docs/brand/brand-system.json", + "docs/brand/asset-manifest.json", + "docs/content-provenance.json", + "docs/visuals/APPARATUS_ASSETS.md", + "docs/visuals/apparatus-frontispiece.svg", + "docs/visuals/elenchion-mark.svg", + "docs/visuals/visual-provenance.json", + "validation/validate_soul_gauntlet_public.py", +] + +# Multiword replacements first. Word boundaries prevent Canon -> canonical corruption. +MAPPING = [ + ("Elenchion Systems", "Lattice"), + ("ELENCHION SYSTEMS", "LATTICE"), + ("Elenchion", "Lattice"), + ("ELENCHION", "LATTICE"), + ("Mercury Runtime", "Quartz"), + ("MERCURY RUNTIME", "QUARTZ"), + ("Strategist Candidate", "Moonstone Candidate"), + ("Formal Plane Candidate", "Zircon Candidate"), + ("Mercury", "Quartz"), + ("MERCURY", "QUARTZ"), + ("Apparatus", "Prism"), + ("APPARATUS", "PRISM"), + ("Axis", "Diamond"), + ("AXIS", "DIAMOND"), + ("Canon", "Sapphire"), + ("CANON", "SAPPHIRE"), + ("Atlas", "Emerald"), + ("ATLAS", "EMERALD"), + ("Crucible", "Ruby"), + ("CRUCIBLE", "RUBY"), + ("Forge", "Garnet"), + ("FORGE", "GARNET"), + ("Chronometer", "Topaz"), + ("CHRONOMETER", "TOPAZ"), + ("Aegis", "Onyx"), + ("AEGIS", "ONYX"), + ("Sextant", "Citrine"), + ("SEXTANT", "CITRINE"), + ("Conclave", "Amethyst"), + ("CONCLAVE", "AMETHYST"), + ("Parallax", "Opal"), + ("PARALLAX", "OPAL"), +] + +SPECIAL = { + "apparatus.brand-system.v1": "prism.brand-system.v1", + "apparatus-brand-01": "prism-brand-01", + "elenchion-apparatus-1": "lattice-prism-1", + "apparatus-semantic-instrument-field": "prism-semantic-instrument-field", + "apparatus-modular-control": "prism-modular-control", + "mercury-pinned-runtime": "quartz-pinned-runtime", + "mercury-checkpoint-eight": "quartz-checkpoint-eight", + "mercury-limitations-visible": "quartz-limitations-visible", + "parallax-adaptation-only": "opal-adaptation-only", + "conclave-independent-review": "amethyst-independent-review", +} + + +def replace_text(text: str) -> str: + for old, new in SPECIAL.items(): + text = text.replace(old, new) + for old, new in MAPPING: + text = re.sub(rf"\b{re.escape(old)}\b", new, text) + return text + + +def digest(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def main() -> int: + touched = [] + for rel in TARGETS: + path = ROOT / rel + if not path.is_file(): + continue + before = path.read_text(encoding="utf-8") + after = replace_text(before) + if after != before: + path.write_text(after, encoding="utf-8", newline="\n") + touched.append(rel) + + # Normalize machine-readable brand identifiers after the prose rename. + brand_path = ROOT / "docs/brand/brand-system.json" + if brand_path.is_file(): + brand = json.loads(brand_path.read_text(encoding="utf-8")) + brand["schema"] = "prism.brand-system.v1" + brand["brand_edition"] = "prism-brand-01" + brand["organization"]["public_name"] = "Lattice" + brand["product"]["public_name"] = "Prism" + brand.setdefault("runtime", {}) + brand["runtime"].update({ + "public_name": "Quartz", + "technical_id": "gauntlet_host", + "status": "INTERIM_ALPHA_OBSERVATION_ONLY", + }) + brand["evidence_boundary"] = ( + "Brand imagery, gemstone names, and historical cues never add evidential " + "weight to technical or scientific claims." + ) + brand_path.write_text(json.dumps(brand, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + # Recompute hashes for project-authored assets whose labels changed. + manifest_path = ROOT / "docs/brand/asset-manifest.json" + if manifest_path.is_file(): + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + for item in manifest: + source = ROOT / str(item.get("source_object_id", "")) + if source.is_file(): + item["source_file_sha256"] = digest(source) + manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + visual_path = ROOT / "docs/visuals/visual-provenance.json" + if visual_path.is_file(): + visual = json.loads(visual_path.read_text(encoding="utf-8")) + visual["brand_edition"] = "lattice-prism-1" + for item in visual.get("visuals", []): + source = ROOT / str(item.get("file", "")) + if source.is_file(): + item["source_file_sha256"] = digest(source) + visual_path.write_text(json.dumps(visual, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + content_path = ROOT / "docs/content-provenance.json" + if content_path.is_file(): + data = json.loads(content_path.read_text(encoding="utf-8")) + data["brand_edition"] = "lattice-prism-1" + content_path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + # The old filenames remain as stable repository paths; their visible titles and + # metadata now carry Lattice/Prism. Remove this one-shot applicator and workflow. + shutil.rmtree(ROOT / "scripts/.prism_rebrand_unused", ignore_errors=True) + (ROOT / "scripts/.apply_prism_gemstone_rebrand.py").unlink(missing_ok=True) + (ROOT / ".github/workflows/apply-prism-gemstone-rebrand.yml").unlink(missing_ok=True) + print(json.dumps({"status": "APPLIED", "touched": touched}, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 1acb88ea83e8681c5fab415dcf89da9d3e5f9f86 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:03:07 -0700 Subject: [PATCH 31/37] chore: run Prism gemstone rebrand applicator --- .../apply-prism-gemstone-rebrand.yml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/apply-prism-gemstone-rebrand.yml diff --git a/.github/workflows/apply-prism-gemstone-rebrand.yml b/.github/workflows/apply-prism-gemstone-rebrand.yml new file mode 100644 index 00000000..3a5ee4ba --- /dev/null +++ b/.github/workflows/apply-prism-gemstone-rebrand.yml @@ -0,0 +1,59 @@ +name: Apply Prism gemstone rebrand + +on: + push: + branches: + - work/elenchion-apparatus-brand + paths: + - scripts/.apply_prism_gemstone_rebrand.py + - .github/workflows/apply-prism-gemstone-rebrand.yml + +permissions: + contents: write + +concurrency: + group: apply-prism-gemstone-rebrand + cancel-in-progress: false + +jobs: + apply: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: work/elenchion-apparatus-brand + fetch-depth: 0 + + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + + - name: Apply rebrand + run: python scripts/.apply_prism_gemstone_rebrand.py + + - name: Validate changed public artifacts + run: | + python -m json.tool docs/brand/brand-system.json >/dev/null + python -m json.tool docs/brand/asset-manifest.json >/dev/null + python -m json.tool docs/content-provenance.json >/dev/null + python -m json.tool docs/visuals/visual-provenance.json >/dev/null + python - <<'PY' + from xml.etree import ElementTree as ET + ET.parse('docs/visuals/apparatus-frontispiece.svg') + ET.parse('docs/visuals/elenchion-mark.svg') + PY + node --check docs/system-field.js + git diff --check -- . ':(exclude)README.md' + + - name: Commit final rebrand + env: + BRANCH: work/elenchion-apparatus-brand + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + test ! -e scripts/.apply_prism_gemstone_rebrand.py + test ! -e .github/workflows/apply-prism-gemstone-rebrand.yml + git commit -m "brand: rename public system to Lattice Prism gemstone architecture" + git push origin "HEAD:${BRANCH}" From b5a35b49d1fc8c3b9ecd1f8ae51a6109619e859f Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:03:37 -0700 Subject: [PATCH 32/37] chore: allow intentional Markdown hard breaks in Prism brand note --- .github/workflows/apply-prism-gemstone-rebrand.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/apply-prism-gemstone-rebrand.yml b/.github/workflows/apply-prism-gemstone-rebrand.yml index 3a5ee4ba..f26a98db 100644 --- a/.github/workflows/apply-prism-gemstone-rebrand.yml +++ b/.github/workflows/apply-prism-gemstone-rebrand.yml @@ -44,7 +44,7 @@ jobs: ET.parse('docs/visuals/elenchion-mark.svg') PY node --check docs/system-field.js - git diff --check -- . ':(exclude)README.md' + git diff --check -- . ':(exclude)README.md' ':(exclude)docs/brand/README.md' - name: Commit final rebrand env: From 6863ddd72a51eaf28c01a1dd93a99f483cae9d5b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 18:03:50 +0000 Subject: [PATCH 33/37] brand: rename public system to Lattice Prism gemstone architecture --- .../apply-prism-gemstone-rebrand.yml | 59 ------- THIRD_PARTY_NOTICES.md | 6 +- docs/brand/BRAND_SYSTEM.md | 24 +-- docs/brand/README.md | 8 +- docs/brand/SOURCE_PACKAGE.md | 4 +- docs/brand/asset-manifest.json | 53 +++--- docs/brand/brand-system.json | 123 +++++++++++-- docs/content-provenance.json | 36 ++-- docs/index.html | 128 +++++++------- docs/system-field.js | 16 +- docs/visuals/APPARATUS_ASSETS.md | 4 +- docs/visuals/apparatus-frontispiece.svg | 10 +- docs/visuals/elenchion-mark.svg | 4 +- docs/visuals/visual-provenance.json | 8 +- scripts/.apply_prism_gemstone_rebrand.py | 161 ------------------ validation/validate_soul_gauntlet_public.py | 14 +- 16 files changed, 270 insertions(+), 388 deletions(-) delete mode 100644 .github/workflows/apply-prism-gemstone-rebrand.yml delete mode 100644 scripts/.apply_prism_gemstone_rebrand.py diff --git a/.github/workflows/apply-prism-gemstone-rebrand.yml b/.github/workflows/apply-prism-gemstone-rebrand.yml deleted file mode 100644 index f26a98db..00000000 --- a/.github/workflows/apply-prism-gemstone-rebrand.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Apply Prism gemstone rebrand - -on: - push: - branches: - - work/elenchion-apparatus-brand - paths: - - scripts/.apply_prism_gemstone_rebrand.py - - .github/workflows/apply-prism-gemstone-rebrand.yml - -permissions: - contents: write - -concurrency: - group: apply-prism-gemstone-rebrand - cancel-in-progress: false - -jobs: - apply: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: work/elenchion-apparatus-brand - fetch-depth: 0 - - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.12' - - - name: Apply rebrand - run: python scripts/.apply_prism_gemstone_rebrand.py - - - name: Validate changed public artifacts - run: | - python -m json.tool docs/brand/brand-system.json >/dev/null - python -m json.tool docs/brand/asset-manifest.json >/dev/null - python -m json.tool docs/content-provenance.json >/dev/null - python -m json.tool docs/visuals/visual-provenance.json >/dev/null - python - <<'PY' - from xml.etree import ElementTree as ET - ET.parse('docs/visuals/apparatus-frontispiece.svg') - ET.parse('docs/visuals/elenchion-mark.svg') - PY - node --check docs/system-field.js - git diff --check -- . ':(exclude)README.md' ':(exclude)docs/brand/README.md' - - - name: Commit final rebrand - env: - BRANCH: work/elenchion-apparatus-brand - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - test ! -e scripts/.apply_prism_gemstone_rebrand.py - test ! -e .github/workflows/apply-prism-gemstone-rebrand.yml - git commit -m "brand: rename public system to Lattice Prism gemstone architecture" - git push origin "HEAD:${BRANCH}" diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 1f9e25b0..081f87e4 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -12,12 +12,12 @@ This branch includes an exact pinned source snapshot of the upstream Hermes Agen | Adoption ledger | `third_party/HERMES_SOURCE_LEDGER.md` | | License notice | `third_party/HERMES_LICENSE.txt` | -The upstream MIT license and copyright notice must be retained in copies or substantial portions of the upstream software. Elenchion Systems, Apparatus, Mercury Runtime, and the other public aliases in this branch do not imply endorsement by Nous Research or the Hermes Agent maintainers. +The upstream MIT license and copyright notice must be retained in copies or substantial portions of the upstream software. Lattice, Prism, Quartz, and the other public aliases in this branch do not imply endorsement by Nous Research or the Hermes Agent maintainers. ## 2. Brand and visual assets -The initial Apparatus frontispiece, calibration mark, and local Canvas2D system field are project-authored assets recorded in `docs/brand/asset-manifest.json`. No museum-hosted or third-party historical image is committed by the branding change. +The initial Prism frontispiece, calibration mark, and local Canvas2D system field are project-authored assets recorded in `docs/brand/asset-manifest.json`. No museum-hosted or third-party historical image is committed by the branding change. ## 3. Working-name boundary -`Elenchion Systems`, `Apparatus`, `Mercury Runtime`, and the instrument aliases are working project identities. This notice does not assert corporate-name, domain, or trademark clearance. +`Lattice`, `Prism`, `Quartz`, and the instrument aliases are working project identities. This notice does not assert corporate-name, domain, or trademark clearance. diff --git a/docs/brand/BRAND_SYSTEM.md b/docs/brand/BRAND_SYSTEM.md index a40e8396..c2c23c2c 100644 --- a/docs/brand/BRAND_SYSTEM.md +++ b/docs/brand/BRAND_SYSTEM.md @@ -1,4 +1,4 @@ -# Apparatus Visual and Marketing System +# Prism Visual and Marketing System ## 1. North star @@ -41,17 +41,17 @@ No font files are committed by this brand change. The static site uses system-sa | Instrument | Visual family | Motif | |---|---|---| -| Axis | geometry / navigation | armillary rings, axes, obligation routes | -| Canon | geometry | compass construction, Euclidean figures, proof points | -| Atlas | botany / taxonomy | specimen stem, leaves, taxon labels, source scale | -| Crucible | morphology / engineering | transformations, mechanism delta, falsifier sequence | -| Forge | engineering | exploded gear train, source/build/entry/verify alignment | -| Chronometer | metrology | calibrated dial, baseline points, decision hand | -| Aegis | inspection | gauge, boundary marks, integrity ledger | -| Sextant | navigation | triangulation and value-of-computation bearing | -| Conclave | comparative specimen board | independent seats, reveal, off-diagonal critique | -| Parallax | optics | paired lenses, ray trace, gap between perspectives | -| Mercury Runtime | instrument cabinet | provider/tool/session mechanisms behind a hard authority boundary | +| Diamond | geometry / navigation | armillary rings, axes, obligation routes | +| Sapphire | geometry | compass construction, Euclidean figures, proof points | +| Emerald | botany / taxonomy | specimen stem, leaves, taxon labels, source scale | +| Ruby | morphology / engineering | transformations, mechanism delta, falsifier sequence | +| Garnet | engineering | exploded gear train, source/build/entry/verify alignment | +| Topaz | metrology | calibrated dial, baseline points, decision hand | +| Onyx | inspection | gauge, boundary marks, integrity ledger | +| Citrine | navigation | triangulation and value-of-computation bearing | +| Amethyst | comparative specimen board | independent seats, reveal, off-diagonal critique | +| Opal | optics | paired lenses, ray trace, gap between perspectives | +| Quartz | instrument cabinet | provider/tool/session mechanisms behind a hard authority boundary | | Obsidian | fracture / stress plate | attack axes, break triples, participation marks | ## 5. Evidence boundary diff --git a/docs/brand/README.md b/docs/brand/README.md index d1ccb3a7..ffa811d8 100644 --- a/docs/brand/README.md +++ b/docs/brand/README.md @@ -1,9 +1,9 @@ -# Apparatus Brand System +# Prism Brand System -**Organization:** Elenchion Systems -**Product suite:** Apparatus +**Organization:** Lattice +**Product suite:** Prism **Product principle:** Evidence before authority -**Brand edition:** `apparatus-brand-01` +**Brand edition:** `prism-brand-01` **Status:** working public identity; technical compatibility preserved; legal name/trademark clearance not performed ## File index diff --git a/docs/brand/SOURCE_PACKAGE.md b/docs/brand/SOURCE_PACKAGE.md index a6406022..5c85ce2b 100644 --- a/docs/brand/SOURCE_PACKAGE.md +++ b/docs/brand/SOURCE_PACKAGE.md @@ -27,10 +27,10 @@ The package included research notes, a visual system, GitHub and website specifi ## Deliberate adaptations -- The old product identity was replaced by Elenchion Systems / Apparatus. +- The old product identity was replaced by Lattice / Prism. - The five “Stones/Gems” became claim-native Instruments. - Soul, Gauntlet, Council, Meditate, and Mirror received new public instrument names while technical aliases remain. -- The Hermes-derived host is described as Mercury Runtime and given an explicit alpha/observation-only boundary. +- The Hermes-derived host is described as Quartz and given an explicit alpha/observation-only boundary. - Black Gem is presented as Obsidian. - The archived mechanism-planner and formal-plane packages are presented as candidates, not shipped authority. - Original SVG and Canvas2D artwork replaced external historical imagery for the initial implementation, avoiding unresolved item-level rights and provenance. diff --git a/docs/brand/asset-manifest.json b/docs/brand/asset-manifest.json index 526dae82..6a567240 100644 --- a/docs/brand/asset-manifest.json +++ b/docs/brand/asset-manifest.json @@ -1,7 +1,7 @@ [ { - "asset_id": "APPARATUS-ASSET-001", - "title": "Apparatus Classical-Scientific Frontispiece", + "asset_id": "PRISM-ASSET-001", + "title": "Prism Classical-Scientific Frontispiece", "creator": "Project-authored with AI-assisted vector generation under project-owner direction", "date": "2026-09-03", "source_institution": "Kitahl/The-Gauntlet project", @@ -12,21 +12,27 @@ "license_id": "PROJECT-ORIGINAL", "commercial_use_allowed": true, "rights_checked_on": "2026-09-03", - "source_file_sha256": "4b91a1cc0fd8b713aca27a5fbd3555562b2392a729ab4efc7c461661bb1d14a8", + "source_file_sha256": "6127e5b093cb5f8a7c9950870d76d72fd8e0b93777a7dae99fbee3dfcc3cecfa", "transformations": [], - "production_files": ["docs/visuals/apparatus-frontispiece.svg"], - "credit_line": "Apparatus frontispiece — Kitahl/The-Gauntlet project, 2026", + "production_files": [ + "docs/visuals/apparatus-frontispiece.svg" + ], + "credit_line": "Prism frontispiece — Kitahl/The-Gauntlet project, 2026", "endorsement_disclaimer_required": true, "review_status": "APPROVED_PROJECT_ORIGINAL", "asset_type": "svg-frontispiece", - "use": ["README hero", "GitHub social-preview source", "website brand reference"], - "alt_text": "Parchment scientific frontispiece with an original measured classical profile, armillary rings, a botanical specimen, and the Apparatus identity.", + "use": [ + "README hero", + "GitHub social-preview source", + "website brand reference" + ], + "alt_text": "Parchment scientific frontispiece with an original measured classical profile, armillary rings, a botanical specimen, and the Prism identity.", "evidence_boundary": "Brand image only. It is not an execution trace, receipt, benchmark result, historical artifact, or proof of product efficacy.", "external_sources": [] }, { - "asset_id": "APPARATUS-ASSET-002", - "title": "Elenchion Systems Calibration Mark", + "asset_id": "PRISM-ASSET-002", + "title": "Lattice Calibration Mark", "creator": "Project-authored with AI-assisted vector generation under project-owner direction", "date": "2026-09-03", "source_institution": "Kitahl/The-Gauntlet project", @@ -37,21 +43,26 @@ "license_id": "PROJECT-ORIGINAL", "commercial_use_allowed": true, "rights_checked_on": "2026-09-03", - "source_file_sha256": "655878c5c97c2bba9328e5031cc692ec4034aa92d89b7749ea693f20f45e3b1c", + "source_file_sha256": "e04d2a15df09d5a97f80401a2212faf58e7e338514f16dc110130ab0a6d501c1", "transformations": [], - "production_files": ["docs/visuals/elenchion-mark.svg"], - "credit_line": "Elenchion Systems calibration mark — Kitahl/The-Gauntlet project, 2026", + "production_files": [ + "docs/visuals/elenchion-mark.svg" + ], + "credit_line": "Lattice calibration mark — Kitahl/The-Gauntlet project, 2026", "endorsement_disclaimer_required": true, "review_status": "APPROVED_PROJECT_ORIGINAL", "asset_type": "svg-brand-mark", - "use": ["website brand mark", "documentation identity"], + "use": [ + "website brand mark", + "documentation identity" + ], "alt_text": "Circular calibration mark formed by instrument arcs, a central axis, and one oxide registration tick.", "evidence_boundary": "Working brand mark only. It does not establish legal trademark clearance, institutional status, or product efficacy.", "external_sources": [] }, { - "asset_id": "APPARATUS-ASSET-003", - "title": "Apparatus Semantic Instrument Field", + "asset_id": "PRISM-ASSET-003", + "title": "Prism Semantic Instrument Field", "creator": "Project-authored with AI-assisted Canvas2D generation under project-owner direction", "date": "2026-09-03", "source_institution": "Kitahl/The-Gauntlet project", @@ -62,14 +73,18 @@ "license_id": "MIT-PROJECT-CODE", "commercial_use_allowed": true, "rights_checked_on": "2026-09-03", - "source_file_sha256": "0b415e0771cc24c4d95ec4d6c87a2be1e0a65d3fbe210402431238cb4a5ba68f", + "source_file_sha256": "36d0ba135999bb17cc809783e04c02705046e2ea3db2f6fa8809518d872cf9b8", "transformations": [], - "production_files": ["docs/system-field.js"], - "credit_line": "Apparatus semantic instrument field — Kitahl/The-Gauntlet project, 2026", + "production_files": [ + "docs/system-field.js" + ], + "credit_line": "Prism semantic instrument field — Kitahl/The-Gauntlet project, 2026", "endorsement_disclaimer_required": true, "review_status": "APPROVED_PROJECT_ORIGINAL", "asset_type": "canvas2d-visualization", - "use": ["GitHub Pages interactive background"], + "use": [ + "GitHub Pages interactive background" + ], "alt_text": "Decorative procedural scientific instrument field that changes among armillary, botanical, geometric, engineering, and calibration motifs.", "evidence_boundary": "Conceptual architecture illustration only. It is not a live system monitor, receipt, benchmark result, or authority surface.", "external_sources": [] diff --git a/docs/brand/brand-system.json b/docs/brand/brand-system.json index 427797bf..7f8facd3 100644 --- a/docs/brand/brand-system.json +++ b/docs/brand/brand-system.json @@ -29,29 +29,116 @@ "white": "#FCFAF4" }, "typography": { - "editorial": ["Source Serif 4", "Georgia", "Times New Roman", "serif"], - "technical": ["IBM Plex Sans", "system-ui", "Segoe UI", "sans-serif"], - "receipt": ["IBM Plex Mono", "ui-monospace", "SFMono-Regular", "monospace"] + "editorial": [ + "Source Serif 4", + "Georgia", + "Times New Roman", + "serif" + ], + "technical": [ + "IBM Plex Sans", + "system-ui", + "Segoe UI", + "sans-serif" + ], + "receipt": [ + "IBM Plex Mono", + "ui-monospace", + "SFMono-Regular", + "monospace" + ] }, "core_instruments": [ - {"public_name":"Diamond","technical_id":"soul","command":"/soul","role":"orchestration and release"}, - {"public_name":"Sapphire","technical_id":"mathbot","command":"/mind","role":"formal reasoning"}, - {"public_name":"Emerald","technical_id":"scoutbot","command":"/space","role":"research discovery"}, - {"public_name":"Ruby","technical_id":"novelbot","command":"/reality","role":"method synthesis"}, - {"public_name":"Garnet","technical_id":"codebot","command":"/power","role":"engineering verification"}, - {"public_name":"Topaz","technical_id":"benchbot","command":"/time","role":"evaluation and benchmarking"}, - {"public_name":"Onyx","technical_id":"infinity-gauntlet","command":"/gauntlet","role":"process assurance"}, - {"public_name":"Citrine","technical_id":"meditate","command":"/meditate","role":"decision preflight"}, - {"public_name":"Amethyst","technical_id":"council-of-elders","command":"/council","role":"independent evidence review"}, - {"public_name":"Opal","technical_id":"foil","command":"/foil","role":"adaptive reasoning complement"} + { + "public_name": "Diamond", + "technical_id": "soul", + "command": "/soul", + "role": "orchestration and release" + }, + { + "public_name": "Sapphire", + "technical_id": "mathbot", + "command": "/mind", + "role": "formal reasoning" + }, + { + "public_name": "Emerald", + "technical_id": "scoutbot", + "command": "/space", + "role": "research discovery" + }, + { + "public_name": "Ruby", + "technical_id": "novelbot", + "command": "/reality", + "role": "method synthesis" + }, + { + "public_name": "Garnet", + "technical_id": "codebot", + "command": "/power", + "role": "engineering verification" + }, + { + "public_name": "Topaz", + "technical_id": "benchbot", + "command": "/time", + "role": "evaluation and benchmarking" + }, + { + "public_name": "Onyx", + "technical_id": "infinity-gauntlet", + "command": "/gauntlet", + "role": "process assurance" + }, + { + "public_name": "Citrine", + "technical_id": "meditate", + "command": "/meditate", + "role": "decision preflight" + }, + { + "public_name": "Amethyst", + "technical_id": "council-of-elders", + "command": "/council", + "role": "independent evidence review" + }, + { + "public_name": "Opal", + "technical_id": "foil", + "command": "/foil", + "role": "adaptive reasoning complement" + } ], "additional_systems": [ - {"public_name":"Quartz","technical_id":"gauntlet_host","status":"INTERIM_ALPHA_OBSERVATION_ONLY"}, - {"public_name":"Obsidian","technical_id":"blackgem","status":"ADVERSARIAL_NEVER_CLEARS"}, - {"public_name":"Moonstone Candidate","technical_id":"postbench mechanism-planner candidate","status":"ENGINEERING_CANDIDATE_NOT_PROMOTED"}, - {"public_name":"Zircon Candidate","technical_id":"postbench Math Foundry candidate","status":"ENGINEERING_CANDIDATE_NOT_PROMOTED"} + { + "public_name": "Quartz", + "technical_id": "gauntlet_host", + "status": "INTERIM_ALPHA_OBSERVATION_ONLY" + }, + { + "public_name": "Obsidian", + "technical_id": "blackgem", + "status": "ADVERSARIAL_NEVER_CLEARS" + }, + { + "public_name": "Moonstone Candidate", + "technical_id": "postbench mechanism-planner candidate", + "status": "ENGINEERING_CANDIDATE_NOT_PROMOTED" + }, + { + "public_name": "Zircon Candidate", + "technical_id": "postbench Math Foundry candidate", + "status": "ENGINEERING_CANDIDATE_NOT_PROMOTED" + } + ], + "status_vocabulary": [ + "VERIFIED_REPOSITORY_FACT", + "DESIGN_DECISION", + "EXPLORATORY_EVIDENCE", + "UNVERIFIED", + "PROHIBITED" ], - "status_vocabulary": ["VERIFIED_REPOSITORY_FACT", "DESIGN_DECISION", "EXPLORATORY_EVIDENCE", "UNVERIFIED", "PROHIBITED"], "evidence_boundary": "Brand imagery, gemstone names, and historical cues never add evidential weight to technical or scientific claims.", "source_package_sha256": "2232a3b18ee2c1ffeb2cf670f6018dc7129d4668db18ed8eef0ae767de2f22c5" } diff --git a/docs/content-provenance.json b/docs/content-provenance.json index 0990e662..5f2fa0d5 100644 --- a/docs/content-provenance.json +++ b/docs/content-provenance.json @@ -1,12 +1,12 @@ { "schema": "egrt/showcase-provenance-2", - "brand_edition": "elenchion-apparatus-1", + "brand_edition": "lattice-prism-1", "generated_on": "2026-09-03", "claims": [ { "id": "public-identity-working-decision", "page_section": "Identity", - "claim": "Elenchion Systems and Apparatus are working public identities adopted by this branch; no corporate-name, trademark, domain, or legal clearance is claimed.", + "claim": "Lattice and Prism are working public identities adopted by this branch; no corporate-name, trademark, domain, or legal clearance is claimed.", "status": "DESIGN_DECISION_WITH_UNVERIFIED_LEGAL_CLEARANCE", "sources": [ "docs/brand/NAMING_ARCHITECTURE.md", @@ -28,9 +28,9 @@ ] }, { - "id": "apparatus-modular-control", + "id": "prism-modular-control", "page_section": "System", - "claim": "Apparatus publicly groups one orchestrator, five claim-native research instruments, four control/review instruments, and additional bounded systems while retaining the existing implementation boundaries.", + "claim": "Prism publicly groups one orchestrator, five claim-native research instruments, four control/review instruments, and additional bounded systems while retaining the existing implementation boundaries.", "status": "DESIGN_DECISION_MAPPED_TO_REPOSITORY_FACTS", "sources": [ "docs/brand/NAMING_ARCHITECTURE.md", @@ -48,9 +48,9 @@ ] }, { - "id": "mercury-pinned-runtime", - "page_section": "Mercury Runtime", - "claim": "The Mercury public layer corresponds to the Gauntlet-owned host around an exact pinned Hermes Agent source snapshot, isolated through parent/worker process and JSONL boundaries.", + "id": "quartz-pinned-runtime", + "page_section": "Quartz", + "claim": "The Quartz public layer corresponds to the Gauntlet-owned host around an exact pinned Hermes Agent source snapshot, isolated through parent/worker process and JSONL boundaries.", "status": "VERIFIED_REPOSITORY_FACT", "sources": [ "vendor/HERMES_SNAPSHOT.json", @@ -63,8 +63,8 @@ ] }, { - "id": "mercury-checkpoint-eight", - "page_section": "Mercury Runtime", + "id": "quartz-checkpoint-eight", + "page_section": "Quartz", "claim": "The recorded FAST-P8 checkpoint reports eight of eight bounded alpha boot checks passed.", "status": "VERIFIED_REPOSITORY_FACT", "sources": [ @@ -88,8 +88,8 @@ ] }, { - "id": "mercury-limitations-visible", - "page_section": "Mercury Runtime", + "id": "quartz-limitations-visible", + "page_section": "Quartz", "claim": "FAST-P8 did not establish paid external-provider operation, autonomous claim-native execution, autonomous replanning, task release, dynamic tool narrowing, profile complement benefit, cross-platform qualification, or behavioral and cost efficacy.", "status": "VERIFIED_NEGATIVE_BOUNDARY", "sources": [ @@ -98,9 +98,9 @@ ] }, { - "id": "parallax-adaptation-only", - "page_section": "Parallax", - "claim": "Parallax is the public name for the existing Mirror/FOIL adaptive complement layer; its route is advisory and cannot clear proof, discovery, engineering, evaluation, or release obligations.", + "id": "opal-adaptation-only", + "page_section": "Opal", + "claim": "Opal is the public name for the existing Mirror/FOIL adaptive complement layer; its route is advisory and cannot clear proof, discovery, engineering, evaluation, or release obligations.", "status": "VERIFIED_REPOSITORY_FACT_PLUS_PUBLIC_ALIAS", "sources": [ "docs/brand/NAMING_ARCHITECTURE.md", @@ -111,9 +111,9 @@ ] }, { - "id": "conclave-independent-review", - "page_section": "Conclave", - "claim": "Conclave maps to the Council protocol for independent seats, commitment/reveal, cross-critique, disagreement preservation, and controlled synthesis.", + "id": "amethyst-independent-review", + "page_section": "Amethyst", + "claim": "Amethyst maps to the Council protocol for independent seats, commitment/reveal, cross-critique, disagreement preservation, and controlled synthesis.", "status": "VERIFIED_REPOSITORY_FACT_PLUS_PUBLIC_ALIAS", "sources": [ "docs/brand/NAMING_ARCHITECTURE.md", @@ -136,7 +136,7 @@ { "id": "candidate-archive-no-promotion", "page_section": "Candidate Systems", - "claim": "The Strategist and Formal Plane public labels refer to archived Mastermind and Math Foundry engineering candidates; their archive does not promote them into Apparatus runtime or evidential authority.", + "claim": "The Strategist and Formal Plane public labels refer to archived Mastermind and Math Foundry engineering candidates; their archive does not promote them into Prism runtime or evidential authority.", "status": "VERIFIED_REPOSITORY_FACT_PLUS_PUBLIC_ALIAS", "sources": [ "docs/brand/NAMING_ARCHITECTURE.md", diff --git a/docs/index.html b/docs/index.html index c186228a..c8324f72 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,21 +3,21 @@ - + - - + + - - Apparatus — Elenchion Systems + + Prism — Lattice - +
- + - ELENCHION SYSTEMSAPPARATUS + LATTICEPRISM
-
01

Frame

Axis translates a goal into explicit tasks, claims, and load-bearing obligations.

-
02

Route

Parallax identifies the least-covered capability; Axis selects the claim-native instrument.

-
03

Observe

Mercury runs models, tools, MCP services, sessions, memory, and delegated operational work.

-
04

Verify

Canon, Atlas, Crucible, Forge, or Chronometer returns evidence with an explicit scope.

-
05

Challenge

Aegis, Conclave, and Obsidian attack false greens, weak independence, and hidden defects.

-
06

Release

Axis reads the latest integrity-valid receipts and returns CLEARED, ISSUE, UNKNOWN, or UNAVAILABLE.

+
01

Frame

Diamond translates a goal into explicit tasks, claims, and load-bearing obligations.

+
02

Route

Opal identifies the least-covered capability; Diamond selects the claim-native instrument.

+
03

Observe

Quartz runs models, tools, MCP services, sessions, memory, and delegated operational work.

+
04

Verify

Sapphire, Emerald, Ruby, Garnet, or Topaz returns evidence with an explicit scope.

+
05

Challenge

Onyx, Amethyst, and Obsidian attack false greens, weak independence, and hidden defects.

+
06

Release

Diamond reads the latest integrity-valid receipts and returns CLEARED, ISSUE, UNKNOWN, or UNAVAILABLE.

@@ -93,14 +93,14 @@

A research system built like an inspection bench.

ROUTE PLATES / FIVE EXAMPLES

The route changes with the obligation.

-

No single model or method owns every question. Apparatus selects a bounded chain according to what must be proved, searched, built, measured, reviewed, or left unresolved.

+

No single model or method owns every question. Prism selects a bounded chain according to what must be proved, searched, built, measured, reviewed, or left unresolved.

-
Novel mechanism

Is this new, buildable, and decision-relevant?

Atlas Crucible Forge Chronometer

-
Formal claim

Can the statement be proved or broken?

Canon Conclave

-
Software release

The tests are green. What can still be false?

Forge Aegis Obsidian

-
Consequential decision

Which uncertainty is worth resolving first?

Sextant Axis Chronometer

-
Adaptive assistance

Which capability is least covered for this task?

Parallax Relevant instrument

+
Novel mechanism

Is this new, buildable, and decision-relevant?

Emerald Ruby Garnet Topaz

+
Formal claim

Can the statement be proved or broken?

Sapphire Amethyst

+
Software release

The tests are green. What can still be false?

Garnet Onyx Obsidian

+
Consequential decision

Which uncertainty is worth resolving first?

Citrine Diamond Topaz

+
Adaptive assistance

Which capability is least covered for this task?

Opal Relevant instrument

@@ -108,10 +108,10 @@

The route changes with the obligation.

-

APPARATUS / OPERATIONAL LAYER

-

Mercury Runtime

+

PRISM / OPERATIONAL LAYER

+

Quartz

Hermes-quality operational mechanisms, contained behind a Gauntlet-owned authority boundary.

-

Mercury is the public name for the interim runtime in gauntlet_host/. It runs the exact pinned MIT-licensed Hermes Agent source as an isolated internal worker. The parent process owns task identity, observation binding, finalization, and the release gate.

+

Quartz is the public name for the interim runtime in gauntlet_host/. It runs the exact pinned MIT-licensed Hermes Agent source as an isolated internal worker. The parent process owns task identity, observation binding, finalization, and the release gate.

Read runtime checkpoint Inspect source ledger @@ -127,7 +127,7 @@

Mercury Runtime

Retry + delegation
Inherited mechanisms
Task identity
Explicit and repository-bound
Evidence authority
Excluded from runtime
-
Release authority
Parent-owned Axis gate
+
Release authority
Parent-owned Diamond gate
BOUNDARYNo automatic claim-native execution, autonomous replanning, task release, external-provider qualification, or demonstrated cost benefit.
@@ -147,55 +147,55 @@

Different evidence requires different machinery.

INSTRUMENT 01mathbot / /mind
-

CANON

+

SAPPHIRE

Formal Reasoning

Translate a claim into explicit objects, assumptions, a proof obligation, a negation, and a verifier that speaks the claim’s native language.

Use
Theorems, derivations, exact arithmetic, formal specifications, probability, counterexamples.
Returns
Proof, measured result, cited theorem, counterexample, or an unresolved obligation.
- Inspect Canon’s technical contract ↗ + Inspect Sapphire’s technical contract ↗
INSTRUMENT 02scoutbot / /space
-

ATLAS

+

EMERALD

Research Discovery

Map the evidence landscape before novelty, current-state, reuse, or attribution claims are allowed to harden.

Use
Literature, standards, prior art, repositories, current technical facts, reusable software.
Returns
Source set, nearest established class, concrete differentiator, provenance, and search limits.
- Inspect Atlas’s technical contract ↗ + Inspect Emerald’s technical contract ↗
INSTRUMENT 03novelbot / /reality
-

CRUCIBLE

+

RUBY

Method Synthesis

Invent only after the nearest established methods fail a specific, named constraint and the delta can be tested.

Use
New algorithms, mechanisms, protocols, or research methods after a verified gap.
Returns
Candidate mechanism, assumptions, invariants, failure modes, negative control, ablation, and verifier plan.
- Inspect Crucible’s technical contract ↗ + Inspect Ruby’s technical contract ↗
INSTRUMENT 04codebot / /power
-

FORGE

+

GARNET

Engineering Verification

Move software claims through the real source boundary, real implementation, real entry point, and the defect classes that could falsify them.

Use
Architecture, implementation, debugging, integration, regression, executable verification.
Returns
Executed checks, defect-class coverage, output hashes, environment, and untested limits.
- Inspect Forge’s technical contract ↗ + Inspect Garnet’s technical contract ↗
INSTRUMENT 05benchbot / /time
-

CHRONOMETER

+

TOPAZ

Evaluation & Benchmarking

Make candidate and baseline compete under the comparison, exclusions, uncertainty model, and stopping rule that answer the actual decision.

Use
Experiment design, matched baselines, ablations, contamination control, cost, stop/go.
Returns
Matched estimate, uncertainty, failures, exclusions, and the bounded decision consequence.
- Inspect Chronometer’s technical contract ↗ + Inspect Topaz’s technical contract ↗
@@ -203,9 +203,9 @@

Evaluation & Benchmarking

AUTHORITY PATH / ONE CONTROL MODEL

Operational intelligence. Evidential restraint.

-

Mercury can execute. Parallax can advise. The instruments can generate claim-scoped receipts. Aegis and the review systems can identify defects. Only Axis governs final release.

+

Quartz can execute. Opal can advise. The instruments can generate claim-scoped receipts. Onyx and the review systems can identify defects. Only Diamond governs final release.

-

TaskObligationsRuntime observationsClaim-native receiptsAssuranceScoped release

+

TaskObligationsRuntime observationsClaim-native receiptsAssuranceScoped release

Non-negotiable invariant

A model may select or propose an action. It may not declare that its own action established truth.

@@ -221,25 +221,25 @@

Complete instrument catalogue.

-
Axis · Orchestration & Release/soulFrame complex work and govern its evidence state.

Professional use

Multi-stage research and engineering tasks that need explicit ownership, integration, and a fail-closed release decision.

How it works

Hashes the goal, creates typed obligations, routes the minimum specialist chain, accepts only valid latest receipts, and returns a scoped verdict.

+
Diamond · Orchestration & Release/soulFrame complex work and govern its evidence state.

Professional use

Multi-stage research and engineering tasks that need explicit ownership, integration, and a fail-closed release decision.

How it works

Hashes the goal, creates typed obligations, routes the minimum specialist chain, accepts only valid latest receipts, and returns a scoped verdict.

-
Canon · Formal Reasoning/mindProof, logic, probability, and counterexamples.

Professional use

Theorem checking, mathematical derivations, formal specifications, probabilistic reasoning, and exact counterexample search.

How it works

Defines objects and assumptions, states the claim and negation, selects a native verifier, attacks edge cases, and reports the verified scope.

+
Sapphire · Formal Reasoning/mindProof, logic, probability, and counterexamples.

Professional use

Theorem checking, mathematical derivations, formal specifications, probabilistic reasoning, and exact counterexample search.

How it works

Defines objects and assumptions, states the claim and negation, selects a native verifier, attacks edge cases, and reports the verified scope.

-
Atlas · Research Discovery/spaceLiterature, prior art, repositories, and standards.

Professional use

Literature reviews, prior-art searches, technical due diligence, current-source verification, and reusable implementation discovery.

How it works

Translates mechanisms into search terms, expands across neighboring fields, inspects primary sources and repositories, deduplicates identities, and records limits.

+
Emerald · Research Discovery/spaceLiterature, prior art, repositories, and standards.

Professional use

Literature reviews, prior-art searches, technical due diligence, current-source verification, and reusable implementation discovery.

How it works

Translates mechanisms into search terms, expands across neighboring fields, inspects primary sources and repositories, deduplicates identities, and records limits.

-
Crucible · Method Synthesis/realityConstruct mechanisms after a verified constraint gap.

Professional use

New methods, algorithms, protocols, and architectures when established approaches fail a named, evidence-backed constraint.

How it works

Requires the prior-art delta, binds the mechanism to the gap, enumerates assumptions and failures, and specifies controls, ablations, and verification.

+
Ruby · Method Synthesis/realityConstruct mechanisms after a verified constraint gap.

Professional use

New methods, algorithms, protocols, and architectures when established approaches fail a named, evidence-backed constraint.

How it works

Requires the prior-art delta, binds the mechanism to the gap, enumerates assumptions and failures, and specifies controls, ablations, and verification.

-
Forge · Engineering Verification/powerArchitecture, implementation, execution, and regression.

Professional use

Building, integrating, debugging, and qualifying software against its real source, environment, entry points, and defect classes.

How it works

Creates a typed verification plan, executes bounded commands without shell interpolation, records hashes and environment, and separates checked from untested behavior.

+
Garnet · Engineering Verification/powerArchitecture, implementation, execution, and regression.

Professional use

Building, integrating, debugging, and qualifying software against its real source, environment, entry points, and defect classes.

How it works

Creates a typed verification plan, executes bounded commands without shell interpolation, records hashes and environment, and separates checked from untested behavior.

-
Chronometer · Evaluation & Benchmarking/timeBaselines, ablations, uncertainty, cost, and stop/go.

Professional use

Matched comparisons, experimental design, benchmark qualification, contamination handling, uncertainty analysis, and cost-aware decisions.

How it works

Freezes arms and exclusions, chooses a matched estimator, preserves null and negative results, corrects multiplicity where required, and binds results to a decision.

+
Topaz · Evaluation & Benchmarking/timeBaselines, ablations, uncertainty, cost, and stop/go.

Professional use

Matched comparisons, experimental design, benchmark qualification, contamination handling, uncertainty analysis, and cost-aware decisions.

How it works

Freezes arms and exclusions, chooses a matched estimator, preserves null and negative results, corrects multiplicity where required, and binds results to a decision.

-
Aegis · Process Assurance/gauntletFalse-green, stale-state, scope, and integrity defense.

Professional use

Auditing a research or engineering process when passing tests, repeated claims, inherited numbers, or stale authority may conceal a defect.

How it works

Records typed events, checks governing-state freshness and ledger integrity, distinguishes framing from cosmetic process, and raises bounded findings.

+
Onyx · Process Assurance/gauntletFalse-green, stale-state, scope, and integrity defense.

Professional use

Auditing a research or engineering process when passing tests, repeated claims, inherited numbers, or stale authority may conceal a defect.

How it works

Records typed events, checks governing-state freshness and ledger integrity, distinguishes framing from cosmetic process, and raises bounded findings.

-
Sextant · Decision Preflight/meditateFacts, assumptions, unknowns, blockers, and next action.

Professional use

Consequential choices, post-failure resets, and stalled projects where premature execution would harden an unsupported direction.

How it works

Separates facts from assumptions, records unknowns and options, identifies the binding blocker, and estimates whether more computation is worth its cost.

+
Citrine · Decision Preflight/meditateFacts, assumptions, unknowns, blockers, and next action.

Professional use

Consequential choices, post-failure resets, and stalled projects where premature execution would harden an unsupported direction.

How it works

Separates facts from assumptions, records unknowns and options, identifies the binding blocker, and estimates whether more computation is worth its cost.

-
Conclave · Evidence Review Panel/councilIndependent commitments, critique, and synthesis.

Professional use

High-stakes review where one model, one reviewer, or untracked consensus would provide weak independence.

How it works

Freezes the artifact and budget, assigns independent seats including a skeptic, commits before reveal, cross-critiques off diagonal, and preserves disagreement.

+
Amethyst · Evidence Review Panel/councilIndependent commitments, critique, and synthesis.

Professional use

High-stakes review where one model, one reviewer, or untracked consensus would provide weak independence.

How it works

Freezes the artifact and budget, assigns independent seats including a skeptic, commits before reveal, cross-critiques off diagonal, and preserves disagreement.

-
Parallax · Adaptive Reasoning Complement/foilFind the task capability least covered by the user.

Professional use

Adaptive work where assistance should complement the user’s demonstrated coverage instead of replacing judgment or repeating existing strengths.

How it works

Uses conservative evidence tiers, task facets, structured calibration, and transfer history to request the smallest useful complement while remaining advisory.

+
Opal · Adaptive Reasoning Complement/foilFind the task capability least covered by the user.

Professional use

Adaptive work where assistance should complement the user’s demonstrated coverage instead of replacing judgment or repeating existing strengths.

How it works

Uses conservative evidence tiers, task facets, structured calibration, and transfer history to request the smallest useful complement while remaining advisory.

@@ -256,7 +256,7 @@

Complete instrument catalogue.

These components extend execution, attack, or research exploration. Their status is stated separately from the ten core contracts.

-
OPERATIONAL

Mercury Runtime

Provider, tool, MCP, context, session, memory, skill, retry, and delegation mechanisms behind an observation-only boundary.

gauntlet_host/
+
OPERATIONAL

Quartz

Provider, tool, MCP, context, session, memory, skill, retry, and delegation mechanisms behind an observation-only boundary.

gauntlet_host/
ADVERSARIAL

Obsidian

Multi-seat attack, participation accounting, cross-critique, and structured break triples. It can raise an issue and can never clear a claim.

blackgem
CANDIDATE

Strategist

Archived mechanism-planning hardening with exact minimum successful repair over a declared finite repair universe. Not promoted into runtime authority.

postbench candidate 2
CANDIDATE

Formal Plane

Archived mathematical execution hardening for trusted-base minimality, process isolation, cleanup, and deterministic qualification.

postbench candidate 2
@@ -273,11 +273,11 @@

Complete instrument catalogue.

ClaimStateInspection path
-
Bounded Mercury alpha boot route works without a false clear8 / 8 RECORDEDCheckpoint ↗
+
Bounded Quartz alpha boot route works without a false clear8 / 8 RECORDEDCheckpoint ↗
Core contracts, state, receipts, hooks, and validators existMECHANICALLY CHECKEDValidation ↗
Exploratory benchmark receipts preserve positive, null, and mixed outcomesEXPLORATORYBoundaries ↗
Complete workflow improves independent human reasoning or discovery in deploymentNOT ESTABLISHEDResearch statement ↗
-
Elenchion Systems / Apparatus naming has legal or trademark clearanceUNVERIFIEDClaims register ↗
+
Lattice / Prism naming has legal or trademark clearanceUNVERIFIEDClaims register ↗

Design boundary: classical sculpture, botany, zoology, engraving, and instrument cues may affect perceived prestige or scientific seriousness. They do not add evidential weight to any row above.

@@ -287,16 +287,16 @@

Complete instrument catalogue.

LEGACY INDEX / NON-BREAKING MIGRATION

Old coordinates remain readable.

The public identity changed without rewriting the underlying contracts. Existing citations, commands, and receipts remain stable.

-
AxisSoul · Research Orchestrator/soul
-
CanonMind · Formal Reasoning/mind
-
AtlasSpace · Research Discovery/space
-
CrucibleReality · Method Synthesis/reality
-
ForgePower · Engineering Verification/power
-
ChronometerTime · Evaluation & Benchmarking/time
-
AegisGauntlet · Process Assurance/gauntlet
-
SextantMeditate · Decision Preflight/meditate
-
ConclaveCouncil · Evidence Review Panel/council
-
ParallaxMirror · Adaptive Reasoning Complement/foil
+
DiamondSoul · Research Orchestrator/soul
+
SapphireMind · Formal Reasoning/mind
+
EmeraldSpace · Research Discovery/space
+
RubyReality · Method Synthesis/reality
+
GarnetPower · Engineering Verification/power
+
TopazTime · Evaluation & Benchmarking/time
+
OnyxGauntlet · Process Assurance/gauntlet
+
CitrineMeditate · Decision Preflight/meditate
+
AmethystCouncil · Evidence Review Panel/council
+
OpalMirror · Adaptive Reasoning Complement/foil
@@ -323,8 +323,8 @@

Complete instrument catalogue.

diff --git a/docs/system-field.js b/docs/system-field.js index eac196e7..f927578e 100644 --- a/docs/system-field.js +++ b/docs/system-field.js @@ -32,11 +32,11 @@ overview: ["METHOD / CONTROL MODEL", "SIX STAGES · ONE EVIDENCE STATE"], workflow: ["ROUTE / OBLIGATION", "THE METHOD CHANGES WITH THE CLAIM"], gems: ["INDEX / INSTRUMENTS", "RUNTIME + TEN CORE CONTRACTS"], - mind: ["INSTRUMENT 01 / CANON", "FORMALIZE → NEGATE → VERIFY"], - space: ["INSTRUMENT 02 / ATLAS", "SEARCH → IDENTIFY → BOUND"], - reality: ["INSTRUMENT 03 / CRUCIBLE", "GAP → MECHANISM → FALSIFIER"], - power: ["INSTRUMENT 04 / FORGE", "SOURCE → ENTRYPOINT → EXECUTION"], - time: ["INSTRUMENT 05 / CHRONOMETER", "BASELINE → MEASURE → DECIDE"], + mind: ["INSTRUMENT 01 / SAPPHIRE", "FORMALIZE → NEGATE → VERIFY"], + space: ["INSTRUMENT 02 / EMERALD", "SEARCH → IDENTIFY → BOUND"], + reality: ["INSTRUMENT 03 / RUBY", "GAP → MECHANISM → FALSIFIER"], + power: ["INSTRUMENT 04 / GARNET", "SOURCE → ENTRYPOINT → EXECUTION"], + time: ["INSTRUMENT 05 / TOPAZ", "BASELINE → MEASURE → DECIDE"], system: ["AUTHORITY / RELEASE PATH", "OBSERVATION ≠ RECEIPT ≠ RELEASE"], quiet: ["ARCHIVE / SOURCE", "INSPECT THE MACHINERY, NOT THE MOOD"] }; @@ -328,7 +328,7 @@ function drawGems() { paperGrid(56, 0.045); const { x, y, r } = rightCenter(0.96); - const names = ["CANON", "ATLAS", "CRUCIBLE", "FORGE", "CHRONOMETER"]; + const names = ["SAPPHIRE", "EMERALD", "RUBY", "GARNET", "TOPAZ"]; names.forEach((name, index) => { const angle = -Math.PI / 2 + index * (Math.PI * 2 / 5); const px = x + Math.cos(angle) * r * 0.7; @@ -340,7 +340,7 @@ }); circle(x, y, r * 0.23, palette.ink, 0.28, 1); crosshair(x, y, 12); - label("AXIS", x, y + 29, "center", palette.oxide, 0.45, 9); + label("DIAMOND", x, y + 29, "center", palette.oxide, 0.45, 9); } function drawMind() { @@ -437,7 +437,7 @@ paperGrid(48, 0.045); const { x, y, r } = rightCenter(1.02); const layers = [ - { radius: r * 0.2, label: "AXIS", color: palette.oxide }, + { radius: r * 0.2, label: "DIAMOND", color: palette.oxide }, { radius: r * 0.43, label: "RECEIPTS", color: palette.verdigris }, { radius: r * 0.68, label: "INSTRUMENTS", color: palette.bronze }, { radius: r * 0.91, label: "RUNTIME", color: palette.ink } diff --git a/docs/visuals/APPARATUS_ASSETS.md b/docs/visuals/APPARATUS_ASSETS.md index cf879652..4747e74c 100644 --- a/docs/visuals/APPARATUS_ASSETS.md +++ b/docs/visuals/APPARATUS_ASSETS.md @@ -1,4 +1,4 @@ -# Apparatus Visual Assets +# Prism Visual Assets ## 1. Inventory @@ -14,7 +14,7 @@ Full rights, authorship, use, alt-text, and boundary records are in [`../brand/a ### Frontispiece -Parchment scientific frontispiece with an original measured classical profile inside armillary rings, a botanical specimen at left, and the words “Elenchion Systems — Apparatus — Evidence before authority.” +Parchment scientific frontispiece with an original measured classical profile inside armillary rings, a botanical specimen at left, and the words “Lattice — Prism — Evidence before authority.” ### Calibration mark diff --git a/docs/visuals/apparatus-frontispiece.svg b/docs/visuals/apparatus-frontispiece.svg index 81e3ec53..98d37329 100644 --- a/docs/visuals/apparatus-frontispiece.svg +++ b/docs/visuals/apparatus-frontispiece.svg @@ -1,8 +1,8 @@ - Apparatus by Elenchion Systems - A parchment-toned scientific frontispiece. A measured classical profile sits inside an armillary instrument beside a botanical specimen, while restrained typography identifies Apparatus as an evidence-governed agent and research system. + Prism by Lattice + A parchment-toned scientific frontispiece. A measured classical profile sits inside an armillary instrument beside a botanical specimen, while restrained typography identifies Prism as an evidence-governed agent and research system. - Original vector artwork for Kitahl/The-Gauntlet. Created for the Apparatus brand edition. No external artwork is embedded or traced. Design image only; not evidence. + Original vector artwork for Kitahl/The-Gauntlet. Created for the Prism brand edition. No external artwork is embedded or traced. Design image only; not evidence. @@ -93,8 +93,8 @@ - ELENCHION SYSTEMS - Apparatus + LATTICE + Prism Evidence before authority. An evidence-governed agent and research system. diff --git a/docs/visuals/elenchion-mark.svg b/docs/visuals/elenchion-mark.svg index 74720778..238ee090 100644 --- a/docs/visuals/elenchion-mark.svg +++ b/docs/visuals/elenchion-mark.svg @@ -1,7 +1,7 @@ - Elenchion Systems calibration mark + Lattice calibration mark A restrained circular calibration mark formed from two instrument arcs, a vertical axis, and one oxide registration tick. - Original vector mark for Elenchion Systems. No external source artwork. + Original vector mark for Lattice. No external source artwork. diff --git a/docs/visuals/visual-provenance.json b/docs/visuals/visual-provenance.json index 88d7abbc..1d44800c 100644 --- a/docs/visuals/visual-provenance.json +++ b/docs/visuals/visual-provenance.json @@ -2,12 +2,12 @@ "schema": "egrt/visual-provenance-1", "showcase_revision": 19, "research_software_version": "0.5.1", - "brand_edition": "elenchion-apparatus-1", + "brand_edition": "lattice-prism-1", "visuals": [ { - "id": "apparatus-semantic-instrument-field", + "id": "prism-semantic-instrument-field", "file": "docs/system-field.js", - "purpose": "Render one local procedural field that translates Apparatus architecture into armillary, botanical, geometric, engineering, metrology, optics, and review motifs as the reader moves through the public system explanation.", + "purpose": "Render one local procedural field that translates Prism architecture into armillary, botanical, geometric, engineering, metrology, optics, and review motifs as the reader moves through the public system explanation.", "sources": [ "docs/ARCHITECTURE.md", "docs/brand/NAMING_ARCHITECTURE.md", @@ -23,7 +23,7 @@ "skills/council-of-elders/SKILL.md", "skills/foil/SKILL.md" ], - "source_file_sha256": "0b415e0771cc24c4d95ec4d6c87a2be1e0a65d3fbe210402431238cb4a5ba68f", + "source_file_sha256": "36d0ba135999bb17cc809783e04c02705046e2ea3db2f6fa8809518d872cf9b8", "design_reference_boundary": "Original local Canvas2D implementation following the supplied classical-scientific brand package. It embeds no external image, font, library, or historical artwork. Separate original SVG assets are recorded in docs/brand/asset-manifest.json.", "boundary": "Conceptual visualization of public module contracts and routing semantics. It is not a live execution trace, scientific figure, benchmark result, receipt, institutional endorsement, or behavioral-efficacy claim." } diff --git a/scripts/.apply_prism_gemstone_rebrand.py b/scripts/.apply_prism_gemstone_rebrand.py deleted file mode 100644 index 591a6c83..00000000 --- a/scripts/.apply_prism_gemstone_rebrand.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env python3 -from __future__ import annotations - -import hashlib -import json -import re -import shutil -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -TARGETS = [ - "README.md", - "THIRD_PARTY_NOTICES.md", - "docs/index.html", - "docs/system-field.js", - "docs/brand/BRAND_SYSTEM.md", - "docs/brand/CLAIMS_REGISTER.md", - "docs/brand/NAMING_ARCHITECTURE.md", - "docs/brand/README.md", - "docs/brand/SOURCE_PACKAGE.md", - "docs/brand/brand-system.json", - "docs/brand/asset-manifest.json", - "docs/content-provenance.json", - "docs/visuals/APPARATUS_ASSETS.md", - "docs/visuals/apparatus-frontispiece.svg", - "docs/visuals/elenchion-mark.svg", - "docs/visuals/visual-provenance.json", - "validation/validate_soul_gauntlet_public.py", -] - -# Multiword replacements first. Word boundaries prevent Canon -> canonical corruption. -MAPPING = [ - ("Elenchion Systems", "Lattice"), - ("ELENCHION SYSTEMS", "LATTICE"), - ("Elenchion", "Lattice"), - ("ELENCHION", "LATTICE"), - ("Mercury Runtime", "Quartz"), - ("MERCURY RUNTIME", "QUARTZ"), - ("Strategist Candidate", "Moonstone Candidate"), - ("Formal Plane Candidate", "Zircon Candidate"), - ("Mercury", "Quartz"), - ("MERCURY", "QUARTZ"), - ("Apparatus", "Prism"), - ("APPARATUS", "PRISM"), - ("Axis", "Diamond"), - ("AXIS", "DIAMOND"), - ("Canon", "Sapphire"), - ("CANON", "SAPPHIRE"), - ("Atlas", "Emerald"), - ("ATLAS", "EMERALD"), - ("Crucible", "Ruby"), - ("CRUCIBLE", "RUBY"), - ("Forge", "Garnet"), - ("FORGE", "GARNET"), - ("Chronometer", "Topaz"), - ("CHRONOMETER", "TOPAZ"), - ("Aegis", "Onyx"), - ("AEGIS", "ONYX"), - ("Sextant", "Citrine"), - ("SEXTANT", "CITRINE"), - ("Conclave", "Amethyst"), - ("CONCLAVE", "AMETHYST"), - ("Parallax", "Opal"), - ("PARALLAX", "OPAL"), -] - -SPECIAL = { - "apparatus.brand-system.v1": "prism.brand-system.v1", - "apparatus-brand-01": "prism-brand-01", - "elenchion-apparatus-1": "lattice-prism-1", - "apparatus-semantic-instrument-field": "prism-semantic-instrument-field", - "apparatus-modular-control": "prism-modular-control", - "mercury-pinned-runtime": "quartz-pinned-runtime", - "mercury-checkpoint-eight": "quartz-checkpoint-eight", - "mercury-limitations-visible": "quartz-limitations-visible", - "parallax-adaptation-only": "opal-adaptation-only", - "conclave-independent-review": "amethyst-independent-review", -} - - -def replace_text(text: str) -> str: - for old, new in SPECIAL.items(): - text = text.replace(old, new) - for old, new in MAPPING: - text = re.sub(rf"\b{re.escape(old)}\b", new, text) - return text - - -def digest(path: Path) -> str: - return hashlib.sha256(path.read_bytes()).hexdigest() - - -def main() -> int: - touched = [] - for rel in TARGETS: - path = ROOT / rel - if not path.is_file(): - continue - before = path.read_text(encoding="utf-8") - after = replace_text(before) - if after != before: - path.write_text(after, encoding="utf-8", newline="\n") - touched.append(rel) - - # Normalize machine-readable brand identifiers after the prose rename. - brand_path = ROOT / "docs/brand/brand-system.json" - if brand_path.is_file(): - brand = json.loads(brand_path.read_text(encoding="utf-8")) - brand["schema"] = "prism.brand-system.v1" - brand["brand_edition"] = "prism-brand-01" - brand["organization"]["public_name"] = "Lattice" - brand["product"]["public_name"] = "Prism" - brand.setdefault("runtime", {}) - brand["runtime"].update({ - "public_name": "Quartz", - "technical_id": "gauntlet_host", - "status": "INTERIM_ALPHA_OBSERVATION_ONLY", - }) - brand["evidence_boundary"] = ( - "Brand imagery, gemstone names, and historical cues never add evidential " - "weight to technical or scientific claims." - ) - brand_path.write_text(json.dumps(brand, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - # Recompute hashes for project-authored assets whose labels changed. - manifest_path = ROOT / "docs/brand/asset-manifest.json" - if manifest_path.is_file(): - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - for item in manifest: - source = ROOT / str(item.get("source_object_id", "")) - if source.is_file(): - item["source_file_sha256"] = digest(source) - manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - visual_path = ROOT / "docs/visuals/visual-provenance.json" - if visual_path.is_file(): - visual = json.loads(visual_path.read_text(encoding="utf-8")) - visual["brand_edition"] = "lattice-prism-1" - for item in visual.get("visuals", []): - source = ROOT / str(item.get("file", "")) - if source.is_file(): - item["source_file_sha256"] = digest(source) - visual_path.write_text(json.dumps(visual, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - content_path = ROOT / "docs/content-provenance.json" - if content_path.is_file(): - data = json.loads(content_path.read_text(encoding="utf-8")) - data["brand_edition"] = "lattice-prism-1" - content_path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - # The old filenames remain as stable repository paths; their visible titles and - # metadata now carry Lattice/Prism. Remove this one-shot applicator and workflow. - shutil.rmtree(ROOT / "scripts/.prism_rebrand_unused", ignore_errors=True) - (ROOT / "scripts/.apply_prism_gemstone_rebrand.py").unlink(missing_ok=True) - (ROOT / ".github/workflows/apply-prism-gemstone-rebrand.yml").unlink(missing_ok=True) - print(json.dumps({"status": "APPLIED", "touched": touched}, indent=2)) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/validation/validate_soul_gauntlet_public.py b/validation/validate_soul_gauntlet_public.py index 03f00c92..f3cc1a4a 100644 --- a/validation/validate_soul_gauntlet_public.py +++ b/validation/validate_soul_gauntlet_public.py @@ -115,17 +115,17 @@ def need(path: str) -> str: if forbidden in foil: fail(f"person-specific Mirror/legacy-foil prior leaked into public skill: {forbidden}") -# Apparatus is the public product identity. Require both the new instrument names +# Prism is the public product identity. Require both the new instrument names # and the stable technical aliases rather than treating superseded public labels # as the only acceptable README wording. for token in ( - "Elenchion Systems", "Apparatus", "Mercury Runtime", - "**Axis**", "`soul`, `/soul`", - "**Aegis**", "`infinity-gauntlet`, `/gauntlet`", - "**Parallax**", "`foil`, `/foil`", + "Lattice", "Prism", "Quartz", + "**Diamond**", "`soul`, `/soul`", + "**Onyx**", "`infinity-gauntlet`, `/gauntlet`", + "**Opal**", "`foil`, `/foil`", ): if token not in readme: - fail(f"README missing Apparatus public or compatibility terminology: {token}") + fail(f"README missing Prism public or compatibility terminology: {token}") for token in ( "Mirror — Adaptive Reasoning Complement", "technical skill name: `foil`", "slash command: `/foil`", "runtime modules: `tools/foil_*`", @@ -147,6 +147,6 @@ def need(path: str) -> str: print("PASS: per-component engineering specifications present") print("PASS: SKILL.md-only module directories preserved") print("PASS: Mastermind absent from runtime imports") -print("PASS: Apparatus public identity + legacy technical compatibility contract") +print("PASS: Prism public identity + legacy technical compatibility contract") print("PASS: Mirror public identity + legacy foil compatibility contract") print("PASS: public Mirror/legacy-foil skill contains no embedded user profile") From 72165cb4c18023439aab2c0c7aa86cb5ef196838 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:08:43 -0700 Subject: [PATCH 34/37] chore: stage Bohr Array rebrand applicator --- scripts/.apply_bohr_array_rebrand.py | 137 +++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 scripts/.apply_bohr_array_rebrand.py diff --git a/scripts/.apply_bohr_array_rebrand.py b/scripts/.apply_bohr_array_rebrand.py new file mode 100644 index 00000000..b202e2fa --- /dev/null +++ b/scripts/.apply_bohr_array_rebrand.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import hashlib +import json +import re +import shutil +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +TARGETS = [ + "README.md", + "THIRD_PARTY_NOTICES.md", + "docs/index.html", + "docs/system-field.js", + "docs/brand/BRAND_SYSTEM.md", + "docs/brand/CLAIMS_REGISTER.md", + "docs/brand/NAMING_ARCHITECTURE.md", + "docs/brand/README.md", + "docs/brand/SOURCE_PACKAGE.md", + "docs/brand/brand-system.json", + "docs/brand/asset-manifest.json", + "docs/content-provenance.json", + "docs/visuals/APPARATUS_ASSETS.md", + "docs/visuals/apparatus-frontispiece.svg", + "docs/visuals/elenchion-mark.svg", + "docs/visuals/visual-provenance.json", + "validation/validate_soul_gauntlet_public.py", +] + +SPECIAL = { + "prism.brand-system.v1": "array.brand-system.v1", + "prism-brand-01": "array-brand-01", + "lattice-prism-1": "bohr-array-1", + "prism-semantic-instrument-field": "array-semantic-instrument-field", + "prism-modular-control": "array-modular-control", +} + + +def replace_brand(text: str) -> str: + for old, new in SPECIAL.items(): + text = text.replace(old, new) + text = re.sub(r"\bLATTICE\b", "BOHR", text) + text = re.sub(r"\bLattice\b", "Bohr", text) + text = re.sub(r"\bPRISM\b", "ARRAY", text) + text = re.sub(r"\bPrism\b", "Array", text) + return text + + +def digest(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def main() -> int: + touched = [] + for rel in TARGETS: + path = ROOT / rel + if not path.is_file(): + continue + before = path.read_text(encoding="utf-8") + after = replace_brand(before) + if after != before: + path.write_text(after, encoding="utf-8", newline="\n") + touched.append(rel) + + # Correct the human-readable rationale after the mechanical brand substitution. + naming_path = ROOT / "docs/brand/NAMING_ARCHITECTURE.md" + if naming_path.is_file(): + text = naming_path.read_text(encoding="utf-8") + text = re.sub( + r"\*\*Bohr\*\* is selected because it is a short scientific term for an ordered structural arrangement\. \*\*Array\*\* is selected because it is a short scientific instrument/object term associated with separating a complex input into inspectable components\.", + "**Bohr** is selected as a short, memorable scientific identity. **Array** is selected as a short scientific/computing term for an ordered collection, matching a suite composed of distinct specialist instruments.", + text, + ) + naming_path.write_text(text, encoding="utf-8", newline="\n") + + readme_path = ROOT / "README.md" + if readme_path.is_file(): + text = readme_path.read_text(encoding="utf-8") + text = text.replace( + "| Organization | **Bohr** | One-word scientific identity: an ordered structure for research and engineering instruments |", + "| Organization | **Bohr** | One-word scientific identity chosen for memorability and technical character |", + ) + text = text.replace( + "| Product suite | **Array** | One runtime, one evidence-control plane, and a portfolio of specialist gemstone instruments |", + "| Product suite | **Array** | An ordered collection: one runtime, one evidence-control plane, and specialist gemstone instruments |", + ) + readme_path.write_text(text, encoding="utf-8", newline="\n") + + brand_path = ROOT / "docs/brand/brand-system.json" + if brand_path.is_file(): + brand = json.loads(brand_path.read_text(encoding="utf-8")) + brand["schema"] = "array.brand-system.v1" + brand["brand_edition"] = "array-brand-01" + brand["organization"]["public_name"] = "Bohr" + brand["product"]["public_name"] = "Array" + brand["runtime"]["public_name"] = "Quartz" + brand["evidence_boundary"] = ( + "Brand imagery, gemstone names, and historical or scientific cues never add " + "evidential weight to technical or scientific claims." + ) + brand_path.write_text(json.dumps(brand, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + manifest_path = ROOT / "docs/brand/asset-manifest.json" + if manifest_path.is_file(): + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + for item in manifest: + source = ROOT / str(item.get("source_object_id", "")) + if source.is_file(): + item["source_file_sha256"] = digest(source) + manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + visual_path = ROOT / "docs/visuals/visual-provenance.json" + if visual_path.is_file(): + visual = json.loads(visual_path.read_text(encoding="utf-8")) + visual["brand_edition"] = "bohr-array-1" + for item in visual.get("visuals", []): + source = ROOT / str(item.get("file", "")) + if source.is_file(): + item["source_file_sha256"] = digest(source) + visual_path.write_text(json.dumps(visual, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + content_path = ROOT / "docs/content-provenance.json" + if content_path.is_file(): + data = json.loads(content_path.read_text(encoding="utf-8")) + data["brand_edition"] = "bohr-array-1" + content_path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + # One-shot payload: leave only the product files. + (ROOT / "scripts/.apply_bohr_array_rebrand.py").unlink(missing_ok=True) + (ROOT / ".github/workflows/apply-bohr-array-rebrand.yml").unlink(missing_ok=True) + print(json.dumps({"status": "APPLIED", "touched": touched}, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 86ad014d3e170c9c9cb38697baa7ddd095e1bca9 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:08:54 -0700 Subject: [PATCH 35/37] chore: run Bohr Array rebrand applicator --- .../workflows/apply-bohr-array-rebrand.yml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/apply-bohr-array-rebrand.yml diff --git a/.github/workflows/apply-bohr-array-rebrand.yml b/.github/workflows/apply-bohr-array-rebrand.yml new file mode 100644 index 00000000..797d1762 --- /dev/null +++ b/.github/workflows/apply-bohr-array-rebrand.yml @@ -0,0 +1,59 @@ +name: Apply Bohr Array rebrand + +on: + push: + branches: + - work/elenchion-apparatus-brand + paths: + - scripts/.apply_bohr_array_rebrand.py + - .github/workflows/apply-bohr-array-rebrand.yml + +permissions: + contents: write + +concurrency: + group: apply-bohr-array-rebrand + cancel-in-progress: false + +jobs: + apply: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: work/elenchion-apparatus-brand + fetch-depth: 0 + + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + + - name: Apply rebrand + run: python scripts/.apply_bohr_array_rebrand.py + + - name: Validate changed public artifacts + run: | + python -m json.tool docs/brand/brand-system.json >/dev/null + python -m json.tool docs/brand/asset-manifest.json >/dev/null + python -m json.tool docs/content-provenance.json >/dev/null + python -m json.tool docs/visuals/visual-provenance.json >/dev/null + python - <<'PY' + from xml.etree import ElementTree as ET + ET.parse('docs/visuals/apparatus-frontispiece.svg') + ET.parse('docs/visuals/elenchion-mark.svg') + PY + node --check docs/system-field.js + git diff --check -- . ':(exclude)README.md' ':(exclude)docs/brand/README.md' + + - name: Commit final rebrand + env: + BRANCH: work/elenchion-apparatus-brand + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + test ! -e scripts/.apply_bohr_array_rebrand.py + test ! -e .github/workflows/apply-bohr-array-rebrand.yml + git commit -m "brand: rename public system to Bohr Array" + git push origin "HEAD:${BRANCH}" From 272a0f50254e1bdacf2da8d5802d7b60b8035f0a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 18:09:05 +0000 Subject: [PATCH 36/37] brand: rename public system to Bohr Array --- .../workflows/apply-bohr-array-rebrand.yml | 59 -------- README.md | 24 +-- THIRD_PARTY_NOTICES.md | 6 +- docs/brand/BRAND_SYSTEM.md | 2 +- docs/brand/CLAIMS_REGISTER.md | 8 +- docs/brand/NAMING_ARCHITECTURE.md | 6 +- docs/brand/README.md | 8 +- docs/brand/SOURCE_PACKAGE.md | 2 +- docs/brand/asset-manifest.json | 24 +-- docs/brand/brand-system.json | 10 +- docs/content-provenance.json | 10 +- docs/index.html | 34 ++--- docs/visuals/APPARATUS_ASSETS.md | 4 +- docs/visuals/apparatus-frontispiece.svg | 10 +- docs/visuals/elenchion-mark.svg | 4 +- docs/visuals/visual-provenance.json | 6 +- scripts/.apply_bohr_array_rebrand.py | 137 ------------------ validation/validate_soul_gauntlet_public.py | 8 +- 18 files changed, 83 insertions(+), 279 deletions(-) delete mode 100644 .github/workflows/apply-bohr-array-rebrand.yml delete mode 100644 scripts/.apply_bohr_array_rebrand.py diff --git a/.github/workflows/apply-bohr-array-rebrand.yml b/.github/workflows/apply-bohr-array-rebrand.yml deleted file mode 100644 index 797d1762..00000000 --- a/.github/workflows/apply-bohr-array-rebrand.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Apply Bohr Array rebrand - -on: - push: - branches: - - work/elenchion-apparatus-brand - paths: - - scripts/.apply_bohr_array_rebrand.py - - .github/workflows/apply-bohr-array-rebrand.yml - -permissions: - contents: write - -concurrency: - group: apply-bohr-array-rebrand - cancel-in-progress: false - -jobs: - apply: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: work/elenchion-apparatus-brand - fetch-depth: 0 - - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.12' - - - name: Apply rebrand - run: python scripts/.apply_bohr_array_rebrand.py - - - name: Validate changed public artifacts - run: | - python -m json.tool docs/brand/brand-system.json >/dev/null - python -m json.tool docs/brand/asset-manifest.json >/dev/null - python -m json.tool docs/content-provenance.json >/dev/null - python -m json.tool docs/visuals/visual-provenance.json >/dev/null - python - <<'PY' - from xml.etree import ElementTree as ET - ET.parse('docs/visuals/apparatus-frontispiece.svg') - ET.parse('docs/visuals/elenchion-mark.svg') - PY - node --check docs/system-field.js - git diff --check -- . ':(exclude)README.md' ':(exclude)docs/brand/README.md' - - - name: Commit final rebrand - env: - BRANCH: work/elenchion-apparatus-brand - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - test ! -e scripts/.apply_bohr_array_rebrand.py - test ! -e .github/workflows/apply-bohr-array-rebrand.yml - git commit -m "brand: rename public system to Bohr Array" - git push origin "HEAD:${BRANCH}" diff --git a/README.md b/README.md index 7c99f6c5..7b86148f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@

- Prism frontispiece: a calibrated classical profile inside an armillary instrument, with botanical and measurement annotations + Array frontispiece: a calibrated classical profile inside an armillary instrument, with botanical and measurement annotations

-# Prism +# Array -**An evidence-governed agent and research system by Lattice.** +**An evidence-governed agent and research system by Bohr.** [![Research software validation](https://github.com/Kitahl/The-Gauntlet/actions/workflows/validate.yml/badge.svg)](https://github.com/Kitahl/The-Gauntlet/actions/workflows/validate.yml) [![Fastpath checkpoint](https://github.com/Kitahl/The-Gauntlet/actions/workflows/fastpath-checkpoint.yml/badge.svg)](https://github.com/Kitahl/The-Gauntlet/actions/workflows/fastpath-checkpoint.yml) @@ -12,7 +12,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Version](https://img.shields.io/badge/version-0.5.1-informational.svg)](CHANGELOG.md) -> **Research status:** Prism is public research software with executable checks, typed evidence receipts, bounded benchmark pilots, and an interim Hermes-derived runtime alpha. It does **not** establish that the complete system improves human reasoning, scientific discovery, or general AI capability in prospective deployment. +> **Research status:** Array is public research software with executable checks, typed evidence receipts, bounded benchmark pilots, and an interim Hermes-derived runtime alpha. It does **not** establish that the complete system improves human reasoning, scientific discovery, or general AI capability in prospective deployment. **Website:** [kitahl.github.io/The-Gauntlet](https://kitahl.github.io/The-Gauntlet/) **5-minute evaluator path:** [`docs/EVALUATOR_QUICKSTART.md`](docs/EVALUATOR_QUICKSTART.md) @@ -25,17 +25,17 @@ | Layer | Public name | Meaning | Compatibility boundary | |---|---|---|---| -| Organization | **Lattice** | One-word scientific identity: an ordered structure for research and engineering instruments | Naming decision only; no claim of legal incorporation, trademark clearance, or historical lineage | -| Product suite | **Prism** | One runtime, one evidence-control plane, and a portfolio of specialist gemstone instruments | Repository remains `Kitahl/The-Gauntlet`; the installed command remains `gauntlet` | +| Organization | **Bohr** | One-word scientific identity chosen for memorability and technical character | Naming decision only; no claim of legal incorporation, trademark clearance, or historical lineage | +| Product suite | **Array** | An ordered collection: one runtime, one evidence-control plane, and specialist gemstone instruments | Repository remains `Kitahl/The-Gauntlet`; the installed command remains `gauntlet` | | Runtime | **Quartz** | Hermes-derived operational layer behind the evidence boundary | Technical runtime remains `gauntlet_host` | | Product principle | **Evidence before authority** | Models and tools may produce observations; claim-native methods and receipts govern factual warrant | Existing task, obligation, receipt, verdict, and release semantics are unchanged | | Visual language | **Scholarly Antiquarian Framing** | Classical, natural-history, mineralogical, and instrument imagery paired with modern evidence panels | Brand language is never evidence for a technical or scientific claim | The gemstone names are public aliases. Existing commands, state, receipts, tests, files, and citations continue to resolve through the stable technical names. See [`docs/brand/NAMING_ARCHITECTURE.md`](docs/brand/NAMING_ARCHITECTURE.md). -## 2. What Prism does +## 2. What Array does -Prism treats a claim like a specimen under examination: +Array treats a claim like a specimen under examination: 1. **Frame** the goal and create explicit load-bearing obligations. 2. **Route** each obligation to the method that can actually establish it. @@ -134,7 +134,7 @@ Every `skills//` directory retains `SKILL.md` as the public reason ### Moonstone Candidate — mechanism planning -The archived mechanism-planner candidate contains bounded minimum successful-repair selection over a declared finite repair universe. Its recorded hardening and inherited checks apply only to that archived candidate; it is **not** promoted into Prism authority or runtime by the archive. See [`research/postbench-candidate2/README.md`](research/postbench-candidate2/README.md). +The archived mechanism-planner candidate contains bounded minimum successful-repair selection over a declared finite repair universe. Its recorded hardening and inherited checks apply only to that archived candidate; it is **not** promoted into Array authority or runtime by the archive. See [`research/postbench-candidate2/README.md`](research/postbench-candidate2/README.md). ### Zircon Candidate — mathematical execution hardening @@ -149,8 +149,8 @@ The archived formal-plane candidate strengthens trusted-base minimality, isolate | Runtime observations cannot directly create canonical receipts or release tasks | **Architecture invariant with executable checks** | [`docs/engineering/PHASE8_USER_CLI_BOOT.md`](docs/engineering/PHASE8_USER_CLI_BOOT.md) · [`.github/phase8_verify.py`](.github/phase8_verify.py) | | Opal profile, onboarding, calibration, and routing mechanics exist | **Mechanically checked; efficacy open** | [`research/FOIL_RESEARCH_BASIS.md`](research/FOIL_RESEARCH_BASIS.md) · [`validation/`](validation/) | | Exploratory benchmark receipts include positive, null, and negative/mixed outcomes | **Exploratory, small-sample evidence** | [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) | -| Prism improves independent human reasoning or scientific discovery in deployment | **Not established** | [`RESEARCH.md`](RESEARCH.md) · [`ROADMAP.md`](ROADMAP.md) | -| Lattice and Prism are cleared corporate/product marks | **Unverified; no legal clearance performed** | [`docs/brand/CLAIMS_REGISTER.md`](docs/brand/CLAIMS_REGISTER.md) | +| Array improves independent human reasoning or scientific discovery in deployment | **Not established** | [`RESEARCH.md`](RESEARCH.md) · [`ROADMAP.md`](ROADMAP.md) | +| Bohr and Array are cleared corporate/product marks | **Unverified; no legal clearance performed** | [`docs/brand/CLAIMS_REGISTER.md`](docs/brand/CLAIMS_REGISTER.md) | The repository does not combine unlike benchmark rows into a single headline score. Passing source checks, green CI, model agreement, or persuasive design are evidence with bounded scope—not automatic scientific validity. @@ -199,7 +199,7 @@ The FAST-P8 harness uses a deterministic local OpenAI-compatible endpoint. Passi ## 9. Brand and website system -The Prism visual system applies the supplied classical-scientific package as a restrained mineralogical research interface: +The Array visual system applies the supplied classical-scientific package as a restrained mineralogical research interface: - parchment, ink, slate, bronze, oxide, verdigris, and gemstone accents; - editorial serif, technical sans, and receipt-mono typography stacks; diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 081f87e4..00c9a31e 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -12,12 +12,12 @@ This branch includes an exact pinned source snapshot of the upstream Hermes Agen | Adoption ledger | `third_party/HERMES_SOURCE_LEDGER.md` | | License notice | `third_party/HERMES_LICENSE.txt` | -The upstream MIT license and copyright notice must be retained in copies or substantial portions of the upstream software. Lattice, Prism, Quartz, and the other public aliases in this branch do not imply endorsement by Nous Research or the Hermes Agent maintainers. +The upstream MIT license and copyright notice must be retained in copies or substantial portions of the upstream software. Bohr, Array, Quartz, and the other public aliases in this branch do not imply endorsement by Nous Research or the Hermes Agent maintainers. ## 2. Brand and visual assets -The initial Prism frontispiece, calibration mark, and local Canvas2D system field are project-authored assets recorded in `docs/brand/asset-manifest.json`. No museum-hosted or third-party historical image is committed by the branding change. +The initial Array frontispiece, calibration mark, and local Canvas2D system field are project-authored assets recorded in `docs/brand/asset-manifest.json`. No museum-hosted or third-party historical image is committed by the branding change. ## 3. Working-name boundary -`Lattice`, `Prism`, `Quartz`, and the instrument aliases are working project identities. This notice does not assert corporate-name, domain, or trademark clearance. +`Bohr`, `Array`, `Quartz`, and the instrument aliases are working project identities. This notice does not assert corporate-name, domain, or trademark clearance. diff --git a/docs/brand/BRAND_SYSTEM.md b/docs/brand/BRAND_SYSTEM.md index c2c23c2c..4e99f0d6 100644 --- a/docs/brand/BRAND_SYSTEM.md +++ b/docs/brand/BRAND_SYSTEM.md @@ -1,4 +1,4 @@ -# Prism Visual and Marketing System +# Array Visual and Marketing System ## 1. North star diff --git a/docs/brand/CLAIMS_REGISTER.md b/docs/brand/CLAIMS_REGISTER.md index 863cdb5a..ddb80af4 100644 --- a/docs/brand/CLAIMS_REGISTER.md +++ b/docs/brand/CLAIMS_REGISTER.md @@ -14,8 +14,8 @@ | ID | Claim | Status | Evidence / action | |---|---|---|---| -| `BR-001` | Lattice is the working public organization identity for this project. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | -| `BR-002` | Prism is the working public name of the integrated tool suite. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | +| `BR-001` | Bohr is the working public organization identity for this project. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | +| `BR-002` | Array is the working public name of the integrated tool suite. | `DESIGN_DECISION` | `NAMING_ARCHITECTURE.md` | | `BR-003` | The repository and installed command remain `Kitahl/The-Gauntlet` and `gauntlet`. | `VERIFIED_REPOSITORY_FACT` | repository metadata; `pyproject.toml`; `gauntlet_host/cli.py` | | `BR-004` | The runtime vendors pinned Hermes Agent source and uses an isolated parent/worker boundary. | `VERIFIED_REPOSITORY_FACT` | `vendor/HERMES_SNAPSHOT.json`; `third_party/HERMES_SOURCE_LEDGER.md`; runtime engineering plan | | `BR-005` | The FAST-P8 record reports 8/8 bounded boot checks passed. | `VERIFIED_REPOSITORY_FACT` | `docs/engineering/HERMES_FAST_P8_CHECKPOINT.json` | @@ -25,9 +25,9 @@ | `BR-009` | Obsidian can raise an issue and can never clear a claim. | `VERIFIED_REPOSITORY_FACT` | `docs/specs/BLACKGEM_ENGINEERING_SPEC.md` | | `BR-010` | Moonstone Candidate and Zircon Candidate are archived engineering candidates, not promoted runtime authority. | `VERIFIED_REPOSITORY_FACT` | `research/postbench-candidate2/README.md` | | `BR-011` | Exploratory benchmark receipts include positive, null, and mixed/negative outcomes. | `EXPLORATORY_EVIDENCE` | `docs/BENCHMARKS.md` and linked receipts | -| `BR-012` | Prism improves independent human reasoning, scientific discovery, or general AI capability in deployment. | `UNVERIFIED` | `RESEARCH.md` states this is not established | +| `BR-012` | Array improves independent human reasoning, scientific discovery, or general AI capability in deployment. | `UNVERIFIED` | `RESEARCH.md` states this is not established | | `BR-013` | Classical-scientific styling proves rigor, correctness, or historical legitimacy. | `PROHIBITED` | Brand evidence-boundary rule | -| `BR-014` | Lattice or Prism has completed corporate-name, trademark, domain, or legal clearance. | `UNVERIFIED` | Professional legal clearance required before registration or commercial launch | +| `BR-014` | Bohr or Array has completed corporate-name, trademark, domain, or legal clearance. | `UNVERIFIED` | Professional legal clearance required before registration or commercial launch | | `BR-015` | The committed frontispiece and calibration mark are original project-authored vectors with no external artwork embedded or traced. | `VERIFIED_REPOSITORY_FACT` | source SVG metadata; `asset-manifest.json`; reproducible SHA-256 hashes | | `BR-016` | Scholarly Antiquarian Framing is an established scientific or marketing term. | `PROHIBITED` | It is a coined internal label from the supplied design package | diff --git a/docs/brand/NAMING_ARCHITECTURE.md b/docs/brand/NAMING_ARCHITECTURE.md index 8bd271f3..e21cc307 100644 --- a/docs/brand/NAMING_ARCHITECTURE.md +++ b/docs/brand/NAMING_ARCHITECTURE.md @@ -4,13 +4,13 @@ | Level | New public identity | Status | Existing identity retained for compatibility | |---|---|---|---| -| Organization | **Lattice** | `DESIGN_DECISION` | Independent project ownership remains unchanged | -| Product suite | **Prism** | `DESIGN_DECISION` | Repository: `Kitahl/The-Gauntlet`; CLI: `gauntlet` | +| Organization | **Bohr** | `DESIGN_DECISION` | Independent project ownership remains unchanged | +| Product suite | **Array** | `DESIGN_DECISION` | Repository: `Kitahl/The-Gauntlet`; CLI: `gauntlet` | | Runtime | **Quartz** | `DESIGN_DECISION` | `gauntlet_host`; pinned Hermes Agent runtime source | | Principle | **Evidence before authority** | `DESIGN_DECISION` | Existing authority/evidence separation | | Visual method | **Scholarly Antiquarian Framing** | coined internal label | No claim that this is an established academic term | -**Lattice** is selected because it is a short scientific term for an ordered structural arrangement. **Prism** is selected because it is a short scientific instrument/object term associated with separating a complex input into inspectable components. These are working public identities only; this document does not claim legal incorporation, trademark availability, domain availability, or institutional endorsement. +**Bohr** is selected as a short, memorable scientific identity. **Array** is selected as a short scientific/computing term for an ordered collection, matching a suite composed of distinct specialist instruments. These are working public identities only; this document does not claim legal incorporation, trademark availability, domain availability, or institutional endorsement. ## 2. Public module map diff --git a/docs/brand/README.md b/docs/brand/README.md index ffa811d8..0e8cda70 100644 --- a/docs/brand/README.md +++ b/docs/brand/README.md @@ -1,9 +1,9 @@ -# Prism Brand System +# Array Brand System -**Organization:** Lattice -**Product suite:** Prism +**Organization:** Bohr +**Product suite:** Array **Product principle:** Evidence before authority -**Brand edition:** `prism-brand-01` +**Brand edition:** `array-brand-01` **Status:** working public identity; technical compatibility preserved; legal name/trademark clearance not performed ## File index diff --git a/docs/brand/SOURCE_PACKAGE.md b/docs/brand/SOURCE_PACKAGE.md index 5c85ce2b..d9ce8d10 100644 --- a/docs/brand/SOURCE_PACKAGE.md +++ b/docs/brand/SOURCE_PACKAGE.md @@ -27,7 +27,7 @@ The package included research notes, a visual system, GitHub and website specifi ## Deliberate adaptations -- The old product identity was replaced by Lattice / Prism. +- The old product identity was replaced by Bohr / Array. - The five “Stones/Gems” became claim-native Instruments. - Soul, Gauntlet, Council, Meditate, and Mirror received new public instrument names while technical aliases remain. - The Hermes-derived host is described as Quartz and given an explicit alpha/observation-only boundary. diff --git a/docs/brand/asset-manifest.json b/docs/brand/asset-manifest.json index 6a567240..61e2cf8c 100644 --- a/docs/brand/asset-manifest.json +++ b/docs/brand/asset-manifest.json @@ -1,7 +1,7 @@ [ { - "asset_id": "PRISM-ASSET-001", - "title": "Prism Classical-Scientific Frontispiece", + "asset_id": "ARRAY-ASSET-001", + "title": "Array Classical-Scientific Frontispiece", "creator": "Project-authored with AI-assisted vector generation under project-owner direction", "date": "2026-09-03", "source_institution": "Kitahl/The-Gauntlet project", @@ -12,12 +12,12 @@ "license_id": "PROJECT-ORIGINAL", "commercial_use_allowed": true, "rights_checked_on": "2026-09-03", - "source_file_sha256": "6127e5b093cb5f8a7c9950870d76d72fd8e0b93777a7dae99fbee3dfcc3cecfa", + "source_file_sha256": "60cdc9198fc9e08d507187734cd34b86baf4446f8068c58eafe087537e37718d", "transformations": [], "production_files": [ "docs/visuals/apparatus-frontispiece.svg" ], - "credit_line": "Prism frontispiece — Kitahl/The-Gauntlet project, 2026", + "credit_line": "Array frontispiece — Kitahl/The-Gauntlet project, 2026", "endorsement_disclaimer_required": true, "review_status": "APPROVED_PROJECT_ORIGINAL", "asset_type": "svg-frontispiece", @@ -26,13 +26,13 @@ "GitHub social-preview source", "website brand reference" ], - "alt_text": "Parchment scientific frontispiece with an original measured classical profile, armillary rings, a botanical specimen, and the Prism identity.", + "alt_text": "Parchment scientific frontispiece with an original measured classical profile, armillary rings, a botanical specimen, and the Array identity.", "evidence_boundary": "Brand image only. It is not an execution trace, receipt, benchmark result, historical artifact, or proof of product efficacy.", "external_sources": [] }, { - "asset_id": "PRISM-ASSET-002", - "title": "Lattice Calibration Mark", + "asset_id": "ARRAY-ASSET-002", + "title": "Bohr Calibration Mark", "creator": "Project-authored with AI-assisted vector generation under project-owner direction", "date": "2026-09-03", "source_institution": "Kitahl/The-Gauntlet project", @@ -43,12 +43,12 @@ "license_id": "PROJECT-ORIGINAL", "commercial_use_allowed": true, "rights_checked_on": "2026-09-03", - "source_file_sha256": "e04d2a15df09d5a97f80401a2212faf58e7e338514f16dc110130ab0a6d501c1", + "source_file_sha256": "e9ea93513e7abccb3b0f8958ed8d5d091990613cdf4ae2c10560a6bdb586e055", "transformations": [], "production_files": [ "docs/visuals/elenchion-mark.svg" ], - "credit_line": "Lattice calibration mark — Kitahl/The-Gauntlet project, 2026", + "credit_line": "Bohr calibration mark — Kitahl/The-Gauntlet project, 2026", "endorsement_disclaimer_required": true, "review_status": "APPROVED_PROJECT_ORIGINAL", "asset_type": "svg-brand-mark", @@ -61,8 +61,8 @@ "external_sources": [] }, { - "asset_id": "PRISM-ASSET-003", - "title": "Prism Semantic Instrument Field", + "asset_id": "ARRAY-ASSET-003", + "title": "Array Semantic Instrument Field", "creator": "Project-authored with AI-assisted Canvas2D generation under project-owner direction", "date": "2026-09-03", "source_institution": "Kitahl/The-Gauntlet project", @@ -78,7 +78,7 @@ "production_files": [ "docs/system-field.js" ], - "credit_line": "Prism semantic instrument field — Kitahl/The-Gauntlet project, 2026", + "credit_line": "Array semantic instrument field — Kitahl/The-Gauntlet project, 2026", "endorsement_disclaimer_required": true, "review_status": "APPROVED_PROJECT_ORIGINAL", "asset_type": "canvas2d-visualization", diff --git a/docs/brand/brand-system.json b/docs/brand/brand-system.json index 7f8facd3..49c05edc 100644 --- a/docs/brand/brand-system.json +++ b/docs/brand/brand-system.json @@ -1,13 +1,13 @@ { - "schema": "prism.brand-system.v1", - "brand_edition": "prism-brand-01", + "schema": "array.brand-system.v1", + "brand_edition": "array-brand-01", "organization": { - "public_name": "Lattice", + "public_name": "Bohr", "status": "DESIGN_DECISION", "legal_clearance": "UNVERIFIED" }, "product": { - "public_name": "Prism", + "public_name": "Array", "descriptor": "evidence-governed agent and research system", "principle": "Evidence before authority", "repository_compatibility_name": "The Gauntlet", @@ -139,6 +139,6 @@ "UNVERIFIED", "PROHIBITED" ], - "evidence_boundary": "Brand imagery, gemstone names, and historical cues never add evidential weight to technical or scientific claims.", + "evidence_boundary": "Brand imagery, gemstone names, and historical or scientific cues never add evidential weight to technical or scientific claims.", "source_package_sha256": "2232a3b18ee2c1ffeb2cf670f6018dc7129d4668db18ed8eef0ae767de2f22c5" } diff --git a/docs/content-provenance.json b/docs/content-provenance.json index 5f2fa0d5..e8c3e6a5 100644 --- a/docs/content-provenance.json +++ b/docs/content-provenance.json @@ -1,12 +1,12 @@ { "schema": "egrt/showcase-provenance-2", - "brand_edition": "lattice-prism-1", + "brand_edition": "bohr-array-1", "generated_on": "2026-09-03", "claims": [ { "id": "public-identity-working-decision", "page_section": "Identity", - "claim": "Lattice and Prism are working public identities adopted by this branch; no corporate-name, trademark, domain, or legal clearance is claimed.", + "claim": "Bohr and Array are working public identities adopted by this branch; no corporate-name, trademark, domain, or legal clearance is claimed.", "status": "DESIGN_DECISION_WITH_UNVERIFIED_LEGAL_CLEARANCE", "sources": [ "docs/brand/NAMING_ARCHITECTURE.md", @@ -28,9 +28,9 @@ ] }, { - "id": "prism-modular-control", + "id": "array-modular-control", "page_section": "System", - "claim": "Prism publicly groups one orchestrator, five claim-native research instruments, four control/review instruments, and additional bounded systems while retaining the existing implementation boundaries.", + "claim": "Array publicly groups one orchestrator, five claim-native research instruments, four control/review instruments, and additional bounded systems while retaining the existing implementation boundaries.", "status": "DESIGN_DECISION_MAPPED_TO_REPOSITORY_FACTS", "sources": [ "docs/brand/NAMING_ARCHITECTURE.md", @@ -136,7 +136,7 @@ { "id": "candidate-archive-no-promotion", "page_section": "Candidate Systems", - "claim": "The Strategist and Formal Plane public labels refer to archived Mastermind and Math Foundry engineering candidates; their archive does not promote them into Prism runtime or evidential authority.", + "claim": "The Strategist and Formal Plane public labels refer to archived Mastermind and Math Foundry engineering candidates; their archive does not promote them into Array runtime or evidential authority.", "status": "VERIFIED_REPOSITORY_FACT_PLUS_PUBLIC_ALIAS", "sources": [ "docs/brand/NAMING_ARCHITECTURE.md", diff --git a/docs/index.html b/docs/index.html index c8324f72..9c786898 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,21 +3,21 @@ - + - - + + - - Prism — Lattice + + Array — Bohr - +
- + - LATTICEPRISM + BOHRARRAY
@@ -93,7 +93,7 @@

A research system built like an inspection bench.

ROUTE PLATES / FIVE EXAMPLES

The route changes with the obligation.

-

No single model or method owns every question. Prism selects a bounded chain according to what must be proved, searched, built, measured, reviewed, or left unresolved.

+

No single model or method owns every question. Array selects a bounded chain according to what must be proved, searched, built, measured, reviewed, or left unresolved.

Novel mechanism

Is this new, buildable, and decision-relevant?

Emerald Ruby Garnet Topaz

@@ -108,7 +108,7 @@

The route changes with the obligation.

-

PRISM / OPERATIONAL LAYER

+

ARRAY / OPERATIONAL LAYER

Quartz

Hermes-quality operational mechanisms, contained behind a Gauntlet-owned authority boundary.

Quartz is the public name for the interim runtime in gauntlet_host/. It runs the exact pinned MIT-licensed Hermes Agent source as an isolated internal worker. The parent process owns task identity, observation binding, finalization, and the release gate.

@@ -205,7 +205,7 @@

Evaluation & Benchmarking

Operational intelligence. Evidential restraint.

Quartz can execute. Opal can advise. The instruments can generate claim-scoped receipts. Onyx and the review systems can identify defects. Only Diamond governs final release.

-

TaskObligationsRuntime observationsClaim-native receiptsAssuranceScoped release

+

TaskObligationsRuntime observationsClaim-native receiptsAssuranceScoped release

Non-negotiable invariant

A model may select or propose an action. It may not declare that its own action established truth.

@@ -277,7 +277,7 @@

Complete instrument catalogue.

Core contracts, state, receipts, hooks, and validators existMECHANICALLY CHECKEDValidation ↗
Exploratory benchmark receipts preserve positive, null, and mixed outcomesEXPLORATORYBoundaries ↗
Complete workflow improves independent human reasoning or discovery in deploymentNOT ESTABLISHEDResearch statement ↗
-
Lattice / Prism naming has legal or trademark clearanceUNVERIFIEDClaims register ↗
+
Bohr / Array naming has legal or trademark clearanceUNVERIFIEDClaims register ↗

Design boundary: classical sculpture, botany, zoology, engraving, and instrument cues may affect perceived prestige or scientific seriousness. They do not add evidential weight to any row above.

@@ -323,8 +323,8 @@

Complete instrument catalogue.

diff --git a/docs/visuals/APPARATUS_ASSETS.md b/docs/visuals/APPARATUS_ASSETS.md index 4747e74c..50e16a93 100644 --- a/docs/visuals/APPARATUS_ASSETS.md +++ b/docs/visuals/APPARATUS_ASSETS.md @@ -1,4 +1,4 @@ -# Prism Visual Assets +# Array Visual Assets ## 1. Inventory @@ -14,7 +14,7 @@ Full rights, authorship, use, alt-text, and boundary records are in [`../brand/a ### Frontispiece -Parchment scientific frontispiece with an original measured classical profile inside armillary rings, a botanical specimen at left, and the words “Lattice — Prism — Evidence before authority.” +Parchment scientific frontispiece with an original measured classical profile inside armillary rings, a botanical specimen at left, and the words “Bohr — Array — Evidence before authority.” ### Calibration mark diff --git a/docs/visuals/apparatus-frontispiece.svg b/docs/visuals/apparatus-frontispiece.svg index 98d37329..3fb924ff 100644 --- a/docs/visuals/apparatus-frontispiece.svg +++ b/docs/visuals/apparatus-frontispiece.svg @@ -1,8 +1,8 @@ - Prism by Lattice - A parchment-toned scientific frontispiece. A measured classical profile sits inside an armillary instrument beside a botanical specimen, while restrained typography identifies Prism as an evidence-governed agent and research system. + Array by Bohr + A parchment-toned scientific frontispiece. A measured classical profile sits inside an armillary instrument beside a botanical specimen, while restrained typography identifies Array as an evidence-governed agent and research system. - Original vector artwork for Kitahl/The-Gauntlet. Created for the Prism brand edition. No external artwork is embedded or traced. Design image only; not evidence. + Original vector artwork for Kitahl/The-Gauntlet. Created for the Array brand edition. No external artwork is embedded or traced. Design image only; not evidence. @@ -93,8 +93,8 @@ - LATTICE - Prism + BOHR + Array Evidence before authority. An evidence-governed agent and research system. diff --git a/docs/visuals/elenchion-mark.svg b/docs/visuals/elenchion-mark.svg index 238ee090..b02bf1cf 100644 --- a/docs/visuals/elenchion-mark.svg +++ b/docs/visuals/elenchion-mark.svg @@ -1,7 +1,7 @@ - Lattice calibration mark + Bohr calibration mark A restrained circular calibration mark formed from two instrument arcs, a vertical axis, and one oxide registration tick. - Original vector mark for Lattice. No external source artwork. + Original vector mark for Bohr. No external source artwork. diff --git a/docs/visuals/visual-provenance.json b/docs/visuals/visual-provenance.json index 1d44800c..ce095fc3 100644 --- a/docs/visuals/visual-provenance.json +++ b/docs/visuals/visual-provenance.json @@ -2,12 +2,12 @@ "schema": "egrt/visual-provenance-1", "showcase_revision": 19, "research_software_version": "0.5.1", - "brand_edition": "lattice-prism-1", + "brand_edition": "bohr-array-1", "visuals": [ { - "id": "prism-semantic-instrument-field", + "id": "array-semantic-instrument-field", "file": "docs/system-field.js", - "purpose": "Render one local procedural field that translates Prism architecture into armillary, botanical, geometric, engineering, metrology, optics, and review motifs as the reader moves through the public system explanation.", + "purpose": "Render one local procedural field that translates Array architecture into armillary, botanical, geometric, engineering, metrology, optics, and review motifs as the reader moves through the public system explanation.", "sources": [ "docs/ARCHITECTURE.md", "docs/brand/NAMING_ARCHITECTURE.md", diff --git a/scripts/.apply_bohr_array_rebrand.py b/scripts/.apply_bohr_array_rebrand.py deleted file mode 100644 index b202e2fa..00000000 --- a/scripts/.apply_bohr_array_rebrand.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 -from __future__ import annotations - -import hashlib -import json -import re -import shutil -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -TARGETS = [ - "README.md", - "THIRD_PARTY_NOTICES.md", - "docs/index.html", - "docs/system-field.js", - "docs/brand/BRAND_SYSTEM.md", - "docs/brand/CLAIMS_REGISTER.md", - "docs/brand/NAMING_ARCHITECTURE.md", - "docs/brand/README.md", - "docs/brand/SOURCE_PACKAGE.md", - "docs/brand/brand-system.json", - "docs/brand/asset-manifest.json", - "docs/content-provenance.json", - "docs/visuals/APPARATUS_ASSETS.md", - "docs/visuals/apparatus-frontispiece.svg", - "docs/visuals/elenchion-mark.svg", - "docs/visuals/visual-provenance.json", - "validation/validate_soul_gauntlet_public.py", -] - -SPECIAL = { - "prism.brand-system.v1": "array.brand-system.v1", - "prism-brand-01": "array-brand-01", - "lattice-prism-1": "bohr-array-1", - "prism-semantic-instrument-field": "array-semantic-instrument-field", - "prism-modular-control": "array-modular-control", -} - - -def replace_brand(text: str) -> str: - for old, new in SPECIAL.items(): - text = text.replace(old, new) - text = re.sub(r"\bLATTICE\b", "BOHR", text) - text = re.sub(r"\bLattice\b", "Bohr", text) - text = re.sub(r"\bPRISM\b", "ARRAY", text) - text = re.sub(r"\bPrism\b", "Array", text) - return text - - -def digest(path: Path) -> str: - return hashlib.sha256(path.read_bytes()).hexdigest() - - -def main() -> int: - touched = [] - for rel in TARGETS: - path = ROOT / rel - if not path.is_file(): - continue - before = path.read_text(encoding="utf-8") - after = replace_brand(before) - if after != before: - path.write_text(after, encoding="utf-8", newline="\n") - touched.append(rel) - - # Correct the human-readable rationale after the mechanical brand substitution. - naming_path = ROOT / "docs/brand/NAMING_ARCHITECTURE.md" - if naming_path.is_file(): - text = naming_path.read_text(encoding="utf-8") - text = re.sub( - r"\*\*Bohr\*\* is selected because it is a short scientific term for an ordered structural arrangement\. \*\*Array\*\* is selected because it is a short scientific instrument/object term associated with separating a complex input into inspectable components\.", - "**Bohr** is selected as a short, memorable scientific identity. **Array** is selected as a short scientific/computing term for an ordered collection, matching a suite composed of distinct specialist instruments.", - text, - ) - naming_path.write_text(text, encoding="utf-8", newline="\n") - - readme_path = ROOT / "README.md" - if readme_path.is_file(): - text = readme_path.read_text(encoding="utf-8") - text = text.replace( - "| Organization | **Bohr** | One-word scientific identity: an ordered structure for research and engineering instruments |", - "| Organization | **Bohr** | One-word scientific identity chosen for memorability and technical character |", - ) - text = text.replace( - "| Product suite | **Array** | One runtime, one evidence-control plane, and a portfolio of specialist gemstone instruments |", - "| Product suite | **Array** | An ordered collection: one runtime, one evidence-control plane, and specialist gemstone instruments |", - ) - readme_path.write_text(text, encoding="utf-8", newline="\n") - - brand_path = ROOT / "docs/brand/brand-system.json" - if brand_path.is_file(): - brand = json.loads(brand_path.read_text(encoding="utf-8")) - brand["schema"] = "array.brand-system.v1" - brand["brand_edition"] = "array-brand-01" - brand["organization"]["public_name"] = "Bohr" - brand["product"]["public_name"] = "Array" - brand["runtime"]["public_name"] = "Quartz" - brand["evidence_boundary"] = ( - "Brand imagery, gemstone names, and historical or scientific cues never add " - "evidential weight to technical or scientific claims." - ) - brand_path.write_text(json.dumps(brand, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - manifest_path = ROOT / "docs/brand/asset-manifest.json" - if manifest_path.is_file(): - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - for item in manifest: - source = ROOT / str(item.get("source_object_id", "")) - if source.is_file(): - item["source_file_sha256"] = digest(source) - manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - visual_path = ROOT / "docs/visuals/visual-provenance.json" - if visual_path.is_file(): - visual = json.loads(visual_path.read_text(encoding="utf-8")) - visual["brand_edition"] = "bohr-array-1" - for item in visual.get("visuals", []): - source = ROOT / str(item.get("file", "")) - if source.is_file(): - item["source_file_sha256"] = digest(source) - visual_path.write_text(json.dumps(visual, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - content_path = ROOT / "docs/content-provenance.json" - if content_path.is_file(): - data = json.loads(content_path.read_text(encoding="utf-8")) - data["brand_edition"] = "bohr-array-1" - content_path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - # One-shot payload: leave only the product files. - (ROOT / "scripts/.apply_bohr_array_rebrand.py").unlink(missing_ok=True) - (ROOT / ".github/workflows/apply-bohr-array-rebrand.yml").unlink(missing_ok=True) - print(json.dumps({"status": "APPLIED", "touched": touched}, indent=2)) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/validation/validate_soul_gauntlet_public.py b/validation/validate_soul_gauntlet_public.py index f3cc1a4a..502f0b63 100644 --- a/validation/validate_soul_gauntlet_public.py +++ b/validation/validate_soul_gauntlet_public.py @@ -115,17 +115,17 @@ def need(path: str) -> str: if forbidden in foil: fail(f"person-specific Mirror/legacy-foil prior leaked into public skill: {forbidden}") -# Prism is the public product identity. Require both the new instrument names +# Array is the public product identity. Require both the new instrument names # and the stable technical aliases rather than treating superseded public labels # as the only acceptable README wording. for token in ( - "Lattice", "Prism", "Quartz", + "Bohr", "Array", "Quartz", "**Diamond**", "`soul`, `/soul`", "**Onyx**", "`infinity-gauntlet`, `/gauntlet`", "**Opal**", "`foil`, `/foil`", ): if token not in readme: - fail(f"README missing Prism public or compatibility terminology: {token}") + fail(f"README missing Array public or compatibility terminology: {token}") for token in ( "Mirror — Adaptive Reasoning Complement", "technical skill name: `foil`", "slash command: `/foil`", "runtime modules: `tools/foil_*`", @@ -147,6 +147,6 @@ def need(path: str) -> str: print("PASS: per-component engineering specifications present") print("PASS: SKILL.md-only module directories preserved") print("PASS: Mastermind absent from runtime imports") -print("PASS: Prism public identity + legacy technical compatibility contract") +print("PASS: Array public identity + legacy technical compatibility contract") print("PASS: Mirror public identity + legacy foil compatibility contract") print("PASS: public Mirror/legacy-foil skill contains no embedded user profile") From facedd8c9591551d309cb14aa481f078d5bd6919 Mon Sep 17 00:00:00 2001 From: Kitahl <222771578+Kitahl@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:10:42 -0700 Subject: [PATCH 37/37] brand: finalize Bohr Array public identity --- docs/brand/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/brand/README.md b/docs/brand/README.md index 0e8cda70..47c3dda3 100644 --- a/docs/brand/README.md +++ b/docs/brand/README.md @@ -2,10 +2,13 @@ **Organization:** Bohr **Product suite:** Array +**Runtime:** Quartz **Product principle:** Evidence before authority **Brand edition:** `array-brand-01` **Status:** working public identity; technical compatibility preserved; legal name/trademark clearance not performed +Public identity is **Bohr / Array / Quartz**. Gemstone names are presentation aliases; technical interfaces and evidence paths remain stable. + ## File index | File | Purpose | @@ -20,7 +23,7 @@ ## Controlling rules -1. Classical, archaeological, botanical, zoological, and technical-engraving cues are product identity only. +1. Classical, archaeological, botanical, zoological, mineralogical, and technical-engraving cues are product identity only. 2. Evidence tables use clean modern panels with explicit state and source paths. 3. No fake founding year, academic seal, patent, accession number, museum endorsement, or antique evidence receipt. 4. Public names may coexist with technical aliases; source identifiers change only through an explicit compatibility migration. @@ -31,8 +34,8 @@ - [x] README redesigned. - [x] GitHub Pages homepage redesigned. -- [x] Company and product hierarchy established. -- [x] Ten core modules renamed publicly with compatibility aliases. +- [x] Company, product, and runtime hierarchy established. +- [x] Ten core modules renamed publicly with gemstone compatibility aliases. - [x] Hermes-derived runtime, adversarial review, and research candidates included with bounded status. - [x] Original frontispiece and calibration mark created. - [x] Asset and content provenance recorded.