aether-context 0.3.1__tar.gz → 0.4.0__tar.gz

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 (105) hide show
  1. {aether_context-0.3.1/aether_context.egg-info → aether_context-0.4.0}/PKG-INFO +5 -1
  2. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/__init__.py +1 -1
  3. aether_context-0.4.0/aether_context/contracts/__init__.py +534 -0
  4. aether_context-0.4.0/aether_context/contracts/schema-v1.json +2201 -0
  5. aether_context-0.4.0/aether_context/crypto.py +105 -0
  6. aether_context-0.4.0/aether_context/engine.py +1637 -0
  7. aether_context-0.4.0/aether_context/policy/__init__.py +66 -0
  8. aether_context-0.4.0/aether_context/retention.py +647 -0
  9. aether_context-0.4.0/aether_context/scale.py +298 -0
  10. aether_context-0.4.0/aether_context/service/__init__.py +279 -0
  11. aether_context-0.4.0/aether_context/storage_v2/__init__.py +125 -0
  12. {aether_context-0.3.1 → aether_context-0.4.0/aether_context.egg-info}/PKG-INFO +5 -1
  13. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context.egg-info/SOURCES.txt +12 -0
  14. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context.egg-info/entry_points.txt +1 -0
  15. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context.egg-info/requires.txt +5 -0
  16. {aether_context-0.3.1 → aether_context-0.4.0}/pyproject.toml +6 -3
  17. aether_context-0.4.0/tests/test_context_ipc.py +65 -0
  18. aether_context-0.4.0/tests/test_hosted_context.py +423 -0
  19. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_release_parity.py +16 -4
  20. aether_context-0.4.0/tests/test_retention_scale.py +1601 -0
  21. {aether_context-0.3.1 → aether_context-0.4.0}/LICENSE +0 -0
  22. {aether_context-0.3.1 → aether_context-0.4.0}/NOTICE.md +0 -0
  23. {aether_context-0.3.1 → aether_context-0.4.0}/README.md +0 -0
  24. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/_log.py +0 -0
  25. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/cli.py +0 -0
  26. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/config.py +0 -0
  27. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/context_pool.py +0 -0
  28. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/encoder.py +0 -0
  29. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/errors.py +0 -0
  30. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/local_llm.py +0 -0
  31. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/mpo.py +0 -0
  32. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/py.typed +0 -0
  33. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/quantize.py +0 -0
  34. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/session.py +0 -0
  35. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/slice_loader.py +0 -0
  36. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/tokenizer.py +0 -0
  37. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/ui.py +0 -0
  38. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context/witness.py +0 -0
  39. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context.egg-info/dependency_links.txt +0 -0
  40. {aether_context-0.3.1 → aether_context-0.4.0}/aether_context.egg-info/top_level.txt +0 -0
  41. {aether_context-0.3.1 → aether_context-0.4.0}/setup.cfg +0 -0
  42. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_aether_agent.py +0 -0
  43. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_commands.py +0 -0
  44. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_config.py +0 -0
  45. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_library.py +0 -0
  46. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_profile.py +0 -0
  47. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_profile_commands.py +0 -0
  48. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_runner.py +0 -0
  49. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_runner_define_command.py +0 -0
  50. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_runner_lock.py +0 -0
  51. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_slash.py +0 -0
  52. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_slash_agents_columns.py +0 -0
  53. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_slash_cmd.py +0 -0
  54. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agent_store.py +0 -0
  55. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_agents_view.py +0 -0
  56. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_api_eval.py +0 -0
  57. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_auth.py +0 -0
  58. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_brains.py +0 -0
  59. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_bridge.py +0 -0
  60. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_cli.py +0 -0
  61. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_cli_disk.py +0 -0
  62. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_cli_dispatch.py +0 -0
  63. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_cli_doctor.py +0 -0
  64. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_cli_setup.py +0 -0
  65. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_cli_surface.py +0 -0
  66. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_config.py +0 -0
  67. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_context_pool.py +0 -0
  68. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_encoder.py +0 -0
  69. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_end_to_end.py +0 -0
  70. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_errors.py +0 -0
  71. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_hardware.py +0 -0
  72. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_install_security.py +0 -0
  73. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_local_llm.py +0 -0
  74. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_local_llm_openai.py +0 -0
  75. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_moat_seal.py +0 -0
  76. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_mpo.py +0 -0
  77. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_multi_runner.py +0 -0
  78. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_ollama_ctl.py +0 -0
  79. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_ollama_integration.py +0 -0
  80. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_onboarding.py +0 -0
  81. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_pool_quantize.py +0 -0
  82. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_profiles.py +0 -0
  83. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_progress.py +0 -0
  84. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_protocol_lockstep.py +0 -0
  85. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_quantize.py +0 -0
  86. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_repl_agents.py +0 -0
  87. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_repl_multi.py +0 -0
  88. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_repl_preflight.py +0 -0
  89. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_run_agent_events_system.py +0 -0
  90. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_safety.py +0 -0
  91. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_session.py +0 -0
  92. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_session_fallback.py +0 -0
  93. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_slash.py +0 -0
  94. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_slash_agents.py +0 -0
  95. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_slash_custom_invoke.py +0 -0
  96. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_slash_ollama.py +0 -0
  97. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_slice_loader.py +0 -0
  98. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_smoke.py +0 -0
  99. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_tokenizer.py +0 -0
  100. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_toolparse.py +0 -0
  101. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_transport.py +0 -0
  102. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_ui.py +0 -0
  103. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_web_tools.py +0 -0
  104. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_witness.py +0 -0
  105. {aether_context-0.3.1 → aether_context-0.4.0}/tests/test_workflow_yaml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-context
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Unlimited Context — virtual memory for an LLM's attention. Local-first, numpy-only core.
5
5
  Author: Aether AI
