urace 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. urace/__init__.py +3 -0
  2. urace/__main__.py +3 -0
  3. urace/_common.py +399 -0
  4. urace/a2a_server.py +382 -0
  5. urace/analytics.py +146 -0
  6. urace/api_interface.py +197 -0
  7. urace/authenticated_journal.py +76 -0
  8. urace/authority.py +189 -0
  9. urace/auto_goals.py +124 -0
  10. urace/bootstrap.py +128 -0
  11. urace/circuit_breaker.py +107 -0
  12. urace/cli.py +10906 -0
  13. urace/cognitive_resume.py +243 -0
  14. urace/command_surface.py +381 -0
  15. urace/compaction.py +1060 -0
  16. urace/content_store.py +72 -0
  17. urace/context_relations.py +45 -0
  18. urace/continuation.py +80 -0
  19. urace/credit_ledger.py +1190 -0
  20. urace/crypto_wallet.py +209 -0
  21. urace/data_transport.py +159 -0
  22. urace/decision_feedback.py +212 -0
  23. urace/decision_record.py +363 -0
  24. urace/derived_artifacts.py +47 -0
  25. urace/destination.py +161 -0
  26. urace/equity_auto_trade.py +1090 -0
  27. urace/evolution_guard.py +506 -0
  28. urace/exchange_cli.py +1048 -0
  29. urace/executor.py +1215 -0
  30. urace/executor_capabilities.py +95 -0
  31. urace/external_metrics.py +163 -0
  32. urace/feature_catalog.py +188 -0
  33. urace/feature_equity.py +235 -0
  34. urace/feature_policy.py +21 -0
  35. urace/federation.py +146 -0
  36. urace/freeform_routing.py +359 -0
  37. urace/implementation_sequence.py +71 -0
  38. urace/inputs.py +478 -0
  39. urace/inspection.py +883 -0
  40. urace/integration_bridge.py +370 -0
  41. urace/intent_trace.py +31 -0
  42. urace/launcher.py +117 -0
  43. urace/ledger_compaction.py +494 -0
  44. urace/ledger_crypto.py +89 -0
  45. urace/mcp_server.py +138 -0
  46. urace/milestone_feedback.py +65 -0
  47. urace/milestone_suggestions.py +40 -0
  48. urace/multi_executor.py +139 -0
  49. urace/outcome_verification.py +349 -0
  50. urace/patterns.py +1348 -0
  51. urace/peer_discovery.py +1315 -0
  52. urace/peer_exchange.py +575 -0
  53. urace/peer_improvement.py +512 -0
  54. urace/peer_selection.py +99 -0
  55. urace/peer_work_market.py +1131 -0
  56. urace/planning_feedback.py +92 -0
  57. urace/portfolio.py +2432 -0
  58. urace/portfolio_github.py +99 -0
  59. urace/preflight.py +52 -0
  60. urace/primitives.py +2339 -0
  61. urace/product_creation.py +94 -0
  62. urace/product_similarity.py +599 -0
  63. urace/product_type_detector.py +63 -0
  64. urace/protocol_catalog.py +14 -0
  65. urace/resource_usage.py +138 -0
  66. urace/rules.py +619 -0
  67. urace/runtime_cycle_cli.py +19 -0
  68. urace/runtime_features.py +525 -0
  69. urace/runtime_product.py +67 -0
  70. urace/schema_export.py +42 -0
  71. urace/scope_priority.py +103 -0
  72. urace/self_metrics.py +160 -0
  73. urace/signed_ledger.py +128 -0
  74. urace/spec_audit.py +376 -0
  75. urace/stun_client.py +72 -0
  76. urace/token_ledger.py +144 -0
  77. urace/tokenomics.py +72 -0
  78. urace/trial.py +68 -0
  79. urace/trial_cli.py +53 -0
  80. urace/tui.py +399 -0
  81. urace/uncertainty_followup.py +21 -0
  82. urace/updater.py +426 -0
  83. urace/user_controls.py +38 -0
  84. urace/validation.py +218 -0
  85. urace/value_discovery.py +382 -0
  86. urace/value_trajectory.py +296 -0
  87. urace/version.py +6 -0
  88. urace/work_policy.py +35 -0
  89. urace/workflow_state.py +267 -0
  90. urace-0.3.0.dist-info/METADATA +10 -0
  91. urace-0.3.0.dist-info/RECORD +94 -0
  92. urace-0.3.0.dist-info/WHEEL +5 -0
  93. urace-0.3.0.dist-info/entry_points.txt +2 -0
  94. urace-0.3.0.dist-info/top_level.txt +1 -0
