@arbiterforge/ca-pi 0.16.6 → 0.17.1
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/plugins/ca-pi/CHANGELOG.md +42 -0
- package/plugins/ca-pi/agents/map-deps.md +26 -26
- package/plugins/ca-pi/agents/map-structure.md +27 -28
- package/plugins/ca-pi/agents/tribunal-lens-reviewer.md +70 -39
- package/plugins/ca-pi/generated/roles.json +2 -5
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/release.json +36 -36
- package/plugins/ca-pi/hooks/_approvallib.py +36 -6
- package/plugins/ca-pi/hooks/_artifactlib.py +29 -2
- package/plugins/ca-pi/hooks/_contextmergelib.py +160 -0
- package/plugins/ca-pi/hooks/_githooks.py +2 -0
- package/plugins/ca-pi/hooks/_host.py +1 -1
- package/plugins/ca-pi/hooks/_tribunalinventorylib.py +653 -0
- package/plugins/ca-pi/hooks/_tribunalrunlib.py +652 -0
- package/plugins/ca-pi/hooks/hostapi.py +1 -1
- package/plugins/ca-pi/hooks/resolve-context-conflict.py +8 -0
- package/plugins/ca-pi/hooks/tribunal.py +113 -0
- package/plugins/ca-pi/package.json +1 -1
- package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +29 -0
- package/plugins/ca-pi/routines/tribunal/SKILL.md +192 -99
- package/plugins/ca-pi/routines/tribunal/references/ai-markers.md +6 -27
- package/plugins/ca-pi/routines/tribunal/references/cost-and-models.md +73 -52
- package/plugins/ca-pi/routines/tribunal/references/finding-record.md +69 -13
- package/plugins/ca-pi/routines/tribunal/references/issue-filing.md +63 -45
- package/plugins/ca-pi/routines/tribunal/references/lenses/appsec.md +55 -12
- package/plugins/ca-pi/routines/tribunal/references/lenses/architecture.md +51 -13
- package/plugins/ca-pi/routines/tribunal/references/lenses/change-closure.md +65 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/coverage.md +51 -10
- package/plugins/ca-pi/routines/tribunal/references/lenses/infra.md +53 -13
- package/plugins/ca-pi/routines/tribunal/references/lenses/migration.md +52 -12
- package/plugins/ca-pi/routines/tribunal/references/lenses/observability.md +52 -11
- package/plugins/ca-pi/routines/tribunal/references/lenses/performance.md +52 -12
- package/plugins/ca-pi/routines/tribunal/references/lenses/reliability.md +53 -13
- package/plugins/ca-pi/routines/tribunal/references/lenses/secrets-supply.md +55 -12
- package/plugins/ca-pi/routines/tribunal/references/lenses/semantic-contract.md +62 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/test-fidelity.md +48 -13
- package/plugins/ca-pi/routines/tribunal/references/lenses/typesafety.md +52 -11
- package/plugins/ca-pi/routines/tribunal/references/report.md +34 -12
- package/plugins/ca-pi/routines/tribunal/references/review-risk.md +78 -0
- package/plugins/ca-pi/routines/tribunal/references/schemas.md +171 -38
- package/plugins/ca-pi/routines/tribunal/references/telemetry.md +52 -17
- package/plugins/ca-pi/routines/tribunal/references/triage.md +95 -47
- package/plugins/ca-pi/routines/tribunal/references/verification-quality.md +61 -0
- package/plugins/ca-pi/skills/ca-pr/SKILL.md +29 -0
- package/plugins/ca-pi/skills/ca-tribunal/SKILL.md +192 -99
|
@@ -1008,11 +1008,38 @@ def _context_release_qualified(installation: Path) -> bool:
|
|
|
1008
1008
|
return False
|
|
1009
1009
|
|
|
1010
1010
|
|
|
1011
|
+
# A whole-tree input snapshot on a large repository can take ~30s on Windows,
|
|
1012
|
+
# so the old 30s bound timed out every publish, arm and verify. The bound is a
|
|
1013
|
+
# wait limit, never a gate: raising it relaxes no check.
|
|
1014
|
+
DEFAULT_ENGINE_TIMEOUT_SECONDS = 120
|
|
1015
|
+
ENGINE_TIMEOUT_ENV = "CODEARBITER_ENGINE_TIMEOUT"
|
|
1016
|
+
_MAX_ENGINE_TIMEOUT_SECONDS = 3600
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def _engine_timeout() -> float:
|
|
1020
|
+
"""Resolve the operator override; a malformed value refuses, never defaults."""
|
|
1021
|
+
raw = os.environ.get(ENGINE_TIMEOUT_ENV)
|
|
1022
|
+
if raw is None:
|
|
1023
|
+
return DEFAULT_ENGINE_TIMEOUT_SECONDS
|
|
1024
|
+
try:
|
|
1025
|
+
if raw != raw.strip():
|
|
1026
|
+
raise ValueError("surrounding whitespace")
|
|
1027
|
+
value = float(raw)
|
|
1028
|
+
except ValueError:
|
|
1029
|
+
value = None
|
|
1030
|
+
# Comparisons with NaN are false and infinity exceeds the cap, so this
|
|
1031
|
+
# single range check also refuses non-finite values.
|
|
1032
|
+
if value is None or not 0 < value <= _MAX_ENGINE_TIMEOUT_SECONDS:
|
|
1033
|
+
raise ArtifactError("INVALID_ENGINE_TIMEOUT",
|
|
1034
|
+
f"{ENGINE_TIMEOUT_ENV} must be a number of seconds in (0, {_MAX_ENGINE_TIMEOUT_SECONDS}]")
|
|
1035
|
+
return value
|
|
1036
|
+
|
|
1037
|
+
|
|
1011
1038
|
class ArtifactClient:
|
|
1012
|
-
def __init__(self, root: str | Path, installation: str | Path, *, timeout: float =
|
|
1039
|
+
def __init__(self, root: str | Path, installation: str | Path, *, timeout: float | None = None):
|
|
1013
1040
|
self.root = _trusted_directory(root, "UNSAFE_ROOT")
|
|
1014
1041
|
self.installation = _trusted_directory(installation, "CAPABILITY_MISSING")
|
|
1015
|
-
self.timeout = timeout
|
|
1042
|
+
self.timeout = _engine_timeout() if timeout is None else timeout
|
|
1016
1043
|
|
|
1017
1044
|
def workflow_preflight(self) -> dict[str, object]:
|
|
1018
1045
|
"""Read fresh installed prerequisites; never attest live host authority."""
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# codeArbiter — restore the committed current side of a legacy code-map conflict.
|
|
3
|
+
#
|
|
4
|
+
# Fixed target and source only. Inspection binds the real merge, all index bytes,
|
|
5
|
+
# conflict rendering and legacy provenance. Application rechecks that identity,
|
|
6
|
+
# retains all three index stages, and never adopts or edits native-owned context.
|
|
7
|
+
# Ordinary H-22 writes remain blocked. No import-time I/O or caller content.
|
|
8
|
+
#
|
|
9
|
+
# Public API:
|
|
10
|
+
# inspect(root) -> dict read-only recovery binding
|
|
11
|
+
# restore_head(root, expected) -> dict restore exact HEAD blob, never stage
|
|
12
|
+
# main(argv=None) -> int bounded JSON CLI
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
import re
|
|
20
|
+
import stat
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
from _appendonlyconflictlib import ResolutionError, _validate_conflicted_worktree, _write_bytes_atomic
|
|
25
|
+
from _artifactlib import ArtifactError, _decode, _open_pinned_regular, _trusted_directory
|
|
26
|
+
from _gitexec import git_executable, root_bound_git_env
|
|
27
|
+
from _protectedstatelib import _committed_context_journal
|
|
28
|
+
from _provenancelib import valid_provenance_record
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
TARGET = ".codearbiter/code-map.md"
|
|
32
|
+
PROVENANCE = ".codearbiter/.provenance/code-map.json"
|
|
33
|
+
MAX_BYTES = 8 << 20
|
|
34
|
+
OID = r"[0-9a-f]{40}(?:[0-9a-f]{24})?"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _require(condition, reason):
|
|
38
|
+
if not condition:
|
|
39
|
+
raise ResolutionError(reason)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _hash(raw):
|
|
43
|
+
return hashlib.sha256(raw).hexdigest()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _git(root, *args):
|
|
47
|
+
env = root_bound_git_env()
|
|
48
|
+
env.update(GIT_OPTIONAL_LOCKS="0", GIT_TERMINAL_PROMPT="0", GIT_NO_REPLACE_OBJECTS="1")
|
|
49
|
+
result = subprocess.run(
|
|
50
|
+
[git_executable(), "--no-pager", "--no-lazy-fetch", "-c", "core.fsmonitor=false",
|
|
51
|
+
"-c", "core.untrackedCache=false", *args],
|
|
52
|
+
cwd=root, env=env, capture_output=True, shell=False, timeout=30)
|
|
53
|
+
_require(result.returncode == 0 and len(result.stdout) <= MAX_BYTES, "Git conflict evidence unavailable")
|
|
54
|
+
return result.stdout
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _read(path):
|
|
58
|
+
_trusted_directory(path.parent, "UNSAFE_CONTEXT_PATH")
|
|
59
|
+
descriptor = _open_pinned_regular(path)
|
|
60
|
+
try:
|
|
61
|
+
info = os.fstat(descriptor)
|
|
62
|
+
_require(stat.S_ISREG(info.st_mode) and not getattr(info, "st_file_attributes", 0) & 0x400
|
|
63
|
+
and info.st_size <= MAX_BYTES, "context evidence must be a bounded regular file")
|
|
64
|
+
with os.fdopen(descriptor, "rb", closefd=False) as handle:
|
|
65
|
+
raw = handle.read(MAX_BYTES + 1)
|
|
66
|
+
_require(len(raw) <= MAX_BYTES, "context evidence is oversized")
|
|
67
|
+
return raw
|
|
68
|
+
finally:
|
|
69
|
+
os.close(descriptor)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _legacy(raw):
|
|
73
|
+
record = _decode(raw)
|
|
74
|
+
_require(valid_provenance_record(record) and record["schema"] == 1
|
|
75
|
+
and record["doc"] == "code-map", "legacy code-map provenance required; native ownership is not recoverable here")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _snapshot(requested_root):
|
|
79
|
+
root = _trusted_directory(requested_root, "UNSAFE_CONTEXT_ROOT")
|
|
80
|
+
actual = Path(_git(root, "rev-parse", "--show-toplevel").decode().strip()).resolve(strict=True)
|
|
81
|
+
_require(actual == root, "explicit repository root required")
|
|
82
|
+
git_dir = _trusted_directory(
|
|
83
|
+
_git(root, "rev-parse", "--absolute-git-dir").decode().strip(), "UNSAFE_GIT_DIRECTORY")
|
|
84
|
+
index = Path(_git(root, "rev-parse", "--path-format=absolute", "--git-path", "index").decode().strip())
|
|
85
|
+
head = _git(root, "rev-parse", "--verify", "HEAD^{commit}").decode().strip()
|
|
86
|
+
incoming = _read(git_dir / "MERGE_HEAD").decode().strip()
|
|
87
|
+
_require(re.fullmatch(OID, head) and re.fullmatch(OID, incoming), "one current merge head required")
|
|
88
|
+
records = _git(root, "ls-files", "--unmerged", "-z", "--", TARGET).split(b"\0")
|
|
89
|
+
stages = []
|
|
90
|
+
for raw in records:
|
|
91
|
+
if raw:
|
|
92
|
+
matched = re.fullmatch(r"100644 (" + OID + r") ([123])\t" + re.escape(TARGET), raw.decode())
|
|
93
|
+
_require(matched is not None, "regular code-map conflict stages required")
|
|
94
|
+
stages.append((matched[2], matched[1]))
|
|
95
|
+
_require([stage for stage, _oid in stages] == ["1", "2", "3"], "complete three-stage code-map conflict required")
|
|
96
|
+
base = _git(root, "merge-base", "--all", head, incoming).decode().strip()
|
|
97
|
+
_require(re.fullmatch(OID, base), "one unambiguous merge base required")
|
|
98
|
+
for revision, (_stage, oid) in zip((base, head, incoming), stages):
|
|
99
|
+
_require(_git(root, "rev-parse", revision + ":" + TARGET).decode().strip() == oid,
|
|
100
|
+
"index conflict no longer matches the current merge")
|
|
101
|
+
current = _read(root / TARGET)
|
|
102
|
+
ours = _git(root, "cat-file", "blob", stages[1][1])
|
|
103
|
+
theirs = _git(root, "cat-file", "blob", stages[2][1])
|
|
104
|
+
# Reuse the existing single-conflict renderer check. Unsupported renderings
|
|
105
|
+
# (including a non-empty diff3 base) refuse rather than discarding human text.
|
|
106
|
+
_validate_conflicted_worktree(current, b"", ours, theirs)
|
|
107
|
+
provenance = _read(root / PROVENANCE)
|
|
108
|
+
_legacy(provenance)
|
|
109
|
+
for revision in (base, head, incoming):
|
|
110
|
+
_legacy(_git(root, "cat-file", "blob", revision + ":" + PROVENANCE))
|
|
111
|
+
for relative in (".codearbiter/.artifacts", ".codearbiter/.artifacts/transactions"):
|
|
112
|
+
if os.path.lexists(root / relative):
|
|
113
|
+
_trusted_directory(root / relative, "UNSAFE_CONTEXT_JOURNAL")
|
|
114
|
+
_require(_committed_context_journal(root, "code-map", TARGET) is False,
|
|
115
|
+
"native or uncertain context ownership requires its writer")
|
|
116
|
+
binding = {"root": str(root), "git_dir": str(git_dir), "head": head, "merge_head": incoming,
|
|
117
|
+
"stages": stages, "index_sha256": _hash(_read(index)),
|
|
118
|
+
"worktree_sha256": _hash(current), "provenance_sha256": _hash(provenance)}
|
|
119
|
+
digest = _hash(json.dumps(binding, sort_keys=True, separators=(",", ":")).encode())
|
|
120
|
+
return root, index, current, ours, {"target": TARGET, "head": head, "merge_head": incoming,
|
|
121
|
+
"head_sha256": _hash(ours), "binding": digest}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def inspect(root):
|
|
125
|
+
return _snapshot(root)[-1]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def restore_head(root, expected):
|
|
129
|
+
_require(isinstance(expected, str) and re.fullmatch(r"[0-9a-f]{64}", expected), "exact inspection binding required")
|
|
130
|
+
root, index, _before, _ours, initial = _snapshot(root)
|
|
131
|
+
_require(initial["binding"] == expected, "stale context conflict binding; inspect again")
|
|
132
|
+
# Git's own lock blocks cooperative index writers during replacement. It is
|
|
133
|
+
# removed on exit; the index itself and all unmerged stages stay byte-identical.
|
|
134
|
+
lock_path = Path(str(index) + ".lock")
|
|
135
|
+
descriptor = os.open(lock_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
136
|
+
try:
|
|
137
|
+
root, _index, before, ours, fresh = _snapshot(root)
|
|
138
|
+
_require(fresh["binding"] == expected, "stale context conflict binding; inspect again")
|
|
139
|
+
_write_bytes_atomic(root / TARGET, ours, expected=before)
|
|
140
|
+
finally:
|
|
141
|
+
os.close(descriptor)
|
|
142
|
+
lock_path.unlink()
|
|
143
|
+
return {**fresh, "status": "restored", "staged": False}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def main(argv=None):
|
|
147
|
+
parser = argparse.ArgumentParser(description="Recover only the exact HEAD-side legacy code-map in a current Git conflict.")
|
|
148
|
+
parser.add_argument("--root", required=True)
|
|
149
|
+
operations = parser.add_subparsers(dest="operation", required=True)
|
|
150
|
+
operations.add_parser("inspect")
|
|
151
|
+
restore = operations.add_parser("restore-head")
|
|
152
|
+
restore.add_argument("--expected", required=True)
|
|
153
|
+
args = parser.parse_args(argv)
|
|
154
|
+
try:
|
|
155
|
+
result = inspect(args.root) if args.operation == "inspect" else restore_head(args.root, args.expected)
|
|
156
|
+
except (OSError, ValueError, RuntimeError, ArtifactError, subprocess.SubprocessError):
|
|
157
|
+
print("context conflict recovery refused: absent, changed, unsafe or unsupported conflict evidence", file=sys.stderr)
|
|
158
|
+
return 1
|
|
159
|
+
print(json.dumps(result, sort_keys=True))
|
|
160
|
+
return 0
|
|
@@ -801,6 +801,8 @@ def _shim(dropin_dir, phase):
|
|
|
801
801
|
f"{_FRESHNESS_PY}"
|
|
802
802
|
"CODEARBITER_556_FRESHNESS\n"
|
|
803
803
|
")\n"
|
|
804
|
+
# Match each line from the probe, including native Windows CRLF output.
|
|
805
|
+
"SKIP=$(printf '%s' \"$SKIP\" | tr '\\r\\n' ' ')\n"
|
|
804
806
|
"SEEN=0\n"
|
|
805
807
|
'for c in "$D"/*.path; do\n'
|
|
806
808
|
' [ -f "$c" ] || continue\n'
|
|
@@ -11,7 +11,7 @@ import hostapi # noqa: E402
|
|
|
11
11
|
class PiHost(hostapi.Host):
|
|
12
12
|
name = "pi"
|
|
13
13
|
adapter_name = "@arbiterforge/ca-pi"
|
|
14
|
-
adapter_version = "0.
|
|
14
|
+
adapter_version = "0.17.1"
|
|
15
15
|
update_target = "ca-pi"
|
|
16
16
|
update_tag_prefix = "ca-pi-v"
|
|
17
17
|
update_command = "pi update npm:@arbiterforge/ca-pi"
|