mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-22 06:39:33 +00:00
Merge pull request #556 from razzant/claude/docs-consolidation-20260902
Some checks are pending
CI / quick-test (push) Waiting to run
CI / full-test (macos-latest) (push) Waiting to run
CI / full-test (ubuntu-latest) (push) Waiting to run
CI / full-test (windows-latest) (push) Waiting to run
CI / betterleaks-platform-smoke (macos-latest) (push) Waiting to run
CI / betterleaks-platform-smoke (ubuntu-latest) (push) Waiting to run
CI / betterleaks-platform-smoke (windows-latest) (push) Waiting to run
CI / integration-test (push) Waiting to run
CI / skill-smoke (macos-latest) (push) Waiting to run
CI / skill-smoke (ubuntu-latest) (push) Waiting to run
CI / skill-smoke (windows-latest) (push) Waiting to run
CI / marker-guards (push) Waiting to run
CI / docker-ui-smoke (push) Waiting to run
CI / build (dmg, macos-latest, macos-arm64, syft_1.50.0_darwin_arm64.tar.gz, syft, e32fdb9d47823fa633748a1efca2528fd77c37469ea93c9e40ab835da44e4cce) (push) Blocked by required conditions
CI / build (tar.gz, ubuntu-latest, linux-x86_64, syft_1.50.0_linux_amd64.tar.gz, syft, bf7b29ff57f06da30918266a0e1c2885a8f99784798d1bdb1628886aa015d788) (push) Blocked by required conditions
CI / build (zip, windows-latest, windows-x64, syft_1.50.0_windows_amd64.zip, syft.exe, 815ee6973ec5dff6a671d7f41b0e78835a8c45b91d5a39f4743ea1cee833d3be) (push) Blocked by required conditions
Claudexor platform gate (API keys — subscription auth NOT covered) / live · windows-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · codex · API key only, subscription NOT covered (push) Waiting to run
CI / ui-smoke (push) Waiting to run
CI / docker-portable-test (push) Waiting to run
CI / release-preflight (push) Blocked by required conditions
CI / vendor-package-smoke (push) Blocked by required conditions
CI / release (push) Blocked by required conditions
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · macos-latest · exact managed runtime, fake harness, no model (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · ubuntu-latest · exact managed runtime, fake harness, no model (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · windows-latest · exact managed runtime, fake harness, no model (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · ubuntu-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Some checks are pending
CI / quick-test (push) Waiting to run
CI / full-test (macos-latest) (push) Waiting to run
CI / full-test (ubuntu-latest) (push) Waiting to run
CI / full-test (windows-latest) (push) Waiting to run
CI / betterleaks-platform-smoke (macos-latest) (push) Waiting to run
CI / betterleaks-platform-smoke (ubuntu-latest) (push) Waiting to run
CI / betterleaks-platform-smoke (windows-latest) (push) Waiting to run
CI / integration-test (push) Waiting to run
CI / skill-smoke (macos-latest) (push) Waiting to run
CI / skill-smoke (ubuntu-latest) (push) Waiting to run
CI / skill-smoke (windows-latest) (push) Waiting to run
CI / marker-guards (push) Waiting to run
CI / docker-ui-smoke (push) Waiting to run
CI / build (dmg, macos-latest, macos-arm64, syft_1.50.0_darwin_arm64.tar.gz, syft, e32fdb9d47823fa633748a1efca2528fd77c37469ea93c9e40ab835da44e4cce) (push) Blocked by required conditions
CI / build (tar.gz, ubuntu-latest, linux-x86_64, syft_1.50.0_linux_amd64.tar.gz, syft, bf7b29ff57f06da30918266a0e1c2885a8f99784798d1bdb1628886aa015d788) (push) Blocked by required conditions
CI / build (zip, windows-latest, windows-x64, syft_1.50.0_windows_amd64.zip, syft.exe, 815ee6973ec5dff6a671d7f41b0e78835a8c45b91d5a39f4743ea1cee833d3be) (push) Blocked by required conditions
Claudexor platform gate (API keys — subscription auth NOT covered) / live · windows-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · codex · API key only, subscription NOT covered (push) Waiting to run
CI / ui-smoke (push) Waiting to run
CI / docker-portable-test (push) Waiting to run
CI / release-preflight (push) Blocked by required conditions
CI / vendor-package-smoke (push) Blocked by required conditions
CI / release (push) Blocked by required conditions
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · macos-latest · exact managed runtime, fake harness, no model (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · ubuntu-latest · exact managed runtime, fake harness, no model (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · windows-latest · exact managed runtime, fake harness, no model (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · ubuntu-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Actualize and shrink the two resident governance documents. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
commit
83a69e673a
14 changed files with 3016 additions and 5188 deletions
16
BIBLE.md
16
BIBLE.md
|
|
@ -106,11 +106,17 @@ on every restart, but one personality that remembers its path.
|
|||
(confirmed metadata / route-fingerprinted owner acknowledgement), never an
|
||||
assumed default. The mode is permitted only when the tier-0 core (system
|
||||
prompt, BIBLE.md, identity.md, scratchpad, durable knowledge index,
|
||||
recent-dialogue horizon) stays always-loaded in full; the agent's self-body
|
||||
documentation (ARCHITECTURE.md, DEVELOPMENT.md) stays RESIDENT in owner-`max`,
|
||||
and narrowing it for a task-class is itself an owner-level horizon decision,
|
||||
not a silent default; reduction is by relocation to on-demand reads with a
|
||||
visible pointer, or by deeper consolidation, never silent truncation; the
|
||||
recent-dialogue horizon) stays always-loaded in full; `docs/ARCHITECTURE.md`
|
||||
stays RESIDENT in owner-`max` for every task class, and `docs/DEVELOPMENT.md`
|
||||
stays resident when the work addresses Ouroboros's own body — a binding to an
|
||||
external working tree, a subagent, or an external API/CLI/scheduled surface
|
||||
receives a visible on-demand pointer instead unless the task itself
|
||||
explicitly requires the handbook (that per-task requirement wins in either
|
||||
direction), while project membership alone is not the signal (an owner-level
|
||||
horizon decision, not a silent default; any further narrowing for a task
|
||||
class is likewise the owner's decision);
|
||||
reduction is by relocation to on-demand reads with a visible pointer, or by
|
||||
deeper consolidation, never silent truncation; the
|
||||
memory horizon is preserved (only granularity varies); whether the blocking
|
||||
scope reviewer applies at all is governed by this same owner-selected mode as an
|
||||
explicit policy coupling under P3 — in `max` the ≥1M blocking scope gate runs, in
|
||||
|
|
|
|||
3616
docs/ARCHITECTURE.md
3616
docs/ARCHITECTURE.md
File diff suppressed because one or more lines are too long
|
|
@ -129,7 +129,7 @@ or Intent/Scope checklists are.
|
|||
| 15 | Changing `supervisor/git_ops.py`, `launcher.py`, `server.py`, `ouroboros/tools/review_helpers.py`, `ouroboros/tools/git.py`, tests, or evolution scheduling/checkpoint code? | Prove two invariants before review spend: (1) pytest/preflight cannot mutate the live repo or live `data/` (`OUROBOROS_DATA_DIR` / `OUROBOROS_SETTINGS_PATH` must be isolated, and `OUROBOROS_MANAGED_BY_LAUNCHER` must not leak into test subprocesses); (2) autonomous restart/reset cannot erase active evolution work — it must either land a reviewed local commit or preserve a rescue/transaction recovery pointer and pause/stop the campaign. |
|
||||
| 16 | Changing `devtools/`? | Keep operator tools isolated from runtime and package discovery: no runtime, web, or release imports from `devtools/`; no generated outputs under `repo/` or live `data/`; no secrets printed or committed. A touched devtool is reviewable executable code even when unrelated devtools stay compact in broad review packs. Domain-specific launch, routing, and methodology guidance stays with the relevant devtool. |
|
||||
| 17 | Diff spawns OS processes (`subprocess.Popen` / `mp.Process` without a bounded wait)? | Route it through `ouroboros.process_custody.spawn_supervised` (or `record_process` write-through) with an explicit `scope` (`task`/`session`/`daemon`) so the orphan reaper can find it; `tests/test_process_custody.py` enforces the allowlist. |
|
||||
| 18 | New/changed **test** spawns a real OS process, binds a real port, or mutates a module-level global/registry? | CI **and the hermetic commit gate** (`preflight_runner.py`, v6.88.0) both run the suite as a parallel `-m "not serial" -n auto` pass plus a serial pass. Mark such a test `@pytest.mark.serial` (or add its file to `_SERIAL_TEST_FILES` in `tests/conftest.py`) — otherwise it flakes on kill/port-reclaim timing, or crashes a worker, which the gate reports as a `PARALLEL_WORKER_CRASH` **hard block** (never retried, never a flake). Static screen before you commit: grep the new test for `subprocess.Popen`/`subprocess.run`, socket or fixed-port binding, `start_new_session=True`, and un-monkeypatched module globals — but syntax cannot prove mocking, so still classify it semantically. The `serial` marker is the fix for *crashes and interference only*: the parallel pass also carries a 300s per-test timeout, and the serial pass carries none, so marking a merely-slow test `serial` moves the hang into the one lane that cannot bound it — make it faster or split it instead. Every other test must stay parallel-safe: `tmp_path` (not fixed `/tmp/...` paths), `monkeypatch.setenv`/`setattr` (not bare `os.environ[...] =`), no execution-order assumptions, reset any mutated module global. See `docs/DEVELOPMENT.md` "The commit gate mirrors the CI split". |
|
||||
| 18 | New/changed **test** spawns a real OS process, binds a real port, or mutates a module-level global/registry? | CI **and the hermetic commit gate** (`preflight_runner.py`) both run the suite as a parallel `-m "not serial" -n auto` pass plus a serial pass. Mark such a test `@pytest.mark.serial` (or add its file to `_SERIAL_TEST_FILES` in `tests/conftest.py`) — otherwise it flakes on kill/port-reclaim timing, or crashes a worker, which the gate reports as a `PARALLEL_WORKER_CRASH` **hard block** (never retried, never a flake). Static screen before you commit: grep the new test for `subprocess.Popen`/`subprocess.run`, socket or fixed-port binding, `start_new_session=True`, and un-monkeypatched module globals — but syntax cannot prove mocking, so still classify it semantically. The `serial` marker is the fix for *crashes and interference only*: the parallel pass also carries a 300s per-test timeout, and the serial pass carries none, so marking a merely-slow test `serial` moves the hang into the one lane that cannot bound it — make it faster or split it instead. Every other test must stay parallel-safe: `tmp_path` (not fixed `/tmp/...` paths), `monkeypatch.setenv`/`setattr` (not bare `os.environ[...] =`), no execution-order assumptions, reset any mutated module global. See `docs/DEVELOPMENT.md` "The commit gate mirrors the CI split". |
|
||||
|
||||
Rule: read before write. Never reconstruct `VERSION`, `pyproject.toml`
|
||||
`version`, or the README badge from memory — one stale reconstruction creates
|
||||
|
|
@ -162,13 +162,13 @@ Used by `commit_reviewed` for all changes to the Ouroboros repository.
|
|||
| 4 | code_quality | Careful code review: bugs, logic errors, crashes, regressions, race conditions, resource leaks? | critical |
|
||||
| 5 | security_issues | Security vulnerabilities: injection, path traversal, secret leakage, unsafe operations? | critical |
|
||||
| 6 | tests_affected | Did code logic change without corresponding test changes? (PASS if only docs/config/memory changed, or if tests already cover the new behavior.) **Critical FAIL requires all three:** (a) name a specific behavior, code path, symbol, or failure scenario that THIS diff introduces or changes; (b) explain why existing or newly staged tests do NOT catch that specific scenario; (c) the gap is concrete, not speculative. Adjacent tests in the same module or for the same feature count as coverage. Requiring an additional overlapping selector/unit/e2e test is only justified when a second distinct failure mode is named explicitly. If the only concern is "I'd feel better with one more test," that is advisory, not critical. | critical |
|
||||
| 7 | architecture_doc | New module, endpoint, or data flow added but ARCHITECTURE.md not updated? (Write "Not applicable" with PASS if no architectural change.) | critical |
|
||||
| 7 | architecture_doc | Did the change REPLACE the current description of every node it touched in `docs/ARCHITECTURE.md` / `docs/DEVELOPMENT.md` (map rows, flows, invariants)? PASS when a new module, endpoint or data flow gains its one-line row and touched descriptions read as current truth; PASS with "Not applicable" when nothing architectural changed. FAIL when the edit only appended a version stamp, a decision codename, a "used to / previously" narrative, or a second statement of a fact already owned elsewhere in the docs (P6 present-tense map, P7 one home per fact — see DEVELOPMENT.md "Documentation contract"). | critical |
|
||||
| 8 | version_bump | Does this commit leave VERSION unchanged, or leave release artifacts out of sync? Exception: external contribution commits are version-neutral per P9 (proposed) / CONTRIBUTING.md — the maintainer's integration commit performs the bump; unchanged carriers on a contributor PR are correct, not a FAIL. | critical |
|
||||
| 9 | changelog_and_badge | VERSION bumped but README.md badge or changelog not updated? (PASS if VERSION not bumped.) | critical |
|
||||
| 10 | tool_registration | New tool function added but not exported in `get_tools()` OR missing explicit entry in `ouroboros/safety.py::TOOL_POLICY`? (PASS if no new tool.) Both surfaces are required: `get_tools()` makes the tool visible; `TOOL_POLICY` makes the per-call safety routing explicit and is guarded by the `test_tool_policy_covers_all_builtin_tools` invariant. | critical |
|
||||
| 11 | context_building | New data/memory files that should appear in LLM context (context.py) but don't? | advisory |
|
||||
| 12 | knowledge_index | Knowledge base topics changed but memory/knowledge/index-full.md not updated? | advisory |
|
||||
| 13 | self_consistency | Does this change affect behavior described in `BIBLE.md`, `prompts/`, `docs/`, or this checklist itself? Check explicitly: (a) version in `ARCHITECTURE.md` header matches `VERSION` file; (b) every tool name `prompts/SYSTEM.md` or `prompts/CONSCIOUSNESS.md` mentions exists in `get_tools()` (or the background whitelist) and means the same thing there — completeness is NOT required, the schemas are the catalog; a prompt edit must not restate a tool schema or a structurally enforced gate, and a prompt that gains a sentence loses an equivalent one; (c) JSONL log/memory file formats described in `ARCHITECTURE.md` match all readers/writers; (d) any behavioral change reflected in `prompts/CONSCIOUSNESS.md` if it affects background loop behavior; (e) DEVELOPMENT.md rules still accurate after the change. Severity must follow the shared `Critical surface whitelist` below — release metadata, tool schema, module map, behavioural documentation, or safety contracts are critical; commentary/prose/stylistic mismatches are advisory. | critical |
|
||||
| 13 | self_consistency | Does this change affect behavior described in `BIBLE.md`, `prompts/`, `docs/`, or this checklist itself? Check explicitly: (a) version in `ARCHITECTURE.md` header matches `VERSION` file; (b) every tool name `prompts/SYSTEM.md` or `prompts/CONSCIOUSNESS.md` mentions exists in `get_tools()` (or the background whitelist) and means the same thing there — completeness is NOT required, the schemas are the catalog; a prompt edit must not restate a tool schema or a structurally enforced gate, and a prompt that gains a sentence loses an equivalent one; a prompt edit is never an incident patch — no keyword, regex or branch added so the model picks a particular tool or route (P5, DEVELOPMENT.md "LLM-first affordances"); and when the diff touches the code that assembles a prompt (`context.py`, review packet/template builders, advisory, consciousness, plan packet, loop nudges) the injected prompt text and that code still agree; (c) JSONL log/memory file formats described in `ARCHITECTURE.md` match all readers/writers; (d) any behavioral change reflected in `prompts/CONSCIOUSNESS.md` if it affects background loop behavior; (e) DEVELOPMENT.md rules still accurate after the change. Severity must follow the shared `Critical surface whitelist` below — release metadata, tool schema, module map, behavioural documentation, or safety contracts are critical; commentary/prose/stylistic mismatches are advisory. | critical |
|
||||
| 14 | light_external_artifacts | If tool/runtime policy changed, does light mode still allow external user deliverables via `user_files`, task-scoped `task_drive`/`artifact_store`, and process `outputs` while blocking Ouroboros repo/control-plane mutation? (The external `claude_code_edit` cwd lane retired with the tool — D10.) Do review prompts avoid recommending `runtime_data/uploads` or skill payloads as generic artifact transport? | critical |
|
||||
| 15 | cross_platform | Does the diff use platform-specific APIs (`os.kill`, `os.setsid`, `os.killpg`, `os.getpgid`, `fcntl`, `msvcrt`, `signal.SIGKILL`, `signal.SIGTERM`, `subprocess` with `start_new_session`/`creationflags`, hardcoded `/` or `\\` in filesystem paths) outside of `ouroboros/platform_layer.py`? Does it import Unix-only or Windows-only modules (`fcntl`, `msvcrt`, `winreg`, `resource`) at any level without a platform guard (`sys.platform`/`IS_WINDOWS` check)? | critical |
|
||||
| 16 | changelog_accuracy | Do the exact wording, test counts, and minor description details in the README Version History row match what the diff actually does? Wording drift, off-by-one test counts, minor inaccuracies in descriptive prose — these belong here, NOT in `self_consistency` or `changelog_and_badge`. This item exists so reviewers have a dedicated advisory bucket for prose-level changelog imprecision that does not affect release metadata, runtime behavior, or safety contracts. | advisory |
|
||||
|
|
|
|||
4348
docs/DEVELOPMENT.md
4348
docs/DEVELOPMENT.md
File diff suppressed because it is too large
Load diff
|
|
@ -152,8 +152,14 @@ def reference_doc_sections(
|
|||
``context_mode`` is the OWNER context mode and decides only the ARCHITECTURE
|
||||
form (full in max, nav map in low — D-ARCH, owner 2026-08-08).
|
||||
``include_development`` is the caller's mode-independent decision whether the
|
||||
self-engineering handbook is inline (self-body/self-mod/evolution work) or an
|
||||
on-demand pointer (project tasks — folder or not — and external surfaces).
|
||||
self-engineering handbook is inline (work on Ouroboros's own body) or an
|
||||
on-demand pointer. The structural signal is the active repo binding, never
|
||||
project membership: an external worktree, a subagent, or an api/cli/scheduled
|
||||
surface gets the pointer, while a direct turn inside a project room with no
|
||||
workspace bound keeps the handbook. An explicit ``context_requires_development``
|
||||
on the task wins in either direction. When it is absent, a truthy
|
||||
``context_requires_self_body_docs`` on the task or its contract keeps the handbook
|
||||
even for an external surface (see ``context.py`` D-DEV).
|
||||
"""
|
||||
parts: List[str] = []
|
||||
on_demand: List[str] = []
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Claudexor v3 control-plane transport.
|
||||
|
||||
Pure I/O, like every gateway (docs/DEVELOPMENT.md "Gateway Rules"): discover the
|
||||
Pure I/O, like every gateway (docs/DEVELOPMENT.md "Gateway Boundary Pattern"): discover the
|
||||
loopback daemon, negotiate the protocol, and translate the typed ``/v2`` surface
|
||||
into Python primitives. No routing, no policy, no harness identity branches — the
|
||||
caller asks for a CAPABILITY (an access profile, an opaque route id) and reads the
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
Project conversion stores a reference to the original owner row on the immutable
|
||||
task binding. A Project room projects that row instead of copying it into
|
||||
``chat.jsonl``. Terminal task projections append to that same canonical biography;
|
||||
``chat_annotations.jsonl`` remains presentation-only and never owns routing or
|
||||
Project state.
|
||||
``chat_annotations.jsonl`` is presentation-first and owns no Project state; its
|
||||
token-bound ``needs_manual_target`` decision card is the one routing-authority
|
||||
exception.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ def _build_advisory_prompt(
|
|||
expected_items = prompt_context.get("expected_items")
|
||||
checklist_name = "Skill Review Checklist" if review_surface == "skill" else "Repo Commit Checklist"
|
||||
if governance_by_retrieval:
|
||||
# agent_session delivery: do NOT inline the ~830KB governance bodies —
|
||||
# agent_session delivery: do NOT inline the governance bodies (hundreds of KB) —
|
||||
# each becomes a resolvable absolute pointer plus a mandatory-read
|
||||
# instruction, and the session reads the docs itself with its own
|
||||
# tools. The authority for this form is the plan-review agent_session
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ creates and deletes baseline refs and worktree registrations in the target's
|
|||
|
||||
Reports per-run provision (baseline build + worktree checkout) and teardown
|
||||
wall-clock, plus tree shape (file count, on-disk size). The delegated-snapshot
|
||||
lifecycle this measures is documented in docs/ARCHITECTURE.md (C1 section).
|
||||
lifecycle this measures is documented in docs/ARCHITECTURE.md,
|
||||
"Delegated subagents".
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -1167,7 +1167,7 @@ def test_schema_and_docs_split_git_staging_from_payload_live_apply():
|
|||
assert "applied LIVE into the non-Git payload" in decision
|
||||
arch = (pathlib.Path(__file__).resolve().parents[1] / "docs" /
|
||||
"ARCHITECTURE.md").read_text(encoding="utf-8")
|
||||
assert "differs is the staging substrate" in arch
|
||||
assert "staging substrate differs" in arch
|
||||
assert "A SKILL-PAYLOAD target captures through the payload adapter" in arch
|
||||
assert "QUEUES the extension reconcile request" in arch
|
||||
|
||||
|
|
|
|||
|
|
@ -346,3 +346,188 @@ def test_prompt_tool_names_resolve_to_registered_tools(tmp_path):
|
|||
f"prompts/CONSCIOUSNESS.md names bare identifiers that are neither registered tools "
|
||||
f"nor classified non-tool identifiers: {sorted(unresolved_bare)}"
|
||||
)
|
||||
|
||||
|
||||
# --- Documentation contract enforcement (DEVELOPMENT.md "Documentation contract") ---
|
||||
#
|
||||
# Three deterministic checks keep the two resident docs a present-tense map:
|
||||
# 1. residue: version stamps, decision codenames and "used to / previously"
|
||||
# narrative may only SHRINK per `## ` section (baseline below, like the code
|
||||
# size ratchet — a section that reaches zero may never grow again);
|
||||
# 2. the endpoint table mirrors the executable route registries;
|
||||
# 3. the settings table mirrors `config.SETTINGS_DEFAULTS` (env-only and retired
|
||||
# rows are declared, not guessed).
|
||||
# Language-tagged code fences (```yaml, ```python …) are examples and are not
|
||||
# scanned; the plain ``` fence holding the §1 module tree IS scanned. The first
|
||||
# ARCHITECTURE line carries the release version by contract and is skipped, as
|
||||
# are DEVELOPMENT's "Mutable external-fact inventory" (dated provenance is the
|
||||
# rule there) and the "Documentation contract" section that quotes the markers.
|
||||
|
||||
DOC_RESIDUE_PATTERNS = {
|
||||
"version_stamp": r"\((?:v\d+\.\d+(?:\.\d+)?(?:-rc\.\d+)?)\)",
|
||||
"version_narrative": r"\b(?:since|before|pre-)v\d+",
|
||||
"narrative": r"\b(?:used to|previously|formerly|was deleted|replaces the earlier|gate[- ]round|round \d+)\b",
|
||||
"codename_paren": r"\((?:GR|AR|BR|CR|D|Q|S|HQ|C|B)\d+[^)]{0,24}\)",
|
||||
"codename_word": r"\bPoltergeist\b|\bphase [A-C]\d?\b|owner(?:-| )(?:decision|ratif)",
|
||||
}
|
||||
DOC_RESIDUE_SKIPPED_SUBSECTIONS = {
|
||||
"docs/DEVELOPMENT.md": ("Mutable external-fact inventory", "Documentation contract"),
|
||||
}
|
||||
|
||||
|
||||
def doc_residue_counts(rel: str, text: str) -> dict:
|
||||
"""Per-`## ` section counts of residue markers (see DOC_RESIDUE_PATTERNS)."""
|
||||
counts: dict = {}
|
||||
section = "(preamble)"
|
||||
fence_lang = None
|
||||
skipping = False
|
||||
skipped = DOC_RESIDUE_SKIPPED_SUBSECTIONS.get(rel, ())
|
||||
for lineno, line in enumerate(text.split("\n"), 1):
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith("```"):
|
||||
fence_lang = (stripped[3:].strip() or "") if fence_lang is None else None
|
||||
continue
|
||||
if fence_lang: # language-tagged example block
|
||||
continue
|
||||
if fence_lang is None and line.startswith("## "):
|
||||
section, skipping = line.strip(), False
|
||||
if fence_lang is None and line.startswith("### "):
|
||||
skipping = any(name in line for name in skipped)
|
||||
if skipping or (rel == "docs/ARCHITECTURE.md" and lineno == 1):
|
||||
continue
|
||||
for kind, pattern in DOC_RESIDUE_PATTERNS.items():
|
||||
hits = len(re.findall(pattern, line))
|
||||
if hits:
|
||||
counts.setdefault(section, {k: 0 for k in DOC_RESIDUE_PATTERNS})[kind] += hits
|
||||
return counts
|
||||
|
||||
|
||||
# Zero-residue ceilings recorded after the 07b53365 cleanup: an absent section or kind is a
|
||||
# zero ceiling, so both documents are held at zero everywhere. A section row exists only
|
||||
# while a re-baseline records a temporary non-zero ceiling, and such a row may only shrink;
|
||||
# an absent section stays at zero.
|
||||
DOC_RESIDUE_BASELINE = {
|
||||
"docs/ARCHITECTURE.md": {},
|
||||
"docs/DEVELOPMENT.md": {},
|
||||
}
|
||||
|
||||
|
||||
def test_resident_docs_residue_only_shrinks():
|
||||
for rel, baseline in DOC_RESIDUE_BASELINE.items():
|
||||
current = doc_residue_counts(rel, _read(rel))
|
||||
for section, counts in current.items():
|
||||
allowed = baseline.get(section, {})
|
||||
for kind, hits in counts.items():
|
||||
assert hits <= allowed.get(kind, 0), (
|
||||
f"{rel} {section!r}: {kind} residue grew to {hits} (baseline "
|
||||
f"{allowed.get(kind, 0)}); replace the node's description instead of "
|
||||
"appending history (DEVELOPMENT.md 'Documentation contract')"
|
||||
)
|
||||
|
||||
|
||||
def _architecture_section(text: str, heading_prefix: str) -> str:
|
||||
lines = text.split("\n")
|
||||
start = next(i for i, l in enumerate(lines) if l.startswith(heading_prefix))
|
||||
end = next((i for i in range(start + 1, len(lines)) if lines[i].startswith("## ")), len(lines))
|
||||
return "\n".join(lines[start:end])
|
||||
|
||||
|
||||
def test_architecture_endpoint_table_mirrors_route_registries(tmp_path):
|
||||
"""Every mounted browser/CLI route and Host Service route has exactly one table
|
||||
row in ARCHITECTURE §4, and no row names a route that is not mounted."""
|
||||
from ouroboros.gateway.endpoint_index import HTTP_ENDPOINTS
|
||||
from ouroboros.gateway import files as gateway_files
|
||||
|
||||
section = _architecture_section(_read("docs/ARCHITECTURE.md"), "## 4.")
|
||||
rows = re.findall(r"^\| (GET|POST|PUT|PATCH|DELETE|ANY|WS|STATIC) \| `([^`]+)` \|", section, re.M)
|
||||
host_prefix = "127.0.0.1:${OUROBOROS_HOST_SERVICE_PORT:-8767}"
|
||||
documented_public = {f"{m} {p}" for m, p in rows if not p.startswith(host_prefix)}
|
||||
documented_host = {f"{m} {p[len(host_prefix):]}" for m, p in rows if p.startswith(host_prefix)}
|
||||
|
||||
expected_public = set(HTTP_ENDPOINTS)
|
||||
for route in gateway_files.file_browser_routes():
|
||||
for method in sorted(getattr(route, "methods", None) or []):
|
||||
if method in ("HEAD", "OPTIONS"):
|
||||
continue
|
||||
expected_public.add(f"{method} {route.path}")
|
||||
# server-level surfaces that are not gateway routes but belong in the map
|
||||
expected_public |= {"GET /", "WS /ws", "STATIC /static/*"}
|
||||
# /api/extensions/{skill}/{rest:path} is mounted for every verb and documented once as ANY
|
||||
expected_public = {e for e in expected_public if not e.endswith("/api/extensions/{skill}/{rest:path}")}
|
||||
expected_public.add("ANY /api/extensions/{skill}/{rest:path}")
|
||||
|
||||
from starlette.routing import Route, WebSocketRoute
|
||||
|
||||
from ouroboros.gateway.host_service import create_host_service_app
|
||||
|
||||
expected_host = set()
|
||||
for route in create_host_service_app(tmp_path).routes:
|
||||
if isinstance(route, WebSocketRoute):
|
||||
expected_host.add(f"WS {route.path}")
|
||||
elif isinstance(route, Route):
|
||||
if route.methods is None:
|
||||
expected_host.add(f"ANY {route.path}")
|
||||
else:
|
||||
expected_host.update(
|
||||
f"{method} {route.path}"
|
||||
for method in route.methods
|
||||
if method not in {"HEAD", "OPTIONS"}
|
||||
)
|
||||
else:
|
||||
raise AssertionError(f"Unhandled Host Service route type: {type(route).__name__}")
|
||||
|
||||
assert documented_public == expected_public, (
|
||||
f"missing rows: {sorted(expected_public - documented_public)}; "
|
||||
f"stale rows: {sorted(documented_public - expected_public)}"
|
||||
)
|
||||
assert documented_host == expected_host, (
|
||||
f"missing host rows: {sorted(expected_host - documented_host)}; "
|
||||
f"stale host rows: {sorted(documented_host - expected_host)}"
|
||||
)
|
||||
assert len(rows) == len(documented_public) + len(documented_host), "duplicate endpoint rows"
|
||||
|
||||
|
||||
# Rows the settings table documents on purpose although `config.SETTINGS_DEFAULTS`
|
||||
# has no such key: operator env-only levers (never a settings.json carrier) and the
|
||||
# retired alias whose migration the table still explains (pinned by test_review_cycles).
|
||||
SETTINGS_TABLE_ENV_ONLY_ROWS = frozenset({
|
||||
"OUROBOROS_TRUST_NONLOCAL_BIND_WITHOUT_PASSWORD", "OUROBOROS_DISABLE_MANAGED_UPDATES",
|
||||
"OUROBOROS_PRESENTATION", "OUROBOROS_USER_FILES_ROOT", "OUROBOROS_OBSERVABILITY_KEEP_RAW",
|
||||
"OUROBOROS_OBSERVABILITY_RETENTION_DAYS", "OUROBOROS_REVIEW_MODEL_TIMEOUT_SEC",
|
||||
"OUROBOROS_REVIEW_MAX_TOKENS", "OUROBOROS_PREFLIGHT_TIMEOUT_SEC", "OUROBOROS_PREFLIGHT_SERIAL",
|
||||
"OUROBOROS_BUNDLE_DIR",
|
||||
})
|
||||
SETTINGS_TABLE_RETIRED_ROWS = frozenset({"OUROBOROS_ACCEPTANCE_MAX_IMPROVEMENT_PASSES"})
|
||||
|
||||
|
||||
def _normalize_default_cell(cell: str) -> str:
|
||||
value = cell.strip()
|
||||
value = re.sub(r"^`(.*)`$", r"\1", value)
|
||||
if value in ('""', "(empty)", "(unset)", "unset", ""):
|
||||
return ""
|
||||
value = re.sub(r'^"(.*)"$', r"\1", value)
|
||||
return value.split()[0].lower() if value else ""
|
||||
|
||||
|
||||
def test_architecture_settings_table_mirrors_config_defaults():
|
||||
"""Every `config.SETTINGS_DEFAULTS` key has one row in ARCHITECTURE's Default
|
||||
settings table with the shipped default; every other row is a declared env-only
|
||||
lever or retired alias."""
|
||||
from ouroboros import config
|
||||
|
||||
section = _architecture_section(_read("docs/ARCHITECTURE.md"), "## 7.")
|
||||
table = section[section.index("### Default settings"):]
|
||||
rows = re.findall(r"^\| ([A-Z][A-Z0-9_]+) \| ([^|]*?) \|", table, re.M)
|
||||
keys = [k for k, _ in rows]
|
||||
assert len(keys) == len(set(keys)), f"duplicate settings rows: {sorted(k for k in set(keys) if keys.count(k) > 1)}"
|
||||
documented = set(keys)
|
||||
expected = set(config.SETTINGS_DEFAULTS)
|
||||
assert expected <= documented, f"settings missing from the table: {sorted(expected - documented)}"
|
||||
undeclared = documented - expected - SETTINGS_TABLE_ENV_ONLY_ROWS - SETTINGS_TABLE_RETIRED_ROWS
|
||||
assert not undeclared, f"table rows that are neither shipped defaults nor declared env-only/retired: {sorted(undeclared)}"
|
||||
mismatched = [
|
||||
(key, cell, str(config.SETTINGS_DEFAULTS[key]))
|
||||
for key, cell in rows
|
||||
if key in expected and _normalize_default_cell(cell) != str(config.SETTINGS_DEFAULTS[key]).lower()
|
||||
]
|
||||
assert not mismatched, f"documented default differs from config.SETTINGS_DEFAULTS: {mismatched}"
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ def test_child_task_handoff_results_never_truncated():
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_repo_read_default_max_lines_is_2000(tmp_path):
|
||||
"""Default max_lines must be 2000 so ARCHITECTURE.md (~1285 lines) fits in one call."""
|
||||
"""Default max_lines must be 2000 so ARCHITECTURE.md fits in one call."""
|
||||
import inspect
|
||||
from ouroboros.tools.core import _repo_read
|
||||
sig = inspect.signature(_repo_read)
|
||||
|
|
@ -212,7 +212,7 @@ def test_repo_read_schema_default_is_2000():
|
|||
|
||||
|
||||
def test_repo_read_can_read_architecture_md_in_one_call(tmp_path):
|
||||
"""A file of ARCHITECTURE.md length (~1285 lines) is returned fully with default max_lines."""
|
||||
"""A file longer than the historical 1050-line default is returned fully with default max_lines."""
|
||||
from ouroboros.tools.core import _repo_read
|
||||
# Simulate a file slightly longer than the old 1050-line default
|
||||
n_lines = 1300
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ _WIZARD_ON_SUMMARY_JS = "() => (document.querySelector('.step-title')?.textConte
|
|||
|
||||
@pytest.mark.ui_browser
|
||||
def test_ui_smoke_agents_panel_wizard_finish_judges_the_roster(direct_server_with_data):
|
||||
"""First-run wizard (docs/ARCHITECTURE.md §3): an unrouted entry added on the Agents step
|
||||
"""First-run wizard (docs/ARCHITECTURE.md §2): an unrouted entry added on the Agents step
|
||||
does not block Continue; Finish on the summary reports it and, back on Agents, the card
|
||||
is already tinted and self-naming; the fix reconciles line and tint together and the
|
||||
second Finish passes the wizard's own checks and enters saving (the save's provider
|
||||
|
|
|
|||
|
|
@ -2882,7 +2882,8 @@ def test_ui_smoke_v679_subagent_depth_zero_round_trips_through_settings(direct_s
|
|||
full page reload — and pins the three neighbouring states so the fix cannot silently break
|
||||
them: 0 (no delegation), a normal positive value, and empty (falls back, does not persist
|
||||
an invalid value). Screenshots are written for vision inspection; a saved screenshot is
|
||||
not verification on its own (docs/DEVELOPMENT.md "Browser/mobile verification").
|
||||
not verification on its own (docs/DEVELOPMENT.md "Responsive and accessible
|
||||
behavior").
|
||||
"""
|
||||
pytest.importorskip("playwright.sync_api", reason="Playwright is not installed")
|
||||
from playwright.sync_api import Error as PlaywrightError
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue