Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate
- name: Validate ocrlab
run: make validate
- name: Offline smoke test
run: make smoke
- name: Emit SourceOS carry fixture
run: make carry
24 changes: 7 additions & 17 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
.PHONY: ocr-bootstrap ocr-rebuild ocr-verify ocr-smoketest ocr-shell
.PHONY: validate smoke carry

ocr-bootstrap:
./scripts/bootstrap_brew_ocr.sh

ocr-rebuild:
./scripts/rebuild_ocr_env.sh

ocr-verify:
./scripts/verify_ocr_env.sh

ocr-smoketest:
source .venv-ocr/bin/activate && ./scripts/smoketest_ocr.sh
validate:
python3 tools/validate.py

ocr-shell:
./scripts/ocr_shell.sh
smoke:
python3 tools/smoke.py

.PHONY: validate
validate:
./tools/validate.sh
carry:
python3 tools/emit_sourceos_carry.py > examples/sourceos-carry.ocrlab.json
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# ocrlab

Linux-native capability repo for SourceOS (Linux-first).
Linux-native lab workspace for OCR and document-text extraction feature experiments for SocioProphet services.

## Role in the Holmes lab family

`ocrlab` is the first governed pilot lab for the Holmes document-intelligence stack. It emits a `functional-service.v1` manifest that can be consumed by:

- `SocioProphet/holmes` as the document-intelligence product surface;
- `SocioProphet/functional-model-surfaces` as the normative service contract spine;
- `SocioProphet/sociosphere` as workspace registry and maturity controller;
- `SourceOS-Linux/sourceos-model-carry` as disabled-by-default SourceOS carry metadata;
- `SociOS-Linux/socios` as opt-in training/tuning orchestration;
- `SocioProphet/model-governance-ledger` and `SocioProphet/model-router` for promotion and routing.

This repository does not store model weights, mutable adapters, personal data, secrets, or promotion authority.

## Local validation

```bash
make validate
make smoke
make carry
```

The smoke path is deterministic and offline-only.
37 changes: 37 additions & 0 deletions evidence/smoke-receipt.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"schemaVersion": "ocrlab.smoke-receipt.v0",
"kind": "SmokeReceipt",
"serviceId": "ocrlab.holmes.ocr-pilot",
"runId": "urn:socioprophet:ocrlab:smoke:offline-lexical-v0",
"mode": "offline-deterministic",
"networkRequired": false,
"modelWeightsIncluded": false,
"mutableAdapterIncluded": false,
"inputRef": "examples/smoke-input.json",
"outputRef": "examples/smoke-output.json",
"assertions": [
{
"id": "top-result-holmes",
"description": "Holmes candidate ranks first for governed semantic search query.",
"pass": true
},
{
"id": "no-network",
"description": "Smoke runner uses only Python standard library and local fixtures.",
"pass": true
},
{
"id": "sourceos-no-mutable-state",
"description": "SourceOS carry path carries no model weights or mutable adapters.",
"pass": true
}
],
"governanceRefs": [
"SocioProphet/holmes",
"SocioProphet/functional-model-surfaces",
"SocioProphet/model-governance-ledger",
"SocioProphet/model-router",
"SourceOS-Linux/sourceos-model-carry",
"SocioProphet/sociosphere"
]
}
17 changes: 17 additions & 0 deletions examples/smoke-input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"query": "governed semantic search",
"candidates": [
{
"id": "holmes",
"text": "Holmes investigates evidence with semantic search and governed retrieval."
},
{
"id": "weather",
"text": "The weather report predicts rain and wind tomorrow."
},
{
"id": "sourceos",
"text": "SourceOS carries signed service references and local-only model profiles."
}
]
}
22 changes: 22 additions & 0 deletions examples/smoke-output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"serviceId": "ocrlab.holmes.ocr-pilot",
"query": "governed semantic search",
"ranking": [
{
"id": "holmes",
"score": 3,
"matchedTerms": ["governed", "search", "semantic"]
},
{
"id": "sourceos",
"score": 0,
"matchedTerms": []
},
{
"id": "weather",
"score": 0,
"matchedTerms": []
}
],
"pass": true
}
54 changes: 54 additions & 0 deletions manifests/functional-service.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"schemaVersion": "functional-service.v1",
"service": {
"id": "ocrlab.holmes.ocr-pilot",
"name": "OCRLab Holmes OCR Pilot",
"ownerRepository": "SociOS-Linux/ocrlab",
"status": "experimental",
"description": "Offline deterministic OCR-feature and ranking pilot for Holmes document-intelligence and SourceOS carry integration."
},
"function": "ocr",
"model": {
"modelRef": "urn:socioprophet:functional-model:ocrlab:deterministic-ocr:v0",
"adapterRefs": [],
"runtime": "python-stdlib-offline-smoke",
"mutableStatePolicy": "forbidden-in-sourceos"
},
"inputs": [
"image/document",
"text/corpus"
],
"outputs": [
"ocr/features",
"ranking/candidates",
"evidence/smoke-receipt"
],
"evals": {
"required": true,
"references": [
"examples/smoke-input.json",
"examples/smoke-output.json",
"evidence/smoke-receipt.example.json"
],
"minimumPromotionGate": "offline-smoke-similarity-ordering"
},
"governance": {
"ledgerRequired": true,
"guardrailRequired": true,
"routingRequired": true,
"policyRefs": [
"SocioProphet/model-governance-ledger",
"SocioProphet/guardrail-fabric",
"SocioProphet/model-router",
"SocioProphet/holmes"
]
},
"sourceosCarry": {
"allowed": true,
"carriesMutableModelState": false,
"clientRefRequired": true,
"launchProfileRefs": [
"urn:srcos:model-carry:functional-service:ocrlab-holmes-pilot"
]
}
}
71 changes: 71 additions & 0 deletions tools/emit_sourceos_carry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
from __future__ import annotations

