loki-mode 7.129.5 → 8.0.0
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.
- package/README.md +81 -5
- package/SKILL.md +66 -5
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +37 -0
- package/autonomy/completion-council.sh +862 -28
- package/autonomy/council-v2.sh +39 -0
- package/autonomy/crash.sh +3 -2
- package/autonomy/grill.sh +42 -0
- package/autonomy/hooks/validate-bash.sh +301 -4
- package/autonomy/lib/cockpit-render.sh +8 -2
- package/autonomy/lib/cr-rematerialize.py +101 -4
- package/autonomy/lib/deadline.py +957 -0
- package/autonomy/lib/dependency-setup.sh +205 -0
- package/autonomy/lib/done-recognition.sh +45 -0
- package/autonomy/lib/efficiency_cost.py +13 -6
- package/autonomy/lib/no_mock_scan.py +793 -0
- package/autonomy/lib/prd-enrich.sh +42 -0
- package/autonomy/lib/proof-analytics-props.py +69 -0
- package/autonomy/lib/proof-generator.py +282 -95
- package/autonomy/lib/proof-template.html +15 -12
- package/autonomy/lib/proof-verify.py +151 -102
- package/autonomy/lib/requirements_contract.py +848 -0
- package/autonomy/lib/sdk-mode.sh +103 -0
- package/autonomy/lib/secret-scan.sh +139 -0
- package/autonomy/lib/spec-expand.sh +208 -0
- package/autonomy/lib/tree_digest.py +266 -0
- package/autonomy/lib/voter-agents.sh +58 -8
- package/autonomy/lib/workspace_diff.py +124 -0
- package/autonomy/loki +189 -17
- package/autonomy/playwright-verify.sh +501 -0
- package/autonomy/prd-checklist.sh +17 -8
- package/autonomy/provider-offer.sh +111 -0
- package/autonomy/run.sh +4417 -676
- package/autonomy/sandbox.sh +42 -0
- package/autonomy/spec-interrogation.sh +148 -5
- package/autonomy/spec.sh +118 -1
- package/autonomy/telemetry.sh +133 -7
- package/autonomy/verify.sh +107 -0
- package/bin/loki +110 -3
- package/completions/_loki +10 -0
- package/completions/loki.bash +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/audit.py +96 -7
- package/dashboard/build_supervisor.py +1619 -0
- package/dashboard/control.py +8 -0
- package/dashboard/prompt_optimizer.py +7 -0
- package/dashboard/server.py +577 -22
- package/dashboard/static/trust.html +1 -1
- package/docs/ARCHITECTURE-OVERVIEW.md +207 -0
- package/docs/AUTONOMI-ECOSYSTEM.md +107 -0
- package/docs/INSTALLATION.md +19 -2
- package/docs/MODEL-EQUIVALENCE-HARNESS-PLAN.md +70 -0
- package/docs/PLANS-INDEX.md +33 -0
- package/docs/PRIVACY.md +29 -1
- package/docs/SIGNED-RECEIPTS.md +102 -0
- package/docs/SONNET5-DEFAULT-PLAN.md +1 -1
- package/docs/V8-ACCEPTANCE-TRIAGE-2026-07-24.md +114 -0
- package/docs/V8-AGENT-SDK-PLAN.md +692 -0
- package/docs/V8-COMPLEXITY-AUDIT-2026-07-24.md +45 -0
- package/docs/V8-MAJOR-RELEASE-PLAN.md +137 -0
- package/docs/V8-OVERNIGHT-PLAN-2026-07-25.md +82 -0
- package/docs/V8-RUNTIME-TRUTH-2026-07-25.md +232 -0
- package/docs/V8-SDK-RESEARCH-RAW.md +129 -0
- package/docs/alternative-installations.md +4 -4
- package/loki-ts/dist/loki.js +674 -285
- package/loki-ts/package.json +2 -0
- package/mcp/__init__.py +1 -1
- package/package.json +7 -6
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +75 -0
- package/providers/codex.sh +85 -13
- package/references/sdk-mode.md +106 -0
- package/skills/model-selection.md +1 -1
- package/skills/quality-gates.md +22 -0
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Bind an immutable specification to a structured requirement verdict schema."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import hmac
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import stat
|
|
12
|
+
import sys
|
|
13
|
+
from copy import deepcopy
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
PRODUCT_QUALITY_CONTRACT_END = "--- END AUTONOMI PRODUCT QUALITY CONTRACT ---"
|
|
19
|
+
PRODUCT_QUALITY_REQUIREMENT_PREFIXES = (
|
|
20
|
+
"Every visible control must perform its stated action.",
|
|
21
|
+
"Do not ship dead forms,",
|
|
22
|
+
"If the user explicitly requests no backend,",
|
|
23
|
+
"Treat commercial claims as user-supplied,",
|
|
24
|
+
"Label illustrative material in the interface and omit unsupported customer logos,",
|
|
25
|
+
"Add or update at least one non-vacuous automated test for the critical path,",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class Contract:
|
|
31
|
+
specification: bytes
|
|
32
|
+
manifest: dict[str, object]
|
|
33
|
+
schema: dict[str, object]
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def manifest_bytes(self) -> bytes:
|
|
37
|
+
return json.dumps(self.manifest, sort_keys=True).encode("utf-8")
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def schema_bytes(self) -> bytes:
|
|
41
|
+
return json.dumps(self.schema, sort_keys=True).encode("utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
MAX_REQUIREMENTS_PER_SHARD = 8
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _shard_path(shard_dir: Path, kind: str, index: int) -> Path:
|
|
48
|
+
if kind not in {"manifest", "schema", "result"} or index < 1:
|
|
49
|
+
raise ValueError("invalid_shard")
|
|
50
|
+
suffix = "json" if kind in {"manifest", "schema"} else "txt"
|
|
51
|
+
return shard_dir / f"{kind}-{index:03d}.{suffix}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _validate_shard_size(raw_size: str | int) -> int:
|
|
55
|
+
text = str(raw_size)
|
|
56
|
+
if re.fullmatch(r"[0-9]+", text) is None:
|
|
57
|
+
raise ValueError("invalid_shard_size")
|
|
58
|
+
size = int(text, 10)
|
|
59
|
+
if not 1 <= size <= MAX_REQUIREMENTS_PER_SHARD:
|
|
60
|
+
raise ValueError("invalid_shard_size")
|
|
61
|
+
return size
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _safe_directory(path: Path) -> None:
|
|
65
|
+
try:
|
|
66
|
+
info = os.lstat(path)
|
|
67
|
+
except OSError as exc:
|
|
68
|
+
raise RuntimeError("unsafe_shard_directory") from exc
|
|
69
|
+
if (
|
|
70
|
+
not stat.S_ISDIR(info.st_mode)
|
|
71
|
+
or info.st_uid != os.getuid()
|
|
72
|
+
or stat.S_IMODE(info.st_mode) != 0o700
|
|
73
|
+
):
|
|
74
|
+
raise RuntimeError("unsafe_shard_directory")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def extract_requirement_units(text: str) -> list[str]:
|
|
78
|
+
"""Return deterministic acceptance units without interpreting product verbs."""
|
|
79
|
+
units: list[str] = []
|
|
80
|
+
paragraph: list[str] = []
|
|
81
|
+
list_item: list[str] = []
|
|
82
|
+
|
|
83
|
+
def flush_paragraph() -> None:
|
|
84
|
+
if not paragraph:
|
|
85
|
+
return
|
|
86
|
+
joined = " ".join(paragraph)
|
|
87
|
+
units.extend(
|
|
88
|
+
part.strip()
|
|
89
|
+
for part in re.split(r"(?<=[.!?])\s+(?=[A-Z0-9])", joined)
|
|
90
|
+
if part.strip()
|
|
91
|
+
)
|
|
92
|
+
paragraph.clear()
|
|
93
|
+
|
|
94
|
+
def flush_item() -> None:
|
|
95
|
+
if list_item:
|
|
96
|
+
units.append(" ".join(list_item))
|
|
97
|
+
list_item.clear()
|
|
98
|
+
|
|
99
|
+
for raw_line in text.splitlines():
|
|
100
|
+
line = raw_line.strip()
|
|
101
|
+
if not line:
|
|
102
|
+
flush_item()
|
|
103
|
+
flush_paragraph()
|
|
104
|
+
continue
|
|
105
|
+
heading = re.fullmatch(r"#{1,6}\s+(.+)", line)
|
|
106
|
+
if heading:
|
|
107
|
+
flush_item()
|
|
108
|
+
flush_paragraph()
|
|
109
|
+
units.append(heading.group(1).strip())
|
|
110
|
+
continue
|
|
111
|
+
item = re.match(
|
|
112
|
+
r"^(?:[-*+]\s+(?:\[[ xX]\]\s*)?|\d+[.)]\s+)(.+)$", line
|
|
113
|
+
)
|
|
114
|
+
if item:
|
|
115
|
+
flush_item()
|
|
116
|
+
flush_paragraph()
|
|
117
|
+
list_item.append(item.group(1).strip())
|
|
118
|
+
elif list_item and raw_line[:1].isspace():
|
|
119
|
+
list_item.append(line)
|
|
120
|
+
else:
|
|
121
|
+
flush_item()
|
|
122
|
+
paragraph.append(line)
|
|
123
|
+
flush_item()
|
|
124
|
+
flush_paragraph()
|
|
125
|
+
return units
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def extract_bound_requirement_units(text: str) -> list[str]:
|
|
129
|
+
"""Extract the user brief when a platform quality policy precedes it."""
|
|
130
|
+
if PRODUCT_QUALITY_CONTRACT_END in text:
|
|
131
|
+
text = text.split(PRODUCT_QUALITY_CONTRACT_END, 1)[1].strip()
|
|
132
|
+
return extract_requirement_units(text)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def extract_product_quality_requirement_units(text: str) -> list[str]:
|
|
136
|
+
"""Select the mandatory review rules from a wrapped product quality policy."""
|
|
137
|
+
if PRODUCT_QUALITY_CONTRACT_END not in text:
|
|
138
|
+
return []
|
|
139
|
+
policy = text.split(PRODUCT_QUALITY_CONTRACT_END, 1)[0]
|
|
140
|
+
units = extract_requirement_units(policy)
|
|
141
|
+
selected: list[str] = []
|
|
142
|
+
for prefix in PRODUCT_QUALITY_REQUIREMENT_PREFIXES:
|
|
143
|
+
matches = [unit for unit in units if unit.startswith(prefix)]
|
|
144
|
+
if len(matches) != 1:
|
|
145
|
+
raise LookupError("incomplete_product_quality_contract")
|
|
146
|
+
selected.append(matches[0])
|
|
147
|
+
return selected
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _schema(spec_sha256: str, ids: list[str], count: int) -> dict[str, object]:
|
|
151
|
+
return {
|
|
152
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
153
|
+
"type": "object",
|
|
154
|
+
"additionalProperties": False,
|
|
155
|
+
"required": [
|
|
156
|
+
"schema",
|
|
157
|
+
"spec_sha256",
|
|
158
|
+
"verdict",
|
|
159
|
+
"requirements",
|
|
160
|
+
"findings",
|
|
161
|
+
],
|
|
162
|
+
"properties": {
|
|
163
|
+
"schema": {"const": "loki-requirements-verdict/v1"},
|
|
164
|
+
"spec_sha256": {"const": spec_sha256},
|
|
165
|
+
"verdict": {"enum": ["PASS", "FAIL"]},
|
|
166
|
+
"requirements": {
|
|
167
|
+
"type": "array",
|
|
168
|
+
"minItems": count,
|
|
169
|
+
"maxItems": count,
|
|
170
|
+
"items": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"additionalProperties": False,
|
|
173
|
+
"required": ["id", "status", "evidence"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"id": {"enum": ids},
|
|
176
|
+
"status": {"enum": ["PASS", "FAIL"]},
|
|
177
|
+
"evidence": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"minLength": 1,
|
|
180
|
+
"maxLength": 180,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
"findings": {
|
|
186
|
+
"type": "array",
|
|
187
|
+
"items": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"additionalProperties": False,
|
|
190
|
+
"required": ["severity", "description"],
|
|
191
|
+
"properties": {
|
|
192
|
+
"severity": {
|
|
193
|
+
"enum": ["Critical", "High", "Medium", "Low"]
|
|
194
|
+
},
|
|
195
|
+
"description": {
|
|
196
|
+
"type": "string",
|
|
197
|
+
"minLength": 1,
|
|
198
|
+
"maxLength": 320,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _limit(raw_limit: str, hard_limit: int) -> int:
|
|
208
|
+
if re.fullmatch(r"[0-9]+", raw_limit) is None:
|
|
209
|
+
raise ValueError("invalid_limit")
|
|
210
|
+
limit = int(raw_limit, 10)
|
|
211
|
+
if not 1 <= limit <= hard_limit:
|
|
212
|
+
raise ValueError("invalid_limit")
|
|
213
|
+
return limit
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _read_regular(
|
|
217
|
+
path: Path, maximum: int, *, exact_mode: int | None = None
|
|
218
|
+
) -> bytes:
|
|
219
|
+
flags = os.O_RDONLY
|
|
220
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
221
|
+
flags |= os.O_NOFOLLOW
|
|
222
|
+
if hasattr(os, "O_CLOEXEC"):
|
|
223
|
+
flags |= os.O_CLOEXEC
|
|
224
|
+
try:
|
|
225
|
+
descriptor = os.open(path, flags)
|
|
226
|
+
info = os.fstat(descriptor)
|
|
227
|
+
path_info = os.lstat(path)
|
|
228
|
+
if (
|
|
229
|
+
not stat.S_ISREG(info.st_mode)
|
|
230
|
+
or not stat.S_ISREG(path_info.st_mode)
|
|
231
|
+
or info.st_uid != os.getuid()
|
|
232
|
+
or info.st_nlink != 1
|
|
233
|
+
or (
|
|
234
|
+
exact_mode is not None
|
|
235
|
+
and stat.S_IMODE(info.st_mode) != exact_mode
|
|
236
|
+
)
|
|
237
|
+
or (info.st_dev, info.st_ino) != (path_info.st_dev, path_info.st_ino)
|
|
238
|
+
):
|
|
239
|
+
raise RuntimeError("unsafe_file")
|
|
240
|
+
content = bytearray()
|
|
241
|
+
while len(content) <= maximum:
|
|
242
|
+
chunk = os.read(descriptor, min(65_536, maximum + 1 - len(content)))
|
|
243
|
+
if not chunk:
|
|
244
|
+
break
|
|
245
|
+
content.extend(chunk)
|
|
246
|
+
final_info = os.fstat(descriptor)
|
|
247
|
+
final_path_info = os.lstat(path)
|
|
248
|
+
if (
|
|
249
|
+
(info.st_dev, info.st_ino) != (final_info.st_dev, final_info.st_ino)
|
|
250
|
+
or (info.st_dev, info.st_ino)
|
|
251
|
+
!= (final_path_info.st_dev, final_path_info.st_ino)
|
|
252
|
+
):
|
|
253
|
+
raise RuntimeError("replaced_file")
|
|
254
|
+
except OSError as exc:
|
|
255
|
+
raise RuntimeError("unreadable") from exc
|
|
256
|
+
finally:
|
|
257
|
+
if "descriptor" in locals():
|
|
258
|
+
os.close(descriptor)
|
|
259
|
+
if len(content) > maximum:
|
|
260
|
+
raise OverflowError(f"oversized:{len(content)}")
|
|
261
|
+
return bytes(content)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _path_identity(path: Path, *, exact_mode: int = 0o600) -> str:
|
|
265
|
+
flags = os.O_RDONLY
|
|
266
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
267
|
+
flags |= os.O_NOFOLLOW
|
|
268
|
+
if hasattr(os, "O_CLOEXEC"):
|
|
269
|
+
flags |= os.O_CLOEXEC
|
|
270
|
+
try:
|
|
271
|
+
descriptor = os.open(path, flags)
|
|
272
|
+
info = os.fstat(descriptor)
|
|
273
|
+
path_info = os.lstat(path)
|
|
274
|
+
except OSError as exc:
|
|
275
|
+
raise RuntimeError("unreadable") from exc
|
|
276
|
+
finally:
|
|
277
|
+
if "descriptor" in locals():
|
|
278
|
+
os.close(descriptor)
|
|
279
|
+
if (
|
|
280
|
+
not stat.S_ISREG(info.st_mode)
|
|
281
|
+
or not stat.S_ISREG(path_info.st_mode)
|
|
282
|
+
or info.st_uid != os.getuid()
|
|
283
|
+
or info.st_nlink != 1
|
|
284
|
+
or stat.S_IMODE(info.st_mode) != exact_mode
|
|
285
|
+
or (info.st_dev, info.st_ino) != (path_info.st_dev, path_info.st_ino)
|
|
286
|
+
):
|
|
287
|
+
raise RuntimeError("unsafe_file")
|
|
288
|
+
return f"{info.st_dev}:{info.st_ino}"
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def artifact_identity(*paths: Path) -> str:
|
|
292
|
+
return ",".join(_path_identity(path) for path in paths)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _write_new(path: Path, content: bytes) -> None:
|
|
296
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
297
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
298
|
+
flags |= os.O_NOFOLLOW
|
|
299
|
+
if hasattr(os, "O_CLOEXEC"):
|
|
300
|
+
flags |= os.O_CLOEXEC
|
|
301
|
+
try:
|
|
302
|
+
descriptor = os.open(path, flags, 0o600)
|
|
303
|
+
os.fchmod(descriptor, 0o600)
|
|
304
|
+
view = memoryview(content)
|
|
305
|
+
while view:
|
|
306
|
+
written = os.write(descriptor, view)
|
|
307
|
+
if written <= 0:
|
|
308
|
+
raise OSError("short write")
|
|
309
|
+
view = view[written:]
|
|
310
|
+
info = os.fstat(descriptor)
|
|
311
|
+
path_info = os.lstat(path)
|
|
312
|
+
if (
|
|
313
|
+
not stat.S_ISREG(info.st_mode)
|
|
314
|
+
or info.st_uid != os.getuid()
|
|
315
|
+
or stat.S_IMODE(info.st_mode) != 0o600
|
|
316
|
+
or info.st_nlink != 1
|
|
317
|
+
or (info.st_dev, info.st_ino) != (path_info.st_dev, path_info.st_ino)
|
|
318
|
+
):
|
|
319
|
+
raise RuntimeError("unsafe_file")
|
|
320
|
+
except FileExistsError:
|
|
321
|
+
if not hmac.compare_digest(
|
|
322
|
+
_read_regular(path, len(content), exact_mode=0o600), content
|
|
323
|
+
):
|
|
324
|
+
raise RuntimeError("contract_mismatch")
|
|
325
|
+
return
|
|
326
|
+
except OSError as exc:
|
|
327
|
+
raise RuntimeError("contract_write_failed") from exc
|
|
328
|
+
finally:
|
|
329
|
+
if "descriptor" in locals():
|
|
330
|
+
os.close(descriptor)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def publish_bound(stage: Path, destination: Path, maximum: int) -> str:
|
|
334
|
+
"""Publish one validated staged result after its producer has quiesced."""
|
|
335
|
+
content = _read_regular(stage, maximum)
|
|
336
|
+
if not content:
|
|
337
|
+
raise RuntimeError("empty_file")
|
|
338
|
+
try:
|
|
339
|
+
destination_info = os.lstat(destination)
|
|
340
|
+
except FileNotFoundError:
|
|
341
|
+
destination_info = None
|
|
342
|
+
if destination_info is not None:
|
|
343
|
+
if stat.S_ISDIR(destination_info.st_mode):
|
|
344
|
+
raise RuntimeError("unsafe_destination")
|
|
345
|
+
os.unlink(destination)
|
|
346
|
+
os.replace(stage, destination)
|
|
347
|
+
published = _read_regular(destination, maximum)
|
|
348
|
+
if not hmac.compare_digest(content, published):
|
|
349
|
+
raise RuntimeError("publish_mismatch")
|
|
350
|
+
return (
|
|
351
|
+
hashlib.sha256(published).hexdigest()
|
|
352
|
+
+ "|"
|
|
353
|
+
+ _path_identity(destination)
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def derive_contract(
|
|
358
|
+
source: Path,
|
|
359
|
+
expected_sha256: str,
|
|
360
|
+
raw_limit: str,
|
|
361
|
+
hard_limit: int,
|
|
362
|
+
) -> Contract:
|
|
363
|
+
limit = _limit(raw_limit, hard_limit)
|
|
364
|
+
content = _read_regular(source, limit)
|
|
365
|
+
actual_sha256 = hashlib.sha256(content).hexdigest()
|
|
366
|
+
if not hmac.compare_digest(actual_sha256, expected_sha256):
|
|
367
|
+
raise PermissionError("hash_mismatch")
|
|
368
|
+
try:
|
|
369
|
+
text = content.decode("utf-8")
|
|
370
|
+
except UnicodeDecodeError as exc:
|
|
371
|
+
raise UnicodeError("invalid_utf8") from exc
|
|
372
|
+
units = extract_bound_requirement_units(text)
|
|
373
|
+
quality_units = extract_product_quality_requirement_units(text)
|
|
374
|
+
if not units:
|
|
375
|
+
raise LookupError("no_requirements")
|
|
376
|
+
requirements = [
|
|
377
|
+
{
|
|
378
|
+
"id": f"R{index:03d}",
|
|
379
|
+
"text": unit,
|
|
380
|
+
"sha256": hashlib.sha256(unit.encode("utf-8")).hexdigest(),
|
|
381
|
+
}
|
|
382
|
+
for index, unit in enumerate(units, 1)
|
|
383
|
+
]
|
|
384
|
+
requirements.extend(
|
|
385
|
+
{
|
|
386
|
+
"id": f"Q{index:03d}",
|
|
387
|
+
"text": unit,
|
|
388
|
+
"sha256": hashlib.sha256(unit.encode("utf-8")).hexdigest(),
|
|
389
|
+
}
|
|
390
|
+
for index, unit in enumerate(quality_units, 1)
|
|
391
|
+
)
|
|
392
|
+
manifest = {
|
|
393
|
+
"schema": "loki-requirements-manifest/v1",
|
|
394
|
+
"spec_sha256": actual_sha256,
|
|
395
|
+
"requirements": requirements,
|
|
396
|
+
}
|
|
397
|
+
return Contract(
|
|
398
|
+
specification=content,
|
|
399
|
+
manifest=manifest,
|
|
400
|
+
schema=_schema(
|
|
401
|
+
actual_sha256,
|
|
402
|
+
[item["id"] for item in requirements],
|
|
403
|
+
len(requirements),
|
|
404
|
+
),
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def derive_shards(contract: Contract, raw_size: str | int) -> list[Contract]:
|
|
409
|
+
"""Split one verified contract into deterministic balanced contiguous shards."""
|
|
410
|
+
maximum = _validate_shard_size(raw_size)
|
|
411
|
+
requirements = contract.manifest.get("requirements")
|
|
412
|
+
if not isinstance(requirements, list) or not requirements:
|
|
413
|
+
raise RuntimeError("invalid_full_contract")
|
|
414
|
+
total = len(requirements)
|
|
415
|
+
shard_count = (total + maximum - 1) // maximum
|
|
416
|
+
base_size, extra = divmod(total, shard_count)
|
|
417
|
+
full_manifest_sha256 = hashlib.sha256(contract.manifest_bytes).hexdigest()
|
|
418
|
+
shards: list[Contract] = []
|
|
419
|
+
offset = 0
|
|
420
|
+
for zero_index in range(shard_count):
|
|
421
|
+
shard_size = base_size + (1 if zero_index < extra else 0)
|
|
422
|
+
selected = deepcopy(requirements[offset : offset + shard_size])
|
|
423
|
+
ids = [item.get("id") for item in selected if isinstance(item, dict)]
|
|
424
|
+
if len(ids) != shard_size or any(not isinstance(item, str) for item in ids):
|
|
425
|
+
raise RuntimeError("invalid_full_contract")
|
|
426
|
+
manifest = {
|
|
427
|
+
"schema": "loki-requirements-manifest/v1",
|
|
428
|
+
"spec_sha256": contract.manifest["spec_sha256"],
|
|
429
|
+
"requirements": selected,
|
|
430
|
+
"shard": {
|
|
431
|
+
"schema": "loki-requirements-shard/v1",
|
|
432
|
+
"index": zero_index + 1,
|
|
433
|
+
"count": shard_count,
|
|
434
|
+
"max_requirements": maximum,
|
|
435
|
+
"requirement_offset": offset,
|
|
436
|
+
"full_requirement_count": total,
|
|
437
|
+
"full_manifest_sha256": full_manifest_sha256,
|
|
438
|
+
},
|
|
439
|
+
}
|
|
440
|
+
shards.append(
|
|
441
|
+
Contract(
|
|
442
|
+
specification=contract.specification,
|
|
443
|
+
manifest=manifest,
|
|
444
|
+
schema=_schema(contract.manifest["spec_sha256"], ids, shard_size),
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
offset += shard_size
|
|
448
|
+
if offset != total:
|
|
449
|
+
raise RuntimeError("incomplete_shards")
|
|
450
|
+
return shards
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def write_shards(
|
|
454
|
+
contract: Contract,
|
|
455
|
+
raw_size: str,
|
|
456
|
+
shard_dir: Path,
|
|
457
|
+
) -> dict[str, object]:
|
|
458
|
+
_safe_directory(shard_dir)
|
|
459
|
+
shards = derive_shards(contract, raw_size)
|
|
460
|
+
identities: list[str] = []
|
|
461
|
+
sizes: list[int] = []
|
|
462
|
+
for index, shard in enumerate(shards, 1):
|
|
463
|
+
manifest_path = _shard_path(shard_dir, "manifest", index)
|
|
464
|
+
schema_path = _shard_path(shard_dir, "schema", index)
|
|
465
|
+
_write_new(manifest_path, shard.manifest_bytes)
|
|
466
|
+
_write_new(schema_path, shard.schema_bytes)
|
|
467
|
+
identities.append(artifact_identity(manifest_path, schema_path))
|
|
468
|
+
sizes.append(len(shard.manifest["requirements"]))
|
|
469
|
+
return {
|
|
470
|
+
"schema": "loki-requirements-shards/v1",
|
|
471
|
+
"count": len(shards),
|
|
472
|
+
"max_requirements": _validate_shard_size(raw_size),
|
|
473
|
+
"sizes": sizes,
|
|
474
|
+
"identities": identities,
|
|
475
|
+
"full_manifest_sha256": hashlib.sha256(contract.manifest_bytes).hexdigest(),
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def verify_shard(
|
|
480
|
+
contract: Contract,
|
|
481
|
+
raw_size: str,
|
|
482
|
+
shard_dir: Path,
|
|
483
|
+
index: int,
|
|
484
|
+
expected_identity: str = "",
|
|
485
|
+
) -> Contract:
|
|
486
|
+
_safe_directory(shard_dir)
|
|
487
|
+
shards = derive_shards(contract, raw_size)
|
|
488
|
+
if index < 1 or index > len(shards):
|
|
489
|
+
raise ValueError("invalid_shard")
|
|
490
|
+
shard = shards[index - 1]
|
|
491
|
+
manifest_path = _shard_path(shard_dir, "manifest", index)
|
|
492
|
+
schema_path = _shard_path(shard_dir, "schema", index)
|
|
493
|
+
for path, expected in (
|
|
494
|
+
(manifest_path, shard.manifest_bytes),
|
|
495
|
+
(schema_path, shard.schema_bytes),
|
|
496
|
+
):
|
|
497
|
+
actual = _read_regular(path, len(expected), exact_mode=0o600)
|
|
498
|
+
if not hmac.compare_digest(actual, expected):
|
|
499
|
+
raise RuntimeError("shard_contract_mismatch")
|
|
500
|
+
actual_identity = artifact_identity(manifest_path, schema_path)
|
|
501
|
+
if expected_identity and not hmac.compare_digest(
|
|
502
|
+
actual_identity, expected_identity
|
|
503
|
+
):
|
|
504
|
+
raise RuntimeError("shard_contract_replaced")
|
|
505
|
+
return shard
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _decode_json_list(raw: str, expected_count: int) -> list[str]:
|
|
509
|
+
try:
|
|
510
|
+
value = json.loads(raw)
|
|
511
|
+
except json.JSONDecodeError as exc:
|
|
512
|
+
raise ValueError("invalid_binding_list") from exc
|
|
513
|
+
if (
|
|
514
|
+
not isinstance(value, list)
|
|
515
|
+
or len(value) != expected_count
|
|
516
|
+
or not all(isinstance(item, str) and item for item in value)
|
|
517
|
+
):
|
|
518
|
+
raise ValueError("invalid_binding_list")
|
|
519
|
+
return value
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _read_bound_result(path: Path, binding: str, maximum: int) -> bytes:
|
|
523
|
+
try:
|
|
524
|
+
expected_sha256, expected_identity = binding.split("|", 1)
|
|
525
|
+
except ValueError as exc:
|
|
526
|
+
raise ValueError("invalid_result_binding") from exc
|
|
527
|
+
if re.fullmatch(r"[0-9a-f]{64}", expected_sha256) is None:
|
|
528
|
+
raise ValueError("invalid_result_binding")
|
|
529
|
+
content = _read_regular(path, maximum, exact_mode=0o600)
|
|
530
|
+
if not hmac.compare_digest(hashlib.sha256(content).hexdigest(), expected_sha256):
|
|
531
|
+
raise RuntimeError("result_mismatch")
|
|
532
|
+
if not hmac.compare_digest(_path_identity(path), expected_identity):
|
|
533
|
+
raise RuntimeError("result_replaced")
|
|
534
|
+
return content
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _parse_legacy_result(content: bytes) -> tuple[str, list[str]]:
|
|
538
|
+
try:
|
|
539
|
+
text = content.decode("utf-8")
|
|
540
|
+
except UnicodeDecodeError as exc:
|
|
541
|
+
raise ValueError("invalid_result") from exc
|
|
542
|
+
lines = text.splitlines()
|
|
543
|
+
if (
|
|
544
|
+
len(lines) < 3
|
|
545
|
+
or lines[0] not in {"VERDICT: PASS", "VERDICT: FAIL"}
|
|
546
|
+
or lines[1] != "FINDINGS:"
|
|
547
|
+
or any(not line.startswith("- ") or len(line) <= 2 for line in lines[2:])
|
|
548
|
+
):
|
|
549
|
+
raise ValueError("invalid_result")
|
|
550
|
+
findings = lines[2:]
|
|
551
|
+
if "- None" in findings and findings != ["- None"]:
|
|
552
|
+
raise ValueError("invalid_result")
|
|
553
|
+
verdict = lines[0].removeprefix("VERDICT: ")
|
|
554
|
+
if verdict == "PASS" and findings != ["- None"]:
|
|
555
|
+
raise ValueError("invalid_result")
|
|
556
|
+
return verdict, findings
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def synthesize_shards(
|
|
560
|
+
contract: Contract,
|
|
561
|
+
raw_size: str,
|
|
562
|
+
shard_dir: Path,
|
|
563
|
+
contract_identities_json: str,
|
|
564
|
+
result_bindings_json: str,
|
|
565
|
+
maximum: int,
|
|
566
|
+
destination: Path,
|
|
567
|
+
) -> str:
|
|
568
|
+
"""Create one logical verdict only after every exact shard result is bound."""
|
|
569
|
+
shards = derive_shards(contract, raw_size)
|
|
570
|
+
contract_identities = _decode_json_list(
|
|
571
|
+
contract_identities_json, len(shards)
|
|
572
|
+
)
|
|
573
|
+
result_bindings = _decode_json_list(result_bindings_json, len(shards))
|
|
574
|
+
verdicts: list[str] = []
|
|
575
|
+
findings: list[str] = []
|
|
576
|
+
for index, (contract_identity, result_binding) in enumerate(
|
|
577
|
+
zip(contract_identities, result_bindings, strict=True), 1
|
|
578
|
+
):
|
|
579
|
+
verify_shard(contract, raw_size, shard_dir, index, contract_identity)
|
|
580
|
+
result_path = _shard_path(shard_dir, "result", index)
|
|
581
|
+
verdict, shard_findings = _parse_legacy_result(
|
|
582
|
+
_read_bound_result(result_path, result_binding, maximum)
|
|
583
|
+
)
|
|
584
|
+
verdicts.append(verdict)
|
|
585
|
+
if shard_findings != ["- None"]:
|
|
586
|
+
findings.extend(shard_findings)
|
|
587
|
+
logical_verdict = "PASS" if all(item == "PASS" for item in verdicts) else "FAIL"
|
|
588
|
+
logical_findings = findings or ["- None"]
|
|
589
|
+
logical = (
|
|
590
|
+
"VERDICT: "
|
|
591
|
+
+ logical_verdict
|
|
592
|
+
+ "\nFINDINGS:\n"
|
|
593
|
+
+ "\n".join(logical_findings)
|
|
594
|
+
+ "\n"
|
|
595
|
+
).encode("utf-8")
|
|
596
|
+
_write_new(destination, logical)
|
|
597
|
+
return hashlib.sha256(logical).hexdigest() + "|" + _path_identity(destination)
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def synthesize_failed_shard(
|
|
601
|
+
contract: Contract,
|
|
602
|
+
raw_size: str,
|
|
603
|
+
shard_dir: Path,
|
|
604
|
+
index: int,
|
|
605
|
+
contract_identity: str,
|
|
606
|
+
result_binding: str,
|
|
607
|
+
maximum: int,
|
|
608
|
+
destination: Path,
|
|
609
|
+
) -> str:
|
|
610
|
+
"""Publish one exact valid FAIL without waiting for irrelevant PASS shards."""
|
|
611
|
+
verify_shard(contract, raw_size, shard_dir, index, contract_identity)
|
|
612
|
+
content = _read_bound_result(
|
|
613
|
+
_shard_path(shard_dir, "result", index), result_binding, maximum
|
|
614
|
+
)
|
|
615
|
+
verdict, _ = _parse_legacy_result(content)
|
|
616
|
+
if verdict != "FAIL":
|
|
617
|
+
raise RuntimeError("shard_did_not_fail")
|
|
618
|
+
_write_new(destination, content)
|
|
619
|
+
return hashlib.sha256(content).hexdigest() + "|" + _path_identity(destination)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def verify_contract(
|
|
623
|
+
source: Path,
|
|
624
|
+
snapshot: Path,
|
|
625
|
+
expected_sha256: str,
|
|
626
|
+
raw_limit: str,
|
|
627
|
+
hard_limit: int,
|
|
628
|
+
manifest_path: Path,
|
|
629
|
+
schema_path: Path,
|
|
630
|
+
expected_identity: str = "",
|
|
631
|
+
) -> Contract:
|
|
632
|
+
contract = derive_contract(source, expected_sha256, raw_limit, hard_limit)
|
|
633
|
+
for path, expected in (
|
|
634
|
+
(snapshot, contract.specification),
|
|
635
|
+
(manifest_path, contract.manifest_bytes),
|
|
636
|
+
(schema_path, contract.schema_bytes),
|
|
637
|
+
):
|
|
638
|
+
actual = _read_regular(path, len(expected), exact_mode=0o600)
|
|
639
|
+
if not hmac.compare_digest(actual, expected):
|
|
640
|
+
raise RuntimeError("contract_mismatch")
|
|
641
|
+
actual_identity = artifact_identity(snapshot, manifest_path, schema_path)
|
|
642
|
+
if expected_identity and not hmac.compare_digest(
|
|
643
|
+
actual_identity, expected_identity
|
|
644
|
+
):
|
|
645
|
+
raise RuntimeError("contract_replaced")
|
|
646
|
+
return contract
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def write_contract(
|
|
650
|
+
source: Path,
|
|
651
|
+
snapshot: Path,
|
|
652
|
+
expected_sha256: str,
|
|
653
|
+
raw_limit: str,
|
|
654
|
+
hard_limit: int,
|
|
655
|
+
manifest_path: Path,
|
|
656
|
+
schema_path: Path,
|
|
657
|
+
) -> str:
|
|
658
|
+
contract = derive_contract(source, expected_sha256, raw_limit, hard_limit)
|
|
659
|
+
try:
|
|
660
|
+
_write_new(snapshot, contract.specification)
|
|
661
|
+
_write_new(manifest_path, contract.manifest_bytes)
|
|
662
|
+
_write_new(schema_path, contract.schema_bytes)
|
|
663
|
+
except RuntimeError:
|
|
664
|
+
raise
|
|
665
|
+
return f"ok:{len(contract.specification)}"
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _emit(contract: Contract, kind: str) -> None:
|
|
669
|
+
if kind == "snapshot":
|
|
670
|
+
sys.stdout.buffer.write(contract.specification)
|
|
671
|
+
elif kind == "manifest":
|
|
672
|
+
sys.stdout.buffer.write(contract.manifest_bytes)
|
|
673
|
+
elif kind == "schema":
|
|
674
|
+
sys.stdout.buffer.write(contract.schema_bytes)
|
|
675
|
+
elif kind == "prompt":
|
|
676
|
+
sys.stdout.write(
|
|
677
|
+
json.dumps(
|
|
678
|
+
{
|
|
679
|
+
"manifest": contract.manifest,
|
|
680
|
+
"schema": contract.schema,
|
|
681
|
+
"specification": contract.specification.decode("utf-8"),
|
|
682
|
+
},
|
|
683
|
+
sort_keys=True,
|
|
684
|
+
)
|
|
685
|
+
)
|
|
686
|
+
elif kind != "none":
|
|
687
|
+
raise ValueError("invalid_emit")
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def main(argv: list[str]) -> int:
|
|
691
|
+
try:
|
|
692
|
+
if len(argv) == 5 and argv[1] == "publish-bound":
|
|
693
|
+
maximum = _limit(argv[4], int(argv[4]))
|
|
694
|
+
sys.stdout.write(
|
|
695
|
+
publish_bound(Path(argv[2]), Path(argv[3]), maximum)
|
|
696
|
+
)
|
|
697
|
+
elif len(argv) in (10, 11) and argv[1] == "verify":
|
|
698
|
+
contract = verify_contract(
|
|
699
|
+
Path(argv[2]),
|
|
700
|
+
Path(argv[3]),
|
|
701
|
+
argv[4],
|
|
702
|
+
argv[5],
|
|
703
|
+
int(argv[6]),
|
|
704
|
+
Path(argv[7]),
|
|
705
|
+
Path(argv[8]),
|
|
706
|
+
argv[10] if len(argv) == 11 else "",
|
|
707
|
+
)
|
|
708
|
+
_emit(contract, argv[9])
|
|
709
|
+
elif len(argv) == 9 and argv[1] == "bind":
|
|
710
|
+
verify_contract(
|
|
711
|
+
Path(argv[2]),
|
|
712
|
+
Path(argv[3]),
|
|
713
|
+
argv[4],
|
|
714
|
+
argv[5],
|
|
715
|
+
int(argv[6]),
|
|
716
|
+
Path(argv[7]),
|
|
717
|
+
Path(argv[8]),
|
|
718
|
+
)
|
|
719
|
+
sys.stdout.write(
|
|
720
|
+
artifact_identity(Path(argv[3]), Path(argv[7]), Path(argv[8]))
|
|
721
|
+
)
|
|
722
|
+
elif len(argv) in (5, 6) and argv[1] == "read-bound":
|
|
723
|
+
maximum = _limit(argv[4], int(argv[4]))
|
|
724
|
+
content = _read_regular(Path(argv[2]), maximum, exact_mode=0o600)
|
|
725
|
+
if not hmac.compare_digest(hashlib.sha256(content).hexdigest(), argv[3]):
|
|
726
|
+
raise PermissionError("hash_mismatch")
|
|
727
|
+
if len(argv) == 6 and not hmac.compare_digest(
|
|
728
|
+
_path_identity(Path(argv[2])), argv[5]
|
|
729
|
+
):
|
|
730
|
+
raise RuntimeError("replaced_file")
|
|
731
|
+
sys.stdout.buffer.write(content)
|
|
732
|
+
elif len(argv) == 12 and argv[1] == "write-shards":
|
|
733
|
+
contract = verify_contract(
|
|
734
|
+
Path(argv[2]),
|
|
735
|
+
Path(argv[3]),
|
|
736
|
+
argv[4],
|
|
737
|
+
argv[5],
|
|
738
|
+
int(argv[6]),
|
|
739
|
+
Path(argv[7]),
|
|
740
|
+
Path(argv[8]),
|
|
741
|
+
argv[9],
|
|
742
|
+
)
|
|
743
|
+
sys.stdout.write(
|
|
744
|
+
json.dumps(
|
|
745
|
+
write_shards(contract, argv[10], Path(argv[11])),
|
|
746
|
+
sort_keys=True,
|
|
747
|
+
)
|
|
748
|
+
)
|
|
749
|
+
elif len(argv) == 15 and argv[1] == "verify-shard":
|
|
750
|
+
contract = verify_contract(
|
|
751
|
+
Path(argv[2]),
|
|
752
|
+
Path(argv[3]),
|
|
753
|
+
argv[4],
|
|
754
|
+
argv[5],
|
|
755
|
+
int(argv[6]),
|
|
756
|
+
Path(argv[7]),
|
|
757
|
+
Path(argv[8]),
|
|
758
|
+
argv[9],
|
|
759
|
+
)
|
|
760
|
+
shard = verify_shard(
|
|
761
|
+
contract,
|
|
762
|
+
argv[10],
|
|
763
|
+
Path(argv[11]),
|
|
764
|
+
int(argv[12]),
|
|
765
|
+
argv[14],
|
|
766
|
+
)
|
|
767
|
+
_emit(shard, argv[13])
|
|
768
|
+
elif len(argv) == 16 and argv[1] == "synthesize-shards":
|
|
769
|
+
contract = verify_contract(
|
|
770
|
+
Path(argv[2]),
|
|
771
|
+
Path(argv[3]),
|
|
772
|
+
argv[4],
|
|
773
|
+
argv[5],
|
|
774
|
+
int(argv[6]),
|
|
775
|
+
Path(argv[7]),
|
|
776
|
+
Path(argv[8]),
|
|
777
|
+
argv[9],
|
|
778
|
+
)
|
|
779
|
+
maximum = _limit(argv[14], int(argv[14]))
|
|
780
|
+
sys.stdout.write(
|
|
781
|
+
synthesize_shards(
|
|
782
|
+
contract,
|
|
783
|
+
argv[10],
|
|
784
|
+
Path(argv[11]),
|
|
785
|
+
argv[12],
|
|
786
|
+
argv[13],
|
|
787
|
+
maximum,
|
|
788
|
+
Path(argv[15]),
|
|
789
|
+
)
|
|
790
|
+
)
|
|
791
|
+
elif len(argv) == 17 and argv[1] == "synthesize-fail":
|
|
792
|
+
contract = verify_contract(
|
|
793
|
+
Path(argv[2]),
|
|
794
|
+
Path(argv[3]),
|
|
795
|
+
argv[4],
|
|
796
|
+
argv[5],
|
|
797
|
+
int(argv[6]),
|
|
798
|
+
Path(argv[7]),
|
|
799
|
+
Path(argv[8]),
|
|
800
|
+
argv[9],
|
|
801
|
+
)
|
|
802
|
+
maximum = _limit(argv[15], int(argv[15]))
|
|
803
|
+
sys.stdout.write(
|
|
804
|
+
synthesize_failed_shard(
|
|
805
|
+
contract,
|
|
806
|
+
argv[10],
|
|
807
|
+
Path(argv[11]),
|
|
808
|
+
int(argv[12]),
|
|
809
|
+
argv[13],
|
|
810
|
+
argv[14],
|
|
811
|
+
maximum,
|
|
812
|
+
Path(argv[16]),
|
|
813
|
+
)
|
|
814
|
+
)
|
|
815
|
+
elif len(argv) == 8:
|
|
816
|
+
print(
|
|
817
|
+
write_contract(
|
|
818
|
+
Path(argv[1]),
|
|
819
|
+
Path(argv[2]),
|
|
820
|
+
argv[3],
|
|
821
|
+
argv[4],
|
|
822
|
+
int(argv[5]),
|
|
823
|
+
Path(argv[6]),
|
|
824
|
+
Path(argv[7]),
|
|
825
|
+
)
|
|
826
|
+
)
|
|
827
|
+
else:
|
|
828
|
+
return 9
|
|
829
|
+
return 0
|
|
830
|
+
except OverflowError as exc:
|
|
831
|
+
print(str(exc))
|
|
832
|
+
return 4
|
|
833
|
+
except PermissionError:
|
|
834
|
+
print("hash_mismatch")
|
|
835
|
+
return 5
|
|
836
|
+
except UnicodeError as exc:
|
|
837
|
+
print(str(exc))
|
|
838
|
+
return 9
|
|
839
|
+
except ValueError:
|
|
840
|
+
print("invalid_limit")
|
|
841
|
+
return 2
|
|
842
|
+
except (OSError, RuntimeError, LookupError) as exc:
|
|
843
|
+
print(str(exc))
|
|
844
|
+
return 9
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
if __name__ == "__main__":
|
|
848
|
+
raise SystemExit(main(sys.argv))
|