@pennixrv/trellis 0.6.34 → 0.6.35
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.
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.6.34",
|
|
3
|
+
"description": "Records the published 0.6.34 release before the sealed-handoff lifecycle release.",
|
|
4
|
+
"breaking": false,
|
|
5
|
+
"recommendMigrate": false,
|
|
6
|
+
"changelog": "**Internal:**\n- Restores the published 0.6.34 release manifest so adjacent-version update paths remain complete.",
|
|
7
|
+
"migrations": [],
|
|
8
|
+
"notes": "No project migration is required. Existing projects can run the normal `trellis update` flow."
|
|
9
|
+
}
|
|
@@ -781,6 +781,22 @@ def clear_active_task(
|
|
|
781
781
|
return previous
|
|
782
782
|
|
|
783
783
|
|
|
784
|
+
def clear_active_task_for_context(
|
|
785
|
+
context_key: str,
|
|
786
|
+
task_path: str,
|
|
787
|
+
repo_root: Path,
|
|
788
|
+
) -> str:
|
|
789
|
+
"""Clear one exact session pointer, refusing an unexpected replacement."""
|
|
790
|
+
context_path = _context_path(repo_root, context_key)
|
|
791
|
+
context = _read_json(context_path)
|
|
792
|
+
if context is None or not _string_value(context.get("current_task")):
|
|
793
|
+
return "absent"
|
|
794
|
+
current = _string_value(context.get("current_task"))
|
|
795
|
+
if not _task_refs_match(current, task_path, repo_root):
|
|
796
|
+
return "changed"
|
|
797
|
+
return "cleared" if context_path.is_file() and _remove_file(context_path) else "absent"
|
|
798
|
+
|
|
799
|
+
|
|
784
800
|
def clear_task_from_sessions(task_path: str, repo_root: Path) -> int:
|
|
785
801
|
"""Delete all session runtime files that point at a task."""
|
|
786
802
|
target = _canonical_task_ref(task_path, repo_root) or normalize_task_ref(task_path)
|
|
@@ -10,7 +10,7 @@ from datetime import datetime, timezone
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
from .active_task import clear_active_task, resolve_active_task, resolve_context_key, set_active_task
|
|
13
|
+
from .active_task import clear_active_task, clear_active_task_for_context, resolve_active_task, resolve_context_key, set_active_task
|
|
14
14
|
from .continuation_record import (
|
|
15
15
|
ContinuationError,
|
|
16
16
|
SAFE_ID,
|
|
@@ -290,6 +290,32 @@ def retire(root: Path, task_id: str, handoff_id: str, core_digest: str, expected
|
|
|
290
290
|
return _public(record) | {"record_digest": digest}
|
|
291
291
|
|
|
292
292
|
|
|
293
|
+
def retire_handoff(root: Path, task_id: str, handoff_id: str, core_digest: str) -> dict[str, Any]:
|
|
294
|
+
"""Release one sealed handoff without requiring the departed source context."""
|
|
295
|
+
path, record = _load(root, task_id, handoff_id)
|
|
296
|
+
with _record_lock(path):
|
|
297
|
+
record = _decode(path)
|
|
298
|
+
if record["core_digest"] != core_digest:
|
|
299
|
+
raise OwnershipError("core_digest does not match")
|
|
300
|
+
if record["state"] == "ready":
|
|
301
|
+
return _public(record)
|
|
302
|
+
if record["state"] not in {"sealed", "retiring"}:
|
|
303
|
+
raise OwnershipError("ownership record is not a sealed handoff")
|
|
304
|
+
if record["state"] == "sealed":
|
|
305
|
+
record["previous_event_digest"] = record["integrity"]["record_digest"]
|
|
306
|
+
record["state"] = "retiring"
|
|
307
|
+
record["generation"] += 1
|
|
308
|
+
_write(path, record)
|
|
309
|
+
cleared = clear_active_task_for_context(record["source_context_key"], record["task"]["path"], root)
|
|
310
|
+
if cleared == "changed":
|
|
311
|
+
raise OwnershipError("source task pointer changed during handoff retirement")
|
|
312
|
+
record["previous_event_digest"] = record["integrity"]["record_digest"]
|
|
313
|
+
record["state"] = "ready"
|
|
314
|
+
record["generation"] += 1
|
|
315
|
+
digest = _write(path, record)
|
|
316
|
+
return _public(record) | {"record_digest": digest}
|
|
317
|
+
|
|
318
|
+
|
|
293
319
|
def claim(root: Path, task_id: str, task_path: str, handoff_id: str, core_digest: str, expected_generation: int) -> dict[str, Any]:
|
|
294
320
|
actor, active = _direct_context(root)
|
|
295
321
|
path, record = _load(root, task_id, handoff_id)
|
|
@@ -410,5 +436,4 @@ def assert_task_mutation_allowed(root: Path, task_path: Path) -> None:
|
|
|
410
436
|
raise OwnershipError("ownership record task path does not match current task")
|
|
411
437
|
if record.get("consumer_session_id") == actor:
|
|
412
438
|
continue
|
|
413
|
-
|
|
414
|
-
raise OwnershipError("fencing_conflict: task is owned by another handoff session")
|
|
439
|
+
raise OwnershipError("fencing_conflict: task is owned by another handoff session")
|
|
@@ -90,6 +90,7 @@ from common.ownership_record import (
|
|
|
90
90
|
consume as consume_ownership,
|
|
91
91
|
quiesce as quiesce_ownership,
|
|
92
92
|
retire as retire_ownership,
|
|
93
|
+
retire_handoff as retire_handoff_ownership,
|
|
93
94
|
seal as seal_ownership,
|
|
94
95
|
status as ownership_status,
|
|
95
96
|
)
|
|
@@ -436,6 +437,10 @@ def cmd_ownership(args: argparse.Namespace) -> int:
|
|
|
436
437
|
repo_root, args.task_id, args.handoff_id, args.core_digest,
|
|
437
438
|
args.expected_generation, args.archive_observation,
|
|
438
439
|
)
|
|
440
|
+
elif command == "retire-handoff":
|
|
441
|
+
result = retire_handoff_ownership(
|
|
442
|
+
repo_root, args.task_id, args.handoff_id, args.core_digest,
|
|
443
|
+
)
|
|
439
444
|
elif command == "claim":
|
|
440
445
|
result = claim_ownership(
|
|
441
446
|
repo_root, args.task_id, args.task, args.handoff_id, args.core_digest, args.expected_generation
|
|
@@ -840,6 +845,9 @@ def main() -> int:
|
|
|
840
845
|
ownership_retire.add_argument("--archive-observation", required=True, choices=("not_required", "observed"))
|
|
841
846
|
add_ownership_common(ownership_retire, expected=True)
|
|
842
847
|
|
|
848
|
+
ownership_retire_handoff = ownership_sub.add_parser("retire-handoff", help="Release one sealed handoff by exact id")
|
|
849
|
+
add_ownership_common(ownership_retire_handoff)
|
|
850
|
+
|
|
843
851
|
ownership_claim = ownership_sub.add_parser("claim", help="Claim a ready handoff as this session")
|
|
844
852
|
ownership_claim.add_argument("--task", required=True, help="Task path to bind to this session")
|
|
845
853
|
add_ownership_common(ownership_claim, expected=True)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pennixrv/trellis",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.35",
|
|
4
4
|
"description": "AI capabilities grow like ivy — Trellis provides the structure to guide them along a disciplined path",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"inquirer": "^9.3.7",
|
|
35
35
|
"undici": "^6.21.0",
|
|
36
36
|
"zod": "^4.4.2",
|
|
37
|
-
"@pennixrv/trellis-core": "0.6.
|
|
37
|
+
"@pennixrv/trellis-core": "0.6.35"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@eslint/js": "^9.18.0",
|