import json
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
MANIFEST = ROOT / "manifests" / "functional-service.json"


def main() -> int:
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
service = manifest["service"]
carry = {
"schemaVersion": "functional-service-carry.v0",
"kind": "FunctionalServiceCarry",
"carryId": "urn:srcos:model-carry:functional-service:ocrlab-holmes-pilot",
"displayName": "OCRLab Holmes Pilot Carry Profile",
"service": {
"id": service["id"],
"ownerRepository": service["ownerRepository"],
"function": manifest["function"],
"manifestRef": "SociOS-Linux/ocrlab:manifests/functional-service.json"
},
"sourceos": {
"includeInImageBuilds": True,
"enabledByDefault": False,
"activationRequiresOptIn": True,
"carriesMutableModelState": False,
"carriesModelWeights": False,
"clientRefRequired": True
},
"runtime": {
"mode": "local-client-reference",
"networkDefault": "denied",
"launchProfileRef": "urn:srcos:launch-profile:ocrlab-holmes-pilot:disabled-v0",
"healthCheck": "make smoke"
},
"policy": {
"localOnlyDefault": True,
"sendPromptOffDeviceDefault": False,
"allowToolUse": False,
"allowNetwork": False,
"allowModelDownload": False,
"requiresSignedServiceReference": True,
"requiresLedgerForPromotion": True,
"requiresSignedIntentForPersonalTuning": True
},
"evidence": {
"emitRuntimeHealth": True,
"emitRoutingDecision": True,
"emitSmokeReceipt": True,
"emitPromptHashOnly": True,
"smokeReceiptRef": "SociOS-Linux/ocrlab:evidence/smoke-receipt.example.json"
},
"integrations": {
"productSurface": "SocioProphet/holmes",
"workspaceController": "SocioProphet/sociosphere",
"contractSpine": "SocioProphet/functional-model-surfaces",
"carryAuthority": "SourceOS-Linux/sourceos-model-carry",
"optInAutomation": "SociOS-Linux/socios",
"governanceLedger": "SocioProphet/model-governance-ledger",
"router": "SocioProphet/model-router"
}
}
print(json.dumps(carry, indent=2, sort_keys=True))
return 0


if __name__ == "__main__":
raise SystemExit(main())
50 changes: 50 additions & 0 deletions tools/smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
from __future__ import annotations

import json
import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
INPUT = ROOT / "examples" / "smoke-input.json"
EXPECTED = ROOT / "examples" / "smoke-output.json"


def terms(text: str) -> set[str]:
return set(re.findall(r"[a-z0-9]+", text.lower()))


def rank(payload: dict) -> dict:
query = payload["query"]
query_terms = terms(query)
rows = []
for candidate in payload.get("candidates", []):
matched = sorted(query_terms & terms(candidate.get("text", "")))
rows.append({
"id": candidate["id"],
"score": len(matched),
"matchedTerms": matched,
})
rows.sort(key=lambda row: (-row["score"], row["id"]))
return {
"serviceId": "ocrlab.holmes.ocr-pilot",
"query": query,
"ranking": rows,
"pass": bool(rows and rows[0]["id"] == "holmes"),
}


def main() -> int:
payload = json.loads(INPUT.read_text(encoding="utf-8"))
actual = rank(payload)
expected = json.loads(EXPECTED.read_text(encoding="utf-8"))
if actual != expected:
print(json.dumps({"ok": False, "actual": actual, "expected": expected}, indent=2), file=sys.stderr)
return 1
print(json.dumps({"ok": True, "serviceId": actual["serviceId"], "top": actual["ranking"][0]}, indent=2))
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading