okstra 0.167.0 → 0.169.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.
Files changed (102) hide show
  1. package/README.md +6 -5
  2. package/docs/architecture/storage-model.md +57 -1
  3. package/docs/architecture.md +70 -2
  4. package/docs/cli.md +8 -4
  5. package/docs/for-ai/skills/okstra-code-review.md +3 -2
  6. package/docs/for-ai/skills/okstra-schedule-gen.md +3 -1
  7. package/docs/pr-template-usage.md +10 -6
  8. package/docs/project-structure-overview.md +14 -11
  9. package/package.json +1 -1
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/agents/workers/claude-worker.md +6 -5
  12. package/runtime/agents/workers/report-writer-worker.md +9 -4
  13. package/runtime/agents/workers/translator-worker.md +6 -4
  14. package/runtime/prompts/coding-preflight/architectures/hexagonal.md +27 -1
  15. package/runtime/prompts/coding-preflight/clean-code.md +13 -0
  16. package/runtime/prompts/duties/acceptance-critic.md +24 -0
  17. package/runtime/prompts/duties/acceptance-verifier.md +24 -0
  18. package/runtime/prompts/duties/analysis-worker.md +24 -0
  19. package/runtime/prompts/duties/code-reviewer.md +24 -0
  20. package/runtime/prompts/duties/common.md +35 -0
  21. package/runtime/prompts/duties/implementation-executor.md +24 -0
  22. package/runtime/prompts/duties/implementation-verifier.md +24 -0
  23. package/runtime/prompts/duties/lead.md +24 -0
  24. package/runtime/prompts/duties/report-writer.md +24 -0
  25. package/runtime/prompts/duties/reverification-worker.md +24 -0
  26. package/runtime/prompts/duties/schedule-verifier.md +24 -0
  27. package/runtime/prompts/duties/scope-critic.md +24 -0
  28. package/runtime/prompts/duties/translator.md +24 -0
  29. package/runtime/prompts/lead/convergence.md +51 -7
  30. package/runtime/prompts/lead/okstra-lead-contract.md +10 -20
  31. package/runtime/prompts/lead/plan-body-verification.md +16 -1
  32. package/runtime/prompts/lead/report-writer.md +20 -5
  33. package/runtime/prompts/lead/team-contract.md +13 -13
  34. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  35. package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
  36. package/runtime/prompts/profiles/_implementation-executor.md +1 -1
  37. package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
  38. package/runtime/prompts/profiles/implementation.md +4 -2
  39. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +6 -0
  40. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +3 -2
  41. package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +8 -0
  42. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +33 -0
  43. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +13 -12
  44. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +6 -0
  45. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +3 -2
  46. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +2 -0
  47. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -3
  48. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +6 -0
  49. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -1
  50. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +6 -0
  51. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -1
  52. package/runtime/python/okstra_ctl/agent_invocation.py +1502 -0
  53. package/runtime/python/okstra_ctl/agent_prompt_cli.py +788 -0
  54. package/runtime/python/okstra_ctl/codex_dispatch.py +2 -107
  55. package/runtime/python/okstra_ctl/context_cost.py +46 -5
  56. package/runtime/python/okstra_ctl/dispatch_core.py +312 -37
  57. package/runtime/python/okstra_ctl/dispatch_state.py +461 -36
  58. package/runtime/python/okstra_ctl/doctor.py +150 -16
  59. package/runtime/python/okstra_ctl/entrypoints/hosts.py +87 -9
  60. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +214 -23
  61. package/runtime/python/okstra_ctl/path_hints.py +26 -0
  62. package/runtime/python/okstra_ctl/paths.py +20 -0
  63. package/runtime/python/okstra_ctl/ports/__init__.py +8 -0
  64. package/runtime/python/okstra_ctl/ports/host.py +3 -0
  65. package/runtime/python/okstra_ctl/ports/host_model.py +60 -0
  66. package/runtime/python/okstra_ctl/pr_template.py +3 -6
  67. package/runtime/python/okstra_ctl/registry/host_registry.py +5 -0
  68. package/runtime/python/okstra_ctl/render.py +217 -12
  69. package/runtime/python/okstra_ctl/report_finalize.py +44 -0
  70. package/runtime/python/okstra_ctl/run.py +368 -51
  71. package/runtime/python/okstra_ctl/session.py +16 -12
  72. package/runtime/python/okstra_ctl/team.py +11 -11
  73. package/runtime/python/okstra_ctl/worker_dispatch.py +104 -0
  74. package/runtime/python/okstra_ctl/worker_prompt_body.py +5 -38
  75. package/runtime/python/okstra_ctl/worker_prompt_contract.py +32 -2
  76. package/runtime/python/okstra_ctl/worker_prompt_policy.py +38 -1
  77. package/runtime/skills/okstra-code-review/SKILL.md +22 -3
  78. package/runtime/skills/okstra-run/SKILL.md +16 -1
  79. package/runtime/skills/okstra-schedule-gen/SKILL.md +15 -1
  80. package/runtime/templates/implementation-worker-preamble.md +0 -10
  81. package/runtime/templates/report-writer-prompt-preamble.md +0 -9
  82. package/runtime/templates/reports/settings.template.json +0 -11
  83. package/runtime/templates/worker-prompt-preamble.md +0 -10
  84. package/runtime/validators/lib/fixtures.sh +93 -0
  85. package/runtime/validators/lib/validate-assets.sh +0 -8
  86. package/runtime/validators/validate-run.py +182 -0
  87. package/src/cli-registry.mjs +14 -0
  88. package/src/commands/execute/agent-prompt.mjs +25 -0
  89. package/src/commands/execute/codex-dispatch.mjs +6 -63
  90. package/src/commands/execute/worker-dispatch.mjs +76 -0
  91. package/src/commands/lifecycle/doctor.mjs +18 -3
  92. package/src/commands/lifecycle/install.mjs +33 -15
  93. package/src/commands/lifecycle/uninstall.mjs +4 -3
  94. package/src/lib/install-assets.mjs +9 -0
  95. package/runtime/agents/workers/antigravity-worker.md +0 -259
  96. package/runtime/agents/workers/codex-worker.md +0 -259
  97. package/runtime/agents/workers/grok-worker.md +0 -259
  98. package/runtime/agents/workers/kimi-worker.md +0 -259
  99. package/runtime/prompts/coding-preflight/scripts/preedit-check.sh +0 -79
  100. package/runtime/templates/operating-standard.md +0 -22
  101. package/src/lib/worker-agent-render.mjs +0 -50
  102. /package/runtime/templates/{prd → pr}/pr-body.template.md +0 -0
