awgit 0.1.0__tar.gz → 0.2.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.
- {awgit-0.1.0 → awgit-0.2.0}/PKG-INFO +2 -2
- {awgit-0.1.0 → awgit-0.2.0}/README.md +1 -1
- {awgit-0.1.0 → awgit-0.2.0}/awgit/cli.py +55 -7
- {awgit-0.1.0 → awgit-0.2.0}/awgit/leases.py +49 -5
- {awgit-0.1.0 → awgit-0.2.0}/awgit.egg-info/PKG-INFO +2 -2
- {awgit-0.1.0 → awgit-0.2.0}/pyproject.toml +1 -1
- {awgit-0.1.0 → awgit-0.2.0}/LICENSE +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/__init__.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/bodies.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/bridge.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/capture.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/data_root.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/diff.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/hooks/chain.sh +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/hooks/post-commit.d/vcs-capture +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/hooks/pre-commit.d/vcs-lease-check +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/identity.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/ledger.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/mcp.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/merge.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/nodeid.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/oplog.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/parser.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/schema.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit/sync.py +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit.egg-info/SOURCES.txt +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit.egg-info/dependency_links.txt +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit.egg-info/entry_points.txt +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit.egg-info/requires.txt +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/awgit.egg-info/top_level.txt +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/setup.cfg +0 -0
- {awgit-0.1.0 → awgit-0.2.0}/tests/test_awgit_standalone.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awgit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Aither World-Graph git — semantic version control on top of git: edit-ops keyed on stable node ids, content-addressed bodies, differential sync.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -52,7 +52,7 @@ what changed, never the snapshot.
|
|
|
52
52
|
## Install
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
pip install awgit
|
|
55
|
+
pip install awgit
|
|
56
56
|
# or from source:
|
|
57
57
|
git clone https://github.com/aitherium/awgit && cd awgit
|
|
58
58
|
pip install -e .
|
|
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import argparse
|
|
17
17
|
import os
|
|
18
|
+
import platform
|
|
18
19
|
import subprocess
|
|
19
20
|
import sys
|
|
20
21
|
from pathlib import Path
|
|
@@ -28,11 +29,38 @@ from awgit.oplog import OpLog
|
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def _actor(args: argparse.Namespace) -> str:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
"""Who is committing. MUST differ per concurrent agent, or leases are useless.
|
|
33
|
+
|
|
34
|
+
Explicit forms win, then one is DERIVED. Deriving is the point: with only
|
|
35
|
+
``AITHER_ACTOR`` this returned "unknown", and ``lease-check`` rejects an
|
|
36
|
+
unknown actor — so turning enforcement on would have blocked every commit on
|
|
37
|
+
the box until every session remembered to export a variable. A guard that can
|
|
38
|
+
only be switched on by breaking the machine never gets switched on, and it
|
|
39
|
+
never was: measured 2026-08-09, ``awgit lease list`` had never returned a
|
|
40
|
+
single lease while a concurrent session overwrote one file four times, the
|
|
41
|
+
last overwrite silently reverting a fix already committed to origin.
|
|
42
|
+
|
|
43
|
+
``CLAUDE_CODE_SESSION_ID`` is the right derivation: the harness sets it per
|
|
44
|
+
session, it is stable for that session's life, and it is inherited by the git
|
|
45
|
+
hook's subprocess (verified). A per-PID value would NOT do — every ``awgit``
|
|
46
|
+
invocation is a new process, so a lease taken by one command would not match
|
|
47
|
+
the next.
|
|
48
|
+
|
|
49
|
+
``user@host`` is the last resort, for humans and other tooling. It is
|
|
50
|
+
deliberately not what agents get: every session here shares one OS account
|
|
51
|
+
and one GitHub identity, so a shared actor makes session A's lease cover
|
|
52
|
+
session B's commit — all of the friction, none of the protection.
|
|
53
|
+
"""
|
|
54
|
+
explicit = getattr(args, "actor", None) or os.environ.get("AITHER_ACTOR")
|
|
55
|
+
if explicit:
|
|
56
|
+
return explicit
|
|
57
|
+
session = os.environ.get("CLAUDE_CODE_SESSION_ID")
|
|
58
|
+
if session:
|
|
59
|
+
return f"claude:{session}"
|
|
60
|
+
user = os.environ.get("USERNAME") or os.environ.get("USER")
|
|
61
|
+
if user:
|
|
62
|
+
return f"{user}@{platform.node() or 'localhost'}"
|
|
63
|
+
return "unknown"
|
|
36
64
|
|
|
37
65
|
|
|
38
66
|
def _cmd_capture(args: argparse.Namespace) -> int:
|
|
@@ -119,9 +147,26 @@ def _cmd_lease(args: argparse.Namespace) -> int:
|
|
|
119
147
|
cmd = args.lease_cmd
|
|
120
148
|
who = _actor(args)
|
|
121
149
|
if cmd == "acquire":
|
|
150
|
+
targets = list(args.targets or [])
|
|
151
|
+
if getattr(args, "staged", False):
|
|
152
|
+
# Lease exactly what the gate will check. Without this, widening
|
|
153
|
+
# coverage beyond .py (D-1880) would have made a routine commit a
|
|
154
|
+
# per-file chore, and a gate that heavy gets routed around instead
|
|
155
|
+
# of satisfied.
|
|
156
|
+
from awgit.leases import is_guarded
|
|
157
|
+
|
|
158
|
+
repo = Path(os.environ.get("VCS_REPO_ROOT", os.getcwd()))
|
|
159
|
+
targets += [f for f in _staged_files(repo) if is_guarded(f)]
|
|
160
|
+
targets = sorted(set(targets))
|
|
161
|
+
if not targets:
|
|
162
|
+
print("vcs: nothing staged that the gate guards — no leases needed")
|
|
163
|
+
return 0
|
|
164
|
+
if not targets:
|
|
165
|
+
print("vcs: acquire needs targets (or --staged)", file=sys.stderr)
|
|
166
|
+
return 1
|
|
122
167
|
try:
|
|
123
168
|
leases = registry.acquire(
|
|
124
|
-
who,
|
|
169
|
+
who, targets, ttl_sec=args.ttl, reason=args.reason
|
|
125
170
|
)
|
|
126
171
|
except LeaseConflictError as exc:
|
|
127
172
|
print(f"vcs: {exc}", file=sys.stderr)
|
|
@@ -376,7 +421,10 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
376
421
|
p_lease = sub.add_parser("lease", help="lease registry operations")
|
|
377
422
|
lsub = p_lease.add_subparsers(dest="lease_cmd", required=True)
|
|
378
423
|
p_la = lsub.add_parser("acquire", help="acquire leases for targets")
|
|
379
|
-
p_la.add_argument("targets", nargs="
|
|
424
|
+
p_la.add_argument("targets", nargs="*")
|
|
425
|
+
p_la.add_argument("--staged", action="store_true",
|
|
426
|
+
help="also lease every STAGED file the gate guards "
|
|
427
|
+
"(the one-command way to satisfy the pre-commit gate)")
|
|
380
428
|
p_la.add_argument("--ttl", type=int, default=300)
|
|
381
429
|
p_la.add_argument("--reason", default="")
|
|
382
430
|
p_la.add_argument("--actor", default=None)
|
|
@@ -243,16 +243,60 @@ class LeaseRegistry:
|
|
|
243
243
|
return [lz for lz in self._leases.values() if lz.actor == actor]
|
|
244
244
|
|
|
245
245
|
|
|
246
|
+
# Files worth guarding: the ones two agents plausibly edit at the same time.
|
|
247
|
+
# Widened from ".py only" on 2026-08-09 (D-1880). The old scope let a `.yml`
|
|
248
|
+
# commit print "lease-check OK" while checking NOTHING, so compose files,
|
|
249
|
+
# workflows, skills and .mcp.json were exactly as clobberable as before the
|
|
250
|
+
# gate existed — and that same day edits to docker-compose.aitheros.yml and
|
|
251
|
+
# concurrent-safe-git/SKILL.md had to be hand-restored after a concurrent
|
|
252
|
+
# overwrite, while the .py the gate did cover was overwritten four times.
|
|
253
|
+
GUARDED_SUFFIXES = (
|
|
254
|
+
".py", ".ts", ".tsx", ".js", ".jsx", ".mjs", # source
|
|
255
|
+
".yml", ".yaml", ".toml", ".json", ".ini", ".cfg", # config
|
|
256
|
+
".sh", ".bash", ".ps1", ".psm1", # scripts
|
|
257
|
+
".md", # rules, skills, docs
|
|
258
|
+
".sql", ".proto", ".env",
|
|
259
|
+
)
|
|
260
|
+
# Bulk content nobody races on. Guarding these would make a routine content
|
|
261
|
+
# commit need dozens of leases, and a gate that heavy gets ROUTED AROUND rather
|
|
262
|
+
# than satisfied — the exact failure that produced this repo's per-file-ignores.
|
|
263
|
+
UNGUARDED_PREFIXES = (
|
|
264
|
+
"AitherOS/apps/AitherVeil/content/", # blog posts, one author at a time
|
|
265
|
+
"AitherOS/Library/", # generated training/runtime data
|
|
266
|
+
".DELIVERABLES/", # published artifacts
|
|
267
|
+
".RESEARCH/INTAKE/", # dossiers
|
|
268
|
+
"TECH_DEBT_ARCHIVE", # frozen
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def is_guarded(path: str) -> bool:
|
|
273
|
+
"""Does the lease gate care about this path?
|
|
274
|
+
|
|
275
|
+
Extension-based with a bulk-content escape, rather than "everything": the
|
|
276
|
+
trade is deliberate. Covering every path means a 50-file commit needs 50
|
|
277
|
+
leases; covering none is where this started.
|
|
278
|
+
"""
|
|
279
|
+
p = (path or "").replace("\\", "/").strip()
|
|
280
|
+
if not p:
|
|
281
|
+
return False
|
|
282
|
+
if p.startswith(UNGUARDED_PREFIXES):
|
|
283
|
+
return False
|
|
284
|
+
return p.endswith(GUARDED_SUFFIXES)
|
|
285
|
+
|
|
286
|
+
|
|
246
287
|
def coverage_gap(
|
|
247
288
|
changed_files: List[str],
|
|
248
289
|
actor: str,
|
|
249
290
|
data_root: Optional[Path] = None,
|
|
250
291
|
) -> List[str]:
|
|
251
|
-
"""Changed
|
|
292
|
+
"""Changed GUARDED files not covered by the actor's active path-leases.
|
|
293
|
+
|
|
294
|
+
The pre-commit gate's primitive: a commit touching a guarded file with no
|
|
295
|
+
active path-lease is an unleased edit — rejected when leases are enforced
|
|
296
|
+
(VCS_LEASES_ENFORCE=1), recorded as ``leased=false`` otherwise.
|
|
252
297
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
are enforced (VCS_LEASES_ENFORCE=1), recorded as ``leased=false`` otherwise.
|
|
298
|
+
``awgit lease acquire --staged`` takes leases on exactly this set, so
|
|
299
|
+
complying is one command rather than a per-file chore.
|
|
256
300
|
"""
|
|
257
301
|
registry = LeaseRegistry(data_root=data_root)
|
|
258
302
|
registry.sweep_expired()
|
|
@@ -261,4 +305,4 @@ def coverage_gap(
|
|
|
261
305
|
for lz in registry.active_leases()
|
|
262
306
|
if lz.actor == actor and lz.kind == "path"
|
|
263
307
|
}
|
|
264
|
-
return [f for f in changed_files if f
|
|
308
|
+
return [f for f in changed_files if is_guarded(f) and f not in covered]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awgit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Aither World-Graph git — semantic version control on top of git: edit-ops keyed on stable node ids, content-addressed bodies, differential sync.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -52,7 +52,7 @@ what changed, never the snapshot.
|
|
|
52
52
|
## Install
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
pip install awgit
|
|
55
|
+
pip install awgit
|
|
56
56
|
# or from source:
|
|
57
57
|
git clone https://github.com/aitherium/awgit && cd awgit
|
|
58
58
|
pip install -e .
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "awgit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Aither World-Graph git — semantic version control on top of git: edit-ops keyed on stable node ids, content-addressed bodies, differential sync."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|