Compare commits

4 Commits

Author SHA1 Message Date
Svrnty c3f6793ce1 docs: reconcile vision workboard readiness 2026-06-12 08:50:44 -04:00
Svrnty 2da8a6d390 CC: add Vision package Docker context 2026-06-06 12:30:02 -04:00
Svrnty d62c5eb744 CC: prepare generic VISION package candidate 2026-06-06 08:25:14 -04:00
Svrnty 8045f46b06 chore: add Cortex child governance 2026-06-01 09:54:26 -04:00
29 changed files with 1690 additions and 7 deletions
+12
View File
@@ -0,0 +1,12 @@
.git
.pytest_cache
.mypy_cache
.ruff_cache
.venv
__pycache__
*.pyc
outputs
worktrees
tests
docs
candidate-manifests
+28
View File
@@ -0,0 +1,28 @@
# Svrnty Vision Child Workspace Rules
This workspace is child-local under the Cortex OS umbrella.
It is not Cortex OS Core authority. Promotion into Core requires a governed Core route.
## Authority Order
1. `/home/svrnty/workspaces/cortex-os/core` active SOT.
2. `/home/svrnty/workspaces/cortex-os/core/AGENTS.md`.
3. This file.
4. `README.md`, `WORKBOARD.yaml`, and local tools.
## Editing Rule
Keep work inside this workspace unless Core explicitly routes promotion.
After editing, run:
```bash
python3 tools/validate_svrnty_vision_child.py
```
## Protected Boundaries
- Do not mutate `../core/` from this workspace.
- Do not mutate sibling repositories.
- Do not import this workspace into Core source.
+37
View File
@@ -0,0 +1,37 @@
# Svrnty Vision Context
`svrnty-vision` is a child-local Cortex OS VISION package candidate for visual
perception and visual production tools. It is not Core authority, not Seed
installed, not Runtime, not Profile Exposure, and not provider admission.
## Terms
Visual Perception Package Candidate
: A child-local candidate package that owns pixel/media perception surfaces under
canonical sense `VISION`.
VISION Sense Family
: The canonical sense family for seeing. It can include textual/source reading
through `/research` and pixel/media perception through `/vision`.
Visual Evidence
: A normalized output record produced by a VISION tool. It discloses package,
tool, source, provider mode, retention, observed content, extracted claims,
confidence, caveats, timestamp, and validation status.
Vision Tool Candidate
: A granular tool inside the visual perception package. Granting the package does
not grant every tool.
Current Route Adapter
: An existing HTTP route that can be mapped to a future Cortex OS tool id without
changing the implementation stack.
Research Handoff
: Research may cite or synthesize Visual Evidence that `/vision` already
produced, but `/vision` does not perform research synthesis, web search, page
fetch, PDF extraction, or deep research workflows.
BTE Compatibility Surface
: Existing route names and BTE-shaped behavior stay usable while the package
candidate is documented for Cortex OS.
+25
View File
@@ -0,0 +1,25 @@
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
SVRNTY_VISION_HOST=0.0.0.0 \
SVRNTY_VISION_PORT=8094
WORKDIR /app
RUN useradd --create-home --shell /usr/sbin/nologin vision
COPY pyproject.toml README.md ./
COPY src ./src
RUN python -m pip install --no-cache-dir --upgrade pip \
&& python -m pip install --no-cache-dir .
USER vision
EXPOSE 8094
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8094/healthz', timeout=3).read()"
CMD ["python", "-m", "svrnty_vision.server"]
+14 -5
View File
@@ -31,12 +31,21 @@ libraries — Pillow/colorthief and rembg respectively. They land in 4b.
## Status ## Status
**Phase 4a (this commit):** scaffold only. All four endpoints return Current implementation: all four listed endpoints exist. `/vlm/analyze` proxies
HTTP 501 Not Implemented. `/healthz` returns 200. to Qwen3-VL through the configured VLM endpoint, `/flux/render` proxies to
ComfyUI FLUX, `/palette/extract` runs in process, `/rembg/cutout` runs in
process, and `/healthz` returns 200.
Phase 4b will port the real logic. Phase 4c deletes the corresponding ## Cortex OS Package Candidate
.NET code from BTE. Phase 4d wires BTE to call svrnty-vision over HTTP.
See `/home/svrnty/workspaces/hermes/sot/01-ROADMAP/BTE-REFACTOR-EXECUTION-PLAN.md`. `svrnty-vision` is also documented as a child-local Visual Perception Package
Candidate under canonical sense `VISION`. Existing BTE-shaped HTTP routes remain
current route adapters; exact Cortex OS candidate tool ids, grant rules, host
adapter candidates, and Visual Evidence contract live in `docs/` and
`candidate-manifests/`.
This does not grant Core promotion, Seed installation, Runtime startup, Profile
Exposure, provider admission, or wildcard tool access.
## Run ## Run
+21
View File
@@ -0,0 +1,21 @@
items:
- id: SVRNTY-VISION-WORK-001
title: Svrnty Vision Child Workspace Governance
status: complete
source: outputs/2026-06-12-svrnty-vision-workboard-readiness-reconciliation.md
owner: jp
- id: SVRNTY-VISION-WORK-002
title: Generic VISION Package Candidate And Visual Evidence Proof
status: complete
source: outputs/2026-06-06-svrnty-vision-work-002-package-candidate-validation.md
owner: jp
- id: SVRNTY-VISION-WORK-003
title: Vision Package Candidate Sandcastle
status: complete
source: outputs/2026-06-12-svrnty-vision-workboard-readiness-reconciliation.md
owner: jp
- id: SVRNTY-VISION-WORK-004
title: Package Docker Build Context
status: complete
source: Dockerfile
owner: jp
@@ -0,0 +1,60 @@
{
"schema_version": "vision.host_adapter_candidate.v1",
"adapter_id": "vision-claude-code-adapter",
"authority": "child-local",
"status": "candidate_only_not_seed_installed",
"host_runtime": "claude-code",
"host_runtime_class": "claude-code",
"package_id": "visual-perception-package-candidate",
"adapter_role": "thin_access_adapter",
"seed_artifact_target": "host-adapters/vision-claude-code-adapter.json",
"wildcard_grant_allowed": false,
"current_tool_candidates": [
"vision.image_analyze",
"vision.image_generate",
"vision.palette_extract",
"vision.background_cutout"
],
"planned_tool_candidates": [
"vision.ocr_read",
"vision.screenshot_observe",
"vision.browser_observe",
"vision.document_layout_read",
"vision.chart_read",
"vision.table_read",
"vision.diagram_read",
"vision.object_detect",
"vision.visual_ground",
"vision.segment",
"vision.video_read",
"vision.image_edit"
],
"disclosure_contract": {
"provider_mode_required": true,
"retention_required": true,
"visual_evidence_required": true
},
"allowed_effects": [
"analyze_image_input",
"generate_image_output",
"extract_palette",
"remove_background",
"return_visual_evidence"
],
"forbidden_effects": [
"research_synthesis",
"textual_web_search",
"textual_page_fetch",
"profile_exposure",
"runtime_start",
"provider_admission",
"wildcard_tool_exposure"
],
"non_authorization": {
"core_promotion": false,
"seed_installation": false,
"runtime_start": false,
"profile_exposure": false,
"provider_admission": false
}
}
@@ -0,0 +1,60 @@
{
"schema_version": "vision.host_adapter_candidate.v1",
"adapter_id": "vision-codex-cli-adapter",
"authority": "child-local",
"status": "candidate_only_not_seed_installed",
"host_runtime": "codex-cli",
"host_runtime_class": "codex-cli",
"package_id": "visual-perception-package-candidate",
"adapter_role": "thin_access_adapter",
"seed_artifact_target": "host-adapters/vision-codex-cli-adapter.json",
"wildcard_grant_allowed": false,
"current_tool_candidates": [
"vision.image_analyze",
"vision.image_generate",
"vision.palette_extract",
"vision.background_cutout"
],
"planned_tool_candidates": [
"vision.ocr_read",
"vision.screenshot_observe",
"vision.browser_observe",
"vision.document_layout_read",
"vision.chart_read",
"vision.table_read",
"vision.diagram_read",
"vision.object_detect",
"vision.visual_ground",
"vision.segment",
"vision.video_read",
"vision.image_edit"
],
"disclosure_contract": {
"provider_mode_required": true,
"retention_required": true,
"visual_evidence_required": true
},
"allowed_effects": [
"analyze_image_input",
"generate_image_output",
"extract_palette",
"remove_background",
"return_visual_evidence"
],
"forbidden_effects": [
"research_synthesis",
"textual_web_search",
"textual_page_fetch",
"profile_exposure",
"runtime_start",
"provider_admission",
"wildcard_tool_exposure"
],
"non_authorization": {
"core_promotion": false,
"seed_installation": false,
"runtime_start": false,
"profile_exposure": false,
"provider_admission": false
}
}
@@ -0,0 +1,60 @@
{
"schema_version": "vision.host_adapter_candidate.v1",
"adapter_id": "vision-pi-code-adapter",
"authority": "child-local",
"status": "candidate_only_not_seed_installed",
"host_runtime": "pi-code",
"host_runtime_class": "pi-code",
"package_id": "visual-perception-package-candidate",
"adapter_role": "thin_access_adapter",
"seed_artifact_target": "host-adapters/vision-pi-code-adapter.json",
"wildcard_grant_allowed": false,
"current_tool_candidates": [
"vision.image_analyze",
"vision.image_generate",
"vision.palette_extract",
"vision.background_cutout"
],
"planned_tool_candidates": [
"vision.ocr_read",
"vision.screenshot_observe",
"vision.browser_observe",
"vision.document_layout_read",
"vision.chart_read",
"vision.table_read",
"vision.diagram_read",
"vision.object_detect",
"vision.visual_ground",
"vision.segment",
"vision.video_read",
"vision.image_edit"
],
"disclosure_contract": {
"provider_mode_required": true,
"retention_required": true,
"visual_evidence_required": true
},
"allowed_effects": [
"analyze_image_input",
"generate_image_output",
"extract_palette",
"remove_background",
"return_visual_evidence"
],
"forbidden_effects": [
"research_synthesis",
"textual_web_search",
"textual_page_fetch",
"profile_exposure",
"runtime_start",
"provider_admission",
"wildcard_tool_exposure"
],
"non_authorization": {
"core_promotion": false,
"seed_installation": false,
"runtime_start": false,
"profile_exposure": false,
"provider_admission": false
}
}
@@ -0,0 +1,84 @@
{
"schema_version": "vision.package_candidate.v1",
"package_id": "visual-perception-package-candidate",
"workspace": "svrnty-vision",
"authority": "child-local",
"status": "candidate_only_not_seed_installed",
"canonical_sense": "VISION",
"package_role": "generic_visual_perception_and_production",
"current_route_adapters": [
{
"route": "POST /vlm/analyze",
"tool_id": "vision.image_analyze",
"capability": "vlm_image_analysis"
},
{
"route": "POST /flux/render",
"tool_id": "vision.image_generate",
"capability": "image_generation"
},
{
"route": "POST /palette/extract",
"tool_id": "vision.palette_extract",
"capability": "palette_extraction"
},
{
"route": "POST /rembg/cutout",
"tool_id": "vision.background_cutout",
"capability": "background_cutout"
}
],
"current_tool_candidates": [
"vision.image_analyze",
"vision.image_generate",
"vision.palette_extract",
"vision.background_cutout"
],
"planned_tool_candidates": [
"vision.ocr_read",
"vision.screenshot_observe",
"vision.browser_observe",
"vision.document_layout_read",
"vision.chart_read",
"vision.table_read",
"vision.diagram_read",
"vision.object_detect",
"vision.visual_ground",
"vision.segment",
"vision.video_read",
"vision.image_edit"
],
"owned_capability_classes": [
"pixel_media_perception",
"visual_evidence_production",
"image_generation",
"image_editing",
"screenshot_and_browser_visual_observation",
"layout_chart_table_diagram_visual_reading"
],
"forbidden_research_owned_capabilities": [
"web_search",
"fetch_page",
"extract_pdf",
"deep_research",
"research_synthesis",
"capsule_writing"
],
"research_handoff": {
"may_produce_visual_evidence_for_research": true,
"may_perform_research_synthesis": false,
"handoff_contract": "candidate-manifests/visual-evidence-contract.json"
},
"grant_policy": {
"wildcard_grant_allowed": false,
"package_default_grants_all_tools": false,
"tool_grants_are_granular": true
},
"non_authorization": {
"core_promotion": false,
"seed_installation": false,
"runtime_start": false,
"profile_exposure": false,
"provider_admission": false
}
}
@@ -0,0 +1,69 @@
{
"schema_version": "vision.tool_grants.v1",
"package_id": "visual-perception-package-candidate",
"authority": "child-local",
"status": "candidate_only_not_seed_installed",
"canonical_sense": "VISION",
"wildcard_grant_allowed": false,
"package_default_grants_all_tools": false,
"current_tool_candidates": [
{
"tool_id": "vision.image_analyze",
"route": "POST /vlm/analyze",
"grant_default": false,
"requires_visual_evidence": true
},
{
"tool_id": "vision.image_generate",
"route": "POST /flux/render",
"grant_default": false,
"requires_visual_evidence": false
},
{
"tool_id": "vision.palette_extract",
"route": "POST /palette/extract",
"grant_default": false,
"requires_visual_evidence": true
},
{
"tool_id": "vision.background_cutout",
"route": "POST /rembg/cutout",
"grant_default": false,
"requires_visual_evidence": true
}
],
"planned_tool_candidates": [
"vision.ocr_read",
"vision.screenshot_observe",
"vision.browser_observe",
"vision.document_layout_read",
"vision.chart_read",
"vision.table_read",
"vision.diagram_read",
"vision.object_detect",
"vision.visual_ground",
"vision.segment",
"vision.video_read",
"vision.image_edit"
],
"required_disclosures": {
"producing_package_id": true,
"producing_tool_id": true,
"capability_surface": true,
"provider_mode": true,
"retention_disclosure": true,
"validation_status": true
},
"forbidden_effects": [
"research_synthesis",
"textual_web_search",
"textual_page_fetch",
"pdf_text_extraction",
"deep_research_workflow",
"capsule_writing",
"runtime_start",
"profile_exposure",
"provider_admission",
"wildcard_tool_exposure"
]
}
@@ -0,0 +1,37 @@
{
"schema_version": "vision.visual_evidence_contract.v1",
"package_id": "visual-perception-package-candidate",
"authority": "child-local",
"status": "candidate_only_not_seed_installed",
"required_fields": [
"producing_package_id",
"producing_tool_id",
"capability_surface",
"source_reference",
"provider_mode",
"retention_disclosure",
"observed_content_summary",
"extracted_claims",
"confidence",
"caveats",
"timestamp",
"validation_status"
],
"first_vertical_proof": {
"source_route": "POST /vlm/analyze",
"source_tool_id": "vision.image_analyze",
"module": "src/svrnty_vision/visual_evidence.py",
"test": "tests/test_visual_evidence.py",
"live_provider_call_required": false
},
"research_handoff": {
"research_may_consume_visual_evidence": true,
"vision_may_write_research_capsules": false,
"vision_may_perform_research_synthesis": false
},
"disclosure_contract": {
"provider_mode_required": true,
"retention_required": true,
"validation_status_required": true
}
}
+29
View File
@@ -0,0 +1,29 @@
# VISION Host Adapter Candidates
Status: candidate only. These adapter manifests do not install into Seed and do
not grant host access.
## Intent
Claude Code, Codex CLI, and Pi-Code should expose the same VISION package
capabilities when Seed accepts the package. Each host adapter is thin: it maps
host-specific command shape to the same package tool ids and the same disclosure
contract.
## Host Targets
- `claude-code`
- `codex-cli`
- `pi-code`
## Parity Rule
Each host adapter candidate must expose the same current tool ids, the same
planned tool ids, no wildcard grant, and the same Visual Evidence disclosure
contract.
## Candidate Manifests
- `candidate-manifests/host-adapters/vision-claude-code-adapter.json`
- `candidate-manifests/host-adapters/vision-codex-cli-adapter.json`
- `candidate-manifests/host-adapters/vision-pi-code-adapter.json`
+70
View File
@@ -0,0 +1,70 @@
# VISION Package Candidate
Status: child-local candidate only. No Core promotion, Seed installation,
Runtime start, Profile Exposure, or provider admission is authorized. No
wildcard grant is authorized by this document.
## Intent
`svrnty-vision` is the generic visual-perception package candidate for the
canonical Cortex OS sense `VISION`. It owns tools that inspect or produce pixels,
images, screenshots, browser observations, layouts, charts, diagrams, grounded
regions, segmentations, video frames, or generated/edited images.
`research` is also under the `VISION` sense family, but it owns textual/source
reading and research workflows. The boundary is by capability, not by sense name:
Research reads sources; Vision sees media.
## Current Route Adapters
| Current route | Candidate tool id | Capability |
| --- | --- | --- |
| `POST /vlm/analyze` | `vision.image_analyze` | Analyze image input with a VLM and return a normalized observation. |
| `POST /flux/render` | `vision.image_generate` | Generate image output through the existing FLUX route. |
| `POST /palette/extract` | `vision.palette_extract` | Extract dominant colors from image input. |
| `POST /rembg/cutout` | `vision.background_cutout` | Remove image background and return cutout output. |
## Planned Tool Candidates
The complete VISION visual-perception package should cover:
- `vision.ocr_read`
- `vision.screenshot_observe`
- `vision.browser_observe`
- `vision.document_layout_read`
- `vision.chart_read`
- `vision.table_read`
- `vision.diagram_read`
- `vision.object_detect`
- `vision.visual_ground`
- `vision.segment`
- `vision.video_read`
- `vision.image_edit`
These are not implemented or granted by this slice. They are named so future
work has a canonical target and does not duplicate Research capabilities.
## Boundary
Owned here:
- Pixel/media perception.
- Visual evidence production.
- Image generation or editing.
- Visual extraction from screenshots, browser views, image files, video frames,
charts, diagrams, and layouts.
Not owned here:
- Web search.
- Page fetch.
- PDF text extraction.
- Research synthesis.
- Deep research planning.
- Capsule writing.
- Profile Exposure.
- Runtime startup.
- Provider admission.
Research can consume Visual Evidence only through an explicit handoff contract.
Vision never becomes a research synthesizer by returning evidence.
+42
View File
@@ -0,0 +1,42 @@
# Visual Evidence Contract
Status: candidate contract. This is route-only evidence for Cortex OS and Seed
review. It does not grant tools or promote the package.
## Required Fields
Every Visual Evidence record must include:
- `producing_package_id`
- `producing_tool_id`
- `capability_surface`
- `source_reference`
- `provider_mode`
- `retention_disclosure`
- `observed_content_summary`
- `extracted_claims`
- `confidence`
- `caveats`
- `timestamp`
- `validation_status`
## First Vertical Proof
The first proof adapts a raw-mode `POST /vlm/analyze` response into Visual
Evidence through a pure Python adapter. It does not call a live provider.
Proof module: `src/svrnty_vision/visual_evidence.py`
Proof test: `tests/test_visual_evidence.py`
## Research Handoff Rule
Research may cite Visual Evidence as an input source if the record includes the
required fields and validation status. Research owns synthesis and capsule writing.
Vision owns the visual observation record only.
## Provider And Retention Disclosure
Provider mode and retention are mandatory because host agents must be able to
disclose how the visual observation was produced. Missing disclosure invalidates
the evidence record.
@@ -0,0 +1,55 @@
# SVRNTY-VISION-WORK-002 Validation Evidence
Date: 2026-06-06
Scope: generic Cortex OS VISION package candidate and Visual Evidence proof for
`svrnty-vision`.
## What Changed
- Added child-local VISION package candidate docs and manifests.
- Mapped current HTTP routes to candidate tool ids.
- Added granular tool-grant candidate manifest with no wildcard grants.
- Added Visual Evidence contract and a pure VLM response adapter proof.
- Added host adapter candidate manifests for Claude Code, Codex CLI, and
Pi-Code.
- Added a local sandcastle handoff for the next Seed acceptance route.
- Fixed package dependency declaration from `rembg` to `rembg[cpu]` so the
background cutout route has the required CPU backend on fresh installs.
## Gates Passed
- `python3 tools/validate_vision_package_candidate.py`
- `python3 tools/validate_vision_tool_grants.py`
- `python3 tools/validate_visual_evidence_contract.py`
- `python3 tools/validate_vision_host_adapter_candidates.py`
- `python3 tools/validate_svrnty_vision_child.py`
- `.venv/bin/python tools/validate_svrnty_vision_child.py`
- `pytest --noconftest tests/test_visual_evidence.py`
- `.venv/bin/pytest tests/ -m 'not integration'`
- `python3 -m py_compile src/svrnty_vision/visual_evidence.py tools/validate_vision_package_candidate.py tools/validate_vision_tool_grants.py tools/validate_visual_evidence_contract.py tools/validate_vision_host_adapter_candidates.py tools/validate_svrnty_vision_child.py`
- `.venv/bin/python -m py_compile src/svrnty_vision/visual_evidence.py tools/validate_vision_package_candidate.py tools/validate_vision_tool_grants.py tools/validate_visual_evidence_contract.py tools/validate_vision_host_adapter_candidates.py tools/validate_svrnty_vision_child.py`
- `git diff --check`
## Test Result
Non-integration suite after `rembg[cpu]` dependency fix:
- 30 passed.
- 10 integration tests deselected.
- 1 Starlette/httpx deprecation warning.
## Boundaries Preserved
- No Core mutation.
- No Seed installation.
- No Runtime start.
- No Profile Exposure.
- No provider admission.
- No wildcard grant.
- No Research synthesis in Vision.
## Follow-Up
Follow-up, optional: update the FastAPI/TestClient dependency path when the
Starlette/httpx deprecation becomes a compatibility issue.
@@ -0,0 +1,45 @@
{
"schema_version": "svrnty-vision-workboard-readiness-reconciliation.v1",
"status": "complete",
"ok": true,
"cc": "core-compliant",
"date": "2026-06-12",
"workspace": "svrnty-vision",
"reconciled_rows": [
{
"id": "SVRNTY-VISION-WORK-001",
"from": "candidate",
"to": "complete",
"reason": "Workspace governance, rules, docs, board, and validator are present."
},
{
"id": "SVRNTY-VISION-WORK-003",
"from": "ready-for-agent",
"to": "complete",
"reason": "The sandcastle handoff exists; successor consumption belongs to governed Seed/Core routes."
}
],
"evidence": [
"AGENTS.md",
"README.md",
"WORKBOARD.yaml",
"CONTEXT.md",
"docs/VISION-PACKAGE-CANDIDATE.md",
"docs/VISUAL-EVIDENCE-CONTRACT.md",
"docs/VISION-HOST-ADAPTER-CANDIDATES.md",
"candidate-manifests/vision-package-candidate.json",
"candidate-manifests/vision-tool-grants.json",
"candidate-manifests/visual-evidence-contract.json",
"tools/validate_svrnty_vision_child.py"
],
"false_claims": {
"core_promotion": false,
"seed_installation": false,
"runtime_started": false,
"profile_exposure_changed": false,
"provider_admitted": false,
"wildcard_grant": false,
"sibling_mutation": false
},
"next_roi": "Consume this validated package candidate through governed Seed/Core routes when package release and Runtime approval gates allow it."
}
@@ -0,0 +1,40 @@
---
title: Svrnty Vision Workboard Readiness Reconciliation
date: 2026-06-12
source: output
cc: core-compliant
status: complete
---
# Svrnty Vision Workboard Readiness Reconciliation
`svrnty-vision` is child-local validated as the Cortex OS VISION package
candidate. Its remaining candidate and ready-for-agent workboard rows were stale
workspace status, not missing local package evidence.
## Reconciled Rows
- `SVRNTY-VISION-WORK-001`: `candidate` -> `complete`; workspace governance,
local rules, docs, board, and validator are present.
- `SVRNTY-VISION-WORK-003`: `ready-for-agent` -> `complete`; the sandcastle
handoff exists and its next ROI belongs to Seed consumption, not more local
Vision package work.
## Evidence
- `python3 tools/validate_svrnty_vision_child.py`: green.
- `docs/VISION-PACKAGE-CANDIDATE.md`: package boundary.
- `docs/VISUAL-EVIDENCE-CONTRACT.md`: Visual Evidence contract.
- `docs/VISION-HOST-ADAPTER-CANDIDATES.md`: host adapter candidates.
- `candidate-manifests/`: package, grants, evidence, and host adapter
manifests.
## Boundaries
No Core promotion, Seed installation, Runtime start, Profile Exposure, provider
admission, wildcard grant, or sibling mutation is claimed here.
## Next ROI
Consume this validated package candidate through governed Seed/Core routes when
package release and Runtime approval gates allow it.
@@ -0,0 +1,34 @@
# Vision Package Candidate Sandcastle
Status: ready for the next agent. This sandcastle belongs inside
`svrnty-vision`; it is not an umbrella repo and not a Core route.
## End State
Make `svrnty-vision` Seed-acceptable as the generic Cortex OS VISION
visual-perception package candidate, with host parity for Claude Code, Codex CLI,
and Pi-Code, granular grants, and Visual Evidence disclosure.
## Current Vertical Slice
- Package boundary documented.
- Current BTE routes mapped to Cortex OS candidate tool ids.
- Planned SOTA visual tool inventory named without implementing all tools.
- Visual Evidence contract documented.
- VLM raw-mode output adapted into Visual Evidence through a pure proof.
- Host adapter candidate parity documented.
## Stop Conditions
- Do not mutate Core from this workspace.
- Do not install into Seed from this workspace.
- Do not start Runtime.
- Do not grant Profile Exposure.
- Do not admit providers.
- Do not wildcard expose all tools.
## Next ROI
Build the first Seed route outside this workspace that can accept these manifests
as candidate artifacts, then prove one host adapter can expose exactly one tool
grant without wildcard access.
+1 -1
View File
@@ -20,7 +20,7 @@ dependencies = [
"httpx>=0.27,<1.0", "httpx>=0.27,<1.0",
"Pillow>=11,<13", "Pillow>=11,<13",
"colorthief>=0.2.1", "colorthief>=0.2.1",
"rembg>=2.0,<3.0", "rembg[cpu]>=2.0,<3.0",
] ]
[project.optional-dependencies] [project.optional-dependencies]
+1 -1
View File
@@ -5,7 +5,7 @@ pydantic-settings>=2.6,<3.0
httpx>=0.27,<1.0 httpx>=0.27,<1.0
Pillow>=11,<13 Pillow>=11,<13
colorthief>=0.2.1 colorthief>=0.2.1
rembg>=2.0,<3.0 rembg[cpu]>=2.0,<3.0
# Test deps # Test deps
pytest>=8.3,<9.0 pytest>=8.3,<9.0
+148
View File
@@ -0,0 +1,148 @@
"""Visual Evidence adapter for Cortex OS VISION package candidates."""
from __future__ import annotations
import json
from typing import Any, Protocol
from pydantic import BaseModel, Field
PACKAGE_ID = "visual-perception-package-candidate"
DEFAULT_VALIDATION_STATUS = "candidate_validated"
class VlmAnalysisResponse(Protocol):
justification: str
model_id: str
raw_scores_json: str
class VisualEvidence(BaseModel):
"""Normalized visual observation record for host disclosure and handoff."""
producing_package_id: str = PACKAGE_ID
producing_tool_id: str
capability_surface: str
source_reference: str
provider_mode: str
retention_disclosure: str
observed_content_summary: str
extracted_claims: list[str] = Field(default_factory=list)
confidence: str
caveats: list[str] = Field(default_factory=list)
timestamp: str
validation_status: str
model_id: str | None = None
raw_observation_ref: str | None = None
def visual_evidence_from_vlm_response(
response: VlmAnalysisResponse,
*,
source_reference: str,
provider_mode: str,
retention_disclosure: str,
timestamp: str,
producing_tool_id: str = "vision.image_analyze",
capability_surface: str = "vision.image_analyze",
confidence: str = "medium",
caveats: list[str] | None = None,
validation_status: str = DEFAULT_VALIDATION_STATUS,
) -> VisualEvidence:
"""Adapt the current VLM response into a Cortex OS Visual Evidence record."""
_require_non_empty("source_reference", source_reference)
_require_non_empty("provider_mode", provider_mode)
_require_non_empty("retention_disclosure", retention_disclosure)
_require_non_empty("timestamp", timestamp)
raw_text = response.raw_scores_json.strip()
parsed = _parse_json_object(raw_text)
summary = _summary_from_response(parsed, response.justification, raw_text)
claims = _claims_from_raw(parsed, raw_text)
if not claims and summary:
claims = [f"summary: {summary}"]
return VisualEvidence(
producing_tool_id=producing_tool_id,
capability_surface=capability_surface,
source_reference=source_reference,
provider_mode=provider_mode,
retention_disclosure=retention_disclosure,
observed_content_summary=summary,
extracted_claims=claims,
confidence=confidence,
caveats=caveats or [],
timestamp=timestamp,
validation_status=validation_status,
model_id=response.model_id,
raw_observation_ref="AnalyzeResponse.raw_scores_json",
)
def _require_non_empty(field_name: str, value: str) -> None:
if not value or not value.strip():
raise ValueError(f"{field_name} is required for Visual Evidence")
def _parse_json_object(raw_text: str) -> dict[str, Any] | None:
if not raw_text:
return None
try:
parsed = json.loads(raw_text)
except json.JSONDecodeError:
return None
return parsed if isinstance(parsed, dict) else None
def _summary_from_response(
parsed: dict[str, Any] | None, justification: str, raw_text: str
) -> str:
if parsed:
for key in (
"observed_content_summary",
"summary",
"description",
"caption",
"text",
):
value = parsed.get(key)
if isinstance(value, str) and value.strip():
return _compact(value)
if justification.strip():
return _compact(justification)
return _compact(raw_text)
def _claims_from_raw(parsed: dict[str, Any] | None, raw_text: str) -> list[str]:
if not parsed:
return [f"raw: {_compact(raw_text)}"] if raw_text else []
claims: list[str] = []
for key, value in parsed.items():
claims.append(f"{key}: {_format_claim_value(value)}")
return claims
def _format_claim_value(value: Any) -> str:
if isinstance(value, list):
if not value:
return "[]"
if all(
isinstance(item, str | int | float | bool) or item is None
for item in value
):
return ", ".join(str(item) for item in value)
return json.dumps(value, sort_keys=True, separators=(",", ":"))
if isinstance(value, dict):
return json.dumps(value, sort_keys=True, separators=(",", ":"))
if value is None:
return "null"
return str(value)
def _compact(text: str, limit: int = 500) -> str:
compacted = " ".join(text.split())
if len(compacted) <= limit:
return compacted
return compacted[: limit - 3].rstrip() + "..."
+57
View File
@@ -0,0 +1,57 @@
from __future__ import annotations
from types import SimpleNamespace
from svrnty_vision.visual_evidence import visual_evidence_from_vlm_response
def test_vlm_raw_mode_response_becomes_visual_evidence() -> None:
response = SimpleNamespace(
rubric_mode="raw",
justification="",
model_id="qwen-test",
raw_scores_json='{"description":"solid red square","objects":["red square"],"detected_text":[]}',
)
evidence = visual_evidence_from_vlm_response(
response,
source_reference="fixture://red-square.png",
provider_mode="sovereign",
retention_disclosure="synchronous_no_async_persistence",
timestamp="2026-06-06T00:00:00Z",
)
assert evidence.producing_package_id == "visual-perception-package-candidate"
assert evidence.producing_tool_id == "vision.image_analyze"
assert evidence.capability_surface == "vision.image_analyze"
assert evidence.source_reference == "fixture://red-square.png"
assert evidence.provider_mode == "sovereign"
assert evidence.retention_disclosure == "synchronous_no_async_persistence"
assert evidence.observed_content_summary == "solid red square"
assert "description: solid red square" in evidence.extracted_claims
assert "objects: red square" in evidence.extracted_claims
assert "detected_text: []" in evidence.extracted_claims
assert evidence.validation_status == "candidate_validated"
assert "research_synthesis" not in evidence.model_dump()
def test_non_json_vlm_response_gets_safe_raw_fallback() -> None:
response = SimpleNamespace(
rubric_mode="raw",
justification="",
model_id="qwen-test",
raw_scores_json="a screenshot of a pricing page with two columns",
)
evidence = visual_evidence_from_vlm_response(
response,
source_reference="fixture://pricing.png",
provider_mode="sovereign",
retention_disclosure="synchronous_no_async_persistence",
timestamp="2026-06-06T00:00:00Z",
)
assert evidence.observed_content_summary == "a screenshot of a pricing page with two columns"
assert evidence.extracted_claims == [
"raw: a screenshot of a pricing page with two columns"
]
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Validate Svrnty Vision child workspace governance shell."""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REQUIRED = [
"AGENTS.md",
"README.md",
"WORKBOARD.yaml",
"CONTEXT.md",
"docs/VISION-PACKAGE-CANDIDATE.md",
"docs/VISUAL-EVIDENCE-CONTRACT.md",
"docs/VISION-HOST-ADAPTER-CANDIDATES.md",
"candidate-manifests/vision-package-candidate.json",
"candidate-manifests/vision-tool-grants.json",
"candidate-manifests/visual-evidence-contract.json",
"outputs/2026-06-12-svrnty-vision-workboard-readiness-reconciliation.md",
"outputs/2026-06-12-svrnty-vision-workboard-readiness-reconciliation.json",
"Dockerfile",
".dockerignore",
"tools/validate_vision_package_docker_context.py",
]
VALIDATORS = [
"tools/validate_vision_package_candidate.py",
"tools/validate_vision_tool_grants.py",
"tools/validate_visual_evidence_contract.py",
"tools/validate_vision_host_adapter_candidates.py",
"tools/validate_vision_package_docker_context.py",
]
def main() -> int:
errors: list[str] = []
validator_outputs: dict[str, str] = {}
for rel in REQUIRED:
if not (ROOT / rel).exists():
errors.append(f"missing:{rel}")
checks = {
"AGENTS.md": ["child-local", "not Cortex OS Core authority", "python3 tools/validate_svrnty_vision_child.py"],
"WORKBOARD.yaml": ["SVRNTY-VISION-WORK-001", "SVRNTY-VISION-WORK-003", "status: complete", "owner: jp"],
"CONTEXT.md": ["Visual Perception Package Candidate", "Research Handoff"],
"docs/VISION-PACKAGE-CANDIDATE.md": ["Research reads sources; Vision sees media", "wildcard grant"],
"outputs/2026-06-12-svrnty-vision-workboard-readiness-reconciliation.md": [
"No Core promotion",
"Seed consumption",
"Runtime start",
],
}
for rel, snippets in checks.items():
path = ROOT / rel
if path.exists():
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
errors.append(f"{rel}:missing:{snippet}")
for rel in VALIDATORS:
path = ROOT / rel
if not path.exists():
errors.append(f"missing:{rel}")
continue
completed = subprocess.run(
[sys.executable, str(path)],
cwd=ROOT,
text=True,
capture_output=True,
check=False,
)
validator_outputs[rel] = completed.stdout.strip()
if completed.returncode != 0:
errors.append(f"validator_failed:{rel}:{completed.stderr.strip()}")
result = {
"ok": not errors,
"validator": "svrnty-vision-child-v1",
"checked": REQUIRED + VALIDATORS,
"errors": errors,
"warnings": [],
"validator_outputs": validator_outputs,
}
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result["ok"] else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Validate VISION host adapter candidate parity."""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
HOSTS = {
"claude-code": "candidate-manifests/host-adapters/vision-claude-code-adapter.json",
"codex-cli": "candidate-manifests/host-adapters/vision-codex-cli-adapter.json",
"pi-code": "candidate-manifests/host-adapters/vision-pi-code-adapter.json",
}
CURRENT_TOOLS = [
"vision.image_analyze",
"vision.image_generate",
"vision.palette_extract",
"vision.background_cutout",
]
PLANNED_TOOLS = [
"vision.ocr_read",
"vision.screenshot_observe",
"vision.browser_observe",
"vision.document_layout_read",
"vision.chart_read",
"vision.table_read",
"vision.diagram_read",
"vision.object_detect",
"vision.visual_ground",
"vision.segment",
"vision.video_read",
"vision.image_edit",
]
def main() -> int:
errors: list[str] = []
checked = list(HOSTS.values()) + ["docs/VISION-HOST-ADAPTER-CANDIDATES.md"]
manifests: dict[str, dict] = {}
for host, rel in HOSTS.items():
manifest = _read_json(ROOT / rel, errors)
if manifest:
manifests[host] = manifest
for host, manifest in manifests.items():
_expect(manifest.get("host_runtime") == host, f"host_runtime_mismatch:{host}", errors)
_expect(manifest.get("package_id") == "visual-perception-package-candidate", f"package_mismatch:{host}", errors)
_expect(manifest.get("adapter_role") == "thin_access_adapter", f"adapter_role_mismatch:{host}", errors)
_expect(manifest.get("wildcard_grant_allowed") is False, f"wildcard_grant_allowed:{host}", errors)
_expect(manifest.get("current_tool_candidates") == CURRENT_TOOLS, f"current_tools_mismatch:{host}", errors)
_expect(manifest.get("planned_tool_candidates") == PLANNED_TOOLS, f"planned_tools_mismatch:{host}", errors)
disclosures = manifest.get("disclosure_contract", {})
for key in ("provider_mode_required", "retention_required", "visual_evidence_required"):
if disclosures.get(key) is not True:
errors.append(f"disclosure_missing:{host}:{key}")
forbidden = set(manifest.get("forbidden_effects", []))
for effect in ("research_synthesis", "textual_web_search", "runtime_start", "wildcard_tool_exposure"):
if effect not in forbidden:
errors.append(f"forbidden_effect_missing:{host}:{effect}")
for key, value in manifest.get("non_authorization", {}).items():
if value is not False:
errors.append(f"non_authorization_enabled:{host}:{key}")
if len(manifests) == len(HOSTS):
current_sets = {tuple(m["current_tool_candidates"]) for m in manifests.values()}
planned_sets = {tuple(m["planned_tool_candidates"]) for m in manifests.values()}
_expect(len(current_sets) == 1, "current_tool_parity_failed", errors)
_expect(len(planned_sets) == 1, "planned_tool_parity_failed", errors)
_check_snippets(
"docs/VISION-HOST-ADAPTER-CANDIDATES.md",
["Claude Code", "Codex CLI", "Pi-Code", "no wildcard grant"],
errors,
)
result = {
"ok": not errors,
"validator": "vision-host-adapter-candidates-v1",
"checked": checked,
"errors": errors,
"warnings": [],
}
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result["ok"] else 1
def _read_json(path: Path, errors: list[str]) -> dict | None:
if not path.exists():
errors.append(f"missing:{path.relative_to(ROOT)}")
return None
try:
return json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
errors.append(f"{path.relative_to(ROOT)}:json:{exc}")
return None
def _check_snippets(rel: str, snippets: list[str], errors: list[str]) -> None:
path = ROOT / rel
if not path.exists():
errors.append(f"missing:{rel}")
return
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
errors.append(f"{rel}:missing:{snippet}")
def _expect(condition: bool, error: str, errors: list[str]) -> None:
if not condition:
errors.append(error)
if __name__ == "__main__":
raise SystemExit(main())
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""Validate the child-local VISION package candidate manifest and docs."""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CURRENT_TOOLS = [
"vision.image_analyze",
"vision.image_generate",
"vision.palette_extract",
"vision.background_cutout",
]
PLANNED_TOOLS = [
"vision.ocr_read",
"vision.screenshot_observe",
"vision.browser_observe",
"vision.document_layout_read",
"vision.chart_read",
"vision.table_read",
"vision.diagram_read",
"vision.object_detect",
"vision.visual_ground",
"vision.segment",
"vision.video_read",
"vision.image_edit",
]
ROUTE_TOOLS = {
"POST /vlm/analyze": "vision.image_analyze",
"POST /flux/render": "vision.image_generate",
"POST /palette/extract": "vision.palette_extract",
"POST /rembg/cutout": "vision.background_cutout",
}
def main() -> int:
errors: list[str] = []
required = [
"CONTEXT.md",
"docs/VISION-PACKAGE-CANDIDATE.md",
"candidate-manifests/vision-package-candidate.json",
]
for rel in required:
if not (ROOT / rel).exists():
errors.append(f"missing:{rel}")
manifest_path = ROOT / "candidate-manifests/vision-package-candidate.json"
manifest = _read_json(manifest_path, errors)
if manifest:
_expect(manifest.get("canonical_sense") == "VISION", "canonical_sense_not_vision", errors)
_expect(
manifest.get("status") == "candidate_only_not_seed_installed",
"status_not_candidate_only",
errors,
)
_expect(manifest.get("current_tool_candidates") == CURRENT_TOOLS, "current_tools_mismatch", errors)
_expect(manifest.get("planned_tool_candidates") == PLANNED_TOOLS, "planned_tools_mismatch", errors)
route_map = {
item.get("route"): item.get("tool_id")
for item in manifest.get("current_route_adapters", [])
}
_expect(route_map == ROUTE_TOOLS, "route_tool_map_mismatch", errors)
grant_policy = manifest.get("grant_policy", {})
_expect(grant_policy.get("wildcard_grant_allowed") is False, "wildcard_grant_allowed", errors)
_expect(
grant_policy.get("package_default_grants_all_tools") is False,
"package_default_grants_all_tools",
errors,
)
_expect(grant_policy.get("tool_grants_are_granular") is True, "tool_grants_not_granular", errors)
for key, value in manifest.get("non_authorization", {}).items():
if value is not False:
errors.append(f"non_authorization_enabled:{key}")
forbidden = set(manifest.get("forbidden_research_owned_capabilities", []))
for capability in ("web_search", "fetch_page", "extract_pdf", "deep_research", "research_synthesis"):
if capability not in forbidden:
errors.append(f"missing_forbidden_research_capability:{capability}")
_check_snippets(
"docs/VISION-PACKAGE-CANDIDATE.md",
[
"Research reads sources; Vision sees media",
"No Core promotion",
"wildcard grant",
"`vision.image_analyze`",
"`vision.video_read`",
],
errors,
)
_check_snippets(
"CONTEXT.md",
["Visual Evidence", "Research Handoff", "BTE Compatibility Surface"],
errors,
)
result = {
"ok": not errors,
"validator": "vision-package-candidate-v1",
"checked": required,
"errors": errors,
"warnings": [],
}
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result["ok"] else 1
def _read_json(path: Path, errors: list[str]) -> dict | None:
if not path.exists():
return None
try:
return json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
errors.append(f"{path.relative_to(ROOT)}:json:{exc}")
return None
def _check_snippets(rel: str, snippets: list[str], errors: list[str]) -> None:
path = ROOT / rel
if not path.exists():
return
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
errors.append(f"{rel}:missing:{snippet}")
def _expect(condition: bool, error: str, errors: list[str]) -> None:
if not condition:
errors.append(error)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Validate the no-live Vision package Docker build context."""
from __future__ import annotations
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DOCKERFILE = ROOT / "Dockerfile"
DOCKERIGNORE = ROOT / ".dockerignore"
WORKBOARD = ROOT / "WORKBOARD.yaml"
REQUIRED_DOCKER_SNIPPETS = [
"FROM python:3.12-slim",
"SVRNTY_VISION_HOST=0.0.0.0",
"SVRNTY_VISION_PORT=8094",
"COPY pyproject.toml README.md ./",
"COPY src ./src",
"python -m pip install --no-cache-dir .",
"USER vision",
"EXPOSE 8094",
"HEALTHCHECK",
"http://127.0.0.1:8094/healthz",
'CMD ["python", "-m", "svrnty_vision.server"]',
]
REQUIRED_IGNORE = {
".git",
".venv",
"__pycache__",
"outputs",
"worktrees",
"tests",
"candidate-manifests",
}
FORBIDDEN_PATTERNS = [
re.compile(r"sk-[A-Za-z0-9_-]{20,}"),
re.compile(r"AIza[0-9A-Za-z_-]{20,}"),
re.compile(r"\b(?:ck|cs)_[0-9A-Za-z]{20,}"),
re.compile(r"(?i)\b[A-Z0-9_]*(?:PASS|PASSWORD|SECRET|TOKEN|KEY)[A-Z0-9_]*\s*=\s*[^`\s#]{8,}"),
]
def main() -> int:
errors: list[str] = []
dockerfile = DOCKERFILE.read_text(encoding="utf-8") if DOCKERFILE.is_file() else ""
dockerignore = DOCKERIGNORE.read_text(encoding="utf-8") if DOCKERIGNORE.is_file() else ""
workboard = WORKBOARD.read_text(encoding="utf-8") if WORKBOARD.is_file() else ""
if not dockerfile:
errors.append("missing:Dockerfile")
if not dockerignore:
errors.append("missing:.dockerignore")
for snippet in REQUIRED_DOCKER_SNIPPETS:
if snippet not in dockerfile:
errors.append(f"dockerfile_missing:{snippet}")
ignore_rows = {line.strip() for line in dockerignore.splitlines() if line.strip() and not line.startswith("#")}
for row in sorted(REQUIRED_IGNORE - ignore_rows):
errors.append(f"dockerignore_missing:{row}")
if "SVRNTY-VISION-WORK-004" not in workboard:
errors.append("workboard_missing:SVRNTY-VISION-WORK-004")
if "Package Docker Build Context" not in workboard:
errors.append("workboard_missing:Package Docker Build Context")
combined = "\n".join([dockerfile, dockerignore, workboard])
for pattern in FORBIDDEN_PATTERNS:
if pattern.search(combined):
errors.append(f"value_pattern_found:{pattern.pattern}")
result = {
"ok": not errors,
"validator": "vision-package-docker-context-no-live",
"checked": ["Dockerfile", ".dockerignore", "WORKBOARD.yaml"],
"errors": errors,
"warnings": [],
}
print(json.dumps(result, indent=2))
return 0 if result["ok"] else 1
if __name__ == "__main__":
raise SystemExit(main())
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Validate granular VISION tool grant candidate manifest."""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CURRENT_TOOLS = [
"vision.image_analyze",
"vision.image_generate",
"vision.palette_extract",
"vision.background_cutout",
]
PLANNED_TOOLS = [
"vision.ocr_read",
"vision.screenshot_observe",
"vision.browser_observe",
"vision.document_layout_read",
"vision.chart_read",
"vision.table_read",
"vision.diagram_read",
"vision.object_detect",
"vision.visual_ground",
"vision.segment",
"vision.video_read",
"vision.image_edit",
]
def main() -> int:
errors: list[str] = []
path = ROOT / "candidate-manifests/vision-tool-grants.json"
manifest = _read_json(path, errors)
if manifest:
_expect(manifest.get("canonical_sense") == "VISION", "canonical_sense_not_vision", errors)
_expect(manifest.get("wildcard_grant_allowed") is False, "wildcard_grant_allowed", errors)
_expect(
manifest.get("package_default_grants_all_tools") is False,
"package_default_grants_all_tools",
errors,
)
current = manifest.get("current_tool_candidates", [])
current_tool_ids = [item.get("tool_id") for item in current]
_expect(current_tool_ids == CURRENT_TOOLS, "current_tool_ids_mismatch", errors)
_expect(manifest.get("planned_tool_candidates") == PLANNED_TOOLS, "planned_tools_mismatch", errors)
for item in current:
if item.get("grant_default") is not False:
errors.append(f"grant_default_enabled:{item.get('tool_id')}")
disclosures = manifest.get("required_disclosures", {})
for field in (
"producing_package_id",
"producing_tool_id",
"capability_surface",
"provider_mode",
"retention_disclosure",
"validation_status",
):
if disclosures.get(field) is not True:
errors.append(f"required_disclosure_missing:{field}")
forbidden = set(manifest.get("forbidden_effects", []))
for effect in (
"research_synthesis",
"textual_web_search",
"textual_page_fetch",
"deep_research_workflow",
"wildcard_tool_exposure",
):
if effect not in forbidden:
errors.append(f"forbidden_effect_missing:{effect}")
result = {
"ok": not errors,
"validator": "vision-tool-grants-v1",
"checked": ["candidate-manifests/vision-tool-grants.json"],
"errors": errors,
"warnings": [],
}
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result["ok"] else 1
def _read_json(path: Path, errors: list[str]) -> dict | None:
if not path.exists():
errors.append(f"missing:{path.relative_to(ROOT)}")
return None
try:
return json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
errors.append(f"{path.relative_to(ROOT)}:json:{exc}")
return None
def _expect(condition: bool, error: str, errors: list[str]) -> None:
if not condition:
errors.append(error)
if __name__ == "__main__":
raise SystemExit(main())
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""Validate the Visual Evidence contract and pure adapter proof."""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
REQUIRED_FIELDS = [
"producing_package_id",
"producing_tool_id",
"capability_surface",
"source_reference",
"provider_mode",
"retention_disclosure",
"observed_content_summary",
"extracted_claims",
"confidence",
"caveats",
"timestamp",
"validation_status",
]
def main() -> int:
errors: list[str] = []
required = [
"docs/VISUAL-EVIDENCE-CONTRACT.md",
"candidate-manifests/visual-evidence-contract.json",
"src/svrnty_vision/visual_evidence.py",
"tests/test_visual_evidence.py",
]
for rel in required:
if not (ROOT / rel).exists():
errors.append(f"missing:{rel}")
manifest = _read_json(ROOT / "candidate-manifests/visual-evidence-contract.json", errors)
if manifest:
_expect(manifest.get("required_fields") == REQUIRED_FIELDS, "required_fields_mismatch", errors)
proof = manifest.get("first_vertical_proof", {})
_expect(proof.get("source_tool_id") == "vision.image_analyze", "proof_tool_mismatch", errors)
_expect(proof.get("live_provider_call_required") is False, "live_provider_required", errors)
handoff = manifest.get("research_handoff", {})
_expect(handoff.get("research_may_consume_visual_evidence") is True, "research_handoff_missing", errors)
_expect(handoff.get("vision_may_write_research_capsules") is False, "vision_writes_capsules", errors)
_expect(handoff.get("vision_may_perform_research_synthesis") is False, "vision_research_synthesis", errors)
_check_snippets(
"docs/VISUAL-EVIDENCE-CONTRACT.md",
[
"`producing_package_id`",
"Research owns synthesis and capsule writing",
"does not call a live provider",
],
errors,
)
_validate_adapter(errors)
result = {
"ok": not errors,
"validator": "visual-evidence-contract-v1",
"checked": required,
"errors": errors,
"warnings": [],
}
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result["ok"] else 1
def _validate_adapter(errors: list[str]) -> None:
try:
from types import SimpleNamespace
from svrnty_vision.visual_evidence import visual_evidence_from_vlm_response
except Exception as exc: # pragma: no cover - validator import report
errors.append(f"adapter_import_failed:{type(exc).__name__}:{exc}")
return
response = SimpleNamespace(
rubric_mode="raw",
justification="",
model_id="qwen-test",
raw_scores_json='{"description":"solid red square","objects":["red square"],"detected_text":[]}',
)
evidence = visual_evidence_from_vlm_response(
response,
source_reference="fixture://red-square.png",
provider_mode="sovereign",
retention_disclosure="synchronous_no_async_persistence",
timestamp="2026-06-06T00:00:00Z",
)
dumped = evidence.model_dump()
for field in REQUIRED_FIELDS:
if field not in dumped:
errors.append(f"adapter_missing_field:{field}")
_expect(dumped.get("producing_package_id") == "visual-perception-package-candidate", "adapter_package_mismatch", errors)
_expect(dumped.get("producing_tool_id") == "vision.image_analyze", "adapter_tool_mismatch", errors)
_expect(dumped.get("observed_content_summary") == "solid red square", "adapter_summary_mismatch", errors)
_expect("description: solid red square" in dumped.get("extracted_claims", []), "adapter_claim_missing", errors)
def _read_json(path: Path, errors: list[str]) -> dict | None:
if not path.exists():
return None
try:
return json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
errors.append(f"{path.relative_to(ROOT)}:json:{exc}")
return None
def _check_snippets(rel: str, snippets: list[str], errors: list[str]) -> None:
path = ROOT / rel
if not path.exists():
return
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
errors.append(f"{rel}:missing:{snippet}")
def _expect(condition: bool, error: str, errors: list[str]) -> None:
if not condition:
errors.append(error)
if __name__ == "__main__":
raise SystemExit(main())