@@ -0,0 +1,1502 @@
1
+ """Compose and verify auditable LLM invocation specifications."""
2
+ from __future__ import annotations
3
+
4
+ import contextlib
5
+ from dataclasses import dataclass
6
+ from datetime import datetime, timezone
7
+ import fcntl
8
+ import hashlib
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ from pathlib import PurePosixPath
13
+ import re
14
+ import tempfile
15
+ from typing import Iterator, Literal, Mapping, get_args
16
+
17
+
18
+ AgentAudience = Literal[
19
+ "lead",
20
+ "analysis-worker",
21
+ "implementation-executor",
22
+ "implementation-verifier",
23
+ "acceptance-verifier",
24
+ "reverification-worker",
25
+ "scope-critic",
26
+ "acceptance-critic",
27
+ "report-writer",
28
+ "translator",
29
+ "code-reviewer",
30
+ "schedule-verifier",
31
+ ]
32
+
33
+ _SUPPORTED_AUDIENCES = frozenset(get_args(AgentAudience))
34
+ _SLUG_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
35
+ _TOP_LEVEL_KEYS = {
36
+ "schemaVersion",
37
+ "invocationId",
38
+ "audience",
39
+ "dispatchKind",
40
+ "workerId",
41
+ "assignmentRef",
42
+ "contractSource",
43
+ "modelAssignment",
44
+ "dutyContract",
45
+ "instruction",
46
+ "prompt",
47
+ "digests",
48
+ }
49
+ _NESTED_KEYS = {
50
+ "contractSource": {"mode", "runManifestPath", "dutyRootPath"},
51
+ "modelAssignment": {
52
+ "provider",
53
+ "model",
54
+ "modelExecutionValue",
55
+ "runner",
56
+ "hostRuntime",
57
+ "hostModelValue",
58
+ },
59
+ "dutyContract": {"id", "version"},
60
+ "instruction": {"sourcePaths"},
61
+ "prompt": {"path"},
62
+ "digests": {
63
+ "catalogDigest",
64
+ "assignmentDigest",
65
+ "dutyDigest",
66
+ "instructionDigest",
67
+ "promptDigest",
68
+ },
69
+ }
70
+
71
+
72
+ class AgentInvocationError(RuntimeError):
73
+ """Raised when an invocation contract cannot be safely materialized."""
74
+
75
+ def __init__(self, message: str, *, reason: str = "invalid_agent_invocation"):
76
+ super().__init__(message)
77
+ self.reason = reason
78
+
79
+
80
+ @dataclass(frozen=True)
81
+ class DutyContract:
82
+ id: str
83
+ version: int
84
+ kind: Literal["common", "role"]
85
+ applies_to: AgentAudience | None
86
+ body: str
87
+ source_path: Path
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class AgentModelAssignment:
92
+ provider: str
93
+ model: str
94
+ model_execution_value: str
95
+ runner: str
96
+ host_runtime: str
97
+ host_model_value: str | None
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class AgentInstructionSource:
102
+ kind: Literal["project", "runtime"]
103
+ path: str
104
+
105
+
106
+ @dataclass(frozen=True)
107
+ class AgentInstruction:
108
+ anchor_lines: tuple[str, ...]
109
+ body: str
110
+ source_paths: tuple[AgentInstructionSource, ...]
111
+
112
+
113
+ @dataclass(frozen=True)
114
+ class AgentInvocationRequest:
115
+ invocation_id: str
116
+ worker_id: str | None
117
+ audience: AgentAudience
118
+ assignment_ref: str | None
119
+ purpose: str | None
120
+ assignment: AgentModelAssignment
121
+ instruction: AgentInstruction
122
+ project_root: Path
123
+ run_manifest_path: Path | None
124
+ duty_root: Path
125
+ prompt_path: Path
126
+ metadata_path: Path
127
+ dispatch_kind: str
128
+
129
+
130
+ @dataclass(frozen=True)
131
+ class PreparedAgentInvocation:
132
+ invocation_id: str
133
+ worker_id: str | None
134
+ assignment_ref: str | None
135
+ prompt_path: Path
136
+ metadata_path: Path
137
+ assignment: AgentModelAssignment
138
+ duty_id: str
139
+ duty_version: int
140
+ catalog_digest: str
141
+ assignment_digest: str
142
+ duty_digest: str
143
+ instruction_digest: str
144
+ prompt_digest: str
145
+
146
+
147
+ @dataclass(frozen=True)
148
+ class MaterializedStandaloneResult:
149
+ purpose: str
150
+ invocation_id: str
151
+ prompt_metadata_path: Path
152
+ result_path: Path
153
+ result_envelope_digest: str
154
+
155
+
156
+ @dataclass(frozen=True)
157
+ class VerifiedStandaloneResult:
158
+ purpose: str
159
+ invocation_id: str
160
+ prompt_metadata_path: Path
161
+ result_path: Path
162
+ completion_path: Path
163
+ result_envelope_digest: str
164
+ returned_body: str
165
+
166
+
167
+ def agent_model_assignment_from_payload(payload: object) -> AgentModelAssignment:
168
+ """Parse the exact persisted six-field model assignment contract."""
169
+ if not isinstance(payload, Mapping) or set(payload) != _NESTED_KEYS["modelAssignment"]:
170
+ raise AgentInvocationError("model assignment payload is invalid")
171
+ string_fields = (
172
+ "provider",
173
+ "model",
174
+ "modelExecutionValue",
175
+ "runner",
176
+ "hostRuntime",
177
+ )
178
+ if any(not isinstance(payload[key], str) for key in string_fields):
179
+ raise AgentInvocationError("model assignment payload is invalid")
180
+ host_model_value = payload["hostModelValue"]
181
+ if host_model_value is not None and not isinstance(host_model_value, str):
182
+ raise AgentInvocationError("model assignment payload is invalid")
183
+ assignment = AgentModelAssignment(
184
+ provider=payload["provider"],
185
+ model=payload["model"],
186
+ model_execution_value=payload["modelExecutionValue"],
187
+ runner=payload["runner"],
188
+ host_runtime=payload["hostRuntime"],
189
+ host_model_value=host_model_value,
190
+ )
191
+ _validate_assignment(assignment)
192
+ return assignment
193
+
194
+
195
+ @dataclass(frozen=True)
196
+ class _MaterializedInvocation:
197
+ prepared: PreparedAgentInvocation
198
+ prompt_bytes: bytes
199
+ metadata: dict[str, object]
200
+ metadata_bytes: bytes
201
+ reservation_root: Path | None
202
+ reservation: dict[str, object] | None
203
+
204
+
205
+ def load_common_duty_contract(duty_root: Path) -> DutyContract:
206
+ """Load the common fragment, which is deliberately outside the role catalog."""
207
+ path = duty_root / "common.md"
208
+ fields, body = _parse_duty_file(path)
209
+ if set(fields) != {"id", "version", "kind"}:
210
+ raise AgentInvocationError(f"invalid common duty frontmatter: {path}")
211
+ if fields["id"] != "common" or fields["kind"] != "common":
212
+ raise AgentInvocationError(f"invalid common duty frontmatter: {path}")
213
+ return DutyContract(
214
+ id="common",
215
+ version=_parse_version(fields["version"], path),
216
+ kind="common",
217
+ applies_to=None,
218
+ body=body,
219
+ source_path=path,
220
+ )
221
+
222
+
223
+ def load_duty_catalog(duty_root: Path) -> dict[AgentAudience, DutyContract]:
224
+ """Load one role contract for every supported invocation audience."""
225
+ catalog: dict[AgentAudience, DutyContract] = {}
226
+ seen_ids: set[str] = set()
227
+ for path in sorted(duty_root.glob("*.md")):
228
+ if path.name == "common.md":
229
+ continue
230
+ duty = _load_role_duty(path)
231
+ if duty.id in seen_ids:
232
+ raise AgentInvocationError(f"duplicate duty id: {duty.id}")
233
+ if duty.applies_to in catalog:
234
+ raise AgentInvocationError(f"duplicate duty audience: {duty.applies_to}")
235
+ seen_ids.add(duty.id)
236
+ catalog[duty.applies_to] = duty
237
+ missing = sorted(_SUPPORTED_AUDIENCES - set(catalog))
238
+ if missing:
239
+ raise AgentInvocationError(f"missing duty audiences: {', '.join(missing)}")
240
+ return catalog
241
+
242
+
243
+ def digest_duty_catalog(duty_root: Path) -> str:
244
+ """Return the canonical digest of every duty file in a snapshot."""
245
+ names = [path.relative_to(duty_root).as_posix() for path in duty_root.glob("*.md")]
246
+ return _digest_framed_files(duty_root, names)
247
+
248
+
249
+ def prepare_agent_invocation(
250
+ request: AgentInvocationRequest,
251
+ ) -> PreparedAgentInvocation:
252
+ """Materialize one immutable prompt and its adjacent completion metadata."""
253
+ _validate_request(request)
254
+ materialized = _materialize(request)
255
+ if materialized.reservation_root is None:
256
+ with _exclusive_lock(_prompt_lock_path(request.prompt_path)):
257
+ _publish_or_reuse(materialized)
258
+ else:
259
+ reservation_lock = materialized.reservation_root / "publish.lock"
260
+ with _exclusive_lock(reservation_lock):
261
+ _publish_or_reuse_reservation(materialized)
262
+ with _exclusive_lock(_prompt_lock_path(request.prompt_path)):
263
+ _publish_or_reuse(materialized)
264
+ errors = verify_agent_invocation(
265
+ request.metadata_path,
266
+ project_root=request.project_root,
267
+ expected_run_manifest_path=request.run_manifest_path,
268
+ expected_assignment=request.assignment,
269
+ expected_invocation_id=request.invocation_id,
270
+ expected_worker_id=request.worker_id,
271
+ expected_assignment_ref=request.assignment_ref,
272
+ expected_audience=request.audience,
273
+ )
274
+ if errors:
275
+ raise AgentInvocationError("; ".join(errors))
276
+ return materialized.prepared
277
+
278
+
279
+ def compose_agent_prompt(request: AgentInvocationRequest) -> str:
280
+ """Render and validate a prompt candidate without publishing artifacts."""
281
+ _validate_request(request)
282
+ return _materialize(request).prompt_bytes.decode("utf-8")
283
+
284
+
285
+ def verify_agent_invocation(
286
+ metadata_path: Path,
287
+ *,
288
+ project_root: Path,
289
+ expected_run_manifest_path: Path | None,
290
+ expected_assignment: AgentModelAssignment | None = None,
291
+ expected_invocation_id: str | None = None,
292
+ expected_worker_id: str | None = None,
293
+ expected_assignment_ref: str | None = None,
294
+ expected_audience: AgentAudience | None = None,
295
+ ) -> list[str]:
296
+ """Return every deterministic violation found in a published invocation."""
297
+ metadata = _read_metadata(metadata_path)
298
+ if metadata is None or not _metadata_schema_is_exact(metadata):
299
+ return ["agent invocation metadata schema is invalid"]
300
+ errors = _verify_expected_identity(
301
+ metadata,
302
+ expected_invocation_id=expected_invocation_id,
303
+ expected_worker_id=expected_worker_id,
304
+ expected_assignment_ref=expected_assignment_ref,
305
+ expected_audience=expected_audience,
306
+ )
307
+ errors.extend(_verify_prompt_file(metadata, project_root))
308
+ errors.extend(_verify_assignment(metadata, expected_assignment))
309
+ errors.extend(
310
+ _verify_contract_source(
311
+ metadata,
312
+ metadata_path=metadata_path,
313
+ project_root=project_root,
314
+ expected_run_manifest_path=expected_run_manifest_path,
315
+ )
316
+ )
317
+ return _deduplicate(errors)
318
+
319
+
320
+ def materialize_standalone_result(
321
+ *,
322
+ project_root: Path,
323
+ purpose: str,
324
+ metadata_path: Path,
325
+ returned_body: bytes,
326
+ ) -> MaterializedStandaloneResult:
327
+ """Publish one immutable raw-return envelope for a standalone invocation."""
328
+ try:
329
+ returned_text = returned_body.decode("utf-8")
330
+ except UnicodeDecodeError as exc:
331
+ raise AgentInvocationError("returned body must be valid UTF-8") from exc
332
+ invocation_id, root = _standalone_lock_identity(
333
+ project_root=project_root, purpose=purpose, metadata_path=metadata_path,
334
+ )
335
+ with _exclusive_lock(root / f"{invocation_id}.result.publish.lock"):
336
+ invocation_id, root, canonical_metadata = _standalone_authority(
337
+ project_root=project_root,
338
+ purpose=purpose,
339
+ metadata_path=metadata_path,
340
+ )
341
+ result_path = root / f"{invocation_id}.result.json"
342
+ envelope = {
343
+ "schemaVersion": 1,
344
+ "purpose": purpose,
345
+ "invocationId": invocation_id,
346
+ "promptMetadataPath": _project_relative(
347
+ canonical_metadata,
348
+ project_root,
349
+ must_exist=True,
350
+ ),
351
+ "returnedBody": returned_text,
352
+ }
353
+ envelope_bytes = _canonical_json(envelope)
354
+ _publish_exclusive(result_path, envelope_bytes)
355
+ return MaterializedStandaloneResult(
356
+ purpose=purpose,
357
+ invocation_id=invocation_id,
358
+ prompt_metadata_path=canonical_metadata,
359
+ result_path=result_path,
360
+ result_envelope_digest=_sha256(envelope_bytes),
361
+ )
362
+
363
+
364
+ def publish_standalone_completion(
365
+ *,
366
+ project_root: Path,
367
+ purpose: str,
368
+ metadata_path: Path,
369
+ completed_at: datetime,
370
+ ) -> Path:
371
+ """Publish the completion marker last, after re-verifying prompt and result."""
372
+ completed = _utc_seconds(completed_at)
373
+ invocation_id, root = _standalone_lock_identity(
374
+ project_root=project_root, purpose=purpose, metadata_path=metadata_path,
375
+ )
376
+ with _exclusive_lock(root / f"{invocation_id}.result.publish.lock"):
377
+ invocation_id, root, canonical_metadata = _standalone_authority(
378
+ project_root=project_root,
379
+ purpose=purpose,
380
+ metadata_path=metadata_path,
381
+ )
382
+ result_path = root / f"{invocation_id}.result.json"
383
+ completion_path = root / f"{invocation_id}.prompt.md.completion.json"
384
+ envelope_bytes, _returned_body = _verified_result_envelope(
385
+ result_path,
386
+ project_root=project_root,
387
+ purpose=purpose,
388
+ invocation_id=invocation_id,
389
+ metadata_path=canonical_metadata,
390
+ )
391
+ completion = {
392
+ "schemaVersion": 1,
393
+ "purpose": purpose,
394
+ "invocationId": invocation_id,
395
+ "status": "completed",
396
+ "promptMetadataPath": _project_relative(
397
+ canonical_metadata,
398
+ project_root,
399
+ must_exist=True,
400
+ ),
401
+ "resultPath": _project_relative(
402
+ result_path,
403
+ project_root,
404
+ must_exist=True,
405
+ ),
406
+ "resultEnvelopeDigest": _sha256(envelope_bytes),
407
+ "completedAt": completed,
408
+ }
409
+ _publish_exclusive(completion_path, _canonical_json(completion))
410
+ return completion_path
411
+
412
+
413
+ def verify_standalone_completion(
414
+ completion_path: Path,
415
+ *,
416
+ project_root: Path,
417
+ expected_purpose: str,
418
+ ) -> VerifiedStandaloneResult:
419
+ """Verify a completion marker and return its already-read immutable body."""
420
+ _validate_slug(expected_purpose, "standalone purpose")
421
+ completion = _load_json_object(completion_path, "standalone completion")
422
+ expected_keys = {
423
+ "schemaVersion",
424
+ "purpose",
425
+ "invocationId",
426
+ "status",
427
+ "promptMetadataPath",
428
+ "resultPath",
429
+ "resultEnvelopeDigest",
430
+ "completedAt",
431
+ }
432
+ if set(completion) != expected_keys or completion.get("schemaVersion") != 1:
433
+ raise AgentInvocationError("standalone completion schema is invalid")
434
+ if completion.get("purpose") != expected_purpose:
435
+ raise AgentInvocationError("standalone completion purpose does not match")
436
+ invocation_id = completion.get("invocationId")
437
+ if not isinstance(invocation_id, str):
438
+ raise AgentInvocationError("standalone completion invocation ID is invalid")
439
+ _validate_slug(invocation_id, "standalone invocation ID")
440
+ root = project_root / ".okstra" / "agent-invocations" / expected_purpose
441
+ canonical_completion = root / f"{invocation_id}.prompt.md.completion.json"
442
+ if completion_path.resolve(strict=True) != canonical_completion.resolve(strict=True):
443
+ raise AgentInvocationError("standalone completion path is not canonical")
444
+ metadata_path = root / f"{invocation_id}.prompt.md.meta.json"
445
+ result_path = root / f"{invocation_id}.result.json"
446
+ if completion.get("status") != "completed":
447
+ raise AgentInvocationError("standalone completion status is invalid")
448
+ if completion.get("promptMetadataPath") != _project_relative(
449
+ metadata_path,
450
+ project_root,
451
+ must_exist=True,
452
+ ):
453
+ raise AgentInvocationError("standalone completion metadata path does not match")
454
+ if completion.get("resultPath") != _project_relative(
455
+ result_path,
456
+ project_root,
457
+ must_exist=True,
458
+ ):
459
+ raise AgentInvocationError("standalone completion result path does not match")
460
+ with _exclusive_lock(root / f"{invocation_id}.result.publish.lock"):
461
+ authority_id, _authority_root, canonical_metadata = _standalone_authority(
462
+ project_root=project_root,
463
+ purpose=expected_purpose,
464
+ metadata_path=metadata_path,
465
+ )
466
+ if authority_id != invocation_id:
467
+ raise AgentInvocationError("standalone completion identity does not match")
468
+ envelope_bytes, returned_body = _verified_result_envelope(
469
+ result_path,
470
+ project_root=project_root,
471
+ purpose=expected_purpose,
472
+ invocation_id=invocation_id,
473
+ metadata_path=canonical_metadata,
474
+ )
475
+ digest = _sha256(envelope_bytes)
476
+ if completion.get("resultEnvelopeDigest") != digest:
477
+ raise AgentInvocationError("result envelope digest does not match completion")
478
+ return VerifiedStandaloneResult(
479
+ purpose=expected_purpose,
480
+ invocation_id=invocation_id,
481
+ prompt_metadata_path=metadata_path,
482
+ result_path=result_path,
483
+ completion_path=canonical_completion,
484
+ result_envelope_digest=digest,
485
+ returned_body=returned_body,
486
+ )
487
+
488
+
489
+ def _standalone_authority(
490
+ *, project_root: Path, purpose: str, metadata_path: Path,
491
+ ) -> tuple[str, Path, Path]:
492
+ _validate_slug(purpose, "standalone purpose")
493
+ root = project_root / ".okstra" / "agent-invocations" / purpose
494
+ metadata = _load_json_object(metadata_path, "agent invocation metadata")
495
+ source = metadata.get("contractSource")
496
+ if not isinstance(source, Mapping) or source.get("mode") != "standalone":
497
+ raise AgentInvocationError("result completion requires standalone metadata")
498
+ invocation_id = metadata.get("invocationId")
499
+ if not isinstance(invocation_id, str):
500
+ raise AgentInvocationError("standalone invocation ID is invalid")
501
+ _validate_slug(invocation_id, "standalone invocation ID")
502
+ canonical_metadata = root / f"{invocation_id}.prompt.md.meta.json"
503
+ if metadata_path.resolve(strict=True) != canonical_metadata.resolve(strict=True):
504
+ raise AgentInvocationError("standalone metadata path is not canonical")
505
+ errors = verify_agent_invocation(
506
+ canonical_metadata,
507
+ project_root=project_root,
508
+ expected_run_manifest_path=None,
509
+ expected_invocation_id=invocation_id,
510
+ )
511
+ if errors:
512
+ raise AgentInvocationError("; ".join(errors))
513
+ return invocation_id, root, canonical_metadata
514
+
515
+
516
+ def _standalone_lock_identity(
517
+ *, project_root: Path, purpose: str, metadata_path: Path,
518
+ ) -> tuple[str, Path]:
519
+ """Derive only the stable lock name; verify all authority after locking."""
520
+ _validate_slug(purpose, "standalone purpose")
521
+ suffix = ".prompt.md.meta.json"
522
+ if not metadata_path.name.endswith(suffix):
523
+ raise AgentInvocationError("standalone metadata path is not canonical")
524
+ invocation_id = metadata_path.name.removesuffix(suffix)
525
+ _validate_slug(invocation_id, "standalone invocation ID")
526
+ return invocation_id, project_root / ".okstra" / "agent-invocations" / purpose
527
+
528
+
529
+ def _verified_result_envelope(
530
+ result_path: Path,
531
+ *,
532
+ project_root: Path,
533
+ purpose: str,
534
+ invocation_id: str,
535
+ metadata_path: Path,
536
+ ) -> tuple[bytes, str]:
537
+ try:
538
+ body = result_path.read_bytes()
539
+ envelope = json.loads(body.decode("utf-8"))
540
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
541
+ raise AgentInvocationError(
542
+ f"standalone result envelope is missing or invalid: {result_path}"
543
+ ) from exc
544
+ expected_keys = {
545
+ "schemaVersion", "purpose", "invocationId", "promptMetadataPath", "returnedBody",
546
+ }
547
+ expected_metadata = _project_relative(
548
+ metadata_path,
549
+ project_root,
550
+ must_exist=True,
551
+ )
552
+ if (
553
+ not isinstance(envelope, dict)
554
+ or set(envelope) != expected_keys
555
+ or envelope.get("schemaVersion") != 1
556
+ or envelope.get("purpose") != purpose
557
+ or envelope.get("invocationId") != invocation_id
558
+ or envelope.get("promptMetadataPath") != expected_metadata
559
+ or not isinstance(envelope.get("returnedBody"), str)
560
+ ):
561
+ raise AgentInvocationError("standalone result envelope contract does not match")
562
+ if body != _canonical_json(envelope):
563
+ raise AgentInvocationError("standalone result envelope is not canonical JSON")
564
+ return body, envelope["returnedBody"]
565
+
566
+
567
+ def _validate_slug(value: str, label: str) -> None:
568
+ if not _SLUG_RE.fullmatch(value):
569
+ raise AgentInvocationError(f"{label} must be a slug")
570
+
571
+
572
+ def _utc_seconds(value: datetime) -> str:
573
+ if value.tzinfo is None or value.utcoffset() != timezone.utc.utcoffset(value):
574
+ raise AgentInvocationError("completedAt must be a UTC datetime")
575
+ return value.astimezone(timezone.utc).replace(microsecond=0).strftime(
576
+ "%Y-%m-%dT%H:%M:%SZ"
577
+ )
578
+
579
+
580
+ def _materialize(request: AgentInvocationRequest) -> _MaterializedInvocation:
581
+ common = load_common_duty_contract(request.duty_root)
582
+ catalog = load_duty_catalog(request.duty_root)
583
+ duty = catalog[request.audience]
584
+ prompt_bytes = _render_prompt(request, common, duty).encode("utf-8")
585
+ digests = _invocation_digests(request, duty, prompt_bytes)
586
+ metadata = _metadata_payload(request, duty, digests)
587
+ prepared = PreparedAgentInvocation(
588
+ invocation_id=request.invocation_id,
589
+ worker_id=request.worker_id,
590
+ assignment_ref=request.assignment_ref,
591
+ prompt_path=request.prompt_path,
592
+ metadata_path=request.metadata_path,
593
+ assignment=request.assignment,
594
+ duty_id=duty.id,
595
+ duty_version=duty.version,
596
+ catalog_digest=digests["catalogDigest"],
597
+ assignment_digest=digests["assignmentDigest"],
598
+ duty_digest=digests["dutyDigest"],
599
+ instruction_digest=digests["instructionDigest"],
600
+ prompt_digest=digests["promptDigest"],
601
+ )
602
+ reservation_root, reservation = _reservation_spec(request)
603
+ return _MaterializedInvocation(
604
+ prepared=prepared,
605
+ prompt_bytes=prompt_bytes,
606
+ metadata=metadata,
607
+ metadata_bytes=_pretty_json(metadata),
608
+ reservation_root=reservation_root,
609
+ reservation=reservation,
610
+ )
611
+
612
+
613
+ def _validate_request(request: AgentInvocationRequest) -> None:
614
+ if request.audience not in _SUPPORTED_AUDIENCES:
615
+ raise AgentInvocationError(f"unknown duty audience: {request.audience}")
616
+ adjacent = request.prompt_path.with_name(request.prompt_path.name + ".meta.json")
617
+ if request.metadata_path != adjacent:
618
+ raise AgentInvocationError("metadata path must be adjacent to prompt path")
619
+ _validate_assignment(request.assignment)
620
+ _validate_instruction(request.instruction)
621
+ _project_relative(request.duty_root, request.project_root, must_exist=True)
622
+ _project_relative(request.prompt_path, request.project_root, must_exist=False)
623
+ _project_relative(request.metadata_path, request.project_root, must_exist=False)
624
+ if request.run_manifest_path is None:
625
+ _validate_standalone_identity(request)
626
+ else:
627
+ _validate_run_identity(request)
628
+
629
+
630
+ def _validate_assignment(assignment: AgentModelAssignment) -> None:
631
+ values = (
632
+ assignment.provider,
633
+ assignment.model,
634
+ assignment.model_execution_value,
635
+ assignment.runner,
636
+ assignment.host_runtime,
637
+ )
638
+ if any(not value.strip() for value in values):
639
+ raise AgentInvocationError("model assignment fields must be non-empty")
640
+ if assignment.runner == "native-session" and not assignment.host_model_value:
641
+ raise AgentInvocationError("native assignment requires a host model value")
642
+ if assignment.runner == "cli-wrapper" and assignment.host_model_value is not None:
643
+ raise AgentInvocationError("CLI assignment must not define a host model value")
644
+
645
+
646
+ def _validate_instruction(instruction: AgentInstruction) -> None:
647
+ if re.search(r"(?m)^## (?:Duty Contract|Task Instructions)\s*$", instruction.body):
648
+ raise AgentInvocationError("task instruction contains a reserved duty heading")
649
+ if any("\n" in line for line in instruction.anchor_lines):
650
+ raise AgentInvocationError("instruction anchorLines must be single lines")
651
+ normalized = [_source_payload(source) for source in instruction.source_paths]
652
+ if not normalized or len({_canonical_json(item) for item in normalized}) != len(normalized):
653
+ raise AgentInvocationError("instruction sourcePaths must be non-empty and unique")
654
+
655
+
656
+ def _validate_standalone_identity(request: AgentInvocationRequest) -> None:
657
+ if not request.purpose or not _SLUG_RE.fullmatch(request.purpose):
658
+ raise AgentInvocationError("standalone purpose must be a slug")
659
+ if not _SLUG_RE.fullmatch(request.invocation_id):
660
+ raise AgentInvocationError("standalone invocation ID must be a slug")
661
+ if request.worker_id is not None or request.assignment_ref is not None:
662
+ raise AgentInvocationError("standalone invocation cannot bind a run worker")
663
+ root = (
664
+ request.project_root
665
+ / ".okstra"
666
+ / "agent-invocations"
667
+ / request.purpose
668
+ )
669
+ expected_prompt = root / f"{request.invocation_id}.prompt.md"
670
+ expected_duty = root / f"{request.invocation_id}.duty-contracts"
671
+ if request.prompt_path != expected_prompt or request.duty_root != expected_duty:
672
+ raise AgentInvocationError("prompt does not use canonical standalone path")
673
+
674
+
675
+ def _validate_run_identity(request: AgentInvocationRequest) -> None:
676
+ if request.purpose is not None:
677
+ raise AgentInvocationError("run invocation cannot define standalone purpose")
678
+ if not request.worker_id or not _SLUG_RE.fullmatch(request.worker_id):
679
+ raise AgentInvocationError("run worker ID must be a non-empty slug")
680
+ if not request.assignment_ref:
681
+ raise AgentInvocationError("run assignment reference is required")
682
+ _project_relative(
683
+ request.run_manifest_path,
684
+ request.project_root,
685
+ must_exist=True,
686
+ )
687
+ suffix = request.assignment_ref.partition("/")[2]
688
+ if suffix and request.assignment_ref.split("/", 1)[0] in {"initial", "reverify"}:
689
+ if suffix != request.worker_id:
690
+ raise AgentInvocationError("run worker ID does not match assignment reference")
691
+
692
+
693
+ def _render_prompt(
694
+ request: AgentInvocationRequest,
695
+ common: DutyContract,
696
+ duty: DutyContract,
697
+ ) -> str:
698
+ assignment = request.assignment
699
+ header = [
700
+ *request.instruction.anchor_lines,
701
+ f"**Provider:** {assignment.provider}",
702
+ f"**Model:** {assignment.model}",
703
+ f"**Model execution value:** {assignment.model_execution_value}",
704
+ f"**Runner:** {assignment.runner}",
705
+ f"**Host runtime:** {assignment.host_runtime}",
706
+ ]
707
+ if assignment.host_model_value is not None:
708
+ header.append(f"**Host model value:** {assignment.host_model_value}")
709
+ duty_body = f"{common.body.rstrip()}\n\n{duty.body.rstrip()}"
710
+ task_body = request.instruction.body.rstrip("\n")
711
+ return (
712
+ "\n".join(header)
713
+ + "\n\n## Duty Contract\n\n"
714
+ + duty_body
715
+ + "\n\n## Task Instructions\n\n"
716
+ + task_body
717
+ + "\n"
718
+ )
719
+
720
+
721
+ def _invocation_digests(
722
+ request: AgentInvocationRequest,
723
+ duty: DutyContract,
724
+ prompt_bytes: bytes,
725
+ ) -> dict[str, str]:
726
+ assignment = _assignment_payload(request.assignment)
727
+ instruction = {
728
+ "anchorLines": list(request.instruction.anchor_lines),
729
+ "body": request.instruction.body,
730
+ "sourcePaths": [
731
+ _source_payload(source) for source in request.instruction.source_paths
732
+ ],
733
+ }
734
+ selected_names = ["common.md", duty.source_path.name]
735
+ return {
736
+ "catalogDigest": digest_duty_catalog(request.duty_root),
737
+ "assignmentDigest": _sha256(_canonical_json(assignment)),
738
+ "dutyDigest": _digest_framed_files(request.duty_root, selected_names),
739
+ "instructionDigest": _sha256(_canonical_json(instruction)),
740
+ "promptDigest": _sha256(prompt_bytes),
741
+ }
742
+
743
+
744
+ def _metadata_payload(
745
+ request: AgentInvocationRequest,
746
+ duty: DutyContract,
747
+ digests: Mapping[str, str],
748
+ ) -> dict[str, object]:
749
+ mode = "run" if request.run_manifest_path is not None else "standalone"
750
+ manifest_path = (
751
+ _project_relative(
752
+ request.run_manifest_path,
753
+ request.project_root,
754
+ must_exist=True,
755
+ )
756
+ if request.run_manifest_path is not None
757
+ else None
758
+ )
759
+ return {
760
+ "schemaVersion": 1,
761
+ "invocationId": request.invocation_id,
762
+ "audience": request.audience,
763
+ "dispatchKind": request.dispatch_kind,
764
+ "workerId": request.worker_id,
765
+ "assignmentRef": request.assignment_ref,
766
+ "contractSource": {
767
+ "mode": mode,
768
+ "runManifestPath": manifest_path,
769
+ "dutyRootPath": _project_relative(
770
+ request.duty_root,
771
+ request.project_root,
772
+ must_exist=True,
773
+ ),
774
+ },
775
+ "modelAssignment": _assignment_payload(request.assignment),
776
+ "dutyContract": {"id": duty.id, "version": duty.version},
777
+ "instruction": {
778
+ "sourcePaths": [
779
+ _source_payload(source)
780
+ for source in request.instruction.source_paths
781
+ ]
782
+ },
783
+ "prompt": {
784
+ "path": _project_relative(
785
+ request.prompt_path,
786
+ request.project_root,
787
+ must_exist=False,
788
+ )
789
+ },
790
+ "digests": dict(digests),
791
+ }
792
+
793
+
794
+ def _reservation_spec(
795
+ request: AgentInvocationRequest,
796
+ ) -> tuple[Path | None, dict[str, object] | None]:
797
+ if request.run_manifest_path is None:
798
+ return None, None
799
+ manifest = _load_json_object(request.run_manifest_path, "run manifest")
800
+ contract = manifest.get("agentContract")
801
+ if not isinstance(contract, dict):
802
+ raise AgentInvocationError("run manifest has no agent contract")
803
+ _validate_manifest_contract(request, manifest, contract)
804
+ reservation_value = contract.get("invocationReservationRootPath")
805
+ reservation_root = _project_path(
806
+ request.project_root,
807
+ reservation_value,
808
+ must_exist=False,
809
+ )
810
+ reservation = {
811
+ "schemaVersion": 1,
812
+ "invocationId": request.invocation_id,
813
+ "workerId": request.worker_id,
814
+ "assignmentRef": request.assignment_ref,
815
+ "audience": request.audience,
816
+ "dispatchKind": request.dispatch_kind,
817
+ "promptPath": _project_relative(
818
+ request.prompt_path,
819
+ request.project_root,
820
+ must_exist=False,
821
+ ),
822
+ "metadataPath": _project_relative(
823
+ request.metadata_path,
824
+ request.project_root,
825
+ must_exist=False,
826
+ ),
827
+ }
828
+ return reservation_root, reservation
829
+
830
+
831
+ def _validate_manifest_contract(
832
+ request: AgentInvocationRequest,
833
+ manifest: Mapping[str, object],
834
+ contract: Mapping[str, object],
835
+ ) -> None:
836
+ duty_path = _project_relative(
837
+ request.duty_root,
838
+ request.project_root,
839
+ must_exist=True,
840
+ )
841
+ if contract.get("dutyRootPath") != duty_path:
842
+ raise AgentInvocationError("duty root does not match run manifest")
843
+ if contract.get("catalogDigest") != digest_duty_catalog(request.duty_root):
844
+ raise AgentInvocationError("catalog digest does not match run duty snapshot")
845
+ allowed = contract.get("allowedAudiences")
846
+ if not isinstance(allowed, list) or request.audience not in allowed:
847
+ raise AgentInvocationError("audience is not allowed by run manifest")
848
+ assignments = manifest.get("invocationAssignments")
849
+ manifest_assignment = (
850
+ assignments.get(request.assignment_ref)
851
+ if isinstance(assignments, dict)
852
+ else None
853
+ )
854
+ if manifest_assignment != _assignment_payload(request.assignment):
855
+ raise AgentInvocationError("model assignment does not match run manifest")
856
+ _validate_authorized_prompt_path(request, contract)
857
+
858
+
859
+ def _validate_authorized_prompt_path(
860
+ request: AgentInvocationRequest,
861
+ contract: Mapping[str, object],
862
+ ) -> None:
863
+ authorized = contract.get("authorizedPaths")
864
+ roots = authorized.get("promptRoots") if isinstance(authorized, dict) else None
865
+ if not isinstance(roots, list) or not roots:
866
+ raise AgentInvocationError("run manifest has no authorized prompt roots")
867
+ prompt = request.prompt_path.resolve(strict=False)
868
+ allowed = [
869
+ _project_path(request.project_root, value, must_exist=True)
870
+ for value in roots
871
+ ]
872
+ if not any(_is_relative_to(prompt, root) for root in allowed):
873
+ raise AgentInvocationError("prompt path is outside authorized prompt roots")
874
+
875
+
876
+ def _publish_or_reuse_reservation(materialized: _MaterializedInvocation) -> None:
877
+ root = materialized.reservation_root
878
+ reservation = materialized.reservation
879
+ if root is None or reservation is None:
880
+ return
881
+ path = root / f"{materialized.prepared.invocation_id}.json"
882
+ if path.exists():
883
+ if _read_json_if_object(path) != reservation:
884
+ raise AgentInvocationError(
885
+ "invocation reservation conflicts with existing invocation",
886
+ reason="invocation_reservation_conflict",
887
+ )
888
+ return
889
+ _publish_exclusive(path, _pretty_json(reservation))
890
+
891
+
892
+ def _publish_or_reuse(materialized: _MaterializedInvocation) -> None:
893
+ prompt = materialized.prepared.prompt_path
894
+ metadata = materialized.prepared.metadata_path
895
+ prompt_exists = prompt.exists()
896
+ metadata_exists = metadata.exists()
897
+ if metadata_exists and not prompt_exists:
898
+ raise _existing_conflict("metadata exists without prompt")
899
+ if prompt_exists and prompt.read_bytes() != materialized.prompt_bytes:
900
+ raise _existing_conflict("existing prompt differs")
901
+ if prompt_exists and metadata_exists:
902
+ if metadata.read_bytes() != materialized.metadata_bytes:
903
+ raise _existing_conflict("existing metadata differs")
904
+ return
905
+ if not prompt_exists:
906
+ _publish_exclusive(prompt, materialized.prompt_bytes)
907
+ _publish_exclusive(metadata, materialized.metadata_bytes)
908
+
909
+
910
+ def _existing_conflict(detail: str) -> AgentInvocationError:
911
+ return AgentInvocationError(
912
+ f"existing_invocation_conflict: {detail}",
913
+ reason="existing_invocation_conflict",
914
+ )
915
+
916
+
917
+ @contextlib.contextmanager
918
+ def _exclusive_lock(path: Path) -> Iterator[None]:
919
+ path.parent.mkdir(parents=True, exist_ok=True)
920
+ handle = path.open("a+")
921
+ try:
922
+ fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
923
+ yield
924
+ finally:
925
+ fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
926
+ handle.close()
927
+
928
+
929
+ def _prompt_lock_path(prompt_path: Path) -> Path:
930
+ return prompt_path.with_name(prompt_path.name + ".publish.lock")
931
+
932
+
933
+ def _publish_exclusive(path: Path, body: bytes) -> None:
934
+ path.parent.mkdir(parents=True, exist_ok=True)
935
+ temp_path: Path | None = None
936
+ try:
937
+ with tempfile.NamedTemporaryFile(
938
+ dir=path.parent,
939
+ prefix=f".{path.name}.",
940
+ suffix=".tmp",
941
+ delete=False,
942
+ ) as handle:
943
+ temp_path = Path(handle.name)
944
+ handle.write(body)
945
+ handle.flush()
946
+ os.fsync(handle.fileno())
947
+ os.link(temp_path, path)
948
+ except FileExistsError as exc:
949
+ raise _existing_conflict(f"published path already exists: {path}") from exc
950
+ finally:
951
+ if temp_path is not None:
952
+ temp_path.unlink(missing_ok=True)
953
+
954
+
955
+ def _verify_expected_identity(
956
+ metadata: Mapping[str, object],
957
+ *,
958
+ expected_invocation_id: str | None,
959
+ expected_worker_id: str | None,
960
+ expected_assignment_ref: str | None,
961
+ expected_audience: AgentAudience | None,
962
+ ) -> list[str]:
963
+ expected = {
964
+ "invocationId": expected_invocation_id,
965
+ "workerId": expected_worker_id,
966
+ "assignmentRef": expected_assignment_ref,
967
+ "audience": expected_audience,
968
+ }
969
+ errors = []
970
+ for key, value in expected.items():
971
+ if value is not None and metadata.get(key) != value:
972
+ errors.append(f"{key} does not match expected invocation identity")
973
+ return errors
974
+
975
+
976
+ def _verify_prompt_file(
977
+ metadata: Mapping[str, object],
978
+ project_root: Path,
979
+ ) -> list[str]:
980
+ prompt = metadata["prompt"]
981
+ digests = metadata["digests"]
982
+ assert isinstance(prompt, dict)
983
+ assert isinstance(digests, dict)
984
+ try:
985
+ path = _project_path(project_root, prompt["path"], must_exist=True)
986
+ except AgentInvocationError:
987
+ return ["prompt path is invalid or missing"]
988
+ body = path.read_bytes()
989
+ if digests["promptDigest"] != _sha256(body):
990
+ return ["prompt digest does not match prompt file"]
991
+ errors = []
992
+ errors.extend(_verify_instruction_digest(metadata, body))
993
+ errors.extend(_verify_model_header(metadata, body))
994
+ return errors
995
+
996
+
997
+ def _verify_instruction_digest(
998
+ metadata: Mapping[str, object],
999
+ prompt_bytes: bytes,
1000
+ ) -> list[str]:
1001
+ try:
1002
+ prompt = prompt_bytes.decode("utf-8")
1003
+ prefix, task_body = _split_prompt(prompt)
1004
+ anchor_lines = _extract_anchor_lines(prefix)
1005
+ except (UnicodeDecodeError, ValueError):
1006
+ return ["prompt structure is invalid"]
1007
+ instruction = metadata["instruction"]
1008
+ digests = metadata["digests"]
1009
+ assert isinstance(instruction, dict)
1010
+ assert isinstance(digests, dict)
1011
+ base = {
1012
+ "anchorLines": anchor_lines,
1013
+ "sourcePaths": instruction["sourcePaths"],
1014
+ }
1015
+ candidates = (
1016
+ {**base, "body": task_body},
1017
+ {**base, "body": task_body.rstrip("\n")},
1018
+ )
1019
+ actual = digests["instructionDigest"]
1020
+ if not any(_sha256(_canonical_json(candidate)) == actual for candidate in candidates):
1021
+ return ["instruction digest does not match prompt instructions"]
1022
+ return []
1023
+
1024
+
1025
+ def _verify_model_header(
1026
+ metadata: Mapping[str, object],
1027
+ prompt_bytes: bytes,
1028
+ ) -> list[str]:
1029
+ assignment = metadata["modelAssignment"]
1030
+ assert isinstance(assignment, dict)
1031
+ prompt = prompt_bytes.decode("utf-8")
1032
+ required = {
1033
+ f"**Provider:** {assignment['provider']}",
1034
+ f"**Model:** {assignment['model']}",
1035
+ f"**Model execution value:** {assignment['modelExecutionValue']}",
1036
+ f"**Runner:** {assignment['runner']}",
1037
+ f"**Host runtime:** {assignment['hostRuntime']}",
1038
+ }
1039
+ host_model = assignment["hostModelValue"]
1040
+ if host_model is not None:
1041
+ required.add(f"**Host model value:** {host_model}")
1042
+ prefix = prompt.split("\n\n## Duty Contract\n\n", 1)[0]
1043
+ if not required.issubset(set(prefix.splitlines())):
1044
+ return ["prompt model header does not match model assignment"]
1045
+ return []
1046
+
1047
+
1048
+ def _split_prompt(prompt: str) -> tuple[str, str]:
1049
+ duty_marker = "\n\n## Duty Contract\n\n"
1050
+ task_marker = "\n\n## Task Instructions\n\n"
1051
+ if prompt.count(duty_marker) != 1 or prompt.count(task_marker) != 1:
1052
+ raise ValueError("prompt markers are invalid")
1053
+ prefix, remainder = prompt.split(duty_marker, 1)
1054
+ _duty, task = remainder.split(task_marker, 1)
1055
+ return prefix, task
1056
+
1057
+
1058
+ def _extract_anchor_lines(prefix: str) -> list[str]:
1059
+ lines = prefix.splitlines()
1060
+ try:
1061
+ provider_index = next(
1062
+ index for index, line in enumerate(lines) if line.startswith("**Provider:**")
1063
+ )
1064
+ except StopIteration as exc:
1065
+ raise ValueError("model header is missing") from exc
1066
+ return lines[:provider_index]
1067
+
1068
+
1069
+ def _verify_assignment(
1070
+ metadata: Mapping[str, object],
1071
+ expected: AgentModelAssignment | None,
1072
+ ) -> list[str]:
1073
+ assignment = metadata["modelAssignment"]
1074
+ digests = metadata["digests"]
1075
+ assert isinstance(assignment, dict)
1076
+ assert isinstance(digests, dict)
1077
+ errors = []
1078
+ if digests["assignmentDigest"] != _sha256(_canonical_json(assignment)):
1079
+ errors.append("assignment digest does not match model assignment")
1080
+ if expected is not None and assignment != _assignment_payload(expected):
1081
+ errors.append("model assignment does not match expected assignment")
1082
+ return errors
1083
+
1084
+
1085
+ def _verify_contract_source(
1086
+ metadata: Mapping[str, object],
1087
+ *,
1088
+ metadata_path: Path,
1089
+ project_root: Path,
1090
+ expected_run_manifest_path: Path | None,
1091
+ ) -> list[str]:
1092
+ source = metadata["contractSource"]
1093
+ assert isinstance(source, dict)
1094
+ mode = source["mode"]
1095
+ if mode == "standalone":
1096
+ if expected_run_manifest_path is not None:
1097
+ return ["standalone invocation cannot use a run manifest"]
1098
+ errors = _verify_metadata_adjacency(metadata, metadata_path, project_root)
1099
+ errors.extend(_verify_duty_snapshot(metadata, project_root, None))
1100
+ return errors
1101
+ if mode != "run":
1102
+ return ["contract source mode is invalid"]
1103
+ if expected_run_manifest_path is None:
1104
+ return ["run invocation requires expected run manifest"]
1105
+ errors = _verify_metadata_adjacency(metadata, metadata_path, project_root)
1106
+ errors.extend(
1107
+ _verify_run_contract(
1108
+ metadata,
1109
+ project_root=project_root,
1110
+ expected_run_manifest_path=expected_run_manifest_path,
1111
+ )
1112
+ )
1113
+ return errors
1114
+
1115
+
1116
+ def _verify_metadata_adjacency(
1117
+ metadata: Mapping[str, object],
1118
+ metadata_path: Path,
1119
+ project_root: Path,
1120
+ ) -> list[str]:
1121
+ prompt = metadata["prompt"]
1122
+ assert isinstance(prompt, dict)
1123
+ try:
1124
+ prompt_path = _project_path(project_root, prompt["path"], must_exist=False)
1125
+ expected = prompt_path.with_name(prompt_path.name + ".meta.json")
1126
+ if metadata_path.resolve(strict=True) != expected.resolve(strict=True):
1127
+ return ["metadata path is not adjacent to prompt path"]
1128
+ except (AgentInvocationError, OSError):
1129
+ return ["metadata path is invalid"]
1130
+ return []
1131
+
1132
+
1133
+ def _verify_run_contract(
1134
+ metadata: Mapping[str, object],
1135
+ *,
1136
+ project_root: Path,
1137
+ expected_run_manifest_path: Path,
1138
+ ) -> list[str]:
1139
+ source = metadata["contractSource"]
1140
+ assert isinstance(source, dict)
1141
+ try:
1142
+ recorded = _project_path(
1143
+ project_root,
1144
+ source["runManifestPath"],
1145
+ must_exist=True,
1146
+ )
1147
+ expected = expected_run_manifest_path.resolve(strict=True)
1148
+ except (AgentInvocationError, OSError):
1149
+ return ["run manifest path is invalid"]
1150
+ if recorded != expected:
1151
+ return ["run manifest path does not match expected run manifest"]
1152
+ try:
1153
+ manifest = _load_json_object(expected, "run manifest")
1154
+ except AgentInvocationError:
1155
+ return ["run manifest is invalid"]
1156
+ errors = _verify_run_manifest_contract(metadata, manifest, project_root)
1157
+ errors.extend(_verify_duty_snapshot(metadata, project_root, manifest))
1158
+ errors.extend(_verify_reservation(metadata, manifest, project_root))
1159
+ return errors
1160
+
1161
+
1162
+ def _verify_run_manifest_contract(
1163
+ metadata: Mapping[str, object],
1164
+ manifest: Mapping[str, object],
1165
+ project_root: Path,
1166
+ ) -> list[str]:
1167
+ source = metadata["contractSource"]
1168
+ digests = metadata["digests"]
1169
+ assert isinstance(source, dict)
1170
+ assert isinstance(digests, dict)
1171
+ contract = manifest.get("agentContract")
1172
+ if not isinstance(contract, dict):
1173
+ return ["run manifest agent contract is missing"]
1174
+ errors = []
1175
+ if contract.get("dutyRootPath") != source["dutyRootPath"]:
1176
+ errors.append("duty root does not match run manifest")
1177
+ if contract.get("catalogDigest") != digests["catalogDigest"]:
1178
+ errors.append("catalog digest does not match run manifest")
1179
+ allowed = contract.get("allowedAudiences")
1180
+ if not isinstance(allowed, list) or metadata["audience"] not in allowed:
1181
+ errors.append("audience is not allowed by run manifest")
1182
+ errors.extend(_verify_manifest_assignment(metadata, manifest))
1183
+ return errors
1184
+
1185
+
1186
+ def _verify_manifest_assignment(
1187
+ metadata: Mapping[str, object],
1188
+ manifest: Mapping[str, object],
1189
+ ) -> list[str]:
1190
+ assignments = manifest.get("invocationAssignments")
1191
+ reference = metadata["assignmentRef"]
1192
+ if not isinstance(assignments, dict) or reference not in assignments:
1193
+ return ["assignment reference is missing from run manifest"]
1194
+ if assignments[reference] != metadata["modelAssignment"]:
1195
+ return ["model assignment does not match run manifest"]
1196
+ return []
1197
+
1198
+
1199
+ def _verify_duty_snapshot(
1200
+ metadata: Mapping[str, object],
1201
+ project_root: Path,
1202
+ manifest: Mapping[str, object] | None,
1203
+ ) -> list[str]:
1204
+ source = metadata["contractSource"]
1205
+ duty_contract = metadata["dutyContract"]
1206
+ digests = metadata["digests"]
1207
+ assert isinstance(source, dict)
1208
+ assert isinstance(duty_contract, dict)
1209
+ assert isinstance(digests, dict)
1210
+ try:
1211
+ root = _project_path(project_root, source["dutyRootPath"], must_exist=True)
1212
+ common = load_common_duty_contract(root)
1213
+ catalog = load_duty_catalog(root)
1214
+ duty = catalog[duty_contract["id"]]
1215
+ except (AgentInvocationError, KeyError):
1216
+ return ["duty snapshot is invalid"]
1217
+ errors = _verify_duty_metadata(duty_contract, duty)
1218
+ actual_catalog = digest_duty_catalog(root)
1219
+ expected_catalog = (
1220
+ manifest["agentContract"]["catalogDigest"]
1221
+ if manifest is not None
1222
+ else digests["catalogDigest"]
1223
+ )
1224
+ if actual_catalog != expected_catalog or digests["catalogDigest"] != actual_catalog:
1225
+ errors.append("catalog digest does not match run duty snapshot")
1226
+ return errors
1227
+ actual_duty = _digest_framed_files(root, ["common.md", duty.source_path.name])
1228
+ if digests["dutyDigest"] != actual_duty:
1229
+ errors.append("duty digest does not match run duty snapshot")
1230
+ return errors
1231
+ errors.extend(_verify_prompt_duty_body(metadata, project_root, common, duty))
1232
+ return errors
1233
+
1234
+
1235
+ def _verify_duty_metadata(
1236
+ recorded: Mapping[str, object],
1237
+ duty: DutyContract,
1238
+ ) -> list[str]:
1239
+ if recorded["id"] != duty.id or recorded["version"] != duty.version:
1240
+ return ["duty identity does not match duty snapshot"]
1241
+ return []
1242
+
1243
+
1244
+ def _verify_prompt_duty_body(
1245
+ metadata: Mapping[str, object],
1246
+ project_root: Path,
1247
+ common: DutyContract,
1248
+ duty: DutyContract,
1249
+ ) -> list[str]:
1250
+ prompt_spec = metadata["prompt"]
1251
+ assert isinstance(prompt_spec, dict)
1252
+ try:
1253
+ prompt_path = _project_path(project_root, prompt_spec["path"], must_exist=True)
1254
+ prompt = prompt_path.read_text(encoding="utf-8")
1255
+ _prefix, remainder = prompt.split("\n\n## Duty Contract\n\n", 1)
1256
+ rendered_duty, _task = remainder.split("\n\n## Task Instructions\n\n", 1)
1257
+ except (AgentInvocationError, OSError, UnicodeDecodeError, ValueError):
1258
+ return ["prompt duty contract does not match duty snapshot"]
1259
+ expected = f"{common.body.rstrip()}\n\n{duty.body.rstrip()}"
1260
+ if rendered_duty != expected:
1261
+ return ["prompt duty contract does not match duty snapshot"]
1262
+ return []
1263
+
1264
+
1265
+ def _verify_reservation(
1266
+ metadata: Mapping[str, object],
1267
+ manifest: Mapping[str, object],
1268
+ project_root: Path,
1269
+ ) -> list[str]:
1270
+ contract = manifest.get("agentContract")
1271
+ if not isinstance(contract, dict):
1272
+ return ["run manifest agent contract is missing"]
1273
+ try:
1274
+ root = _project_path(
1275
+ project_root,
1276
+ contract["invocationReservationRootPath"],
1277
+ must_exist=True,
1278
+ )
1279
+ path = root / f"{metadata['invocationId']}.json"
1280
+ actual = _load_json_object(path, "invocation reservation")
1281
+ except (AgentInvocationError, KeyError):
1282
+ return ["invocation reservation is missing or invalid"]
1283
+ prompt = metadata["prompt"]
1284
+ assert isinstance(prompt, dict)
1285
+ expected = {
1286
+ "schemaVersion": 1,
1287
+ "invocationId": metadata["invocationId"],
1288
+ "workerId": metadata["workerId"],
1289
+ "assignmentRef": metadata["assignmentRef"],
1290
+ "audience": metadata["audience"],
1291
+ "dispatchKind": metadata["dispatchKind"],
1292
+ "promptPath": prompt["path"],
1293
+ "metadataPath": f"{prompt['path']}.meta.json",
1294
+ }
1295
+ if actual != expected:
1296
+ return ["invocation reservation does not match invocation metadata"]
1297
+ return []
1298
+
1299
+
1300
+ def _metadata_schema_is_exact(metadata: Mapping[str, object]) -> bool:
1301
+ if set(metadata) != _TOP_LEVEL_KEYS or metadata.get("schemaVersion") != 1:
1302
+ return False
1303
+ for key, expected_keys in _NESTED_KEYS.items():
1304
+ value = metadata.get(key)
1305
+ if not isinstance(value, dict) or set(value) != expected_keys:
1306
+ return False
1307
+ sources = metadata["instruction"]["sourcePaths"]
1308
+ if not isinstance(sources, list) or not sources:
1309
+ return False
1310
+ if any(not isinstance(item, dict) or set(item) != {"kind", "path"} for item in sources):
1311
+ return False
1312
+ return _digest_paths(metadata) == {
1313
+ "digests.catalogDigest",
1314
+ "digests.assignmentDigest",
1315
+ "digests.dutyDigest",
1316
+ "digests.instructionDigest",
1317
+ "digests.promptDigest",
1318
+ }
1319
+
1320
+
1321
+ def _digest_paths(value: object, prefix: str = "") -> set[str]:
1322
+ if not isinstance(value, dict):
1323
+ return set()
1324
+ found: set[str] = set()
1325
+ for key, child in value.items():
1326
+ path = f"{prefix}.{key}" if prefix else key
1327
+ if key == "digest" or key.endswith("Digest"):
1328
+ found.add(path)
1329
+ found.update(_digest_paths(child, path))
1330
+ return found
1331
+
1332
+
1333
+ def _assignment_payload(assignment: AgentModelAssignment) -> dict[str, object]:
1334
+ return {
1335
+ "provider": assignment.provider,
1336
+ "model": assignment.model,
1337
+ "modelExecutionValue": assignment.model_execution_value,
1338
+ "runner": assignment.runner,
1339
+ "hostRuntime": assignment.host_runtime,
1340
+ "hostModelValue": assignment.host_model_value,
1341
+ }
1342
+
1343
+
1344
+ def _source_payload(source: AgentInstructionSource) -> dict[str, str]:
1345
+ if source.kind not in {"project", "runtime"}:
1346
+ raise AgentInvocationError("instruction sourcePaths contains an invalid kind")
1347
+ path = source.path
1348
+ pure = PurePosixPath(path)
1349
+ if (
1350
+ not path
1351
+ or "\\" in path
1352
+ or pure.is_absolute()
1353
+ or any(part in {"", ".", ".."} for part in pure.parts)
1354
+ ):
1355
+ raise AgentInvocationError("instruction sourcePaths must use relative POSIX paths")
1356
+ return {"kind": source.kind, "path": pure.as_posix()}
1357
+
1358
+
1359
+ def _project_relative(
1360
+ path: Path,
1361
+ project_root: Path,
1362
+ *,
1363
+ must_exist: bool,
1364
+ ) -> str:
1365
+ root = project_root.resolve(strict=True)
1366
+ try:
1367
+ if must_exist:
1368
+ resolved = path.resolve(strict=True)
1369
+ else:
1370
+ resolved = path.parent.resolve(strict=True) / path.name
1371
+ return resolved.relative_to(root).as_posix()
1372
+ except (OSError, ValueError) as exc:
1373
+ raise AgentInvocationError(f"path escapes project root: {path}") from exc
1374
+
1375
+
1376
+ def _project_path(
1377
+ project_root: Path,
1378
+ value: object,
1379
+ *,
1380
+ must_exist: bool,
1381
+ ) -> Path:
1382
+ if not isinstance(value, str) or not value:
1383
+ raise AgentInvocationError("project-relative path is invalid")
1384
+ pure = PurePosixPath(value)
1385
+ if pure.is_absolute() or any(part in {"", ".", ".."} for part in pure.parts):
1386
+ raise AgentInvocationError("project-relative path is invalid")
1387
+ candidate = project_root.joinpath(*pure.parts)
1388
+ _project_relative(candidate, project_root, must_exist=must_exist)
1389
+ return candidate.resolve(strict=must_exist)
1390
+
1391
+
1392
+ def _is_relative_to(path: Path, root: Path) -> bool:
1393
+ try:
1394
+ path.relative_to(root)
1395
+ except ValueError:
1396
+ return False
1397
+ return True
1398
+
1399
+
1400
+ def _digest_framed_files(root: Path, relative_names: list[str]) -> str:
1401
+ framed = bytearray(b"okstra-digest-v1\0")
1402
+ for name in sorted(relative_names):
1403
+ name_bytes = name.encode("utf-8")
1404
+ body = (root / name).read_bytes()
1405
+ framed.extend(len(name_bytes).to_bytes(8, "big"))
1406
+ framed.extend(name_bytes)
1407
+ framed.extend(len(body).to_bytes(8, "big"))
1408
+ framed.extend(body)
1409
+ return _sha256(bytes(framed))
1410
+
1411
+
1412
+ def _sha256(data: bytes) -> str:
1413
+ return f"sha256:{hashlib.sha256(data).hexdigest()}"
1414
+
1415
+
1416
+ def _canonical_json(value: object) -> bytes:
1417
+ return json.dumps(
1418
+ value,
1419
+ ensure_ascii=False,
1420
+ sort_keys=True,
1421
+ separators=(",", ":"),
1422
+ allow_nan=False,
1423
+ ).encode("utf-8")
1424
+
1425
+
1426
+ def _pretty_json(value: object) -> bytes:
1427
+ return (
1428
+ json.dumps(value, ensure_ascii=False, indent=2, allow_nan=False) + "\n"
1429
+ ).encode("utf-8")
1430
+
1431
+
1432
+ def _load_json_object(path: Path, label: str) -> dict[str, object]:
1433
+ value = _read_json_if_object(path)
1434
+ if value is None:
1435
+ raise AgentInvocationError(f"{label} is missing or invalid: {path}")
1436
+ return value
1437
+
1438
+
1439
+ def _read_json_if_object(path: Path) -> dict[str, object] | None:
1440
+ try:
1441
+ value = json.loads(path.read_text(encoding="utf-8"))
1442
+ except (OSError, json.JSONDecodeError):
1443
+ return None
1444
+ return value if isinstance(value, dict) else None
1445
+
1446
+
1447
+ def _read_metadata(path: Path) -> dict[str, object] | None:
1448
+ return _read_json_if_object(path)
1449
+
1450
+
1451
+ def _deduplicate(errors: list[str]) -> list[str]:
1452
+ return list(dict.fromkeys(errors))
1453
+
1454
+
1455
+ def _load_role_duty(path: Path) -> DutyContract:
1456
+ fields, body = _parse_duty_file(path)
1457
+ if set(fields) != {"id", "version", "kind", "appliesTo"}:
1458
+ raise AgentInvocationError(f"invalid role duty frontmatter: {path}")
1459
+ audience = fields["appliesTo"]
1460
+ if audience not in _SUPPORTED_AUDIENCES:
1461
+ raise AgentInvocationError(f"unknown duty audience: {audience}")
1462
+ if fields["kind"] != "role" or fields["id"] != audience:
1463
+ raise AgentInvocationError(f"invalid role duty frontmatter: {path}")
1464
+ return DutyContract(
1465
+ id=fields["id"],
1466
+ version=_parse_version(fields["version"], path),
1467
+ kind="role",
1468
+ applies_to=audience,
1469
+ body=body,
1470
+ source_path=path,
1471
+ )
1472
+
1473
+
1474
+ def _parse_duty_file(path: Path) -> tuple[dict[str, str], str]:
1475
+ try:
1476
+ text = path.read_text(encoding="utf-8")
1477
+ except OSError as exc:
1478
+ raise AgentInvocationError(f"cannot read duty contract: {path}") from exc
1479
+ lines = text.splitlines(keepends=True)
1480
+ if not lines or lines[0].strip() != "---":
1481
+ raise AgentInvocationError(f"missing duty frontmatter: {path}")
1482
+ try:
1483
+ end = next(index for index, line in enumerate(lines[1:], 1) if line.strip() == "---")
1484
+ except StopIteration as exc:
1485
+ raise AgentInvocationError(f"unterminated duty frontmatter: {path}") from exc
1486
+ fields: dict[str, str] = {}
1487
+ for line in lines[1:end]:
1488
+ key, separator, value = line.partition(":")
1489
+ if not separator or not key.strip() or key.strip() in fields:
1490
+ raise AgentInvocationError(f"invalid duty frontmatter: {path}")
1491
+ fields[key.strip()] = value.strip()
1492
+ return fields, "".join(lines[end + 1 :]).lstrip("\n")
1493
+
1494
+
1495
+ def _parse_version(value: str, path: Path) -> int:
1496
+ try:
1497
+ version = int(value)
1498
+ except ValueError as exc:
1499
+ raise AgentInvocationError(f"invalid duty version: {path}") from exc
1500
+ if version < 1:
1501
+ raise AgentInvocationError(f"invalid duty version: {path}")
1502
+ return version