6
6
  Maintainer: Aether AI
@@ -34,6 +34,9 @@ Requires-Dist: transformers>=4.40; extra == "hf"
34
34
  Requires-Dist: torch>=2.2; extra == "hf"
35
35
  Provides-Extra: fast
36
36
  Requires-Dist: hnswlib>=0.8; extra == "fast"
37
+ Provides-Extra: hosted
38
+ Requires-Dist: pydantic<3,>=2.10; extra == "hosted"
39
+ Requires-Dist: cryptography<51,>=44; extra == "hosted"
37
40
  Provides-Extra: all
38
41
  Requires-Dist: aether-context[fast,hf,llamacpp]; extra == "all"
39
42
  Provides-Extra: dev
@@ -42,6 +45,7 @@ Requires-Dist: pytest-cov; extra == "dev"
42
45
  Requires-Dist: ruff>=0.5; extra == "dev"
43
46
  Requires-Dist: mypy>=1.10; extra == "dev"
44
47
  Requires-Dist: pyyaml>=6; extra == "dev"
48
+ Requires-Dist: aether-context[hosted]; extra == "dev"
45
49
  Dynamic: license-file
46
50
 
47
51
  <div align="center">
@@ -24,6 +24,6 @@ Public surface (intentionally tiny):
24
24
  from aether_context.local_llm import load_model
25
25
  from aether_context.session import Session
26
26
 
27
- __version__ = "0.3.1"
27
+ __version__ = "0.4.0"
28
28
 
29
29
  __all__ = ["Session", "load_model", "__version__"]
