@arbiterforge/ca-pi 0.15.8 → 0.15.12
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 +2 -2
- package/package.json +1 -1
- package/plugins/ca-pi/CHANGELOG.md +42 -0
- package/plugins/ca-pi/extensions/codearbiter-child.js +2 -2
- package/plugins/ca-pi/extensions/codearbiter.js +33 -23
- 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 +6 -6
- package/plugins/ca-pi/hooks/_artifactauthoritylib.py +473 -34
- package/plugins/ca-pi/hooks/_artifactlib.py +78 -5
- package/plugins/ca-pi/hooks/_bashguardlib.py +9 -8
- package/plugins/ca-pi/hooks/_hooklib.py +1 -1
- package/plugins/ca-pi/hooks/_host.py +1 -1
- package/plugins/ca-pi/hooks/_sensitivelib.py +28 -14
- package/plugins/ca-pi/hooks/artifact-authority-hook.py +1 -0
- package/plugins/ca-pi/hooks/artifact-authority.py +5 -0
- package/plugins/ca-pi/hooks/git-enforce.py +7 -6
- package/plugins/ca-pi/hooks/hostapi.py +1 -1
- package/plugins/ca-pi/includes/artifacts.md +56 -1
- package/plugins/ca-pi/includes/pi-host-notes.md +1 -1
- package/plugins/ca-pi/includes/security-gate-record.md +5 -3
- package/plugins/ca-pi/package.json +1 -1
- package/plugins/ca-pi/skills/ca-doctor/SKILL.md +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Bounded stdlib bridge to an installation-pinned ca-artifact executable.
|
|
3
3
|
|
|
4
|
-
No imports perform I/O. No PATH search, environment-selected
|
|
5
|
-
parser duplication, command execution
|
|
4
|
+
No imports perform I/O. No native-binary PATH search, environment-selected
|
|
5
|
+
engine, schema parser duplication, document command execution, or host registration.
|
|
6
6
|
The caller supplies a trusted installation directory, never a repository setting.
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
@@ -742,10 +742,83 @@ def _preflight_current_acceptance(
|
|
|
742
742
|
}
|
|
743
743
|
|
|
744
744
|
|
|
745
|
-
def
|
|
745
|
+
def _native_git_excludes(git: Path, root: Path) -> dict[str, str]:
|
|
746
|
+
"""Resolve only the governed root's ignore policy with the producer's Git.
|
|
747
|
+
|
|
748
|
+
Git expands the explicit setting or its documented XDG/home default. No
|
|
749
|
+
general configuration or host environment crosses the native boundary.
|
|
750
|
+
Mapped roots with different policies still must pass exact closure equality.
|
|
751
|
+
"""
|
|
752
|
+
from _gitexec import root_bound_git_env
|
|
753
|
+
|
|
754
|
+
default = os.environ.get("XDG_CONFIG_HOME")
|
|
755
|
+
default = str(Path(default) / "git" / "ignore") if default else "~/.config/git/ignore"
|
|
756
|
+
environment = root_bound_git_env()
|
|
757
|
+
environment.pop("GIT_CONFIG", None) # git-config-only override; inventory commands ignore it.
|
|
758
|
+
try:
|
|
759
|
+
code, raw, _ = _bounded_child(
|
|
760
|
+
[str(git), "--no-lazy-fetch", "--no-optional-locks", "-C", str(root),
|
|
761
|
+
"-c", "core.fsmonitor=false", "-c", "protocol.allow=never", "config",
|
|
762
|
+
"--null", "--path", "--default", default, "--get", "core.excludesFile"],
|
|
763
|
+
b"", None, 5, environment=environment, cwd=root)
|
|
764
|
+
if code != 0 or not raw.endswith(b"\0") or b"\0" in raw[:-1] or len(raw) > 4096:
|
|
765
|
+
return {}
|
|
766
|
+
value = raw[:-1].decode("utf-8", "strict")
|
|
767
|
+
if not value:
|
|
768
|
+
return {}
|
|
769
|
+
path = Path(value)
|
|
770
|
+
path = (path if path.is_absolute() else root / path).resolve()
|
|
771
|
+
except (ArtifactError, OSError, RuntimeError, UnicodeError, ValueError):
|
|
772
|
+
return {}
|
|
773
|
+
return {"GIT_CONFIG_COUNT": "1", "GIT_CONFIG_KEY_0": "core.excludesFile",
|
|
774
|
+
"GIT_CONFIG_VALUE_0": str(path)}
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def _native_child_environment(argv: list[str]) -> dict[str, str]:
|
|
778
|
+
"""Transport one host Git identity for fixed native workspace probes only.
|
|
779
|
+
|
|
780
|
+
CODEARBITER_GIT_EXECUTABLE is the shared _gitexec host override. Resolve the
|
|
781
|
+
ordinary host PATH in this bridge, excluding CWD/relative entries and the
|
|
782
|
+
governed checkout; the native engine receives only that identity and the
|
|
783
|
+
resolved core.excludesFile setting, never PATH or general ambient state.
|
|
784
|
+
Cold ordinary operations remain usable when no safe Git is available.
|
|
785
|
+
"""
|
|
786
|
+
try:
|
|
787
|
+
root = Path(argv[argv.index("--root") + 1]).resolve(strict=True)
|
|
788
|
+
boundaries = [root, *(parent for parent in root.parents if (parent / ".git").exists())]
|
|
789
|
+
except (ValueError, IndexError, OSError):
|
|
790
|
+
return {}
|
|
791
|
+
configured = os.environ.get("CODEARBITER_GIT_EXECUTABLE")
|
|
792
|
+
if configured:
|
|
793
|
+
candidates = [Path(configured)]
|
|
794
|
+
else:
|
|
795
|
+
name = "git.exe" if os.name == "nt" else "git"
|
|
796
|
+
candidates = [Path(directory) / name for directory in os.environ.get("PATH", "").split(os.pathsep)
|
|
797
|
+
if directory and Path(directory).is_absolute()]
|
|
798
|
+
for candidate in candidates:
|
|
799
|
+
try:
|
|
800
|
+
if not candidate.is_absolute() or any(candidate.is_relative_to(boundary) for boundary in boundaries):
|
|
801
|
+
continue
|
|
802
|
+
resolved = candidate.resolve(strict=True)
|
|
803
|
+
if (any(resolved.is_relative_to(boundary) for boundary in boundaries)
|
|
804
|
+
or not stat.S_ISREG(resolved.stat().st_mode) or not os.access(resolved, os.X_OK)):
|
|
805
|
+
continue
|
|
806
|
+
except (OSError, RuntimeError):
|
|
807
|
+
continue
|
|
808
|
+
return {"CODEARBITER_GIT_EXECUTABLE": str(resolved),
|
|
809
|
+
**_native_git_excludes(resolved, root)}
|
|
810
|
+
return {}
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def _bounded_child(argv: list[str], request: bytes, fd: int | None, timeout: float,
|
|
814
|
+
*, environment: dict[str, str] | None = None,
|
|
815
|
+
cwd: Path | None = None) -> tuple[int, bytes, bytes]:
|
|
746
816
|
options = dict(stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
747
|
-
close_fds=True, start_new_session=True,
|
|
748
|
-
|
|
817
|
+
close_fds=True, start_new_session=True,
|
|
818
|
+
env=_native_child_environment(argv) if environment is None else environment)
|
|
819
|
+
if cwd is not None:
|
|
820
|
+
options["cwd"] = cwd
|
|
821
|
+
if os.name != "nt" and fd is not None:
|
|
749
822
|
options["pass_fds"] = (fd,)
|
|
750
823
|
process = subprocess.Popen(argv, **options)
|
|
751
824
|
buffers = [bytearray(), bytearray()]
|
|
@@ -83,8 +83,8 @@ import sys
|
|
|
83
83
|
from _hooklib import (
|
|
84
84
|
ADR_LIFECYCLE_LOG_BASENAME, AUDIT_LOG_BASENAMES, AUDIT_LOG_FLAT_BASENAMES,
|
|
85
85
|
AUDIT_LOG_NAMES, CRYPTO_RE, DECISION_AUDIT_LOG_NAMES, DECISION_LOG_BASENAME, DECISIONS_DIR_RE,
|
|
86
|
-
GATE_MARKER_NAMES,
|
|
87
|
-
content_digest, is_migration_path, line_digest,
|
|
86
|
+
GATE_MARKER_NAMES, SECRET_RE, SECURITY_DIFF_GIT_ARGS, block,
|
|
87
|
+
content_digest, is_migration_path, line_digest, security_scan_diff,
|
|
88
88
|
)
|
|
89
89
|
from _gitexec import git_executable
|
|
90
90
|
import _gitlib # reused for its spawn-free, worktree-aware (.git-as-a-FILE /
|
|
@@ -1508,11 +1508,12 @@ def _check_h09b_h10b_crypto_secret(commit, add, cwd, root):
|
|
|
1508
1508
|
"""H-09b / H-10b: BLOCK a commit that introduces crypto/secret changes without
|
|
1509
1509
|
a recorded security-gate pass. The crypto-compliance / secret-handling skills
|
|
1510
1510
|
record the pass via hooks/security-pass.py — a marker holding the digest of
|
|
1511
|
-
every sensitive line the gate approved. Two checks, both required:
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1511
|
+
every sensitive line the gate approved. Two checks, both required: a pass
|
|
1512
|
+
is recorded AND it covers every sensitive line being committed. Coverage is
|
|
1513
|
+
by line digest with no time window (as H-14 binds migrations by content): a
|
|
1514
|
+
pass stays valid for exactly the lines it reviewed, and any new or changed
|
|
1515
|
+
sensitive line is uncovered and blocks, so a pass for one diff can never
|
|
1516
|
+
launder a different one. Scans the staged diff, plus the worktree diff when
|
|
1516
1517
|
the commit uses -a/--all or the same command stages files."""
|
|
1517
1518
|
if not commit:
|
|
1518
1519
|
return
|
|
@@ -1541,7 +1542,7 @@ def _check_h09b_h10b_crypto_secret(commit, add, cwd, root):
|
|
|
1541
1542
|
tag = "H-09b" if touches_crypto else "H-10b"
|
|
1542
1543
|
skill = "crypto-compliance" if touches_crypto else "secret-handling"
|
|
1543
1544
|
marker = os.path.join(_marker_root(root), ".codearbiter", ".markers", "security-gate-passed")
|
|
1544
|
-
if not
|
|
1545
|
+
if not os.path.isfile(marker):
|
|
1545
1546
|
block(tag, f"This commit introduces {kind} changes, but no security-gate pass is "
|
|
1546
1547
|
f"recorded (.codearbiter/.markers/security-gate-passed). Run the "
|
|
1547
1548
|
f"{skill} gate (it records the pass), then commit. To bypass a "
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
# own checkout — the root gate MARKERS
|
|
47
47
|
# (.codearbiter/.markers/) live under (#604)
|
|
48
48
|
# repo_rel(fpath, root) -> str repo-relative POSIX path, or "" if outside root
|
|
49
|
-
# line_digest(line) -> str
|
|
49
|
+
# line_digest(line, path=None) -> str path-bound sha256 of one diff line (H-09b/H-10b gate)
|
|
50
50
|
# content_digest(text) -> str sha256 hex of a whole file's content (H-14 gate)
|
|
51
51
|
# migration_globs(root) -> tuple[list, list] (includes, excludes) for migration detection
|
|
52
52
|
# scope_globs(root, defaults, decl_re) -> tuple[list, list] generic glob scope resolver
|
|
@@ -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.15.
|
|
14
|
+
adapter_version = "0.15.12"
|
|
15
15
|
update_target = "ca-pi"
|
|
16
16
|
update_tag_prefix = "ca-pi-v"
|
|
17
17
|
update_command = "pi update npm:@arbiterforge/ca-pi"
|
|
@@ -281,7 +281,7 @@ def sensitive_scan_added_lines(diff_text):
|
|
|
281
281
|
# bindings use a separate marker namespace; a user-authored source string or
|
|
282
282
|
# an old line-only approval cannot impersonate one of these records.
|
|
283
283
|
# SecurityScan(crypto, digests): classification and exact approval identities.
|
|
284
|
-
# security_scan_lines(lines) -> SecurityScan|None: line-bound scan or refusal.
|
|
284
|
+
# security_scan_lines(lines, path=None) -> SecurityScan|None: line-bound scan or refusal.
|
|
285
285
|
# security_scan_source(path, source, added_rows) -> SecurityScan|None.
|
|
286
286
|
# security_scan_diff(text) -> SecurityScan|None: None means untrusted context.
|
|
287
287
|
SecurityScan = namedtuple("SecurityScan", "crypto digests")
|
|
@@ -320,20 +320,23 @@ def _cross_line_crypto_spans(lines):
|
|
|
320
320
|
return spans
|
|
321
321
|
|
|
322
322
|
|
|
323
|
-
def _scan_bound_lines(lines, contextual_bindings):
|
|
323
|
+
def _scan_bound_lines(lines, contextual_bindings, path=None):
|
|
324
324
|
"""Each cross-line match needs its own recognized contextual identity."""
|
|
325
325
|
spans = _cross_line_crypto_spans(lines)
|
|
326
326
|
if not spans <= contextual_bindings.keys():
|
|
327
327
|
return None
|
|
328
328
|
return SecurityScan(bool(spans) or any(CRYPTO_RE.search(line) for line in lines), {
|
|
329
|
-
line_digest(line) for line in lines
|
|
329
|
+
line_digest(line, path) for line in lines
|
|
330
330
|
if CRYPTO_RE.search(line) or SECRET_RE.search(line)
|
|
331
331
|
} | set(contextual_bindings.values()))
|
|
332
332
|
|
|
333
333
|
|
|
334
|
-
def security_scan_lines(lines):
|
|
335
|
-
"""Preserve same-line bindings; refuse crypto spanning unbound lines.
|
|
336
|
-
|
|
334
|
+
def security_scan_lines(lines, path=None):
|
|
335
|
+
"""Preserve same-line bindings; refuse crypto spanning unbound lines.
|
|
336
|
+
|
|
337
|
+
`path` binds each approval to the file the line lives in, so a reviewed
|
|
338
|
+
line cannot be admitted again after moving to a different file."""
|
|
339
|
+
return _scan_bound_lines(lines, {}, path)
|
|
337
340
|
|
|
338
341
|
|
|
339
342
|
def security_scan_source(path, source, added_rows):
|
|
@@ -348,7 +351,7 @@ def security_scan_source(path, source, added_rows):
|
|
|
348
351
|
if any(row < 1 or row > len(lines) for row in added_rows):
|
|
349
352
|
return None
|
|
350
353
|
added = [lines[row - 1] for row in added_rows]
|
|
351
|
-
result = security_scan_lines(added)
|
|
354
|
+
result = security_scan_lines(added, path)
|
|
352
355
|
candidates = {row for row in added_rows
|
|
353
356
|
if _is_context_item_line(lines[row - 1])}
|
|
354
357
|
if not candidates:
|
|
@@ -405,7 +408,7 @@ def security_scan_source(path, source, added_rows):
|
|
|
405
408
|
bindings = {(positions[first[0]], first[1], positions[last[0]], last[1]): contexts[last[0]]
|
|
406
409
|
for first, last in import_spans
|
|
407
410
|
if first[0] in positions and last[0] in positions}
|
|
408
|
-
result = _scan_bound_lines(added, bindings)
|
|
411
|
+
result = _scan_bound_lines(added, bindings, path)
|
|
409
412
|
if result is None:
|
|
410
413
|
return None
|
|
411
414
|
return SecurityScan(result.crypto or bool(contexts), result.digests | set(contexts.values()))
|
|
@@ -484,7 +487,9 @@ def security_scan_diff(diff_text):
|
|
|
484
487
|
# in the legacy permissive walker before context validation even runs.
|
|
485
488
|
if not any(line.startswith("+") and _is_context_item_line(line[1:])
|
|
486
489
|
for line in section):
|
|
487
|
-
|
|
490
|
+
# One file per section: bind its approvals to that file's path.
|
|
491
|
+
paths = {path for path, _ in attributed if path}
|
|
492
|
+
contextual = security_scan_lines(added, paths.pop() if len(paths) == 1 else None)
|
|
488
493
|
else:
|
|
489
494
|
if not section[0].startswith("diff --git "):
|
|
490
495
|
return None
|
|
@@ -508,14 +513,23 @@ def security_scan_diff(diff_text):
|
|
|
508
513
|
return result
|
|
509
514
|
|
|
510
515
|
|
|
511
|
-
def line_digest(line):
|
|
516
|
+
def line_digest(line, path=None):
|
|
512
517
|
"""Digest of one added diff line, for the security-gate binding
|
|
513
518
|
(H-09b/H-10b). The gate-pass marker stores these digests instead of being
|
|
514
519
|
an empty `touch`d file, so a recorded pass admits only the exact sensitive
|
|
515
|
-
lines it reviewed
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
520
|
+
lines it reviewed. A pass has no time window, so the digest also binds the
|
|
521
|
+
repository-relative `path` the line was reviewed in: the identical line in
|
|
522
|
+
a different file is a different digest and needs its own review. Without
|
|
523
|
+
a path (an unattributable diff section) both the recording and the
|
|
524
|
+
checking side fall back to the line alone. Trailing whitespace is
|
|
525
|
+
stripped so CRLF translation between worktree and index never breaks the
|
|
526
|
+
match."""
|
|
527
|
+
text = line.rstrip()
|
|
528
|
+
if path is None:
|
|
529
|
+
return hashlib.sha256(text.encode("utf-8", "replace")).hexdigest()
|
|
530
|
+
return "path-v1:" + hashlib.sha256(json.dumps(
|
|
531
|
+
[path, text], separators=(",", ":"), ensure_ascii=False
|
|
532
|
+
).encode("utf-8", "replace")).hexdigest()
|
|
519
533
|
|
|
520
534
|
|
|
521
535
|
def content_digest(text):
|
|
@@ -64,6 +64,7 @@ def claude_main() -> int:
|
|
|
64
64
|
_artifactauthoritylib.observe_verifier_hook(root, payload, host="claude")
|
|
65
65
|
elif (
|
|
66
66
|
(event in {"PreToolUse", "PostToolUse"} and tool == "Agent")
|
|
67
|
+
or (event == "PostToolUse" and tool == _artifactauthoritylib.CLAUDE_HANDBACK_TOOL)
|
|
67
68
|
or (event == "PreToolUse" and tool == "SendMessage")
|
|
68
69
|
or event in {"SubagentStart", "SubagentStop"}
|
|
69
70
|
):
|
|
@@ -38,6 +38,10 @@ def main(argv=None) -> int:
|
|
|
38
38
|
choices=("verification", "spec_review", "quality_review"),
|
|
39
39
|
)
|
|
40
40
|
arm.add_argument("--workspace", action="append", default=[], metavar="LABEL=PATH")
|
|
41
|
+
arm.add_argument("--completion-receipt", action="append", metavar="RECEIPT_REF",
|
|
42
|
+
help="exact published verification receipt selected for completion review; repeat per task")
|
|
43
|
+
arm.add_argument("--supporting-file", action="append", metavar="ABSOLUTE_PATH",
|
|
44
|
+
help="explicit bounded regular file retained as completion review data")
|
|
41
45
|
arm.add_argument(
|
|
42
46
|
"--host", choices=sorted(_artifactauthoritylib.HOSTS), default=None,
|
|
43
47
|
help="observing host; defaults to the host this package was built for",
|
|
@@ -83,6 +87,7 @@ def main(argv=None) -> int:
|
|
|
83
87
|
workspace_roots=workspaces or None, host=host,
|
|
84
88
|
reviewer_model=args.reviewer_model,
|
|
85
89
|
codex_review_profile=args.codex_review_profile,
|
|
90
|
+
completion_receipts=args.completion_receipt, supporting_files=args.supporting_file,
|
|
86
91
|
)
|
|
87
92
|
if host == "claude" and args.activity == "verification":
|
|
88
93
|
# The Claude verifier hook pins this exact shipped script and
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
#
|
|
12
12
|
# It mirrors pre-bash.py's commit/push gates and reuses the SAME detection
|
|
13
13
|
# primitives from _hooklib (CRYPTO_RE / SECRET_RE / line_digest / content_digest
|
|
14
|
-
# / is_migration_path /
|
|
14
|
+
# / is_migration_path / sensitive_scan_added_lines), so the two
|
|
15
15
|
# enforcement points can never drift on what counts as sensitive or as a
|
|
16
16
|
# migration, on how a gate-pass marker binds to the lines it approved, or on
|
|
17
17
|
# which path (gate-events.log, #279) is exempt from the sensitive-line scan.
|
|
@@ -30,9 +30,9 @@ import hostapi # noqa: E402 — host seam (ADR-0011)
|
|
|
30
30
|
import _entrylib # noqa: E402 — shared run() dispatch (jscpd dedup)
|
|
31
31
|
from _gitexec import git_executable # noqa: E402
|
|
32
32
|
from _hooklib import ( # noqa: E402
|
|
33
|
-
CRYPTO_RE,
|
|
33
|
+
CRYPTO_RE, SECRET_RE, SECURITY_DIFF_GIT_ARGS,
|
|
34
34
|
arbiter_active, content_digest, is_migration_path, line_digest,
|
|
35
|
-
|
|
35
|
+
security_scan_diff, set_host, utf8_stdio,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
|
|
@@ -223,8 +223,9 @@ def pre_commit(root):
|
|
|
223
223
|
block("H-01", f"Direct commit to {target} is prohibited (ORCHESTRATOR §3) — this is "
|
|
224
224
|
f"the git-level backstop (#161). Create a feature branch.")
|
|
225
225
|
|
|
226
|
-
# H-09b / H-10b: a commit introducing crypto/secret changes needs a
|
|
227
|
-
# line-covering security-gate pass
|
|
226
|
+
# H-09b / H-10b: a commit introducing crypto/secret changes needs a recorded,
|
|
227
|
+
# line-covering security-gate pass (bound by line digest, no time window).
|
|
228
|
+
# Reuses the exact _hooklib primitives.
|
|
228
229
|
added = cached_added_lines(cwd)
|
|
229
230
|
if added is None:
|
|
230
231
|
block("H-09b", "the staged diff for the crypto/secret scan could not be read — "
|
|
@@ -237,7 +238,7 @@ def pre_commit(root):
|
|
|
237
238
|
skill = "crypto-compliance" if touches_crypto else "secret-handling"
|
|
238
239
|
marker_root = _marker_root(root)
|
|
239
240
|
marker = os.path.join(marker_root, ".codearbiter", ".markers", "security-gate-passed")
|
|
240
|
-
if not
|
|
241
|
+
if not os.path.isfile(marker):
|
|
241
242
|
block(tag, f"This commit introduces {kind} changes, but no security-gate pass is "
|
|
242
243
|
f"recorded (#161 git backstop). Run the {skill} gate, then commit.")
|
|
243
244
|
approved = _marker_set(marker_root, "security-gate-passed")
|
|
@@ -313,7 +313,7 @@ class Host:
|
|
|
313
313
|
|
|
314
314
|
name = "claude"
|
|
315
315
|
adapter_name = "ca"
|
|
316
|
-
adapter_version = "2.23.
|
|
316
|
+
adapter_version = "2.23.4"
|
|
317
317
|
|
|
318
318
|
# Update-notifier descriptor. Each independently versioned host overrides
|
|
319
319
|
# these three values in its per-plugin _host.py. Keeping the target,
|
|
@@ -262,13 +262,27 @@ the filename, not a leaf-name alias. Named unittest commands require `-v` or
|
|
|
262
262
|
Plain exit checks cannot attest named tests.
|
|
263
263
|
|
|
264
264
|
Named npm commands support inspectable single-runner scripts, a contained
|
|
265
|
-
repository-relative `--prefix`, and arguments forwarded after `--`.
|
|
265
|
+
repository-relative `--prefix`, and arguments forwarded after `--`. A root script
|
|
266
|
+
may delegate once through `npm -w <name> run <script>` or
|
|
267
|
+
`npm --workspace <name> run <script>` when the name resolves uniquely in an array
|
|
268
|
+
of at most 32 explicit, contained workspace directories. The runner binds the root
|
|
269
|
+
manifest and every inspected workspace manifest. Globs, linked directories,
|
|
270
|
+
multiple workspace selectors and recursive npm delegation are unsupported.
|
|
271
|
+
Workspace delegation uses the declared cwd; a non-dot `--prefix` is unsupported
|
|
272
|
+
for this shape.
|
|
273
|
+
Preserve both npm argument boundaries when adding a reporter: for a root
|
|
274
|
+
`test:client` script containing `npm -w @example/client run test`, declare
|
|
275
|
+
`npm run test:client -- -- --reporter=verbose` to forward the reporter to Vitest.
|
|
276
|
+
The original root command still executes the full script. Compound
|
|
266
277
|
scripts and pre/post lifecycle scripts require separately declared direct-runner
|
|
267
278
|
commands; retain every check from the original suite when splitting commands.
|
|
268
279
|
On Windows, only the qualified standard `npm.cmd` layout is adapted: the runner
|
|
269
280
|
pins the wrapper, sibling `node.exe`, sibling `node_modules/npm/bin/npm-cli.js`,
|
|
270
281
|
and script manifest. It deliberately selects that sibling CLI rather than the
|
|
271
282
|
shim's optional global-prefix redirection. Unknown batch wrappers fail closed.
|
|
283
|
+
For workspace delegation, it also pins `npm-prefix.js` and checks that the nested
|
|
284
|
+
wrapper uses the same CLI, the default script shell, and no configured Node
|
|
285
|
+
options. Local executables shadowing nested npm are rejected.
|
|
272
286
|
Direct Node script entrypoints are also pinned. These launch-file bindings do
|
|
273
287
|
not establish transitive dependency closure for deliberately excluded inputs.
|
|
274
288
|
|
|
@@ -287,6 +301,47 @@ rewrite the returned envelope yourself.
|
|
|
287
301
|
The host hooks bind the exact launch call, child start and
|
|
288
302
|
the child's first stop; a pasted or coordinator-authored decision is not review
|
|
289
303
|
authority.
|
|
304
|
+
|
|
305
|
+
When reviewing completed work that relies on a verification run, linked
|
|
306
|
+
worktrees or supporting evidence, explicitly select the completion path. Add
|
|
307
|
+
`--completion-receipt <exact-published-receipt>` once per task and
|
|
308
|
+
`--supporting-file <absolute-regular-file>` for each supporting document to the
|
|
309
|
+
review `arm` command. A task review selects one verification receipt; a scope
|
|
310
|
+
review selects the exact receipt for every member. Supporting files require a
|
|
311
|
+
receipt selection. The existing review-before-verification path remains
|
|
312
|
+
available; it does not satisfy an explicitly requested completion review.
|
|
313
|
+
|
|
314
|
+
The native engine validates the selected published verification chain and
|
|
315
|
+
derives the worktree mappings from its qualified observation. It retains the
|
|
316
|
+
selected identities, worktree closure and supporting bytes in the immutable
|
|
317
|
+
context. Supporting material is data for review, not authority. Selection is
|
|
318
|
+
explicit: no newest-receipt search, directory import or attachment execution.
|
|
319
|
+
At most 16 regular files are admitted, each at most 64 KiB and together at most
|
|
320
|
+
256 KiB; links, reparse indirection, duplicate paths and changed content fail
|
|
321
|
+
closed. Read canonical spec and plan artifacts through the native artifact API;
|
|
322
|
+
they cannot be imported as supporting files. The generated prompt directs the reviewer to assess performed work
|
|
323
|
+
against each criterion, step and `done_when` obligation, using selected evidence
|
|
324
|
+
and source references. A consistent plan or an empty diff is insufficient.
|
|
325
|
+
|
|
326
|
+
Use the returned `completion_sha256` in the native `task-review` or
|
|
327
|
+
`accept-scope` request. The completed review and its exact verification run must
|
|
328
|
+
agree; a definition-only review or a different successful run is rejected.
|
|
329
|
+
The recorded task selection remains required by downstream freshness and scope
|
|
330
|
+
acceptance. Scope review uses validated task completion bindings. If verification
|
|
331
|
+
is rerun, obtain and record its completed task review before selecting that run
|
|
332
|
+
for scope review; do not infer equivalence between receipts.
|
|
333
|
+
|
|
334
|
+
Creating the first completion context recomputes the current raw worktree
|
|
335
|
+
fingerprint and requires it to match the selected verifier's recorded fingerprint.
|
|
336
|
+
Later freshness checks allow native task-status and execution bookkeeping updates
|
|
337
|
+
in validated canonical artifacts while retaining their normative content hashes.
|
|
338
|
+
Changes to task definitions, other HTML or other governance files still affect
|
|
339
|
+
freshness under the normal snapshot policy. The closure covers
|
|
340
|
+
tracked working-tree bytes and non-ignored untracked files, not ignored
|
|
341
|
+
dependencies or outputs. The completion assessment is still an independent
|
|
342
|
+
reviewer's judgment; schema validation does not prove that every file was read
|
|
343
|
+
or every conclusion is correct. Preserve the unchanged launch envelope and
|
|
344
|
+
genuine host capture throughout this path.
|
|
290
345
|
Publish the completed request through the same `publish` command. `task-review`
|
|
291
346
|
records the separate verification and spec-review receipts. After every task in
|
|
292
347
|
the scope reaches `REVIEW`, reverify against one current source snapshot, run the
|
|
@@ -74,7 +74,7 @@ file maps those actions to Pi's extension API.
|
|
|
74
74
|
operator-launched Pi CLI, imported module, package root, and reported version.
|
|
75
75
|
It does not prove publisher authenticity. Verify the source separately with
|
|
76
76
|
`pi list` and `pi config`.
|
|
77
|
-
- The supported promotion target is exact Pi 0.
|
|
77
|
+
- The supported promotion target is exact Pi 1.0.0. The package is
|
|
78
78
|
published as `npm:@arbiterforge/ca-pi` on every `ca-pi-v*` tag (ADR-0029);
|
|
79
79
|
the pinned Git tag remains the reproducible install. A Pi-native embedded
|
|
80
80
|
farm worker is a future spike, not an installed dependency.
|
|
@@ -14,9 +14,11 @@ code instead of the first's (#577) — then run:
|
|
|
14
14
|
|
|
15
15
|
It writes `<project-root>/.codearbiter/.markers/security-gate-passed` containing a digest of
|
|
16
16
|
every sensitive added line it approved. The PreToolUse commit hook (**H-09b** for crypto/TLS, **H-10b**
|
|
17
|
-
for secrets) blocks any commit whose staged diff touches a guarded pattern until this marker
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
for secrets) blocks any commit whose staged diff touches a guarded pattern until this marker exists
|
|
18
|
+
AND covers every sensitive line being committed. There is no time window: a pass stays valid for
|
|
19
|
+
exactly the lines it reviewed, in the files it reviewed them in. Any new or changed sensitive line,
|
|
20
|
+
or a reviewed line moved to another file, is uncovered and blocks, so a pass recorded for one diff
|
|
21
|
+
cannot launder a later, different change.
|
|
20
22
|
|
|
21
23
|
On any BLOCK, do **not** record the pass — the commit stays blocked until the finding is resolved and
|
|
22
24
|
the gate genuinely passes. A premature or unconditional recording defeats the gate.
|
|
@@ -32,7 +32,7 @@ repository binary, mutate an artifact, or silently fall back to Markdown.
|
|
|
32
32
|
Pi bash wrapper. The exact shared-core `[H-03]` block is healthy and cannot stage files; execution
|
|
33
33
|
or a different block is unhealthy. This self-test does not traverse Pi's active dispatcher. Do not
|
|
34
34
|
rerun or respell it.
|
|
35
|
-
4. Its `active-dispatch` row remains degraded because supported Pi 0.
|
|
35
|
+
4. Its `active-dispatch` row remains degraded because supported Pi 1.0.0 public extension
|
|
36
36
|
APIs cannot submit that deterministic call through the active dispatcher. PI-AC-28 remains blocked
|
|
37
37
|
until supported-version real-host promotion/CI evidence closes the gap.
|
|
38
38
|
5. Resolve Python once, run `<plugin-root>/hooks/doctor.py`, and present its mechanical report
|