@adlc/prosecute

中文文档
1.12.0发布于 2026/10/10MITES Module构建来源已验证近 30 天下载 8211

P5 review-evidence recorder for ticket- and revision-bound findings and dry-pass completion.

原始使用文档
文档目录7 节
  1. Usage
  2. Input shape
  3. Trust-root tier — required cross-model review (T39)
  4. Truncation anti-rollback anchor (opt-in, #355)
  5. Exit codes
  6. Core gaps
  7. ADLC phase

@adlc/prosecute

Design decision: this is a recorder, not a reviewer. @adlc/prosecute makes zero model calls and runs no adversarial review of its own. It is a P5 evidence ledger: it validates, hashes, and appends normalized reviewer-produced pass records to .adlc/manifest.jsonl. The actual model-judged review is a separate tool -- run npx adversarial-review (or adlc review, which passes args through to it) -- and feed its output into prosecute as the --input evidence. If you run adlc prosecute expecting it to find bugs, it will not; it only proves that a real review already did.

P5 review-evidence recorder. It consumes normalized reviewer-produced pass evidence, records ticket- and revision-scoped P5 evidence to .adlc/manifest.jsonl, and passes only after two consecutive dry passes with at least three distinct dry lenses.

Usage

adlc-prosecute --input p5-passes.json --ticket T1 --dir .adlc --json

Input shape

{
  "target": "feature branch",
  "provenance": {
      "reviewer": "local-reviewer",
      "session": "codex-session-123",
      "command": "npx adversarial-review --scope working-tree --include-files",
      "transcript": ".omo/evidence/p5-review.txt"
  },
  "review_packet": {
      "prompt": ".omo/evidence/p5-prompt.txt",
      "prompt_hash": "sha256-of-prompt-file",
      "inputs": ".omo/evidence/p5-inputs.txt",
      "inputs_hash": "sha256-of-reviewed-input-packet",
      "clean_worktree": "git-worktree:..."
  },
  "passes": [
    {
      "lens": "security",
      "findings": [
        {
          "id": "F1",
          "severity": "high",
          "category": "security",
          "file": "src/auth.js",
          "line_start": 10,
          "line_end": 12,
          "evidence": "quoted changed code",
          "claim": "token bypass",
          "recommendation": "validate issuer",
          "confidence": 0.8,
          "verified_status": "verified"
        }
      ]
    },
    {
      "lens": "security",
      "findings": [],
      "dry_evidence": "review transcript reports no verified security findings"
    },
    {
      "lens": "correctness",
      "findings": [],
      "dry_evidence": "review transcript reports no verified correctness findings"
    }
  ]
}

verified_status must be verified, killed, or needs-human. Killed findings must include verification.reason, verification.method, and verification.evidence. Inputs must use the built-in lens names: security, correctness, tests, behavior, integration, or docs. Clean reviews with zero finding candidates are accepted when the dry passes include evidence. If no finding is marked verified, the input must include no_findings_attestation with reason, method, and evidence. Only passes with zero findings count as dry; killed findings are recorded but do not advance dry-pass convergence.

The transcript is not treated as a generic attachment. It must be readable, at least 64 bytes, and it must reference both the --ticket value and the resolved reviewed revision string (git-worktree:<hash> unless --revision is supplied). This binds the recorded P5 evidence to the ticket and revision that P6 later checks, but it still does not prove that an external reviewer ran. Use the named provenance.command and transcript from the actual skeptical review run, not a hand-written placeholder.

The review packet binds the reviewer prompt and reviewed input packet to P5 evidence. prompt and inputs are file paths, their hashes must match the supplied SHA-256 values, and clean_worktree must equal the exact reviewed revision.

Evidence file location is enforced, not just convention. If provenance.transcript, review_packet.prompt, or review_packet.inputs resolves to a path inside the worktree, it must live under .adlc/ or .omo/evidence/ -- lib/run.mjs's isEvidencePath() rejects any other in-worktree location. This is a trust-boundary control: it stops review "evidence" from pointing at an arbitrary file elsewhere in the repo that could be edited to fake a clean review. .omo/evidence/ is otherwise-gitignored scratch space, but the three files backing the bundled example (docs/examples/p5-passes.json) are deliberately carved out of that ignore rule and tracked -- see the comment in .gitignore before treating anything under .omo/ as safe to delete.

Trust-root tier — required cross-model review (T39)

For the trust-root tier, a clean same-model P5 is not sufficient. The CLI computes the changed-file set from the WORKING TREE vs <base> (default --base main) — the two-dot git diff --name-only <base> (tracked changes incl. uncommitted) unioned with untracked, non-ignored files (git ls-files --others --exclude-standard) — and classifies it with lib/tier.mjs. Working-tree-inclusive is load-bearing: prosecution binds to the working-tree revision, so a three-dot <base>...HEAD diff (committed only) would miss an uncommitted edit to a trust-root file and let a converged P5 exit 0 with no attestation. A change is trust-root tier iff it touches an enforcement package (packages/rails-guard|prosecute|gate-manifest|build-gate/), a gated-artifact producer (packages/ticket-prune|ticket-sync/), a declared rails deny-path of any ticket, a CI control surface (anything under .github/workflows/ or .github/actions/, test paths included — a pull request runs its own copy of these, so an edit there can make a required check pass without running), or a trust-root file (scripts/rails-guard-ci.mjs, docs/ci/rails-guard.yml, the root package.json/package-lock.json/.npmrc, CODEOWNERS in any location GitHub reads, and the repo guard files listed in lib/tier.mjs). The ticket store is not a tier surface: rails-guard-ci already refuses any change to an existing ticket. The ticket table for rails-deny-path tiering is read from the same --dir the prosecution uses (falling back to .adlc/tickets.json), so rails declared under a custom --dir are not invisible to the tier. A completed ticket's rails (completed: true, strict boolean) contribute no rails-deny-path reason — they auto-expire, matching rail-freeze's own T36 completion lifecycle reading, so a change under packages/ does not tier forever just because some long-finished ticket once declared packages/** as a rail (#905). Duplicate ticket ids across the base and worktree/HEAD ticket tables are merged before classification, with the base tip winning for completed and rails unioned across copies — so a branch cut before a ticket completes on the base still benefits once the base tip completes it, and a HEAD-only completion claim still cannot exempt a ticket the base tip keeps active. For such a change, a passing P5 additionally requires a cross-model-review approve in the manifest whose provider is distinct from the author and whose revision equals the reviewed revision. Missing → exit 2.

Author identity is anchored to the prosecution run. A tiered run MUST declare the author via --author-provider <p> (or ADLC_AUTHOR_PROVIDER); distinctness is measured against that prosecution-declared author, not the entry's self-reported authorProvider (an attestation defines both sides, so comparing only its own fields is forgeable). The gate also requires the record to have been made for that author context. A tiered run with no author-provider fails closed (exit 1) — distinctness cannot be proven without knowing the author.

Fail-closed on an unresolvable base. Tiering needs the base ref to compute the diff. If <base> cannot be resolved (e.g. a shallow CI checkout with no main), the CLI refuses the run with exit 1 rather than silently skipping the cross-model requirement — a silent ungated pass is the fail-open class this gate exists to prevent. CI must provide the base (fetch it, e.g. git fetch --no-tags origin main, or pass an explicit --base <ref>). Hermetic unit runs that assert convergence only (not tiering) pass --base HEAD so, from a clean committed worktree, the working-tree diff is empty and the tier gate stays off (run such checks post-commit; uncommitted trust-root edits deliberately tier).

Record the attestation (after an actual cross-model review approves) with:

ADLC_MANIFEST_KEY=<key> adlc prosecute record-cross-model --ticket <id> \
  --provider <p> --author-provider <a> --verdict approve [--input <passes.json>] [--revision <r>]

Recording requires the signing key (#370). The gate trusts an attestation only via its HMAC signature, so an unsigned entry is inert. Without ADLC_MANIFEST_KEY the command therefore fails closed — exit 1, nothing written, and a message naming the consequence — rather than writing an unsigned entry and reporting success. That old behaviour cost the operator a full distinct-provider review before CI revealed the attestation was worthless, and the entry was permanent: the manifest is append-only and hash-chained. Note the key is commonly kept in a gitignored .env.local in the main checkout, which is absent from every git worktree; record-cross-model reads it from ./.env.local (never tier-check — see lib/load-env-local.mjs), so from a worktree source it explicitly.

Writing an unsigned entry on purpose — #326's forge-resistance test does, to prove the gate rejects one — needs the explicit --allow-unsigned. An unsigned write never prints the success line; it prints a warning naming the consequence instead. --json reports "signed": true|false alongside the recorded entry either way.

It resolves the revision the same way the gate does (resolveProsecutionRevision), so pass the same --input/--revision you use for the gate run. --provider must differ from --author-provider — a same-model attestation is refused at record time and rejected by the gate (lib/cross-model.mjs, fail-closed). Like rails-guard this cannot prove a model ran; the author identity now comes from the prosecution invocation (not a self-report), and the record is an auditable, revision-bound, append-only, distinct-provider, author-anchored entry. See ADR-0007.

Truncation anti-rollback anchor (opt-in, #355)

This is a hardening mode for this repo's own dogfooding. It is NOT a required ADLC capability — no adopter needs or is expected to use it.

The cross-model gate above closes forgery (signatures cover the entry) and rewrite (the hash chain + per-entry signature reject an edited-in-place line) — see the HONEST LIMIT comment in lib/cross-model.mjs. One gap remained: an author who controls the PR branch can truncate the manifest, dropping a signed needs-attention revocation entirely and leaving a valid, shorter, validly-signed chain ending in an earlier genuine approve. Because the revision hash excludes manifest paths (so recording an attestation doesn't change what it's attesting to), the dropped revocation is invisible to a required check alone — it evaluates only the truncated tree.

lib/attestation-store.mjs closes it with a protected-ref mirror: an orphan branch (adlc-attestations, ruleset-protected — see docs/github-rulesets/adlc-attestations-ruleset.json) holds every signed cross-model entry a trusted CI run has ever observed, appended by the new mirror-attestations subcommand. tier-check gains an optional --attestation-store <path> flag; when passed, a signed entry the store has seen that is missing from the PR's own manifest fails the gate closed with a distinct "ROLLBACK/TRUNCATION DETECTED" message (separate from "no attestation" and "chain broken"). Omitting the flag reproduces today's behavior exactly — this is why the mechanism is opt-in rather than baked into the shared library's default path. Only this repo's own .github/workflows/cross-model-gate.yml is wired to use it (that workflow has never been a distributed template, unlike docs/ci/*.yml).

Documented residual limit: a revocation is protected only once a trusted CI run has observed it (pushed to the PR and the workflow ran at least once before truncation). A recorded-but-never-pushed revocation is invisible to everyone — the same exposure as an unsubmitted review. This is strictly better than the forgery/rewrite-only gate above, not a claim of completeness.

A tampered or key-rotated store entry FAILS CLOSED, not silently ignored. readObservedAttestations and mirrorObservedAttestations both throw if any store entry's signature does not verify — this is either an ADLC_MANIFEST_KEY rotation (every historical store entry needs migrating onto the new key, the same treatment the main manifest chain already requires) or tampering by whoever has bypass-level write access to adlc-attestations. Rotating the signing key is therefore a migration event for this store too: rebootstrap or re-sign it, don't expect tier-check --attestation-store to keep working across a rotation without that step.

Observed entries are scoped by (revision, authorProvider), not by PR/branch identity — deliberately. Two different PRs that coincidentally produce a byte-identical reviewed change (same base, same diff — a duplicate branch, for instance) share observed-entry history, so a revocation mirrored from one can block the other. This is intentional, not a gap: revision (the change-set identity from #365) already means "the identity of the reviewed change," and #365's own carry-forward feature treats an identical change-set digest as grounds to carry a verdict forward. Treating an identical digest as grounds to carry a revocation forward too is the symmetric, consistent consequence — adding a PR-unique discriminator to avoid this would let an attacker dodge a revoked review by opening a new PR with byte-identical content. See the design doc for the full threat model, the premortem/parallax findings that shaped the final design, and why the store path must live outside the tree being tiered.

Exit codes

  • 0: two consecutive dry passes were recorded (or a finding/attestation was recorded)
  • 1: operational error
  • 2: verified/needs-human findings remain, the convergence budget ended before two dry passes, a trust-root-tier change lacks a matching cross-model attestation, or (with --attestation-store) a rollback/truncation was detected

Core gaps

This package records cross-model attestations through @adlc/gate-manifest's chained record() (see lib/cross-model.mjs) and reads them back via @adlc/core's readEntries. The trust-root-tier classifier (lib/tier.mjs) lives here rather than in @adlc/core (frozen) because it is prosecute-specific policy; if a second package ever needs the same binary trust-root decision, the enumerated surfaces (enforcement packages, producers, trust-root files) would ideally graduate into @adlc/core alongside the existing railpath/risk-tier helpers so the list has a single source of truth.

ADLC phase

P5 Prosecute. This tool makes the review-evidence and dry-pass record executable, but the reviewer command named in provenance.command remains the source of the review.

版本与发布记录

@adlc/prosecute 共发布 16 个版本,首次发布于 2026/06/23;过去 12 个月发布了 16 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.12.0latest2026/10/1027 天前
1.11.12026/09/091 个月前
1.11.02026/08/252 个月前
1.10.02026/08/112 个月前
1.9.02026/08/092 个月前
1.8.02026/08/082 个月前

关于 @adlc/prosecute 的常见问题

@adlc/prosecute 是什么?

P5 review-evidence recorder for ticket- and revision-bound findings and dry-pass completion.

如何安装 @adlc/prosecute?

在项目目录运行 npm install @adlc/prosecute,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 adlc-prosecute,可以用 npx adlc-prosecute 直接运行。

@adlc/prosecute 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/adlc__prosecute 是否存在。

@adlc/prosecute 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@adlc/prosecute 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@adlc/prosecute 可以商用吗?

@adlc/prosecute 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@adlc/prosecute 的下载量有多少?

根据 npm 官方下载统计,@adlc/prosecute 在 2026/10/05 – 2026/11/03 这 30 天内被下载 8,211 次,日均约 274 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@adlc/prosecute 还在维护吗?

最新版本 1.12.0 发布于 2026/10/10(27 天前);至今共发布 16 个版本,其中过去 12 个月发布了 16 个;首次发布于 2026/06/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。