algo-cli-runtime 0.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algo_cli/__init__.py +3 -0
- algo_cli/__main__.py +7 -0
- algo_cli/_internal/__init__.py +12 -0
- algo_cli/_internal/policy_chain.py +259 -0
- algo_cli/action_registry.py +1047 -0
- algo_cli/agent_blocks.py +550 -0
- algo_cli/agent_pipeline.py +1457 -0
- algo_cli/agent_threads.py +308 -0
- algo_cli/animations.py +316 -0
- algo_cli/cache_admission.py +209 -0
- algo_cli/capability_mask.py +66 -0
- algo_cli/chat_protocol.py +116 -0
- algo_cli/chatgpt_auth.py +510 -0
- algo_cli/chatgpt_client.py +657 -0
- algo_cli/code_rag.py +479 -0
- algo_cli/config.py +651 -0
- algo_cli/context_budget.py +679 -0
- algo_cli/credential_helpers.py +315 -0
- algo_cli/deliberation.py +29 -0
- algo_cli/display.py +1470 -0
- algo_cli/evals/__init__.py +21 -0
- algo_cli/evals/algorithm_effectiveness.py +560 -0
- algo_cli/evals/competitive_harness_rating.py +702 -0
- algo_cli/evals/cot_quality.py +220 -0
- algo_cli/evals/harness_retrieval_benchmark.py +401 -0
- algo_cli/evals/performance_regression.py +136 -0
- algo_cli/evals/scorecard_grading.py +308 -0
- algo_cli/evals/session_distribution.py +84 -0
- algo_cli/execution_guardrails.py +806 -0
- algo_cli/extensions_manifest.py +84 -0
- algo_cli/git_evidence.py +227 -0
- algo_cli/google_workspace.py +407 -0
- algo_cli/google_workspace_auth.py +523 -0
- algo_cli/harness.py +2587 -0
- algo_cli/identity.py +557 -0
- algo_cli/index_compute_lab.py +228 -0
- algo_cli/inference_harness.py +70 -0
- algo_cli/intelligence/__init__.py +1103 -0
- algo_cli/intelligence/acrobat_config.py +307 -0
- algo_cli/intelligence/acrobat_manifests.py +338 -0
- algo_cli/intelligence/acrobat_models.py +195 -0
- algo_cli/intelligence/acrobat_pipeline.py +295 -0
- algo_cli/intelligence/acrobat_runtime.py +302 -0
- algo_cli/intelligence/acrobat_security.py +261 -0
- algo_cli/intelligence/acrobat_workflows.py +226 -0
- algo_cli/intelligence/actionability.py +165 -0
- algo_cli/intelligence/adversarial_audit.py +136 -0
- algo_cli/intelligence/agent_arena.py +92 -0
- algo_cli/intelligence/agent_benchmark.py +236 -0
- algo_cli/intelligence/agent_runtime.py +171 -0
- algo_cli/intelligence/agents_as_tools.py +70 -0
- algo_cli/intelligence/artifact_binding.py +80 -0
- algo_cli/intelligence/autonomous_engineer.py +1976 -0
- algo_cli/intelligence/backpressure.py +99 -0
- algo_cli/intelligence/bloom_filter.py +186 -0
- algo_cli/intelligence/bonferroni.py +66 -0
- algo_cli/intelligence/boundary_compaction.py +98 -0
- algo_cli/intelligence/catalog_verifier.py +172 -0
- algo_cli/intelligence/cavecrew.py +118 -0
- algo_cli/intelligence/changelog.py +176 -0
- algo_cli/intelligence/checkpoint_resume.py +92 -0
- algo_cli/intelligence/circuit_breaker.py +88 -0
- algo_cli/intelligence/clarification_gate.py +101 -0
- algo_cli/intelligence/code_graph.py +180 -0
- algo_cli/intelligence/coderank.py +97 -0
- algo_cli/intelligence/consistent_hash.py +150 -0
- algo_cli/intelligence/consortium_synthesis.py +139 -0
- algo_cli/intelligence/construction/__init__.py +241 -0
- algo_cli/intelligence/construction/common.py +273 -0
- algo_cli/intelligence/construction/documents.py +496 -0
- algo_cli/intelligence/construction/labor_units.py +1395 -0
- algo_cli/intelligence/construction/payments.py +470 -0
- algo_cli/intelligence/construction/risk.py +784 -0
- algo_cli/intelligence/content_extractor.py +132 -0
- algo_cli/intelligence/context_adaptive.py +102 -0
- algo_cli/intelligence/context_ops.py +95 -0
- algo_cli/intelligence/count_min.py +145 -0
- algo_cli/intelligence/cow_state.py +103 -0
- algo_cli/intelligence/critic_loop.py +119 -0
- algo_cli/intelligence/cross_source.py +113 -0
- algo_cli/intelligence/daemon_mode.py +99 -0
- algo_cli/intelligence/dag_orchestration.py +151 -0
- algo_cli/intelligence/deep_research.py +155 -0
- algo_cli/intelligence/degenerate_detector.py +78 -0
- algo_cli/intelligence/delta_report.py +92 -0
- algo_cli/intelligence/discovery_event_log.py +92 -0
- algo_cli/intelligence/document_ingest.py +298 -0
- algo_cli/intelligence/dual_layer_validate.py +151 -0
- algo_cli/intelligence/echo_fidelity.py +73 -0
- algo_cli/intelligence/ema_tuning.py +104 -0
- algo_cli/intelligence/event_log.py +92 -0
- algo_cli/intelligence/evidence_graph.py +114 -0
- algo_cli/intelligence/extension_host.py +162 -0
- algo_cli/intelligence/extension_manifest.py +115 -0
- algo_cli/intelligence/falsification_suite.py +178 -0
- algo_cli/intelligence/finance/__init__.py +169 -0
- algo_cli/intelligence/finance/anomalies.py +135 -0
- algo_cli/intelligence/finance/ap_ar.py +351 -0
- algo_cli/intelligence/finance/cash.py +162 -0
- algo_cli/intelligence/finance/close.py +332 -0
- algo_cli/intelligence/finance/common.py +244 -0
- algo_cli/intelligence/finance/construction.py +135 -0
- algo_cli/intelligence/finance/controls.py +172 -0
- algo_cli/intelligence/finance/evidence.py +119 -0
- algo_cli/intelligence/finance/exceptions.py +157 -0
- algo_cli/intelligence/finance/reconciliations.py +254 -0
- algo_cli/intelligence/finance/revenue.py +109 -0
- algo_cli/intelligence/finance/tax.py +74 -0
- algo_cli/intelligence/finance/workpapers.py +111 -0
- algo_cli/intelligence/finding_record.py +120 -0
- algo_cli/intelligence/flow_dag.py +267 -0
- algo_cli/intelligence/gatherer.py +223 -0
- algo_cli/intelligence/golden_master.py +98 -0
- algo_cli/intelligence/graph_rag.py +195 -0
- algo_cli/intelligence/group_chat.py +143 -0
- algo_cli/intelligence/hash_dedup.py +145 -0
- algo_cli/intelligence/hyperloglog.py +128 -0
- algo_cli/intelligence/incremental_index.py +316 -0
- algo_cli/intelligence/index_store.py +16 -0
- algo_cli/intelligence/iteration_plan.py +133 -0
- algo_cli/intelligence/kernel_plugins.py +167 -0
- algo_cli/intelligence/lesson_catalog.py +135 -0
- algo_cli/intelligence/llm_fallback.py +169 -0
- algo_cli/intelligence/log2_histogram.py +267 -0
- algo_cli/intelligence/lsp_integration.py +147 -0
- algo_cli/intelligence/memory_evolution.py +117 -0
- algo_cli/intelligence/minhash_lsh.py +182 -0
- algo_cli/intelligence/multi_model_score.py +174 -0
- algo_cli/intelligence/multi_tier_grade.py +211 -0
- algo_cli/intelligence/negative_controls.py +113 -0
- algo_cli/intelligence/numeric_clamp.py +63 -0
- algo_cli/intelligence/occ_editor.py +66 -0
- algo_cli/intelligence/output_normalize.py +112 -0
- algo_cli/intelligence/parallel_delegation.py +98 -0
- algo_cli/intelligence/parallel_fanout.py +104 -0
- algo_cli/intelligence/permission_modes.py +105 -0
- algo_cli/intelligence/pre_push_gate.py +68 -0
- algo_cli/intelligence/prefetch.py +171 -0
- algo_cli/intelligence/process_framework.py +217 -0
- algo_cli/intelligence/project_graph.py +387 -0
- algo_cli/intelligence/query_expansion.py +146 -0
- algo_cli/intelligence/ralph_loop.py +117 -0
- algo_cli/intelligence/rate_limiter.py +153 -0
- algo_cli/intelligence/refactor_transaction.py +94 -0
- algo_cli/intelligence/research_workspace.py +108 -0
- algo_cli/intelligence/retraction_ledger.py +72 -0
- algo_cli/intelligence/saga_pattern.py +88 -0
- algo_cli/intelligence/session_fork.py +100 -0
- algo_cli/intelligence/shadow_editor.py +67 -0
- algo_cli/intelligence/shell_session.py +213 -0
- algo_cli/intelligence/source_registry.py +143 -0
- algo_cli/intelligence/spawn_scales.py +99 -0
- algo_cli/intelligence/stat_stability.py +104 -0
- algo_cli/intelligence/structural_validator.py +148 -0
- algo_cli/intelligence/subagent_spawner.py +111 -0
- algo_cli/intelligence/symmetric_verify.py +70 -0
- algo_cli/intelligence/task_classifier.py +129 -0
- algo_cli/intelligence/team_execution.py +122 -0
- algo_cli/intelligence/tiered_access.py +121 -0
- algo_cli/intelligence/utility_registry.py +159 -0
- algo_cli/intuition_engine.py +560 -0
- algo_cli/intuition_injector.py +82 -0
- algo_cli/kernels/__init__.py +5 -0
- algo_cli/kernels/manifest.py +763 -0
- algo_cli/main.py +3903 -0
- algo_cli/memory_candidates.py +541 -0
- algo_cli/memory_echo_veil.py +394 -0
- algo_cli/memory_runtime.py +112 -0
- algo_cli/model_info.py +548 -0
- algo_cli/model_profile.py +160 -0
- algo_cli/model_routing.py +74 -0
- algo_cli/oneshot.py +331 -0
- algo_cli/perf_telemetry.py +389 -0
- algo_cli/plugins.py +245 -0
- algo_cli/private_event_store.py +654 -0
- algo_cli/quantization/__init__.py +24 -0
- algo_cli/quantization/lloyd_max.py +98 -0
- algo_cli/quantization/turbo_quant.py +308 -0
- algo_cli/reasoning/__init__.py +46 -0
- algo_cli/reasoning/combinatorial.py +356 -0
- algo_cli/reasoning/graph_of_thought.py +297 -0
- algo_cli/reasoning/mcts.py +220 -0
- algo_cli/reasoning/neuro_symbolic.py +250 -0
- algo_cli/reasoning/react.py +246 -0
- algo_cli/reasoning/reflexion.py +225 -0
- algo_cli/reasoning/tree_of_thought.py +241 -0
- algo_cli/reasoning_bridge.py +150 -0
- algo_cli/reconciliation.py +284 -0
- algo_cli/reflex.py +385 -0
- algo_cli/resources/docs/ALGO.md +13958 -0
- algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
- algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
- algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
- algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
- algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
- algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
- algo_cli/resources/docs/main-split-map.md +35 -0
- algo_cli/resources/docs/privacy-and-context.md +48 -0
- algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
- algo_cli/resources/skills/README.md +26 -0
- algo_cli/resources/skills/algo-cli.md +59 -0
- algo_cli/resources/skills/edit-file-precision.md +49 -0
- algo_cli/resources/skills/harness-search-first.md +47 -0
- algo_cli/resources/skills/memory-recall-ritual.md +51 -0
- algo_cli/resources/skills/qol-algorithms.md +224 -0
- algo_cli/resources/skills/smart-error-recovery.md +56 -0
- algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
- algo_cli/retrieval_algorithms.py +127 -0
- algo_cli/runtime_qos.py +236 -0
- algo_cli/runtime_services.py +320 -0
- algo_cli/session_commands.py +95 -0
- algo_cli/session_mode.py +113 -0
- algo_cli/skills.py +430 -0
- algo_cli/slash_dispatch.py +1265 -0
- algo_cli/small_context.py +206 -0
- algo_cli/spawn_budget.py +89 -0
- algo_cli/task_ledger.py +84 -0
- algo_cli/task_router.py +197 -0
- algo_cli/tool_context.py +94 -0
- algo_cli/tool_contract.py +99 -0
- algo_cli/tool_policy.py +357 -0
- algo_cli/tool_runtime.py +647 -0
- algo_cli/tools.py +3056 -0
- algo_cli/url_scheme.py +174 -0
- algo_cli/verify.py +154 -0
- algo_cli/version_manifest.py +178 -0
- algo_cli/vision_screenshot_verify.py +76 -0
- algo_cli/workspace_resolver.py +68 -0
- algo_cli/x_account.py +209 -0
- algo_cli/xai_auth.py +374 -0
- algo_cli/xai_client.py +600 -0
- algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
- algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
- algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
- algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
- algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
- ollama_cli/__init__.py +67 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""B163, B164, B165, B175: Acrobat-derived runtime patterns.
|
|
2
|
+
|
|
3
|
+
- B163: Deterministic Variant / Experiment Bucketing
|
|
4
|
+
- B164: Broker / Sidecar Process Boundary
|
|
5
|
+
- B165: Chunked Web Resource Loading
|
|
6
|
+
- B175: Script Bytecode Compilation Cache
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any, Callable, Protocol
|
|
13
|
+
import hashlib
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ── B163: Deterministic Variant / Experiment Bucketing ────────────────
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class VariantRange:
|
|
22
|
+
"""A numeric range mapping to a variant."""
|
|
23
|
+
start: int # inclusive
|
|
24
|
+
end: int # inclusive
|
|
25
|
+
variant: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ExperimentDefinition:
|
|
30
|
+
"""An experiment with variant ranges (B163)."""
|
|
31
|
+
experiment_id: str
|
|
32
|
+
ranges: list[VariantRange] = field(default_factory=list)
|
|
33
|
+
control_variant: str = "control"
|
|
34
|
+
|
|
35
|
+
def assign(self, subject_id: str) -> str:
|
|
36
|
+
"""Deterministically assign a variant based on subject_id hash."""
|
|
37
|
+
bucket = self._hash_bucket(subject_id)
|
|
38
|
+
for r in self.ranges:
|
|
39
|
+
if r.start <= bucket <= r.end:
|
|
40
|
+
return r.variant
|
|
41
|
+
return self.control_variant
|
|
42
|
+
|
|
43
|
+
def _hash_bucket(self, subject_id: str) -> int:
|
|
44
|
+
"""Stable hash to bucket 1-1000."""
|
|
45
|
+
h = hashlib.sha256(f"{self.experiment_id}:{subject_id}".encode()).hexdigest()
|
|
46
|
+
return int(h[:8], 16) % 1000 + 1
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ExperimentRegistry:
|
|
50
|
+
"""Registry of experiments (B163)."""
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
self._experiments: dict[str, ExperimentDefinition] = {}
|
|
54
|
+
|
|
55
|
+
def register(self, experiment: ExperimentDefinition) -> None:
|
|
56
|
+
self._experiments[experiment.experiment_id] = experiment
|
|
57
|
+
|
|
58
|
+
def assign(self, experiment_id: str, subject_id: str) -> str:
|
|
59
|
+
"""Assign a variant for the given experiment and subject."""
|
|
60
|
+
exp = self._experiments.get(experiment_id)
|
|
61
|
+
if not exp:
|
|
62
|
+
return "control"
|
|
63
|
+
return exp.assign(subject_id)
|
|
64
|
+
|
|
65
|
+
def all_experiments(self) -> list[str]:
|
|
66
|
+
return list(self._experiments.keys())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ── B164: Broker / Sidecar Process Boundary ───────────────────────────
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class SidecarRequest:
|
|
74
|
+
"""A request to a sidecar worker (B164)."""
|
|
75
|
+
command: str
|
|
76
|
+
params: dict[str, Any] = field(default_factory=dict)
|
|
77
|
+
timeout_seconds: float = 30.0
|
|
78
|
+
permission_envelope: dict[str, bool] = field(default_factory=dict)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class SidecarResult:
|
|
83
|
+
"""Result from a sidecar worker (B164)."""
|
|
84
|
+
success: bool = True
|
|
85
|
+
output: Any = None
|
|
86
|
+
error: str = ""
|
|
87
|
+
timed_out: bool = False
|
|
88
|
+
crashed: bool = False
|
|
89
|
+
duration_ms: float = 0.0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class SidecarWorker(Protocol):
|
|
93
|
+
"""Protocol for sidecar workers (B164)."""
|
|
94
|
+
def execute(self, request: SidecarRequest) -> SidecarResult: ...
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class InProcessSidecar:
|
|
98
|
+
"""A simple in-process sidecar for testing (B164)."""
|
|
99
|
+
|
|
100
|
+
def __init__(self, handlers: dict[str, Callable[[dict], Any]] | None = None) -> None:
|
|
101
|
+
self._handlers = handlers or {}
|
|
102
|
+
|
|
103
|
+
def register(self, command: str, handler: Callable[[dict], Any]) -> None:
|
|
104
|
+
self._handlers[command] = handler
|
|
105
|
+
|
|
106
|
+
def execute(self, request: SidecarRequest) -> SidecarResult:
|
|
107
|
+
start = time.monotonic()
|
|
108
|
+
handler = self._handlers.get(request.command)
|
|
109
|
+
if not handler:
|
|
110
|
+
return SidecarResult(
|
|
111
|
+
success=False,
|
|
112
|
+
error=f"unknown command: {request.command}",
|
|
113
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
114
|
+
)
|
|
115
|
+
# Check permission envelope
|
|
116
|
+
if request.permission_envelope.get("external_send") and not request.permission_envelope.get("approved"):
|
|
117
|
+
return SidecarResult(
|
|
118
|
+
success=False,
|
|
119
|
+
error="external_send not approved",
|
|
120
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
121
|
+
)
|
|
122
|
+
try:
|
|
123
|
+
output = handler(request.params)
|
|
124
|
+
return SidecarResult(
|
|
125
|
+
success=True,
|
|
126
|
+
output=output,
|
|
127
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
128
|
+
)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
return SidecarResult(
|
|
131
|
+
success=False,
|
|
132
|
+
error=str(e),
|
|
133
|
+
crashed=True,
|
|
134
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class BrokerManager:
|
|
139
|
+
"""Manages broker/sidecar process boundaries (B164)."""
|
|
140
|
+
|
|
141
|
+
def __init__(self) -> None:
|
|
142
|
+
self._workers: dict[str, SidecarWorker] = {}
|
|
143
|
+
|
|
144
|
+
def register_worker(self, name: str, worker: SidecarWorker) -> None:
|
|
145
|
+
self._workers[name] = worker
|
|
146
|
+
|
|
147
|
+
def dispatch(self, worker_name: str, request: SidecarRequest) -> SidecarResult:
|
|
148
|
+
"""Dispatch a request to a named worker."""
|
|
149
|
+
worker = self._workers.get(worker_name)
|
|
150
|
+
if not worker:
|
|
151
|
+
return SidecarResult(success=False, error=f"unknown worker: {worker_name}")
|
|
152
|
+
try:
|
|
153
|
+
return worker.execute(request)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
# Worker crash does not kill the broker
|
|
156
|
+
return SidecarResult(success=False, error=str(e), crashed=True)
|
|
157
|
+
|
|
158
|
+
def available_workers(self) -> list[str]:
|
|
159
|
+
return list(self._workers.keys())
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ── B165: Chunked Web Resource Loading ──────────────────────────────
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class ResourceChunk:
|
|
167
|
+
"""A chunk of a web resource (B165)."""
|
|
168
|
+
chunk_id: str
|
|
169
|
+
version: str = ""
|
|
170
|
+
size_bytes: int = 0
|
|
171
|
+
optional: bool = True
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class ChunkLoadResult:
|
|
176
|
+
"""Result of loading a chunk."""
|
|
177
|
+
chunk_id: str
|
|
178
|
+
loaded: bool = True
|
|
179
|
+
from_cache: bool = False
|
|
180
|
+
error: str = ""
|
|
181
|
+
load_time_ms: float = 0.0
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ChunkedResourceLoader:
|
|
185
|
+
"""Lazy-loads optional feature chunks (B165)."""
|
|
186
|
+
|
|
187
|
+
def __init__(self) -> None:
|
|
188
|
+
self._chunks: dict[str, ResourceChunk] = {}
|
|
189
|
+
self._cache: dict[str, Any] = {}
|
|
190
|
+
self._loaded: set[str] = set()
|
|
191
|
+
|
|
192
|
+
def register_chunk(self, chunk: ResourceChunk) -> None:
|
|
193
|
+
self._chunks[chunk.chunk_id] = chunk
|
|
194
|
+
|
|
195
|
+
def load(self, chunk_id: str, loader: Callable[[], Any] | None = None) -> ChunkLoadResult:
|
|
196
|
+
"""Load a chunk by ID, using the provided loader function."""
|
|
197
|
+
start = time.monotonic()
|
|
198
|
+
chunk = self._chunks.get(chunk_id)
|
|
199
|
+
if not chunk:
|
|
200
|
+
return ChunkLoadResult(chunk_id=chunk_id, loaded=False, error=f"unknown chunk: {chunk_id}")
|
|
201
|
+
if chunk_id in self._cache:
|
|
202
|
+
return ChunkLoadResult(
|
|
203
|
+
chunk_id=chunk_id, loaded=True, from_cache=True,
|
|
204
|
+
load_time_ms=(time.monotonic() - start) * 1000,
|
|
205
|
+
)
|
|
206
|
+
if loader:
|
|
207
|
+
try:
|
|
208
|
+
self._cache[chunk_id] = loader()
|
|
209
|
+
self._loaded.add(chunk_id)
|
|
210
|
+
return ChunkLoadResult(
|
|
211
|
+
chunk_id=chunk_id, loaded=True,
|
|
212
|
+
load_time_ms=(time.monotonic() - start) * 1000,
|
|
213
|
+
)
|
|
214
|
+
except Exception as e:
|
|
215
|
+
return ChunkLoadResult(
|
|
216
|
+
chunk_id=chunk_id, loaded=False, error=str(e),
|
|
217
|
+
load_time_ms=(time.monotonic() - start) * 1000,
|
|
218
|
+
)
|
|
219
|
+
return ChunkLoadResult(
|
|
220
|
+
chunk_id=chunk_id, loaded=False, error="no loader provided",
|
|
221
|
+
load_time_ms=(time.monotonic() - start) * 1000,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def is_loaded(self, chunk_id: str) -> bool:
|
|
225
|
+
return chunk_id in self._loaded
|
|
226
|
+
|
|
227
|
+
def invalidate(self, chunk_id: str) -> bool:
|
|
228
|
+
"""Invalidate a cached chunk."""
|
|
229
|
+
if chunk_id in self._cache:
|
|
230
|
+
del self._cache[chunk_id]
|
|
231
|
+
self._loaded.discard(chunk_id)
|
|
232
|
+
return True
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
def all_chunks(self) -> list[str]:
|
|
236
|
+
return list(self._chunks.keys())
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ── B175: Script Bytecode Compilation Cache ──────────────────────────
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@dataclass
|
|
243
|
+
class CacheEntry:
|
|
244
|
+
"""A compiled script cache entry (B175)."""
|
|
245
|
+
source_path: str
|
|
246
|
+
cache_path: str
|
|
247
|
+
source_mtime: float
|
|
248
|
+
platform: str
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class ScriptBytecodeCache:
|
|
252
|
+
"""Caches compiled scripts to avoid re-parsing on every startup (B175)."""
|
|
253
|
+
|
|
254
|
+
def __init__(self, platform: str = "win") -> None:
|
|
255
|
+
self._platform = platform
|
|
256
|
+
self._entries: dict[str, CacheEntry] = {}
|
|
257
|
+
self._compile_fn: Callable[[str], bytes] | None = None
|
|
258
|
+
|
|
259
|
+
def set_compiler(self, compile_fn: Callable[[str], bytes]) -> None:
|
|
260
|
+
self._compile_fn = compile_fn
|
|
261
|
+
|
|
262
|
+
def get_or_compile(self, source_path: str, source_mtime: float) -> tuple[bytes, bool]:
|
|
263
|
+
"""Get cached bytecode or compile from source.
|
|
264
|
+
Returns (bytecode, from_cache).
|
|
265
|
+
"""
|
|
266
|
+
entry = self._entries.get(source_path)
|
|
267
|
+
if entry and entry.source_mtime >= source_mtime and entry.platform == self._platform:
|
|
268
|
+
# Cache hit
|
|
269
|
+
return b"", True # In real impl, would read cache file
|
|
270
|
+
|
|
271
|
+
# Cache miss - compile
|
|
272
|
+
if not self._compile_fn:
|
|
273
|
+
raise RuntimeError("no compiler set")
|
|
274
|
+
bytecode = self._compile_fn(source_path)
|
|
275
|
+
self._entries[source_path] = CacheEntry(
|
|
276
|
+
source_path=source_path,
|
|
277
|
+
cache_path=f"{source_path}.cache",
|
|
278
|
+
source_mtime=source_mtime,
|
|
279
|
+
platform=self._platform,
|
|
280
|
+
)
|
|
281
|
+
return bytecode, False
|
|
282
|
+
|
|
283
|
+
def invalidate(self, source_path: str) -> bool:
|
|
284
|
+
"""Invalidate a cache entry."""
|
|
285
|
+
if source_path in self._entries:
|
|
286
|
+
del self._entries[source_path]
|
|
287
|
+
return True
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
def is_stale(self, source_path: str, source_mtime: float) -> bool:
|
|
291
|
+
"""Check if a cache entry is stale."""
|
|
292
|
+
entry = self._entries.get(source_path)
|
|
293
|
+
if not entry:
|
|
294
|
+
return True
|
|
295
|
+
return entry.source_mtime < source_mtime or entry.platform != self._platform
|
|
296
|
+
|
|
297
|
+
def cache_stats(self) -> dict[str, int]:
|
|
298
|
+
"""Return cache statistics."""
|
|
299
|
+
return {
|
|
300
|
+
"entries": len(self._entries),
|
|
301
|
+
"platform": hash(self._platform), # just for testing
|
|
302
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""B159, B161, B173: Acrobat-derived security patterns.
|
|
2
|
+
|
|
3
|
+
- B159: Search/Redaction Pattern Packs
|
|
4
|
+
- B161: Pre-Model Disqualification Rules
|
|
5
|
+
- B173: Native Messaging Host Allowlist
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ── B159: Search/Redaction Pattern Packs ───────────────────────────────
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class RedactionPattern:
|
|
20
|
+
"""A single redaction pattern (B159)."""
|
|
21
|
+
display_name: str
|
|
22
|
+
regex: str
|
|
23
|
+
examples: str = ""
|
|
24
|
+
category: str = "general"
|
|
25
|
+
severity: str = "medium" # "low", "medium", "high"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class RedactionCode:
|
|
30
|
+
"""A legal/business reason code for redaction (B159)."""
|
|
31
|
+
name: str
|
|
32
|
+
description: str = ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class RedactionMatch:
|
|
37
|
+
"""A matched span for redaction."""
|
|
38
|
+
pattern_name: str
|
|
39
|
+
span: tuple[int, int]
|
|
40
|
+
text: str
|
|
41
|
+
code: str = ""
|
|
42
|
+
locale: str = "ENU"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class RedactionPatternPack:
|
|
47
|
+
"""A locale-specific pack of redaction patterns (B159)."""
|
|
48
|
+
locale: str = "ENU"
|
|
49
|
+
patterns: list[RedactionPattern] = field(default_factory=list)
|
|
50
|
+
codes: list[RedactionCode] = field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
def scan(self, text: str, enabled_categories: set[str] | None = None) -> list[RedactionMatch]:
|
|
53
|
+
"""Scan text for matches against all patterns."""
|
|
54
|
+
matches: list[RedactionMatch] = []
|
|
55
|
+
for pattern in self.patterns:
|
|
56
|
+
if enabled_categories and pattern.category not in enabled_categories:
|
|
57
|
+
continue
|
|
58
|
+
try:
|
|
59
|
+
for m in re.finditer(pattern.regex, text):
|
|
60
|
+
matches.append(RedactionMatch(
|
|
61
|
+
pattern_name=pattern.display_name,
|
|
62
|
+
span=(m.start(), m.end()),
|
|
63
|
+
text=m.group(),
|
|
64
|
+
locale=self.locale,
|
|
65
|
+
))
|
|
66
|
+
except re.error:
|
|
67
|
+
continue
|
|
68
|
+
return matches
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RedactionPackRegistry:
|
|
72
|
+
"""Registry of locale-specific redaction packs (B159)."""
|
|
73
|
+
|
|
74
|
+
def __init__(self) -> None:
|
|
75
|
+
self._packs: dict[str, RedactionPatternPack] = {}
|
|
76
|
+
|
|
77
|
+
def register(self, pack: RedactionPatternPack) -> None:
|
|
78
|
+
self._packs[pack.locale] = pack
|
|
79
|
+
|
|
80
|
+
def get(self, locale: str) -> RedactionPatternPack | None:
|
|
81
|
+
# Try exact locale
|
|
82
|
+
if locale in self._packs:
|
|
83
|
+
return self._packs[locale]
|
|
84
|
+
# Try language prefix
|
|
85
|
+
lang = locale.split("_")[0].upper()
|
|
86
|
+
if lang in self._packs:
|
|
87
|
+
return self._packs[lang]
|
|
88
|
+
# Fall back to ENU
|
|
89
|
+
return self._packs.get("ENU")
|
|
90
|
+
|
|
91
|
+
def scan(self, text: str, locale: str = "ENU", enabled_categories: set[str] | None = None) -> list[RedactionMatch]:
|
|
92
|
+
"""Scan text using the best available locale pack."""
|
|
93
|
+
pack = self.get(locale)
|
|
94
|
+
if not pack:
|
|
95
|
+
return []
|
|
96
|
+
return pack.scan(text, enabled_categories)
|
|
97
|
+
|
|
98
|
+
def available_locales(self) -> list[str]:
|
|
99
|
+
return list(self._packs.keys())
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Default patterns (ENU)
|
|
103
|
+
def default_enu_pack() -> RedactionPatternPack:
|
|
104
|
+
"""Create a default ENU redaction pack with common PII patterns."""
|
|
105
|
+
return RedactionPatternPack(
|
|
106
|
+
locale="ENU",
|
|
107
|
+
patterns=[
|
|
108
|
+
RedactionPattern(
|
|
109
|
+
display_name="Phone Numbers",
|
|
110
|
+
regex=r"\b\d{3}[-.\s]\d{3}[-.\s]\d{4}\b|\b\d{3}[-.\s]\d{4}\b",
|
|
111
|
+
examples="555-123-4567, 555-1212",
|
|
112
|
+
category="phone",
|
|
113
|
+
severity="medium",
|
|
114
|
+
),
|
|
115
|
+
RedactionPattern(
|
|
116
|
+
display_name="Credit Cards",
|
|
117
|
+
regex=r"(\b)(((\d{4}(-|\s|\.|_)?){3}(\d{3,4})))",
|
|
118
|
+
examples="1234-5678-9012-3456",
|
|
119
|
+
category="credit_card",
|
|
120
|
+
severity="high",
|
|
121
|
+
),
|
|
122
|
+
RedactionPattern(
|
|
123
|
+
display_name="Social Security Numbers",
|
|
124
|
+
regex=r"(\b)((\d{3}(-|\s|\.|_)\d{2}(-|\s|\.|_)\d{4})|(\d{9}))(\b)",
|
|
125
|
+
examples="123-45-6789",
|
|
126
|
+
category="ssn",
|
|
127
|
+
severity="high",
|
|
128
|
+
),
|
|
129
|
+
RedactionPattern(
|
|
130
|
+
display_name="Email Addresses",
|
|
131
|
+
regex=r"([a-zA-Z0-9_])([a-zA-Z0-9_\-\.])*@([a-zA-Z0-9\-])+\.([a-zA-Z\.]+)",
|
|
132
|
+
examples="user@example.com",
|
|
133
|
+
category="email",
|
|
134
|
+
severity="medium",
|
|
135
|
+
),
|
|
136
|
+
],
|
|
137
|
+
codes=[
|
|
138
|
+
RedactionCode(name="(b) (1) (A)", description="FOIA exemption"),
|
|
139
|
+
RedactionCode(name="(b) (6)", description="Personal privacy"),
|
|
140
|
+
],
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ── B161: Pre-Model Disqualification Rules ─────────────────────────────
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass
|
|
148
|
+
class DisqualificationRule:
|
|
149
|
+
"""A deterministic disqualification rule (B161)."""
|
|
150
|
+
rule_id: str
|
|
151
|
+
tag_type: str # "table", "artifact", "toc", "any", etc.
|
|
152
|
+
tag_subtype: str = ""
|
|
153
|
+
nested_in_tag: str = ""
|
|
154
|
+
action: str = "disqualify" # "disqualify" or "allow"
|
|
155
|
+
conditions: list[dict[str, Any]] = field(default_factory=list)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class DisqualificationResult:
|
|
160
|
+
"""Result of checking disqualification rules."""
|
|
161
|
+
disqualified: bool
|
|
162
|
+
rule_id: str = ""
|
|
163
|
+
reason: str = ""
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class DisqualificationEngine:
|
|
167
|
+
"""Checks candidates against deterministic rules before model calls (B161)."""
|
|
168
|
+
|
|
169
|
+
def __init__(self) -> None:
|
|
170
|
+
self._rules: list[DisqualificationRule] = []
|
|
171
|
+
|
|
172
|
+
def add_rule(self, rule: DisqualificationRule) -> None:
|
|
173
|
+
self._rules.append(rule)
|
|
174
|
+
|
|
175
|
+
def check(self, candidate: dict[str, Any]) -> DisqualificationResult:
|
|
176
|
+
"""Check a candidate against all rules.
|
|
177
|
+
|
|
178
|
+
Candidate should have: tag_type, tag_subtype, nested_in (dict with tag_type).
|
|
179
|
+
"""
|
|
180
|
+
for rule in self._rules:
|
|
181
|
+
if rule.tag_type != "any" and rule.tag_type != candidate.get("tag_type"):
|
|
182
|
+
continue
|
|
183
|
+
if rule.tag_subtype and rule.tag_subtype != candidate.get("tag_subtype"):
|
|
184
|
+
continue
|
|
185
|
+
if rule.nested_in_tag:
|
|
186
|
+
nested = candidate.get("nested_in", {})
|
|
187
|
+
if nested.get("tag_type") != rule.nested_in_tag:
|
|
188
|
+
continue
|
|
189
|
+
if rule.action == "disqualify":
|
|
190
|
+
return DisqualificationResult(
|
|
191
|
+
disqualified=True,
|
|
192
|
+
rule_id=rule.rule_id,
|
|
193
|
+
reason=f"disqualified by rule: {rule.rule_id}",
|
|
194
|
+
)
|
|
195
|
+
return DisqualificationResult(disqualified=False)
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def rule_count(self) -> int:
|
|
199
|
+
return len(self._rules)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def default_disqual_controls() -> list[DisqualificationRule]:
|
|
203
|
+
"""Default disqualification rules from Acrobat's disqual-controls.json."""
|
|
204
|
+
return [
|
|
205
|
+
DisqualificationRule(
|
|
206
|
+
rule_id="table_inside_list",
|
|
207
|
+
tag_type="table",
|
|
208
|
+
nested_in_tag="list",
|
|
209
|
+
),
|
|
210
|
+
DisqualificationRule(
|
|
211
|
+
rule_id="rule_watermark",
|
|
212
|
+
tag_type="artifact",
|
|
213
|
+
tag_subtype="watermark",
|
|
214
|
+
),
|
|
215
|
+
DisqualificationRule(
|
|
216
|
+
rule_id="toc",
|
|
217
|
+
tag_type="toc",
|
|
218
|
+
),
|
|
219
|
+
DisqualificationRule(
|
|
220
|
+
rule_id="rule_True_MC",
|
|
221
|
+
tag_type="any",
|
|
222
|
+
),
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ── B173: Native Messaging Host Allowlist ─────────────────────────────
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass
|
|
230
|
+
class NativeMessagingHostManifest:
|
|
231
|
+
"""A native messaging host manifest (B173)."""
|
|
232
|
+
name: str
|
|
233
|
+
description: str = ""
|
|
234
|
+
path: str = ""
|
|
235
|
+
host_type: str = "stdio"
|
|
236
|
+
allowed_origins: list[str] = field(default_factory=list)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class NativeMessagingAllowlist:
|
|
240
|
+
"""Validates incoming messages against an allowlist of origins (B173)."""
|
|
241
|
+
|
|
242
|
+
def __init__(self) -> None:
|
|
243
|
+
self._hosts: dict[str, NativeMessagingHostManifest] = {}
|
|
244
|
+
|
|
245
|
+
def register(self, manifest: NativeMessagingHostManifest) -> None:
|
|
246
|
+
self._hosts[manifest.name] = manifest
|
|
247
|
+
|
|
248
|
+
def is_allowed(self, host_name: str, origin: str) -> bool:
|
|
249
|
+
"""Check if an origin is allowed for a host."""
|
|
250
|
+
host = self._hosts.get(host_name)
|
|
251
|
+
if not host:
|
|
252
|
+
return False
|
|
253
|
+
if not host.allowed_origins:
|
|
254
|
+
return False # empty allowlist rejects all
|
|
255
|
+
return origin in host.allowed_origins
|
|
256
|
+
|
|
257
|
+
def get_host(self, name: str) -> NativeMessagingHostManifest | None:
|
|
258
|
+
return self._hosts.get(name)
|
|
259
|
+
|
|
260
|
+
def all_hosts(self) -> list[str]:
|
|
261
|
+
return list(self._hosts.keys())
|