dos-kernel 0.22.0__py3-none-win_amd64.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 (178) hide show
  1. dos/__init__.py +261 -0
  2. dos/_bin/dos-hook.exe +0 -0
  3. dos/_filelock.py +255 -0
  4. dos/_job_policy.py +97 -0
  5. dos/_tree.py +145 -0
  6. dos/admission.py +433 -0
  7. dos/answer_shape.py +299 -0
  8. dos/arbiter.py +859 -0
  9. dos/archive_lock.py +266 -0
  10. dos/arg_provenance.py +814 -0
  11. dos/attest.py +472 -0
  12. dos/breaker.py +311 -0
  13. dos/churn.py +226 -0
  14. dos/claim_extract.py +229 -0
  15. dos/claim_ttl.py +150 -0
  16. dos/cli.py +8721 -0
  17. dos/commit_audit.py +666 -0
  18. dos/completion.py +466 -0
  19. dos/concurrency_class.py +154 -0
  20. dos/config.py +1380 -0
  21. dos/config_lint.py +464 -0
  22. dos/cooldown.py +390 -0
  23. dos/coverage.py +387 -0
  24. dos/dangling_intent.py +287 -0
  25. dos/data_class.py +397 -0
  26. dos/decisions.py +1274 -0
  27. dos/decisions_tui.py +251 -0
  28. dos/dispatch_top.py +740 -0
  29. dos/dispatch_top_tui.py +116 -0
  30. dos/drivers/__init__.py +40 -0
  31. dos/drivers/ci_status.py +630 -0
  32. dos/drivers/citation_resolve.py +703 -0
  33. dos/drivers/decision_stop.py +98 -0
  34. dos/drivers/export_file.py +173 -0
  35. dos/drivers/export_otlp.py +275 -0
  36. dos/drivers/export_statsd.py +242 -0
  37. dos/drivers/hook_dialects.py +391 -0
  38. dos/drivers/job.py +47 -0
  39. dos/drivers/llm_judge.py +360 -0
  40. dos/drivers/memory_recall.py +1231 -0
  41. dos/drivers/notify_slack.py +373 -0
  42. dos/drivers/notify_webhook.py +251 -0
  43. dos/drivers/operator_judge.py +114 -0
  44. dos/drivers/os_acceptance.py +228 -0
  45. dos/drivers/paste_log.py +132 -0
  46. dos/drivers/plan_scope.py +133 -0
  47. dos/drivers/self_improve.py +375 -0
  48. dos/drivers/similarity_judge.py +249 -0
  49. dos/drivers/state_diff.py +274 -0
  50. dos/drivers/supervisor.py +347 -0
  51. dos/drivers/watchdog.py +363 -0
  52. dos/drivers/workshop.py +160 -0
  53. dos/durable_schema.py +344 -0
  54. dos/effect_witness.py +393 -0
  55. dos/efficiency.py +318 -0
  56. dos/enforce.py +414 -0
  57. dos/enumerate.py +776 -0
  58. dos/env_print.py +378 -0
  59. dos/event_severity.py +258 -0
  60. dos/evidence.py +692 -0
  61. dos/exec_capability.py +256 -0
  62. dos/export_cursor.py +143 -0
  63. dos/exporter.py +320 -0
  64. dos/firing_label.py +353 -0
  65. dos/fleet_roll.py +226 -0
  66. dos/gate_classify.py +827 -0
  67. dos/gh4_coverage.py +179 -0
  68. dos/git_delta.py +122 -0
  69. dos/guard.py +215 -0
  70. dos/health.py +552 -0
  71. dos/help_summary.py +519 -0
  72. dos/home.py +934 -0
  73. dos/hook_binary.py +194 -0
  74. dos/hook_dialect.py +271 -0
  75. dos/hook_exit.py +191 -0
  76. dos/hook_install.py +437 -0
  77. dos/id_alloc.py +304 -0
  78. dos/improve.py +499 -0
  79. dos/intent_ledger.py +635 -0
  80. dos/interpret.py +176 -0
  81. dos/intervention.py +769 -0
  82. dos/intervention_eval.py +371 -0
  83. dos/journal_delta.py +308 -0
  84. dos/judge_eval.py +328 -0
  85. dos/judges.py +366 -0
  86. dos/lane_infer.py +127 -0
  87. dos/lane_journal.py +1001 -0
  88. dos/lane_lease.py +952 -0
  89. dos/lane_overlap.py +228 -0
  90. dos/lease_health.py +282 -0
  91. dos/lifecycle.py +211 -0
  92. dos/liveness.py +352 -0
  93. dos/lock_modes.py +185 -0
  94. dos/log_source.py +395 -0
  95. dos/loop_decide.py +1746 -0
  96. dos/marker_gate.py +254 -0
  97. dos/marker_sensor.py +396 -0
  98. dos/noop_streak.py +280 -0
  99. dos/notify.py +479 -0
  100. dos/observe.py +175 -0
  101. dos/oracle.py +1661 -0
  102. dos/overlap_eval.py +214 -0
  103. dos/overlap_policy.py +342 -0
  104. dos/packet_sidecar.py +267 -0
  105. dos/phase_shipped.py +1985 -0
  106. dos/pick_priority.py +225 -0
  107. dos/pickable.py +369 -0
  108. dos/picker_oracle.py +1037 -0
  109. dos/plan_board.py +513 -0
  110. dos/plan_board_tui.py +113 -0
  111. dos/plan_source.py +455 -0
  112. dos/posttool_sensor.py +528 -0
  113. dos/precursor_gate.py +499 -0
  114. dos/precursor_gate_eval.py +239 -0
  115. dos/preflight.py +825 -0
  116. dos/pretool_sensor.py +490 -0
  117. dos/proc_delta.py +181 -0
  118. dos/productivity.py +296 -0
  119. dos/provider_limit.py +242 -0
  120. dos/py.typed +4 -0
  121. dos/reason_morphology.py +299 -0
  122. dos/reasons.py +449 -0
  123. dos/reconcile.py +173 -0
  124. dos/recurring_wedge.py +206 -0
  125. dos/render.py +393 -0
  126. dos/result_state.py +468 -0
  127. dos/resume.py +578 -0
  128. dos/resume_evidence.py +293 -0
  129. dos/retention.py +344 -0
  130. dos/reward.py +372 -0
  131. dos/rewind.py +587 -0
  132. dos/rewind_evidence.py +168 -0
  133. dos/rewind_tokens.py +252 -0
  134. dos/run_id.py +342 -0
  135. dos/scope.py +520 -0
  136. dos/scope_source.py +382 -0
  137. dos/scout.py +982 -0
  138. dos/self_modify.py +209 -0
  139. dos/sibling_scan.py +569 -0
  140. dos/skills/EXAMPLES.md +584 -0
  141. dos/skills/dos-class-cycle/SKILL.md +107 -0
  142. dos/skills/dos-dispatch/SKILL.md +177 -0
  143. dos/skills/dos-dispatch-loop/SKILL.md +254 -0
  144. dos/skills/dos-goal-gate/SKILL.md +269 -0
  145. dos/skills/dos-next-up/SKILL.md +231 -0
  146. dos/skills/dos-promote/SKILL.md +114 -0
  147. dos/skills/dos-replan/SKILL.md +159 -0
  148. dos/skills/dos-replan-loop/SKILL.md +114 -0
  149. dos/skills/dos-self-improve/SKILL.md +213 -0
  150. dos/skills/dos-supervise-loop/SKILL.md +180 -0
  151. dos/skills/dos-unstick/SKILL.md +108 -0
  152. dos/skills/dos-witness-claim/SKILL.md +251 -0
  153. dos/stamp.py +1002 -0
  154. dos/state_health.py +387 -0
  155. dos/status.py +114 -0
  156. dos/stop_policy.py +334 -0
  157. dos/supervise.py +1014 -0
  158. dos/testwitness.py +392 -0
  159. dos/timeline.py +1027 -0
  160. dos/tokens.py +485 -0
  161. dos/tool_stream.py +393 -0
  162. dos/tool_stream_eval.py +226 -0
  163. dos/trace.py +524 -0
  164. dos/verdict.py +140 -0
  165. dos/verdict_cli.py +189 -0
  166. dos/verdict_journal.py +497 -0
  167. dos/verdict_rollup.py +217 -0
  168. dos/verdicts.py +181 -0
  169. dos/wedge_reason.py +282 -0
  170. dos_kernel-0.22.0.dist-info/METADATA +859 -0
  171. dos_kernel-0.22.0.dist-info/RECORD +178 -0
  172. dos_kernel-0.22.0.dist-info/WHEEL +5 -0
  173. dos_kernel-0.22.0.dist-info/entry_points.txt +39 -0
  174. dos_kernel-0.22.0.dist-info/licenses/LICENSE +21 -0
  175. dos_kernel-0.22.0.dist-info/top_level.txt +2 -0
  176. dos_mcp/__init__.py +52 -0
  177. dos_mcp/py.typed +2 -0
  178. dos_mcp/server.py +779 -0