@@ -0,0 +1,534 @@
1
+ """Closed SC-CONTEXT-01 wire contracts. Requires the optional hosted extra."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from typing import Annotated, Literal
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
10
+
11
+ Ident = Annotated[str, Field(pattern=r"^[A-Za-z0-9][A-Za-z0-9_.:@/-]{0,199}$")]
12
+ Digest = Annotated[str, Field(pattern=r"^[0-9a-f]{64}$")]
13
+ Sha = Annotated[str, Field(pattern=r"^[0-9a-f]{40}$")]
14
+
15
+
16
+ def canonical(value: object) -> bytes:
17
+ """Canonical JSON subset: UTF-8, sorted keys, integers, no floats or NaN.
18
+
19
+ Wire timestamps are integer Unix seconds. This is deliberately a smaller
20
+ domain than arbitrary JSON so JS and Python produce the same digest.
21
+ """
22
+ if isinstance(value, BaseModel):
23
+ value = value.model_dump(mode="json")
24
+
25
+ def validate(item: object) -> None:
26
+ if item is None or isinstance(item, (bool, str)):
27
+ return
28
+ if isinstance(item, int) and abs(item) <= 9007199254740991:
29
+ return
30
+ if isinstance(item, list):
31
+ for child in item:
32
+ validate(child)
33
+ return
34
+ if isinstance(item, dict) and all(isinstance(k, str) and k.isascii() for k in item):
35
+ for child in item.values():
36
+ validate(child)
37
+ return
38
+ raise ValueError("canonical JSON requires safe integers and string keys")
39
+
40
+ validate(value)
41
+ return json.dumps(
42
+ value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
43
+ ).encode("utf-8")
44
+
45
+
46
+ def digest(value: object) -> str:
47
+ return hashlib.sha256(canonical(value)).hexdigest()
48
+
49
+
50
+ class Closed(BaseModel):
51
+ model_config = ConfigDict(extra="forbid", frozen=True, strict=True)
52
+
53
+
54
+ class NamespaceV1(Closed):
55
+ owner_id: Ident
56
+ project_id: Ident
57
+ objective_id: Ident
58
+ context_cycle_id: Ident
59
+
60
+
61
+ class ContextRequestV1(Closed):
62
+ schema_version: Literal["ContextRequestV1"] = "ContextRequestV1"
63
+ mode: Literal["required", "preferred", "disabled"] = "required"
64
+ pool_gb: int = Field(default=1, ge=1, le=32)
65
+ captain_mode: Literal["durable_handoff", "interactive_checkpoint", "interactive_required"] = (
66
+ "durable_handoff"
67
+ )
68
+ retention_class: Literal["ephemeral", "audit"] = "ephemeral"
69
+ resume_context_cycle_id: Ident | None = None
70
+ promotion_mode: Literal["verified_only", "disabled"] = "verified_only"
71
+
72
+
73
+ class ContextProfileV1(Closed):
74
+ schema_version: Literal["ContextProfileV1"] = "ContextProfileV1"
75
+ name: Literal["bounded-canary/v1", "hosted-v1"] = "bounded-canary/v1"
76
+ index_implementation: Literal["sqlite-hmac-inverted/v1"] = "sqlite-hmac-inverted/v1"
77
+ index_version: str = Field(min_length=3, max_length=40)
78
+ embedding_version: Literal["none-lexical/v1"] = "none-lexical/v1"
79
+ storage_version: Literal["sqlite-wal-aes256gcm/v2"] = "sqlite-wal-aes256gcm/v2"
80
+ max_records: int = Field(default=10000, ge=1, le=1000000)
81
+ max_indexed_bytes: int = Field(default=64_000_000, ge=1024, le=32_000_000_000)
82
+ max_record_bytes: int = Field(default=32768, ge=256, le=262144)
83
+ max_capsule_tokens: int = Field(default=4096, ge=128, le=65536)
84
+ max_candidates: int = Field(default=256, ge=1, le=4096)
85
+ resident_cache_bytes: int = Field(default=8_388_608, ge=1_048_576, le=268_435_456)
86
+ disk_free_floor: int = Field(default=268_435_456, ge=0)
87
+ retention_seconds: int = Field(default=86400, ge=60, le=604800)
88
+ max_checkpoint_bytes: int = Field(default=64_000_000, ge=1024, le=128_000_000)
89
+ max_concurrent_cycles: int = Field(default=4, ge=1, le=64)
90
+ max_snapshot_bytes: int = Field(default=8_000_000, ge=1024, le=16_000_000)
91
+ max_operations_per_cycle: int = Field(default=20000, ge=16, le=2000000)
92
+ max_calls_per_minute: int = Field(default=600, ge=10, le=6000)
93
+ search_slo_ms: int = Field(default=500, ge=1, le=10000)
94
+ rebuild_slo_seconds: int = Field(default=300, ge=1, le=86400)
95
+ benchmark_receipt: Digest | None = None
96
+
97
+ @model_validator(mode="after")
98
+ def production_evidence(self) -> "ContextProfileV1":
99
+ if self.name == "hosted-v1" and self.benchmark_receipt is None:
100
+ raise ValueError("hosted-v1 requires a measured benchmark receipt")
101
+ return self
102
+
103
+
104
+ class ContextBindingV1(Closed):
105
+ schema_version: Literal["ContextBindingV1"] = "ContextBindingV1"
106
+ namespace: NamespaceV1
107
+ context_bucket_id: Ident
108
+ repository_id: Ident
109
+ repo_main_sha: Sha
110
+ project_graph_id: Ident
111
+ graph_revision: int = Field(ge=1)
112
+ graph_checksum: Digest
113
+ plan_digest: Digest
114
+ execution_profile_digest: Digest
115
+ shared_ir_digest: Digest
116
+ authorization_receipt_ref: Ident
117
+ authorization_digest: Digest
118
+ policy_digest: Digest
119
+ redaction_digest: Digest
120
+ profile_digest: Digest
121
+ captain_binding_id: Ident
122
+ retention_class: Literal["ephemeral", "audit"] = "ephemeral"
123
+ created_at: int = Field(ge=0)
124
+ expires_at: int = Field(ge=0)
125
+
126
+
127
+ class CapabilityV1(Closed):
128
+ schema_version: Literal["ContextCapabilityV1"] = "ContextCapabilityV1"
129
+ namespace: NamespaceV1
130
+ principal_id: Ident
131
+ lane_id: Ident
132
+ task_id: Ident
133
+ role: Literal["worker", "reviewer", "coordinator", "verifier", "captain", "durability"]
134
+ operations: list[
135
+ Literal[
136
+ "append",
137
+ "retrieve",
138
+ "checkpoint",
139
+ "seal",
140
+ "status",
141
+ "hydrate",
142
+ "expire",
143
+ "retention",
144
+ ]
145
+ ] = Field(min_length=1, max_length=8)
146
+ source_class: Literal[
147
+ "user_intent",
148
+ "project_memory_verified",
149
+ "repository_verified",
150
+ "tool_observation",
151
+ "ci_verified",
152
+ "worker_summary",
153
+ "model_note",
154
+ "legacy_untrusted",
155
+ ]
156
+ binding_digest: Digest
157
+ policy_digest: Digest
158
+ profile_digest: Digest
159
+ fence: int = Field(ge=1)
160
+ expires_at: int = Field(ge=0)
161
+ max_bytes: int = Field(ge=1, le=262144)
162
+ max_tokens: int = Field(ge=1, le=65536)
163
+
164
+
165
+ class SignedV1(Closed):
166
+ payload: dict
167
+ key_id: Ident
168
+ signature: str = Field(min_length=64, max_length=128)
169
+
170
+
171
+ class PromotionCandidateV1(Closed):
172
+ record_id: Ident
173
+ content_digest: Digest
174
+ evidence_refs: list[Digest] = Field(min_length=1, max_length=32)
175
+
176
+
177
+ class ContextCheckpointReceiptV1(Closed):
178
+ schema_version: Literal["ContextCheckpointReceiptV1"] = "ContextCheckpointReceiptV1"
179
+ namespace: NamespaceV1
180
+ checkpoint_number: int = Field(ge=1)
181
+ cursor: int = Field(ge=0)
182
+ segment_chain_root: Digest
183
+ manifest_checksum: Digest
184
+ durability: Literal["local"]
185
+ bytes: int = Field(ge=1)
186
+ records: int = Field(ge=0)
187
+ policy_digest: Digest
188
+ key_ref: Ident | None = None
189
+ key_provider: str | None = Field(default=None, min_length=1, max_length=100)
190
+ key_version: Ident | None = None
191
+
192
+ @model_validator(mode="after")
193
+ def key_reference_pair(self) -> "ContextCheckpointReceiptV1":
194
+ if len({item is None for item in (self.key_ref, self.key_provider, self.key_version)}) != 1:
195
+ raise ValueError("checkpoint key reference, provider and version must be paired")
196
+ return self
197
+
198
+
199
+ class ContextHydrateReceiptV1(Closed):
200
+ """Host-replacement evidence bound to one exact checkpoint replay."""
201
+
202
+ schema_version: Literal["ContextHydrateReceiptV1"] = "ContextHydrateReceiptV1"
203
+ operation: Literal["hydrate"] = "hydrate"
204
+ namespace: NamespaceV1
205
+ context_cycle_id: Ident
206
+ manifest_checksum: Digest
207
+ checkpoint_number: int = Field(ge=1)
208
+ cursor: int = Field(ge=0)
209
+ root: Digest
210
+ key_ref: Ident | None = None
211
+ key_provider: str | None = Field(default=None, min_length=1, max_length=100)
212
+ key_version: Ident | None = None
213
+ replay_digest: Digest
214
+ fence: int = Field(ge=1)
215
+
216
+ @model_validator(mode="after")
217
+ def key_reference_pair(self) -> "ContextHydrateReceiptV1":
218
+ if len({item is None for item in (self.key_ref, self.key_provider, self.key_version)}) != 1:
219
+ raise ValueError("hydrate key reference, provider and version must be paired")
220
+ if self.context_cycle_id != self.namespace.context_cycle_id:
221
+ raise ValueError("hydrate cycle does not match namespace")
222
+ return self
223
+
224
+
225
+ class ContextFreezeReceiptV1(Closed):
226
+ schema_version: Literal["ContextFreezeReceiptV1"] = "ContextFreezeReceiptV1"
227
+ context_cycle_id: Ident
228
+ cursor: int = Field(ge=0)
229
+ root: Digest
230
+ binding_digest: Digest
231
+ promotion_candidates: list[PromotionCandidateV1] = Field(
232
+ default_factory=list, max_length=1000
233
+ )
234
+ promotion_candidates_root: Digest
235
+
236
+ @model_validator(mode="after")
237
+ def candidate_root(self) -> "ContextFreezeReceiptV1":
238
+ if digest([item.model_dump() for item in self.promotion_candidates]) != (
239
+ self.promotion_candidates_root
240
+ ):
241
+ raise ValueError("promotion candidate root mismatch")
242
+ return self
243
+
244
+
245
+ class AppendRequestV1(Closed):
246
+ idempotency_key: Ident
247
+ text: str = Field(min_length=1, max_length=262144)
248
+ plane: Literal["P2", "P3", "P4"] = "P3"
249
+ content_type: Literal[
250
+ "summary", "decision", "artifact_ref", "tool_evidence", "candidate_fact"
251
+ ] = "summary"
252
+ action_ref: Ident
253
+ evidence_refs: list[Digest] = Field(default_factory=list, max_length=32)
254
+ supersedes: Ident | None = None
255
+
256
+
257
+ class RetrieveRequestV1(Closed):
258
+ turn_id: Ident
259
+ query: str = Field(max_length=8192)
260
+ native_window: int = Field(ge=256, le=10000000)
261
+ remaining_prompt_budget: int = Field(ge=0, le=10000000)
262
+ used_prompt_tokens: int = Field(default=0, ge=0, le=10000000)
263
+ event_cursor: int | None = Field(default=None, ge=0)
264
+
265
+
266
+ class RemoteDeletionReceiptV1(Closed):
267
+ """Independent object-store evidence for the last committed checkpoint."""
268
+
269
+ schema_version: Literal["ContextRemoteDeletionReceiptV1"] = (
270
+ "ContextRemoteDeletionReceiptV1"
271
+ )
272
+ namespace: NamespaceV1
273
+ manifest_checksum: Digest
274
+ object_ref_digest: Digest
275
+ deleted: Literal[True]
276
+ issued_at: int = Field(ge=0)
277
+ expires_at: int = Field(ge=0)
278
+
279
+ @model_validator(mode="after")
280
+ def short_lived(self) -> "RemoteDeletionReceiptV1":
281
+ if self.expires_at <= self.issued_at or self.expires_at - self.issued_at > 900:
282
+ raise ValueError("remote deletion evidence must be short-lived")
283
+ return self
284
+
285
+
286
+ class RetentionCommandV1(Closed):
287
+ """Short-lived authority for an idempotent hold, release, or expiry."""
288
+
289
+ schema_version: Literal["ContextRetentionCommandV1"] = "ContextRetentionCommandV1"
290
+ operation: Literal["hold", "release", "expire"]
291
+ namespace: NamespaceV1
292
+ idempotency_key: Ident
293
+ expected_revision: int = Field(ge=1)
294
+ reason_code: Literal[
295
+ "audit_hold",
296
+ "legal_hold",
297
+ "incident_hold",
298
+ "hold_released",
299
+ "policy_expiry",
300
+ "operator_expiry",
301
+ ]
302
+ remote_deletion_receipt: SignedV1 | None = None
303
+ checkpoint_object_ref_digest: Digest | None = None
304
+ issued_at: int = Field(ge=0)
305
+ expires_at: int = Field(ge=0)
306
+
307
+ @model_validator(mode="after")
308
+ def operation_reason(self) -> "RetentionCommandV1":
309
+ allowed = {
310
+ "hold": {"audit_hold", "legal_hold", "incident_hold"},
311
+ "release": {"hold_released"},
312
+ "expire": {"policy_expiry", "operator_expiry"},
313
+ }
314
+ if self.reason_code not in allowed[self.operation]:
315
+ raise ValueError("retention reason does not match operation")
316
+ if self.operation == "expire":
317
+ if (self.remote_deletion_receipt is None) != (
318
+ self.checkpoint_object_ref_digest is None
319
+ ):
320
+ raise ValueError("remote deletion receipt and object reference must be paired")
321
+ elif (
322
+ self.remote_deletion_receipt is not None
323
+ or self.checkpoint_object_ref_digest is not None
324
+ ):
325
+ raise ValueError("remote deletion evidence applies only to expiry")
326
+ if self.expires_at <= self.issued_at or self.expires_at - self.issued_at > 900:
327
+ raise ValueError("retention authority must be short-lived")
328
+ return self
329
+
330
+
331
+ class ManagedKeyProviderReceiptV1(Closed):
332
+ """Independent qualification for a shared managed key provider."""
333
+
334
+ schema_version: Literal["ContextManagedKeyProviderReceiptV1"] = (
335
+ "ContextManagedKeyProviderReceiptV1"
336
+ )
337
+ provider: str = Field(min_length=1, max_length=100)
338
+ key_version: Ident
339
+ shared_hydrate_supported: Literal[True]
340
+ verified_destruction_supported: Literal[True]
341
+ issued_at: int = Field(ge=0)
342
+ expires_at: int = Field(ge=0)
343
+
344
+ @model_validator(mode="after")
345
+ def bounded_qualification(self) -> "ManagedKeyProviderReceiptV1":
346
+ if self.expires_at <= self.issued_at or self.expires_at - self.issued_at > 604800:
347
+ raise ValueError("managed key qualification may be valid for at most seven days")
348
+ return self
349
+
350
+
351
+ class KeyDestructionReceiptV1(Closed):
352
+ """Independent managed-KMS evidence that an exact cycle key was destroyed."""
353
+
354
+ schema_version: Literal["ContextKeyDestructionReceiptV1"] = (
355
+ "ContextKeyDestructionReceiptV1"
356
+ )
357
+ provider: str = Field(min_length=1, max_length=100)
358
+ key_ref: Ident
359
+ namespace_digest: Digest
360
+ key_version: Ident
361
+ destroyed: Literal[True]
362
+ destroyed_at: int = Field(ge=0)
363
+ issued_at: int = Field(ge=0)
364
+ expires_at: int = Field(ge=0)
365
+
366
+ @model_validator(mode="after")
367
+ def short_lived(self) -> "KeyDestructionReceiptV1":
368
+ if not self.destroyed_at <= self.issued_at < self.expires_at:
369
+ raise ValueError("managed key destruction time ordering is invalid")
370
+ if self.expires_at - self.issued_at > 900:
371
+ raise ValueError("managed key destruction evidence must be short-lived")
372
+ return self
373
+
374
+
375
+ class RuntimeBuildManifestV1(Closed):
376
+ """Signed build identity for the exact installed runtime package bytes."""
377
+
378
+ schema_version: Literal["ContextRuntimeBuildManifestV1"] = (
379
+ "ContextRuntimeBuildManifestV1"
380
+ )
381
+ distribution: Literal["aether-context"] = "aether-context"
382
+ version: str = Field(min_length=1, max_length=40)
383
+ source_revision: Sha
384
+ package_tree_digest: Digest
385
+ wheel_digest: Digest
386
+ recall_dataset_digest: Digest
387
+ data_plane_case_generator_digest: Digest | None = None
388
+ built_at: int = Field(ge=0)
389
+
390
+
391
+ class ExecutorStabilityReceiptV1(Closed):
392
+ """Independent CI/executor observation bound to one measured build."""
393
+
394
+ schema_version: Literal["ContextExecutorStabilityReceiptV1"] = (
395
+ "ContextExecutorStabilityReceiptV1"
396
+ )
397
+ source_revision: Sha
398
+ profile_benchmark_digest: Digest
399
+ target_concurrency: int = Field(ge=1)
400
+ ci_receipt_digest: Digest
401
+ executor_stable: Literal[True]
402
+ started_at: int = Field(ge=0)
403
+ finished_at: int = Field(ge=0)
404
+ issued_at: int = Field(ge=0)
405
+ expires_at: int = Field(ge=0)
406
+
407
+ @model_validator(mode="after")
408
+ def ordered_observation(self) -> "ExecutorStabilityReceiptV1":
409
+ if not self.started_at <= self.finished_at <= self.issued_at < self.expires_at:
410
+ raise ValueError("executor observation ordering is invalid")
411
+ if self.expires_at - self.issued_at > 604800:
412
+ raise ValueError("executor evidence may be valid for at most seven days")
413
+ return self
414
+
415
+
416
+ class DataPlaneIsolationReceiptV1(Closed):
417
+ """Independent randomized isolation evidence from the hosted request boundary."""
418
+
419
+ schema_version: Literal["ContextDataPlaneIsolationReceiptV1"] = (
420
+ "ContextDataPlaneIsolationReceiptV1"
421
+ )
422
+ source_revision: Sha
423
+ profile_benchmark_digest: Digest
424
+ boundary: Literal["hosted-data-plane/v1"] = "hosted-data-plane/v1"
425
+ case_generator_digest: Digest
426
+ cases: int = Field(ge=1_000_000)
427
+ unauthorized_records: int = Field(ge=0)
428
+ result_digest: Digest
429
+ started_at: int = Field(ge=0)
430
+ finished_at: int = Field(ge=0)
431
+ issued_at: int = Field(ge=0)
432
+ expires_at: int = Field(ge=0)
433
+
434
+ @model_validator(mode="after")
435
+ def ordered_observation(self) -> "DataPlaneIsolationReceiptV1":
436
+ if not self.started_at <= self.finished_at <= self.issued_at < self.expires_at:
437
+ raise ValueError("data-plane isolation observation ordering is invalid")
438
+ if self.expires_at - self.issued_at > 604800:
439
+ raise ValueError("data-plane isolation evidence may be valid for at most seven days")
440
+ return self
441
+
442
+
443
+ class HostedScaleReceiptV1(Closed):
444
+ """Independent, measured evidence for one exact hosted profile."""
445
+
446
+ schema_version: Literal["HostedScaleReceiptV1"] = "HostedScaleReceiptV1"
447
+ profile_benchmark_digest: Digest
448
+ source_revision: Sha
449
+ source_tree_clean: Literal[True]
450
+ index_implementation: str = Field(min_length=1, max_length=100)
451
+ index_version: str = Field(min_length=1, max_length=100)
452
+ embedding_version: str = Field(min_length=1, max_length=100)
453
+ storage_version: str = Field(min_length=1, max_length=100)
454
+ configured_max_records: int = Field(ge=1)
455
+ configured_max_indexed_bytes: int = Field(ge=1024)
456
+ indexed_records: int = Field(ge=1)
457
+ indexed_bytes: int = Field(ge=1)
458
+ reachable_tokens: int = Field(ge=1)
459
+ resident_peak_bytes: int = Field(ge=1)
460
+ retrieval_samples: int = Field(ge=20)
461
+ target_concurrency: int = Field(ge=1)
462
+ retrieval_p50_ms: int = Field(ge=0)
463
+ retrieval_p95_ms: int = Field(ge=0)
464
+ retrieval_p99_ms: int = Field(ge=0)
465
+ checkpoint_ms: int = Field(ge=0)
466
+ checkpoint_receipt_digest: Digest
467
+ hydrate_ms: int = Field(ge=0)
468
+ hydrate_receipt_digest: Digest
469
+ rebuild_ms: int = Field(ge=0)
470
+ rebuild_receipt_digest: Digest
471
+ recall_dataset_name: str = Field(min_length=1, max_length=100)
472
+ recall_dataset_digest: Digest
473
+ recall_basis_points: int = Field(ge=0, le=10000)
474
+ isolation_cases: int = Field(ge=1_000_000)
475
+ unauthorized_records: int = Field(ge=0)
476
+ isolation_result_digest: Digest
477
+ isolation_evidence_class: Literal["predicate", "data_plane"]
478
+ data_plane_isolation_receipt: SignedV1 | None = None
479
+ executor_stable: bool
480
+ executor_stability_receipt: SignedV1 | None = None
481
+ started_at: int = Field(ge=0)
482
+ finished_at: int = Field(ge=0)
483
+ issued_at: int = Field(ge=0)
484
+ expires_at: int = Field(ge=0)
485
+
486
+ @model_validator(mode="after")
487
+ def ordered_measurements(self) -> "HostedScaleReceiptV1":
488
+ if not (
489
+ self.retrieval_p50_ms <= self.retrieval_p95_ms <= self.retrieval_p99_ms
490
+ and self.started_at <= self.finished_at <= self.issued_at < self.expires_at
491
+ ):
492
+ raise ValueError("scale measurement ordering is invalid")
493
+ if self.expires_at - self.issued_at > 604800:
494
+ raise ValueError("scale evidence may be valid for at most seven days")
495
+ if self.executor_stable != (self.executor_stability_receipt is not None):
496
+ raise ValueError("executor stability claim requires independent evidence")
497
+ if (self.isolation_evidence_class == "data_plane") != (
498
+ self.data_plane_isolation_receipt is not None
499
+ ):
500
+ raise ValueError("data-plane isolation claim requires independent evidence")
501
+ return self
502
+
503
+
504
+ class ClosureProofV1(Closed):
505
+ schema_version: Literal["ContextClosureProofV1"] = "ContextClosureProofV1"
506
+ namespace: NamespaceV1
507
+ binding_digest: Digest
508
+ final_root: Digest
509
+ final_cursor: int = Field(ge=0)
510
+ execution_dag_digest: Digest
511
+ plan_ir_digest: Digest
512
+ shared_ir_digest: Digest
513
+ repo_main_sha: Sha
514
+ git_head_sha: Sha
515
+ pr_head_sha: Sha
516
+ pr_url: str = Field(pattern=r"^https://[^/?#]+/[^/?#]+/[^/?#]+/pull/[1-9][0-9]*$")
517
+ ci_receipt: Digest
518
+ nano_receipt: Digest
519
+ proof_receipt: Digest
520
+ memory_candidate_digest: Digest
521
+ promotion_set_root: Digest
522
+ accepted_record_ids: list[Ident] = Field(default_factory=list, max_length=1000)
523
+ rejected_record_ids: list[Ident] = Field(default_factory=list, max_length=1000)
524
+ expires_at: int = Field(ge=0)
525
+
526
+ @model_validator(mode="after")
527
+ def exact_heads(self) -> "ClosureProofV1":
528
+ if self.git_head_sha != self.pr_head_sha:
529
+ raise ValueError("PR head must equal the independently verified Git head")
530
+ if len(set(self.accepted_record_ids)) != len(self.accepted_record_ids):
531
+ raise ValueError("duplicate promoted record")
532
+ if set(self.accepted_record_ids) & set(self.rejected_record_ids):
533
+ raise ValueError("promotion sets overlap")
534
+ return self