@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.
Files changed (27) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/plugins/ca-pi/CHANGELOG.md +42 -0
  4. package/plugins/ca-pi/extensions/codearbiter-child.js +2 -2
  5. package/plugins/ca-pi/extensions/codearbiter.js +33 -23
  6. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
  7. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
  8. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
  9. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
  10. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
  11. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
  12. package/plugins/ca-pi/helpers/artifacts/release.json +6 -6
  13. package/plugins/ca-pi/hooks/_artifactauthoritylib.py +473 -34
  14. package/plugins/ca-pi/hooks/_artifactlib.py +78 -5
  15. package/plugins/ca-pi/hooks/_bashguardlib.py +9 -8
  16. package/plugins/ca-pi/hooks/_hooklib.py +1 -1
  17. package/plugins/ca-pi/hooks/_host.py +1 -1
  18. package/plugins/ca-pi/hooks/_sensitivelib.py +28 -14
  19. package/plugins/ca-pi/hooks/artifact-authority-hook.py +1 -0
  20. package/plugins/ca-pi/hooks/artifact-authority.py +5 -0
  21. package/plugins/ca-pi/hooks/git-enforce.py +7 -6
  22. package/plugins/ca-pi/hooks/hostapi.py +1 -1
  23. package/plugins/ca-pi/includes/artifacts.md +56 -1
  24. package/plugins/ca-pi/includes/pi-host-notes.md +1 -1
  25. package/plugins/ca-pi/includes/security-gate-record.md +5 -3
  26. package/plugins/ca-pi/package.json +1 -1
  27. 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 executable, schema
5
- parser duplication, command execution from a document, or new host registration.
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 _bounded_child(argv: list[str], request: bytes, fd: int, timeout: float) -> tuple[int, bytes, bytes]:
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, env={})
748
- if os.name != "nt":
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, MARKER_FRESHNESS_MINUTES, SECRET_RE, SECURITY_DIFF_GIT_ARGS, block,
87
- content_digest, is_migration_path, line_digest, marker_fresh, security_scan_diff,
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
- freshness (< 30 min) AND coverage (every sensitive line being committed is
1513
- in the approved set). Coverage is what closes the TOCTOU window: a pass
1514
- minted for one diff can no longer launder a different diff committed inside
1515
- the freshness window. Scans the staged diff, plus the worktree diff when
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 marker_fresh(marker, MARKER_FRESHNESS_MINUTES):
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 sha256 hex of one diff line (H-09b/H-10b gate)
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.8"
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
- return _scan_bound_lines(lines, {})
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
- contextual = security_scan_lines(added)
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 — not whatever lands in the next 30 minutes. Trailing
516
- whitespace is stripped so CRLF translation between worktree and index
517
- never breaks the match."""
518
- return hashlib.sha256(line.rstrip().encode("utf-8", "replace")).hexdigest()
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 / marker_fresh / sensitive_scan_added_lines), so the two
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, MARKER_FRESHNESS_MINUTES, SECRET_RE, SECURITY_DIFF_GIT_ARGS,
33
+ CRYPTO_RE, SECRET_RE, SECURITY_DIFF_GIT_ARGS,
34
34
  arbiter_active, content_digest, is_migration_path, line_digest,
35
- marker_fresh, security_scan_diff, set_host, utf8_stdio,
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 fresh,
227
- # line-covering security-gate pass. Reuses the exact _hooklib primitives.
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 marker_fresh(marker, MARKER_FRESHNESS_MINUTES):
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.1"
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 `--`. Compound
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.84.1. The package is
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 is fresh
18
- (< 30 min) AND covers every sensitive line being committed — a pass recorded for one diff cannot
19
- launder a later, different change through the freshness window.
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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arbiterforge/ca-pi",
3
- "version": "0.15.8",
3
+ "version": "0.15.12",
4
4
  "private": true,
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
@@ -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.84.1 public extension
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