dos/archive_lock.py ADDED
@@ -0,0 +1,266 @@
1
+ """Mutex for the /fanout-true-headless-multi-agent Step 9.5 archive ceremony.
2
+
3
+ Two near-concurrent fanouts can both reach Step 9.5 inside a ~30s window. Both
4
+ write to `docs/_fanout_runs/INDEX.md` and stage `docs/_plans/execution-state.yaml`
5
+ through `git add`. Without serialization, the second orchestrator's edits are
6
+ absorbed into the first orchestrator's commit (queue finding #34, observed
7
+ 20260508T0502Z vs 0458Z `f88a800`).
8
+
9
+ Lock semantics:
10
+ Path: docs/_fanout_runs/.archive.lock (single shared mutex; gitignored)
11
+ Owner: free-text tag, e.g. `fanout-<UTC-ts>`.
12
+ TTL: 5 minutes — Step 9.5 archive should never run longer; older = orphan.
13
+
14
+ Acquire is atomic O_CREAT|O_EXCL. On EEXIST, the helper reads the existing
15
+ lock and decides:
16
+ - same owner → re-entrant; refresh acquired_at, exit 0.
17
+ - other owner, age <TTL → poll up to --retries times at --retry-interval
18
+ seconds; if still busy, exit 1.
19
+ - other owner, age ≥TTL → steal via an atomic compare-and-swap on the
20
+ lock inode (`_steal_stale_lock`): rename the
21
+ stale lock to a per-stealer temp (atomic; only
22
+ one stealer wins) then O_EXCL-recreate. Two
23
+ concurrent stealers can never both end up
24
+ holding the mutex (the old unlink+recreate was a
25
+ TOCTOU that let them).
26
+
27
+ Release unlinks the lock if owner matches; on owner mismatch (something
28
+ stole it) prints a warning to stderr and still exits 0 — the archive
29
+ ceremony shouldn't fail over a release race.
30
+
31
+ Subcommands:
32
+ acquire <owner> [--retries 5] [--retry-interval 2] [--ttl-seconds 300]
33
+ Exit 0 on success (printed: "acquired" | "re-entrant <prev-acquired-at>"
34
+ | "stole-stale <prev-owner> age=<seconds>s").
35
+ Exit 1 if the lock is owned by another live process and retries
36
+ are exhausted (printed: "busy <prev-owner> age=<seconds>s").
37
+
38
+ release <owner> [--force]
39
+ Exit 0 if removed (or absent). With --force, removes regardless of
40
+ owner (operator-orphaned cleanup).
41
+
42
+ status
43
+ Prints lock state: "free" | "held <owner> age=<seconds>s"
44
+ | "stale <owner> age=<seconds>s".
45
+ """
46
+ from __future__ import annotations
47
+
48
+ import argparse
49
+ import datetime as dt
50
+ import os
51
+ import sys
52
+ import time
53
+ from pathlib import Path
54
+
55
+ if hasattr(sys.stdout, "reconfigure"):
56
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
57
+
58
+ from dos import _filelock
59
+ from dos import config as _config
60
+
61
+ DEFAULT_TTL_SECONDS = 300
62
+ DEFAULT_RETRIES = 5
63
+ DEFAULT_RETRY_INTERVAL = 2.0
64
+
65
+
66
+ def _lock_path() -> Path:
67
+ """The cross-process archive lock path for the active workspace.
68
+
69
+ Resolves against the injected config (separation refactor), with an env
70
+ override for tests. Re-resolved each call so a test that re-points the
71
+ workspace after import still redirects.
72
+ """
73
+ env = os.environ.get("DISPATCH_ARCHIVE_LOCK_PATH")
74
+ if env:
75
+ return Path(env)
76
+ return _config.active().paths.archive_lock
77
+
78
+
79
+ # Module-level handle for back-compat with callers that read the attribute.
80
+ LOCK_PATH = _lock_path()
81
+
82
+
83
+ def _now() -> dt.datetime:
84
+ return dt.datetime.now(dt.timezone.utc)
85
+
86
+
87
+ def _now_iso() -> str:
88
+ return _now().strftime("%Y-%m-%dT%H:%M:%SZ")
89
+
90
+
91
+ def _parse_iso(s: str) -> dt.datetime | None:
92
+ for fmt in ("%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%MZ"):
93
+ try:
94
+ return dt.datetime.strptime(s, fmt).replace(tzinfo=dt.timezone.utc)
95
+ except (ValueError, TypeError):
96
+ continue
97
+ return None
98
+
99
+
100
+ def _read_lock_at(path: Path) -> dict | None:
101
+ """Parse a lock file at an arbitrary path → its `{key: value}` info dict.
102
+
103
+ Delegates to the shared `_filelock.read_lock` so this mutex and its siblings
104
+ (lane_lease, home) parse a lock the one same way the shared steal CAS does."""
105
+ return _filelock.read_lock(path)
106
+
107
+
108
+ def _read_lock() -> dict | None:
109
+ return _read_lock_at(_lock_path())
110
+
111
+
112
+ def _write_lock(owner: str) -> None:
113
+ """Atomic O_CREAT|O_EXCL create. Raises FileExistsError if lock present.
114
+
115
+ Delegates to the shared `_filelock.write_lock` — the one O_EXCL create every
116
+ mutex in the package uses."""
117
+ _filelock.write_lock(_lock_path(), owner)
118
+
119
+
120
+ def _steal_stale_lock(owner: str, stale: dict) -> bool:
121
+ """Atomically steal the SPECIFIC stale lock `stale` for `owner`. True iff WE won.
122
+
123
+ Delegates to the shared `_filelock.steal_stale` — the ONE value-keyed
124
+ compare-and-swap every mutex steals through (so the TOCTOU that let two stealers
125
+ both hold the lock cannot be re-introduced by a per-site copy). `stale` is the
126
+ lock-info dict the caller just `_read_lock`'d; the CAS only displaces the EXACT
127
+ stale lock it names, restoring a racer's fresh lock on mismatch. See
128
+ `_filelock.steal_stale` for the full three-step discipline."""
129
+ return _filelock.steal_stale(_lock_path(), owner, stale)
130
+
131
+
132
+ def _refresh_lock(owner: str) -> None:
133
+ """Re-write the lock with the current timestamp (re-entrant case)."""
134
+ body = f"owner: {owner}\nacquired_at: {_now_iso()}\npid: {os.getpid()}\n"
135
+ _lock_path().write_text(body, encoding="utf-8")
136
+
137
+
138
+ def _age_seconds(info: dict) -> float | None:
139
+ ts = _parse_iso(info.get("acquired_at", ""))
140
+ if ts is None:
141
+ return None
142
+ return (_now() - ts).total_seconds()
143
+
144
+
145
+ def cmd_acquire(args: argparse.Namespace) -> int:
146
+ owner = args.owner
147
+ ttl = args.ttl_seconds
148
+ retries = args.retries
149
+ interval = args.retry_interval
150
+
151
+ for attempt in range(retries + 1):
152
+ try:
153
+ _write_lock(owner)
154
+ print(f"acquired {owner}")
155
+ return 0
156
+ except FileExistsError:
157
+ pass
158
+
159
+ info = _read_lock()
160
+ if info is None:
161
+ # Lock was unlinked between EEXIST and read; retry the create.
162
+ continue
163
+
164
+ prev_owner = info.get("owner", "<unknown>")
165
+ age = _age_seconds(info)
166
+
167
+ if prev_owner == owner:
168
+ _refresh_lock(owner)
169
+ prev_at = info.get("acquired_at", "<unknown>")
170
+ print(f"re-entrant {owner} prev-acquired-at={prev_at}")
171
+ return 0
172
+
173
+ if age is not None and age >= ttl:
174
+ # Atomic compare-and-swap steal keyed on the stale lock's identity
175
+ # (`info`): only the process that displaces the EXACT stale lock it
176
+ # observed proceeds; a concurrent stealer that already won (and
177
+ # re-created a fresh lock) is detected and conceded to, so two
178
+ # processes can never both come away holding the mutex (the old
179
+ # unlink-then-create TOCTOU). A lost steal falls through to retry.
180
+ if not _steal_stale_lock(owner, info):
181
+ continue
182
+ print(f"stole-stale {prev_owner} age={age:.0f}s")
183
+ return 0
184
+
185
+ if attempt < retries:
186
+ age_str = f"{age:.0f}s" if age is not None else "?s"
187
+ print(f" waiting on {prev_owner} (age={age_str}); retry {attempt + 1}/{retries}", file=sys.stderr)
188
+ time.sleep(interval)
189
+ continue
190
+
191
+ age_str = f"{age:.0f}s" if age is not None else "?s"
192
+ print(f"busy {prev_owner} age={age_str}", file=sys.stderr)
193
+ return 1
194
+
195
+ return 1
196
+
197
+
198
+ def cmd_release(args: argparse.Namespace) -> int:
199
+ owner = args.owner
200
+ info = _read_lock()
201
+ if info is None:
202
+ print("released (no-lock)")
203
+ return 0
204
+
205
+ if args.force:
206
+ try:
207
+ _lock_path().unlink()
208
+ except FileNotFoundError:
209
+ pass
210
+ print(f"force-released (was: {info.get('owner', '<unknown>')})")
211
+ return 0
212
+
213
+ if info.get("owner") != owner:
214
+ print(f"WARN: lock owner mismatch (have={info.get('owner', '<unknown>')}, expected={owner}); leaving alone", file=sys.stderr)
215
+ return 0
216
+
217
+ try:
218
+ _lock_path().unlink()
219
+ except FileNotFoundError:
220
+ pass
221
+ print(f"released {owner}")
222
+ return 0
223
+
224
+
225
+ def cmd_status(args: argparse.Namespace) -> int:
226
+ info = _read_lock()
227
+ if info is None:
228
+ print("free")
229
+ return 0
230
+ owner = info.get("owner", "<unknown>")
231
+ age = _age_seconds(info)
232
+ age_str = f"{age:.0f}s" if age is not None else "?s"
233
+ if age is not None and age >= args.ttl_seconds:
234
+ print(f"stale {owner} age={age_str}")
235
+ else:
236
+ print(f"held {owner} age={age_str}")
237
+ return 0
238
+
239
+
240
+ def main() -> int:
241
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
242
+ sub = ap.add_subparsers(dest="cmd", required=True)
243
+
244
+ p_acq = sub.add_parser("acquire", help="Acquire the archive lock (atomic + retry-with-backoff)")
245
+ p_acq.add_argument("owner", help='Owner tag, e.g. "fanout-20260508T1900Z"')
246
+ p_acq.add_argument("--retries", type=int, default=DEFAULT_RETRIES)
247
+ p_acq.add_argument("--retry-interval", type=float, default=DEFAULT_RETRY_INTERVAL, help="Seconds between retries")
248
+ p_acq.add_argument("--ttl-seconds", type=int, default=DEFAULT_TTL_SECONDS, help="Stale-threshold; older locks get stolen")
249
+
250
+ p_rel = sub.add_parser("release", help="Release the archive lock")
251
+ p_rel.add_argument("owner", help="Owner tag — must match holder unless --force")
252
+ p_rel.add_argument("--force", action="store_true", help="Remove regardless of owner (operator orphan-cleanup)")
253
+
254
+ p_st = sub.add_parser("status", help="Print current lock state")
255
+ p_st.add_argument("--ttl-seconds", type=int, default=DEFAULT_TTL_SECONDS)
256
+
257
+ args = ap.parse_args()
258
+ return {
259
+ "acquire": cmd_acquire,
260
+ "release": cmd_release,
261
+ "status": cmd_status,
262
+ }[args.cmd](args)
263
+
264
+
265
+ if __name__ == "__main__":
266
+ sys.exit(main())