This commit is contained in:
1 parent
4db3f45164
commit
d25c04cdd2
175 files changed
+56243
-15
No files matched your search
@@ -0,0 +1,243 @@
|
||||
"""Measure the SQLite search index on a deterministic synthetic corpus.
|
||||
|
||||
This is an adapter benchmark, not the PRD's end-to-end or semantic-quality gate.
|
||||
Run with ``./scripts/python-tool python scripts/benchmark_search.py``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import sqlite3
|
||||
import statistics
|
||||
import tempfile
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from gewu.adapters.sqlite_search import SQLiteSearchIndex
|
||||
from gewu.domain.contracts import (
|
||||
Claim,
|
||||
Confidence,
|
||||
EvidenceReference,
|
||||
KnowledgeNode,
|
||||
Lifecycle,
|
||||
Locator,
|
||||
NodeType,
|
||||
ReviewState,
|
||||
SupportAssessment,
|
||||
SupportStatus,
|
||||
)
|
||||
from gewu.domain.validation import assertion_digest, evidence_digest
|
||||
|
||||
|
||||
def base_node() -> KnowledgeNode:
|
||||
text = "Interactive experiences welcomed visitors during the synthetic benchmark."
|
||||
assertion = assertion_digest(text)
|
||||
evidence = EvidenceReference(
|
||||
source_revision_id="benchmark:source:r1",
|
||||
extraction_id="a" * 64,
|
||||
assertion_path="claims[0].text",
|
||||
assertion_digest=assertion,
|
||||
quote=text,
|
||||
locator=Locator(kind="text_span", block_id="benchmark-block", start=0, end=len(text)),
|
||||
normalization_profile="unicode-nfc-lf-v1",
|
||||
)
|
||||
now = datetime.now(UTC)
|
||||
claim = Claim(
|
||||
claim_id="benchmark:claim",
|
||||
text=text,
|
||||
evidence_type="reported",
|
||||
evidence=(evidence,),
|
||||
assessment=SupportAssessment(
|
||||
assessment_id="benchmark:assessment",
|
||||
assertion_digest=assertion,
|
||||
evidence_digests=(evidence_digest(evidence),),
|
||||
assessor_id="benchmark:assessor",
|
||||
run_id="benchmark:run",
|
||||
method="synthetic benchmark fixture",
|
||||
status=SupportStatus.SUPPORTED,
|
||||
rationale="Fixture only; not a semantic assessment.",
|
||||
),
|
||||
)
|
||||
return KnowledgeNode(
|
||||
node_id="benchmark:template",
|
||||
project_id="bench-project-000",
|
||||
node_type=NodeType.FINDING,
|
||||
title="Interactive visitor evidence template",
|
||||
summary=text,
|
||||
created=now,
|
||||
updated=now,
|
||||
review_state=ReviewState.REVIEWED,
|
||||
lifecycle=Lifecycle.ACTIVE,
|
||||
confidence=Confidence.MEDIUM,
|
||||
claim_support="supported",
|
||||
run_id="benchmark:run",
|
||||
agent_id="benchmark:agent",
|
||||
model_id="synthetic-fixture",
|
||||
claims=(claim,),
|
||||
)
|
||||
|
||||
|
||||
class SyntheticRepository:
|
||||
"""Small in-memory canonical port that feeds validated production records."""
|
||||
|
||||
def __init__(self, nodes_per_project: int, projects: int) -> None:
|
||||
revision = hashlib.sha256(f"benchmark:{nodes_per_project}:{projects}".encode()).hexdigest()
|
||||
self._revision = revision
|
||||
self._records: dict[str, bytes] = {}
|
||||
self.project_ids = tuple(f"bench-project-{index:03d}" for index in range(projects))
|
||||
template = base_node()
|
||||
for project_index, project_id in enumerate(self.project_ids):
|
||||
project_key = hashlib.sha256(project_id.encode()).hexdigest()
|
||||
for node_index in range(nodes_per_project):
|
||||
node_id = f"benchmark:{project_index:03d}:node-{node_index:06d}"
|
||||
node_key = hashlib.sha256(node_id.encode()).hexdigest()
|
||||
path = f"projects/{project_key}/nodes/{node_key}.yaml"
|
||||
node = template.model_copy(
|
||||
update={
|
||||
"node_id": node_id,
|
||||
"project_id": project_id,
|
||||
"title": f"Interactive visitor evidence {project_index} {node_index}",
|
||||
"summary": (
|
||||
"Interactive experiences welcomed visitors; "
|
||||
f"synthetic record {node_index}."
|
||||
),
|
||||
}
|
||||
)
|
||||
self._records[path] = node.model_dump_json().encode("utf-8")
|
||||
|
||||
def head(self) -> str:
|
||||
return self._revision
|
||||
|
||||
def list_files(self, revision: str, prefix: str) -> tuple[str, ...]:
|
||||
if revision != self._revision:
|
||||
raise ValueError("Unknown synthetic revision")
|
||||
return tuple(path for path in self._records if path.startswith(prefix))
|
||||
|
||||
def read_file(self, revision: str, path: str) -> bytes | None:
|
||||
if revision != self._revision:
|
||||
raise ValueError("Unknown synthetic revision")
|
||||
return self._records.get(path)
|
||||
|
||||
|
||||
def percentile(samples: Sequence[float], value: float) -> float:
|
||||
ordered = sorted(samples)
|
||||
position = max(0, math.ceil(len(ordered) * value) - 1)
|
||||
return ordered[position]
|
||||
|
||||
|
||||
def processor_name() -> str:
|
||||
reported = platform.processor()
|
||||
if reported:
|
||||
return reported
|
||||
if platform.system() == "Linux":
|
||||
try:
|
||||
for line in Path("/proc/cpuinfo").read_text(encoding="utf-8").splitlines():
|
||||
if line.startswith("model name"):
|
||||
return line.partition(":")[2].strip()
|
||||
except OSError:
|
||||
pass
|
||||
return "not reported"
|
||||
|
||||
|
||||
def run(nodes_per_project: int, projects: int, samples: int, root: Path) -> dict[str, object]:
|
||||
build_started = time.perf_counter()
|
||||
repository = SyntheticRepository(nodes_per_project, projects)
|
||||
seed_seconds = time.perf_counter() - build_started
|
||||
index = SQLiteSearchIndex(root / "search.sqlite3", repository) # type: ignore[arg-type]
|
||||
|
||||
cold_started = time.perf_counter()
|
||||
cold_result = index.search(
|
||||
query="interactive visitors",
|
||||
project_ids=(repository.project_ids[0],),
|
||||
include_shared=False,
|
||||
limit=20,
|
||||
)
|
||||
cold_seconds = time.perf_counter() - cold_started
|
||||
|
||||
timings: list[float] = []
|
||||
for _ in range(samples):
|
||||
started = time.perf_counter()
|
||||
result = index.search(
|
||||
query="interactive visitors",
|
||||
project_ids=(repository.project_ids[0],),
|
||||
include_shared=False,
|
||||
limit=20,
|
||||
)
|
||||
timings.append((time.perf_counter() - started) * 1000)
|
||||
if len(result.nodes) != 20:
|
||||
raise RuntimeError(f"Expected 20 results, received {len(result.nodes)}")
|
||||
|
||||
total_nodes = nodes_per_project * projects
|
||||
return {
|
||||
"benchmark": "sqlite-search-adapter-synthetic-v1",
|
||||
"evidence_scope": (
|
||||
"synthetic adapter benchmark; excludes HTTP/auth, Git object reads, "
|
||||
"and semantic relevance"
|
||||
),
|
||||
"environment": {
|
||||
"platform": platform.platform(),
|
||||
"processor": processor_name(),
|
||||
"logical_cpus": os.cpu_count(),
|
||||
"python": platform.python_version(),
|
||||
"sqlite": sqlite3.sqlite_version,
|
||||
},
|
||||
"workload": {
|
||||
"projects": projects,
|
||||
"nodes_per_project": nodes_per_project,
|
||||
"total_nodes": total_nodes,
|
||||
"query_project": repository.project_ids[0],
|
||||
"query": "interactive visitors",
|
||||
"page_size": 20,
|
||||
"warm_samples": samples,
|
||||
},
|
||||
"measurements": {
|
||||
"synthetic_seed_seconds": round(seed_seconds, 3),
|
||||
"cold_index_rebuild_and_first_search_seconds": round(cold_seconds, 3),
|
||||
"warm_search_ms": {
|
||||
"p50": round(statistics.median(timings), 3),
|
||||
"p95": round(percentile(timings, 0.95), 3),
|
||||
"max": round(max(timings), 3),
|
||||
},
|
||||
"first_result_count": len(cold_result.nodes),
|
||||
},
|
||||
"limitations": [
|
||||
"synthetic records reuse one valid claim/citation shape and are not a relevance corpus",
|
||||
"in-memory canonical reads omit Git filesystem and HTTP/API overhead",
|
||||
"the PRD hardware, cache state, concurrency, and percentile decisions are not fixed",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--nodes-per-project", type=int, default=10_000)
|
||||
parser.add_argument("--projects", type=int, default=1)
|
||||
parser.add_argument("--samples", type=int, default=50)
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
if min(args.nodes_per_project, args.projects, args.samples) < 1:
|
||||
parser.error("node, project, and sample counts must be positive")
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="gewu-search-benchmark-") as temporary:
|
||||
report = run(
|
||||
args.nodes_per_project,
|
||||
args.projects,
|
||||
args.samples,
|
||||
Path(temporary),
|
||||
)
|
||||
rendered = json.dumps(report, indent=2, sort_keys=True) + "\n"
|
||||
if args.output:
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(rendered, encoding="utf-8")
|
||||
print(rendered, end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Enforce inward dependencies in the domain package."""
|
||||
|
||||
import ast
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FORBIDDEN = ("fastapi", "mcp", "gewu.adapters", "gewu.bootstrap", "gewu.transports")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
domain = ROOT / "src" / "gewu" / "domain"
|
||||
violations: list[str] = []
|
||||
for path in domain.rglob("*.py"):
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
names = [alias.name for alias in node.names]
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
names = [node.module or ""]
|
||||
else:
|
||||
continue
|
||||
for name in names:
|
||||
if any(name == prefix or name.startswith(prefix + ".") for prefix in FORBIDDEN):
|
||||
violations.append(f"{path.relative_to(ROOT)}:{node.lineno}: {name}")
|
||||
if violations:
|
||||
raise SystemExit("Forbidden domain dependencies:\n" + "\n".join(violations))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Bind the GEWU agent pack to the MCP tools actually exposed by the service."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from gewu.adapters.git_repository import GitCanonicalRepository
|
||||
from gewu.adapters.sqlite_identity import SQLiteIdentityStore
|
||||
from gewu.adapters.sqlite_search import SQLiteSearchIndex
|
||||
from gewu.adapters.text_extractor import TextMarkdownExtractor
|
||||
from gewu.application.acceptance import AcceptanceService
|
||||
from gewu.application.context import ValidationContext
|
||||
from gewu.application.reading import CanonicalNodeReader, CanonicalSourceReader
|
||||
from gewu.application.search import CanonicalSearchService
|
||||
from gewu.application.uploads import LocalArtifactStore
|
||||
from gewu.domain.operations import Transport, operations_for
|
||||
from gewu.transports.mcp_server import create_mcp_server
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PACK = ROOT / "agents" / "gewu"
|
||||
CONTRACT_PATH = PACK / "contracts" / "service-v1.json"
|
||||
SCHEMA_OUTPUT = PACK / "contracts" / "schemas"
|
||||
CATALOG_OUTPUT = PACK / "contracts" / "catalog-v1.json"
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
async def _tool_inventory(data_root: Path) -> list[dict[str, Any]]:
|
||||
repository = GitCanonicalRepository(data_root / "knowledge.git")
|
||||
identities = SQLiteIdentityStore(data_root / "identity.sqlite3")
|
||||
artifact_store = LocalArtifactStore(data_root / "artifacts")
|
||||
|
||||
def context_provider() -> ValidationContext:
|
||||
return ValidationContext({}, {}, {}, {}, {})
|
||||
|
||||
acceptance_service = AcceptanceService(
|
||||
repository=repository,
|
||||
artifact_store=artifact_store,
|
||||
workflow_root=data_root / "workflow",
|
||||
context_provider=context_provider,
|
||||
actor_is_current=lambda _actor: True,
|
||||
)
|
||||
search_service = CanonicalSearchService(
|
||||
index=SQLiteSearchIndex(data_root / "index.sqlite3", repository),
|
||||
project_ids_provider=lambda: (),
|
||||
canonical_head=repository.head,
|
||||
)
|
||||
server = create_mcp_server(
|
||||
identity_provider=identities,
|
||||
node_reader=CanonicalNodeReader(repository),
|
||||
source_reader=CanonicalSourceReader(repository),
|
||||
search_service=search_service,
|
||||
artifact_store=artifact_store,
|
||||
extractor=TextMarkdownExtractor(),
|
||||
acceptance_service=acceptance_service,
|
||||
context_provider=context_provider,
|
||||
)
|
||||
tools = await server.list_tools()
|
||||
return [
|
||||
{
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"input_schema": tool.input_schema,
|
||||
}
|
||||
for tool in tools
|
||||
]
|
||||
|
||||
|
||||
def build_contract(data_root: Path, *, schema_output: Path | None = None) -> dict[str, Any]:
|
||||
catalog_path = ROOT / "schema" / "v1" / "catalog.json"
|
||||
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
|
||||
tools = asyncio.run(_tool_inventory(data_root))
|
||||
expected_names = {item.name for item in operations_for(Transport.MCP)}
|
||||
actual_names = {tool["name"] for tool in tools}
|
||||
if actual_names != expected_names:
|
||||
raise RuntimeError("MCP tool inventory does not match the explicit operation catalog")
|
||||
if schema_output is not None:
|
||||
schema_output.mkdir(parents=True, exist_ok=True)
|
||||
record_schemas = []
|
||||
for source in sorted((ROOT / "schema" / "v1" / "records").glob("*.schema.json")):
|
||||
destination = schema_output / source.name if schema_output is not None else source
|
||||
if schema_output is not None:
|
||||
shutil.copyfile(source, destination)
|
||||
record_schemas.append({"path": f"schemas/{source.name}", "sha256": _sha256(destination)})
|
||||
return {
|
||||
"contract_id": "gewu.service.v1",
|
||||
"contract_version": 1,
|
||||
"api_version": catalog["api_version"],
|
||||
"catalog_sha256": _sha256(catalog_path),
|
||||
"operations": [item for item in catalog["operations"] if "mcp" in item["transports"]],
|
||||
"record_schemas": record_schemas,
|
||||
"tools": tools,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
CONTRACT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(ROOT / "schema" / "v1" / "catalog.json", CATALOG_OUTPUT)
|
||||
with tempfile.TemporaryDirectory(prefix="gewu-agent-contract-") as temporary:
|
||||
contract = build_contract(Path(temporary), schema_output=SCHEMA_OUTPUT)
|
||||
CONTRACT_PATH.write_text(
|
||||
json.dumps(contract, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Export the application operation allowlist for generated adapters."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from gewu.domain.operations import catalog_bytes
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DESTINATION = ROOT / "schema" / "v1" / "catalog.json"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
DESTINATION.write_bytes(catalog_bytes())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Export the composed HTTP API schema without touching the operator's data root."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from gewu.bootstrap.app import create_local_app
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DESTINATION = ROOT / "schema" / "v1" / "openapi.json"
|
||||
|
||||
|
||||
def componentize_record_schema(
|
||||
name: str, record_schema: dict[str, Any], components: dict[str, Any]
|
||||
) -> None:
|
||||
definitions = record_schema.pop("$defs", {})
|
||||
|
||||
def rewrite_local_refs(value: Any) -> Any:
|
||||
if isinstance(value, list):
|
||||
return [rewrite_local_refs(item) for item in value]
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
rewritten = {key: rewrite_local_refs(item) for key, item in value.items() if key != "$defs"}
|
||||
reference = rewritten.get("$ref")
|
||||
if isinstance(reference, str) and reference.startswith("#/$defs/"):
|
||||
definition_name = reference.removeprefix("#/$defs/")
|
||||
rewritten["$ref"] = f"#/components/schemas/{name}__{definition_name}"
|
||||
return rewritten
|
||||
|
||||
for definition_name, definition in definitions.items():
|
||||
component_name = f"{name}__{definition_name}"
|
||||
components.setdefault(component_name, rewrite_local_refs(definition))
|
||||
components.setdefault(name, rewrite_local_refs(record_schema))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
previous_data_root = os.environ.get("GEWU_DATA_ROOT")
|
||||
with tempfile.TemporaryDirectory(prefix="gewu-openapi-") as temporary:
|
||||
os.environ["GEWU_DATA_ROOT"] = str(Path(temporary) / "data")
|
||||
try:
|
||||
schema: dict[str, Any] = create_local_app().openapi()
|
||||
finally:
|
||||
if previous_data_root is None:
|
||||
os.environ.pop("GEWU_DATA_ROOT", None)
|
||||
else:
|
||||
os.environ["GEWU_DATA_ROOT"] = previous_data_root
|
||||
schema_components = schema.setdefault("components", {}).setdefault("schemas", {})
|
||||
for record_schema in sorted((ROOT / "schema" / "v1" / "records").glob("*.schema.json")):
|
||||
component_name = record_schema.name.removesuffix(".schema.json")
|
||||
schema_value = json.loads(record_schema.read_text(encoding="utf-8"))
|
||||
componentize_record_schema(component_name, schema_value, schema_components)
|
||||
DESTINATION.parent.mkdir(parents=True, exist_ok=True)
|
||||
DESTINATION.write_text(
|
||||
json.dumps(schema, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Export public Pydantic records as versioned JSON Schemas."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from gewu.domain.contracts import (
|
||||
AcceptanceManifest,
|
||||
AutoAcceptPolicy,
|
||||
DomainError,
|
||||
ExtractionRecord,
|
||||
KnowledgeNode,
|
||||
NeighborRequest,
|
||||
ProjectRecord,
|
||||
ProposalReceipt,
|
||||
ProposalRevision,
|
||||
ProposalStatus,
|
||||
ReadEnvelope,
|
||||
RunCheckpointRequest,
|
||||
RunCreateRequest,
|
||||
RunRecord,
|
||||
Scope,
|
||||
SearchRequest,
|
||||
ServiceDiagnostics,
|
||||
SourceRevisionRecord,
|
||||
TypedEdge,
|
||||
)
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DESTINATION = ROOT / "schema" / "v1" / "records"
|
||||
MODELS = (
|
||||
AcceptanceManifest,
|
||||
AutoAcceptPolicy,
|
||||
DomainError,
|
||||
ExtractionRecord,
|
||||
KnowledgeNode,
|
||||
NeighborRequest,
|
||||
ProjectRecord,
|
||||
ProposalReceipt,
|
||||
ProposalRevision,
|
||||
ProposalStatus,
|
||||
ReadEnvelope,
|
||||
ServiceDiagnostics,
|
||||
RunCheckpointRequest,
|
||||
RunCreateRequest,
|
||||
RunRecord,
|
||||
SearchRequest,
|
||||
Scope,
|
||||
SourceRevisionRecord,
|
||||
TypedEdge,
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
DESTINATION.mkdir(parents=True, exist_ok=True)
|
||||
for model in MODELS:
|
||||
output = DESTINATION / f"{model.__name__}.schema.json"
|
||||
output.write_text(
|
||||
json.dumps(model.model_json_schema(mode="validation"), indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Provision one local service credential; the bearer key is shown only once."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from gewu.adapters.sqlite_identity import SQLiteIdentityStore
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--credential-id", required=True)
|
||||
parser.add_argument("--principal-id", required=True)
|
||||
parser.add_argument(
|
||||
"--role", action="append", choices=("admin", "editor", "viewer", "agent"), required=True
|
||||
)
|
||||
parser.add_argument("--project", action="append", default=[])
|
||||
arguments = parser.parse_args()
|
||||
data_root = Path(os.environ.get("GEWU_DATA_ROOT", "./.data/gewu")).expanduser().absolute()
|
||||
data_root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
token = SQLiteIdentityStore(data_root / "identity.sqlite3").provision(
|
||||
credential_id=arguments.credential_id,
|
||||
principal_id=arguments.principal_id,
|
||||
roles=tuple(arguments.role),
|
||||
project_ids=tuple(arguments.project),
|
||||
)
|
||||
print("Store this bearer credential securely; it cannot be displayed again:")
|
||||
print(token)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
exec uv run "$@"
|
||||
fi
|
||||
|
||||
python_bin=".venv/bin/python"
|
||||
if [[ ! -x "$python_bin" ]]; then
|
||||
python3 -m venv .venv
|
||||
fi
|
||||
|
||||
if ! "$python_bin" -m pip --version >/dev/null 2>&1; then
|
||||
"$python_bin" -m ensurepip --upgrade
|
||||
fi
|
||||
|
||||
marker=".venv/.gewu-dev-installed"
|
||||
if [[ ! -f "$marker" ]]; then
|
||||
"$python_bin" -m pip install --disable-pip-version-check -e '.[dev]'
|
||||
touch "$marker"
|
||||
fi
|
||||
|
||||
tool="$1"
|
||||
shift
|
||||
exec ".venv/bin/$tool" "$@"
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Install the explicitly labeled two-project test registry for disposable work."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
|
||||
from gewu.application.paths import reject_symlink_components
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--replace", action="store_true")
|
||||
arguments = parser.parse_args()
|
||||
data_root = Path(os.environ.get("GEWU_DATA_ROOT", "./.data/gewu")).expanduser().absolute()
|
||||
reject_symlink_components(data_root)
|
||||
control_root = data_root / "control"
|
||||
reject_symlink_components(control_root)
|
||||
control_root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
control_root.chmod(0o700)
|
||||
destination = control_root / "registry.json"
|
||||
if destination.exists() and not arguments.replace:
|
||||
raise SystemExit("Registry exists; pass --replace only to deliberately reset it")
|
||||
|
||||
fixture_path = (
|
||||
Path(__file__).resolve().parents[1] / "schema" / "fixtures" / "pilot-projects.json"
|
||||
)
|
||||
|
||||
def reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]:
|
||||
result: dict[str, object] = {}
|
||||
for key, value in pairs:
|
||||
if key in result:
|
||||
raise ValueError("Test fixture has duplicate JSON keys")
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
fixture = json.loads(
|
||||
fixture_path.read_text(encoding="utf-8"), object_pairs_hook=reject_duplicate_keys
|
||||
)
|
||||
project_fields = {
|
||||
"project_id",
|
||||
"title",
|
||||
"status",
|
||||
"north_star",
|
||||
"key_questions",
|
||||
"autonomy_policy_id",
|
||||
}
|
||||
projects = []
|
||||
for item in fixture["projects"]:
|
||||
project = {key: value for key, value in item.items() if key in project_fields}
|
||||
projects.append(project)
|
||||
config = {
|
||||
"schema_version": 1,
|
||||
"projects": projects,
|
||||
"policies": fixture["test_policies"],
|
||||
}
|
||||
temporary = control_root / f".{secrets.token_hex(12)}.tmp"
|
||||
try:
|
||||
with temporary.open("x", encoding="utf-8") as stream:
|
||||
json.dump(config, stream, ensure_ascii=False, allow_nan=False, indent=2)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
os.chmod(temporary, 0o600)
|
||||
os.replace(temporary, destination)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
print("Installed disposable fixture projects and test-only acceptance policies.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user