urace/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """URACE persistent lifecycle control plane."""
2
+
3
+ __version__ = "0.3.0"
urace/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .launcher import main
2
+
3
+ raise SystemExit(main())
urace/_common.py ADDED
@@ -0,0 +1,399 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import fcntl
5
+ import datetime
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import tempfile
10
+ import time
11
+ import uuid
12
+ from contextlib import contextmanager
13
+ from datetime import datetime as _datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Any, Generator
16
+
17
+
18
+ RUNTIME_ROOT = Path(__file__).resolve().parents[1]
19
+ SYSTEM_ROOT = RUNTIME_ROOT.parent
20
+ ROOT = SYSTEM_ROOT.parent
21
+ CONFIG_PATH = Path(__file__).with_name("config.json")
22
+
23
+
24
+ def now() -> str:
25
+ return _datetime.now(timezone.utc).isoformat()
26
+
27
+
28
+ def identifier(prefix: str) -> str:
29
+ return f"{prefix}-{uuid.uuid4().hex[:12]}"
30
+
31
+
32
+ def atomic_write_text(path: Path, content: str) -> None:
33
+ path.parent.mkdir(parents=True, exist_ok=True)
34
+ with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle:
35
+ handle.write(content)
36
+ handle.flush()
37
+ os.fsync(handle.fileno())
38
+ temporary = Path(handle.name)
39
+ temporary.replace(path)
40
+ directory = os.open(path.parent, os.O_RDONLY)
41
+ try:
42
+ os.fsync(directory)
43
+ finally:
44
+ os.close(directory)
45
+
46
+
47
+ def atomic_write_json(path: Path, value: dict[str, Any]) -> None:
48
+ atomic_write_text(path, json.dumps(value, indent=2, sort_keys=True) + "\n")
49
+
50
+
51
+ def durable_unlink(path: Path) -> None:
52
+ path.unlink(missing_ok=True)
53
+ if path.parent.exists():
54
+ directory = os.open(path.parent, os.O_RDONLY)
55
+ try:
56
+ os.fsync(directory)
57
+ finally:
58
+ os.close(directory)
59
+
60
+
61
+ def process_exists(pid: int) -> bool:
62
+ try:
63
+ os.kill(pid, 0)
64
+ return True
65
+ except ProcessLookupError:
66
+ return False
67
+ except PermissionError:
68
+ return True
69
+
70
+
71
+ def _publish_state_lock(lock: Path, marker: str) -> int:
72
+ # Publish only fully written metadata. A process killed before publication
73
+ # leaves no lock that another process could mistake for a live owner.
74
+ descriptor, temporary_name = tempfile.mkstemp(prefix=".state-lock-", dir=lock.parent)
75
+ temporary = Path(temporary_name)
76
+ try:
77
+ payload = marker.encode()
78
+ if os.write(descriptor, payload) != len(payload):
79
+ raise OSError("Incomplete state lock metadata write")
80
+ os.fsync(descriptor)
81
+ os.link(temporary, lock)
82
+ return descriptor
83
+ except BaseException:
84
+ os.close(descriptor)
85
+ raise
86
+ finally:
87
+ temporary.unlink(missing_ok=True)
88
+
89
+
90
+ @contextmanager
91
+ def _published_state_lock(path: Path) -> Generator[None, None, None]:
92
+ path.parent.mkdir(parents=True, exist_ok=True)
93
+ lock = path.with_suffix(".lock")
94
+ descriptor = None
95
+ try:
96
+ for attempt in range(2):
97
+ try:
98
+ marker = json.dumps({"pid": os.getpid(), "started_at": _datetime.now(timezone.utc).isoformat()})
99
+ descriptor = _publish_state_lock(lock, marker)
100
+ break
101
+ except FileExistsError as error:
102
+ try:
103
+ raw = lock.read_text(encoding="utf-8").strip()
104
+ owner = json.loads(raw)["pid"]
105
+ except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError):
106
+ try:
107
+ owner = int(raw.strip())
108
+ except (NameError, ValueError):
109
+ owner = -1
110
+ if attempt == 0 and not process_exists(owner):
111
+ try:
112
+ _obs_path = path.parent / "stale-lock-observations.jsonl"
113
+ _obs_entry = json.dumps({"at": _datetime.now(timezone.utc).isoformat(), "stale_pid": owner, "lock": str(lock), "disposition": "removed"})
114
+ from .authenticated_journal import append_row
115
+ append_row(_obs_path,json.loads(_obs_entry))
116
+ except OSError:
117
+ pass
118
+ lock.unlink(missing_ok=True)
119
+ continue
120
+ raise RuntimeError(f"state is locked by live process {owner}: {lock}") from error
121
+ yield
122
+ finally:
123
+ if descriptor is not None:
124
+ os.close(descriptor)
125
+ lock.unlink(missing_ok=True)
126
+
127
+
128
+ @contextmanager
129
+ def state_lock(path: Path) -> Generator[None, None, None]:
130
+ path.parent.mkdir(parents=True, exist_ok=True)
131
+ # Keep this inode: unlinking an advisory lock file would let contenders
132
+ # acquire different inodes and enter the protected section concurrently.
133
+ guard = path.with_suffix(".lock.guard")
134
+ descriptor = os.open(guard, os.O_CREAT | os.O_RDWR, 0o600)
135
+ try:
136
+ try:
137
+ fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
138
+ except BlockingIOError as error:
139
+ raise RuntimeError(f"state is locked by live process (kernel guard): {guard}") from error
140
+ with _published_state_lock(path):
141
+ yield
142
+ finally:
143
+ # Closing also releases the kernel guard on exceptions and process exit.
144
+ os.close(descriptor)
145
+
146
+
147
+ def load_config() -> dict[str, Any]:
148
+ cfg = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
149
+ env_product = os.environ.get("URACE_ACTIVE_PRODUCT")
150
+ if env_product:
151
+ cfg["active_product"] = env_product
152
+ active=cfg.get("active_product")
153
+ entry=next((e for e in cfg.get("product_registry",[]) if e.get("name")==active and e.get("kind")=="bootstrapped" and e.get("status")=="ACTIVE"),None)
154
+ if entry:
155
+ from .product_creation import validate_entry
156
+ validate_entry(entry,ROOT)
157
+ registry=cfg["product_registry"];cfg={**cfg,**copy_mapping(entry["configuration"]),"product_registry":registry,"active_product":active,"_product_overlay_name":active,"product_improvement_enabled":entry.get("improvement_enabled",True)}
158
+ return cfg
159
+
160
+
161
+ def copy_mapping(value):
162
+ return json.loads(json.dumps(value))
163
+
164
+
165
+ def save_config(config: dict[str, Any]) -> None:
166
+ overlay=config.get('_product_overlay_name')
167
+ if overlay:
168
+ base=json.loads(CONFIG_PATH.read_text());registry=config.get('product_registry',base.get('product_registry',[]))
169
+ if config.get('active_product')==overlay:
170
+ entry=next((e for e in registry if e.get('name')==overlay and e.get('kind')=='bootstrapped'),None)
171
+ if entry is None:raise ValueError('Active isolated product disappeared')
172
+ entry['configuration']={k:v for k,v in config.items() if k not in ('product_registry','active_product','_product_overlay_name')}
173
+ from .product_creation import validate_entry
174
+ validate_entry(entry,ROOT)
175
+ else:base['active_product']=config.get('active_product')
176
+ base['product_registry']=registry;atomic_write_json(CONFIG_PATH,base);return
177
+ atomic_write_json(CONFIG_PATH,config)
178
+
179
+
180
+ def state_path(config: dict[str, Any]) -> Path:
181
+ active = config.get("active_product")
182
+ if active:
183
+ registry = config.get("product_registry", [])
184
+ entry = next(
185
+ (e for e in registry if e.get("name") == active and e.get("status", "ACTIVE") == "ACTIVE"),
186
+ None,
187
+ )
188
+ if entry:
189
+ path = Path(entry["state_path"])
190
+ return path if path.is_absolute() else RUNTIME_ROOT / path
191
+ path = Path(config["state_path"])
192
+ return path if path.is_absolute() else RUNTIME_ROOT / path
193
+
194
+
195
+ def load_state(config: dict[str, Any]) -> dict[str, Any]:
196
+ path = state_path(config)
197
+ if not path.exists():
198
+ raise RuntimeError("URACE is not initialized; run `urace init`")
199
+ return json.loads(path.read_text(encoding="utf-8"))
200
+
201
+
202
+ def load_state_for_read(config: dict[str, Any]) -> dict[str, Any] | None:
203
+ import sys
204
+ try:
205
+ return load_state(config)
206
+ except (RuntimeError, OSError, json.JSONDecodeError) as error:
207
+ print(f"urace: {error}", file=sys.stderr)
208
+ return None
209
+
210
+
211
+ def _check_state_invariants(state: dict) -> None:
212
+ """Raise ValueError if state violates core invariants.
213
+
214
+ Only enforced on bootstrapped states (state_version >= 1) to avoid
215
+ rejecting minimal test stubs or pre-init state documents.
216
+ """
217
+ # Invariant: schema_version must not decrease
218
+ if state.get('schema_version', 1) < 1:
219
+ raise ValueError(f"Invariant violated: schema_version={state.get('schema_version')}")
220
+ # Only enforce structural invariants on post-bootstrap state documents
221
+ if int(state.get('state_version', 0)) < 1:
222
+ return
223
+ # Invariant: self_evolution_policy must be well-formed if present
224
+ sep = state.get('self_evolution_policy')
225
+ if sep is not None and (not isinstance(sep, dict) or 'mode' not in sep):
226
+ raise ValueError("Invariant violated: self_evolution_policy malformed")
227
+ # Invariant: authority must be non-empty if present (prevent accidental erasure)
228
+ auth = state.get('authority')
229
+ if auth is not None and not auth:
230
+ raise ValueError("Invariant violated: authority was present but is now empty")
231
+
232
+
233
+ def save_state(config: dict[str, Any], state: dict[str, Any]) -> None:
234
+ path = state_path(config)
235
+ path.parent.mkdir(parents=True, exist_ok=True)
236
+ from .resource_usage import archive_calls
237
+ archive_calls(config, state)
238
+ _check_state_invariants(state)
239
+ state["state_version"] += 1
240
+ state["updated_at"] = now()
241
+ atomic_write_json(path, state)
242
+ from .resource_usage import write_projection
243
+ try:write_projection(config,state)
244
+ except OSError:
245
+ # State is already durable. Reporting can use its existing full-state fallback.
246
+ import sys
247
+ print('urace: receipt read model unavailable; full-state fallback retained',file=sys.stderr)
248
+
249
+
250
+ def transaction_path(config: dict[str, Any]) -> Path:
251
+ return state_path(config).parent / "transaction.json"
252
+
253
+
254
+ def input_directory(config: dict[str, Any]) -> Path:
255
+ return state_path(config).parent / "inbox"
256
+
257
+
258
+ # ── §62h Technique Applicability Gate ────────────────────────────────────────
259
+
260
+ _TECHNIQUE_NAMES = {"T1", "T2", "T3", "T4", "T5", "T6"}
261
+ _SD_ALWAYS_TRUE = {"T1", "T2", "T3", "T4", "T6"}
262
+
263
+
264
+ def technique_applicability_gate(
265
+ technique: str,
266
+ state: dict,
267
+ config: dict,
268
+ *,
269
+ applied_this_cycle: set | None = None,
270
+ ) -> dict:
271
+ """Evaluate TechniqueApplicabilityGate for one technique.
272
+
273
+ Returns TechniqueApplicationRecord (verdict, reason, sd_result, applied_at).
274
+ Caller must append to state['technique_application_log'] if desired.
275
+ """
276
+ from datetime import datetime, timezone
277
+ _at = datetime.now(timezone.utc).isoformat()
278
+ applied_this_cycle = applied_this_cycle or set()
279
+
280
+ # Guard 1: dormant with no pending obligation
281
+ if state.get("lifecycle_state") == "DORMANT":
282
+ pending_log = state.get("technique_application_log", [])
283
+ has_obligation = any(
284
+ r.get("technique") == technique and r.get("verdict") == "DEFER"
285
+ for r in pending_log[-20:]
286
+ )
287
+ if not has_obligation:
288
+ return {"id": identifier("ta"), "technique": technique, "verdict": "SKIP",
289
+ "reason": "dormant_no_obligation", "applied_at": _at}
290
+
291
+ # Guard 2: idempotent — already applied this cycle
292
+ if technique in applied_this_cycle:
293
+ return {"id": identifier("ta"), "technique": technique, "verdict": "SKIP",
294
+ "reason": "already_applied_this_cycle", "applied_at": _at}
295
+
296
+ # Guard 3: self-determinable → APPLY
297
+ sd_result = technique in _SD_ALWAYS_TRUE or technique not in {"T5"}
298
+ if sd_result:
299
+ return {"id": identifier("ta"), "technique": technique, "verdict": "APPLY",
300
+ "reason": "self_determinable", "applied_at": _at, "sd_result": True}
301
+
302
+ # Guard 4: SD=False, check credits
303
+ executor_state = state.get("executor_state", {})
304
+ daily_calls = executor_state.get("calls_today", 0)
305
+ daily_limit = config.get("executor", {}).get("max_executor_calls_per_30d", 20)
306
+ credits_remaining = None if daily_limit is None else max(0, daily_limit - daily_calls)
307
+ technique_credit_cost = 1 # one executor call to determine semantic boundary
308
+ if credits_remaining is None or credits_remaining > technique_credit_cost:
309
+ return {"id": identifier("ta"), "technique": technique, "verdict": "DELEGATE_EXECUTOR",
310
+ "reason": "sd_false_credits_available", "applied_at": _at,
311
+ "sd_result": False, "executor_delegated": False, "credits_consumed": 0}
312
+
313
+ # Guard 5: defer
314
+ return {"id": identifier("ta"), "technique": technique, "verdict": "DEFER",
315
+ "reason": "sd_false_insufficient_credits", "applied_at": _at, "sd_result": False}
316
+
317
+
318
+ # ── §62i AP11/AP12/AP18 helper functions ─────────────────────────────────────
319
+
320
+ def bridge_record(
321
+ bridge_name: str,
322
+ source_schema: str,
323
+ target_schema: str,
324
+ *,
325
+ loss_declared: bool = False,
326
+ dropped_fields: list | None = None,
327
+ ) -> dict:
328
+ """AP11 — produce a BridgeRecord for a bridge translation event."""
329
+ from datetime import datetime, timezone
330
+ r: dict = {
331
+ "id": identifier("br"),
332
+ "bridge_name": bridge_name,
333
+ "source_schema": source_schema,
334
+ "target_schema": target_schema,
335
+ "translated_at": datetime.now(timezone.utc).isoformat(),
336
+ }
337
+ if loss_declared:
338
+ r["loss_declared"] = True
339
+ if dropped_fields:
340
+ r["dropped_fields"] = dropped_fields
341
+ return r
342
+
343
+
344
+ def orchestrator_record(
345
+ orchestrator_name: str,
346
+ started_at: str,
347
+ phases_completed: list,
348
+ outcome: str,
349
+ *,
350
+ completed_at: str | None = None,
351
+ failure_phase: str | None = None,
352
+ postconditions_met: bool | None = None,
353
+ ) -> dict:
354
+ """AP12 — produce an OrchestratorRecord for a completed orchestration."""
355
+ r: dict = {
356
+ "id": identifier("orch"),
357
+ "orchestrator_name": orchestrator_name,
358
+ "started_at": started_at,
359
+ "phases_completed": phases_completed,
360
+ "outcome": outcome,
361
+ }
362
+ if completed_at:
363
+ r["completed_at"] = completed_at
364
+ if failure_phase:
365
+ r["failure_phase"] = failure_phase
366
+ if postconditions_met is not None:
367
+ r["postconditions_met"] = postconditions_met
368
+ return r
369
+
370
+
371
+ def scheduled_task_due(task: dict, now_ts: float) -> bool:
372
+ """Return True if a ScheduledTask is due for execution."""
373
+ from datetime import datetime, timezone
374
+ schedule = task.get("schedule", {})
375
+ kind = schedule.get("kind")
376
+ next_run = schedule.get("next_run_at")
377
+ if not next_run:
378
+ return kind == "IMMEDIATE"
379
+ try:
380
+ due_ts = datetime.fromisoformat(next_run).timestamp()
381
+ return now_ts >= due_ts
382
+ except (ValueError, TypeError):
383
+ return False
384
+
385
+
386
+ def nullable_limit(settings: dict, key: str, default: int) -> int | None:
387
+ """An explicit null is unlimited; absence keeps the compatibility default."""
388
+ value=settings.get(key,default)
389
+ return None if value is None else int(value)
390
+
391
+
392
+ def integration_context() -> dict[str, str]:
393
+ """Correlation only: environment metadata never grants authority or budget."""
394
+ keys={'job_id':'URACE_BRIDGE_JOB','root_request_id':'URACE_BRIDGE_ROOT_REQUEST','project_id':'URACE_BRIDGE_PROJECT','executor_id':'URACE_BRIDGE_EXECUTOR'}
395
+ result={}
396
+ for key,env in keys.items():
397
+ value=os.environ.get(env)
398
+ if value and len(value)<=512 and all(ord(c)>=32 for c in value):result[key]=value
399
+ return result if 'job_id' in result else {}