CC: prepare generic VISION package candidate
This commit is contained in:
+37
@@ -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.
|
||||
@@ -31,12 +31,21 @@ libraries — Pillow/colorthief and rembg respectively. They land in 4b.
|
||||
|
||||
## Status
|
||||
|
||||
**Phase 4a (this commit):** scaffold only. All four endpoints return
|
||||
HTTP 501 Not Implemented. `/healthz` returns 200.
|
||||
Current implementation: all four listed endpoints exist. `/vlm/analyze` proxies
|
||||
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
|
||||
.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`.
|
||||
## Cortex OS Package Candidate
|
||||
|
||||
`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
|
||||
|
||||
|
||||
@@ -4,3 +4,13 @@ items:
|
||||
status: candidate
|
||||
source: README.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: ready-for-agent
|
||||
source: outputs/sandcastle-vision-package-candidate/README.md
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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`
|
||||
@@ -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.
|
||||
@@ -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,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
@@ -20,7 +20,7 @@ dependencies = [
|
||||
"httpx>=0.27,<1.0",
|
||||
"Pillow>=11,<13",
|
||||
"colorthief>=0.2.1",
|
||||
"rembg>=2.0,<3.0",
|
||||
"rembg[cpu]>=2.0,<3.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ pydantic-settings>=2.6,<3.0
|
||||
httpx>=0.27,<1.0
|
||||
Pillow>=11,<13
|
||||
colorthief>=0.2.1
|
||||
rembg>=2.0,<3.0
|
||||
rembg[cpu]>=2.0,<3.0
|
||||
|
||||
# Test deps
|
||||
pytest>=8.3,<9.0
|
||||
|
||||
@@ -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() + "..."
|
||||
@@ -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"
|
||||
]
|
||||
@@ -3,20 +3,42 @@
|
||||
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"]
|
||||
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",
|
||||
]
|
||||
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",
|
||||
]
|
||||
|
||||
|
||||
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", "status: candidate", "owner: jp"],
|
||||
"CONTEXT.md": ["Visual Perception Package Candidate", "Research Handoff"],
|
||||
"docs/VISION-PACKAGE-CANDIDATE.md": ["Research reads sources; Vision sees media", "wildcard grant"],
|
||||
}
|
||||
for rel, snippets in checks.items():
|
||||
path = ROOT / rel
|
||||
@@ -25,7 +47,29 @@ def main() -> int:
|
||||
for snippet in snippets:
|
||||
if snippet not in text:
|
||||
errors.append(f"{rel}:missing:{snippet}")
|
||||
result = {"ok": not errors, "validator": "svrnty-vision-child-v1", "checked": REQUIRED, "errors": errors, "warnings": []}
|
||||
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
|
||||
|
||||
|
||||
@@ -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())
|
||||
@@ -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,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())
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user