Skip to content

[#1276 follow-up] Lint provisioning: installer digest-tuple + before-Claude tool install + setup lint_mode + review-runner isolation - #1963

Merged
The01Geek merged 34 commits into
mainfrom
issue-1388-1276-follow-up-lint-provisioning-installer-digest
Aug 26, 2026
Merged

[#1276 follow-up] Lint provisioning: installer digest-tuple + before-Claude tool install + setup lint_mode + review-runner isolation#1963
The01Geek merged 34 commits into
mainfrom
issue-1388-1276-follow-up-lint-provisioning-installer-digest

Conversation

@prflow-implementer

@prflow-implementer prflow-implementer Bot commented Aug 25, 2026

Copy link
Copy Markdown

Summary

  • Provisions the bounded ShellCheck/Ruff lint set declared in .prflow/lint-manifest.json before the Claude action starts, so implement runs no longer spend paid turns rediscovering and installing lint tools.
  • Ships the manifest, its helper, the setup action, and workflow templates as one digest-bound compatibility tuple, publishing the tracked marker .prflow/install-state.json only after the staged set validates — a run refuses to provision when the marker is absent, a digest disagrees, the manifest is missing, or a component is out of step with its declared peer.
  • Isolates the read-only review runner: it materializes the setup-project-env action body from trusted base-ref bytes and runs with lint_mode: none, so a PR editing that action cannot execute its edit in the review job.

Changes

Lint-provisioning core (Python): scripts/lint_provision.py resolves the (os, arch, tool) tuple to a pinned artifact + trusted URL and computes the AC5 cache key; scripts/install_state.py builds/parses/validates the marker and gates provisioning through a fail-closed check_readiness; lib/generate-install-state.py generates the tracked marker and backs the drift --check.

Composite action: .github/actions/setup-project-env/action.yml gains a closed lint_mode input (provision | none); an unknown value is refused. provision installs the manifest's ShellCheck/Ruff run-local (no sudo), verifying artifact digest and executable version before readiness and re-verifying cache restores under the key {OS, arch, tool, version, digest, installer version}. none does no lint-tool work and validates no manifest. The end-to-end installer is .github/actions/setup-project-env/provision-lint-tools.sh.

Workflow wiring: devflow.yml, devflow-implement.yml, and devflow-runner.yml pass lint_mode none, provision, and none respectively. devflow-runner.yml adds a hardensetup step that fetches the trusted BASE_REF, materializes every .github/actions/setup-project-env file from FETCH_HEAD, prunes PR-added files, and runs before the uses: step.

Installer + shipping: install.sh ships the manifest and publishes the marker only after validating the staged set. vendor-slice.sh copies both .prflow/lint-manifest.json and .prflow/install-state.json to consumers. .gitignore re-includes the marker beside the manifest so both ship.

CI: ci.yml adds a candidate-manifest job under contents: read (no repository write credentials) that validates and exercises the manifest matrix plus marker readiness/drift.

Resolves

Resolves #1388

Test Plan

  • lib/test/test_python_scripts.py [#1276 follow-up] Lint provisioning: installer digest-tuple + before-Claude tool install + setup lint_mode + review-runner isolation #1388 blocks pass: build_plan tuple/artifact resolution, cache_key shape, the install_state.validate_state six-shape fail-closed matrix, check_readiness (first-install / backfill / version-skew both directions / component-removed / manifest-missing), and the provision-lint-tools.sh end-to-end fail-closed arms.
  • run.sh cross-file pins pass: the composite-action lint_mode contract, the three-caller wiring counts, the review-runner isolation (hardening precedes use, whole-dir base-ref materialization), and the ship pins for the manifest + marker.
  • Drift gate green: lib/generate-install-state.py --check passes against the committed marker.

Visual Changes

N/A

Breaking Changes

None

prompt-surface growth: no tracked *.md under skills/, agents/, or .prflow/prompt-extensions/ changed between 24cd458853ad38f818b696d4ff2b421b50d545b2 and 949de2d15b8803a8785f17964bec202636028ef2 — no table rendered.

Generated via /prflow:implement (v2.34.22, claude-opus-4-8, low)

…e-workflow wiring + review-runner trusted-base isolation (checkpoint)
…er + generator + drift gate + .gitignore re-include (checkpoint)
…arker; vendor-slice ships both; record-path skew support (checkpoint)
…ds) + gitignore comment reconcile (checkpoint)
…w-up-lint-provisioning-installer-digest

# Conflicts:
#	lib/test/test_python_scripts.py
…w-runner hardening, single --version exec, exit-code clarity)
…ken version match, cache-path honesty, Plan/StateResult/Readiness invariant guards, +zip/reuse/matrix tests
…r the {OS,arch,tool,version,digest,installer} key (#1388)
@mintlify

mintlify Bot commented Aug 25, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
pr-flow 🟢 Ready View Preview Aug 25, 2026, 7:39 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@prflow-implementer
prflow-implementer Bot marked this pull request as ready for review August 25, 2026 20:08
@prflow-implementer

Copy link
Copy Markdown
Author

/prflow:review

@prflow-reviewer

prflow-reviewer Bot commented Aug 25, 2026

Copy link
Copy Markdown

PRFlow Review — PR #1963

Status: ❌ REJECT
Diff profile: engine_self_modifying + has_new_types → full checklist, full roster (+ type-design-analyzer, pr-test-analyzer). Stale-prose lint disabled by config.
Run: View run
Reviewed HEAD: 9d522ea
Last updated: Phase 4 complete

Blueprint

  • Classify diff (Phase 0.5)
  • Generate verification checklist (Phase 1) — 57 items (3 batches, no merges)
  • Verify checklist (Phase 2) — 55 passed, 2 failed, 0 inconclusive
  • Review agents (Phase 3) — 6 agents returned
  • Aggregate & verdict (Phase 4)
  • Run complete — everything this run owed

Run telemetry: 3 checklist-generators + 1 deduper + 56 verifiers + 1 lite + 6 Phase-3 agents. Formal review posted: REQUEST_CHANGES (marker stamped). Read-only cloud review tier — durable telemetry persist is writable-run-only; no persist performed.


Verdict: REJECT (an explicit acceptance criterion is not met, and two security-relevant behaviors are pinned only by string presence)

This is a strong, carefully-engineered change — CI is green, the Python helpers follow the "unknown is not zero" fail-closed discipline rigorously, and the provisioning vector it introduces (setup-project-env lint provisioning) is genuinely closed. The REJECT rests on a specific, actionable gap rather than on code correctness: acceptance criterion 8 is not literally met, and its verification checklist item failed.

Issue Compliance

Reviewed against issue #1388: [#1276 follow-up] Lint provisioning — criteria from the /prflow:implement workpad comment (this run's authoritative set, possibly narrowed from the issue); scope unchanged. Requirement-based checklist items are included in the verification results below.

  • No divergence between the workpad's criteria and the issue body's.

Verification Checklist Results

55 passed, 2 failed, 0 inconclusive — 1 via lite probe, 56 via agent.

  • VC-6: FAIL — AC "the retained review runner invokes no PR-head composite action; a PR-head composite-action edit fixture is unreachable in that runner" [.github/workflows/devflow-runner.yml]. The hardensetup step materializes trusted base-ref bytes over ./.github/actions/setup-project-env only. The same review job still invokes ./.github/actions/read-project-config and ./.github/actions/vendor-plugin from the PR-head workspace — the in-tree comment beside the vendor-plugin use even states this: "The composite action itself is still resolved from the PR-head workspace — a residual this change records rather than closes." So the AC's second clause is false for a fixture editing either of those two actions; only a setup-project-env edit fixture is now unreachable. Corroborated by the final-pass reviewer (see 🟠 Important Add 2.3.0 changed-contract + 2.3.4 boundary-assumption sweeps to /implement #1).
  • VC-41: FAIL — AC "Linux/macOS/Windows × x86_64/arm64 … fixtures exist where upstream artifacts do" [lib/test/test_python_scripts.py]. The unit test pins exact URL/digest for shellcheck linux/x86_64, shellcheck windows/x86_64, ruff macos/arm64, and the windows/arm64 unsupported case — but never exercises linux/arm64 or macos/x86_64. (Mitigating: the manifest declares artifacts for the tuples upstream ships — VC-52 PASS — and CI's lint-manifest job iterates the full KNOWN_TOOLS × KNOWN_OS × KNOWN_ARCH product for no-crash + URL-presence; the residual gap is exact-string URL correctness for the two unpinned established tuples.)

Two verifier FAILs were disproved by the orchestrator against HEAD and are recorded PASS: VC-7 (vendor-slice.sh does cp both .prflow/lint-manifest.json and .prflow/install-state.json at HEAD, lines 81-83 — the verifier read the base tree) and VC-33 (test_python_scripts.py does load install_state at HEAD line 35991 and exercises the full validate_state/parse_state/check_readiness/build_state matrix). VC-21 raw-FAIL is wording-only (no double-resolution mismatch exists — the helper consumes the exported SCRIPTS_DIR; the code is correct), normalized to PASS.

✅ Passed items (54 of 57) — click to expand
  • VC-1: installer ships a digest-bound tuple, publishes install-state.json only after validating the staged set [install.sh]
  • VC-2: setup-project-env accepts a closed lint_mode input; unknown value refused via wildcard exit 1 [action.yml]
  • VC-3: provision mode installs run-local (no sudo), digest-before-extract, version-after-install, cache re-verified [provision-lint-tools.sh]
  • VC-4: none mode exits before any lint-tool/manifest work [action.yml]
  • VC-5: devflow.yml/devflow-implement.yml/devflow-runner.yml pass none/provision/none [workflows]
  • VC-7: .gitignore re-includes install-state.json; vendor-slice.sh copies both files [.gitignore, vendor-slice.sh]
  • VC-8: CI lint-manifest job runs under contents:read, no App token [ci.yml]
  • VC-9: changeset with bump: patch frontmatter [.changeset]
  • VC-10: provision-lint-tools.sh install_state.py verify --state/--manifest + NOT-READY contract
  • VC-11: lint_provision.py plan TSV field order + rc==3 unsupported
  • VC-12: cache-key flags + install-state.json installer_version key
  • VC-13: ci.yml uses KNOWN_TOOLS/OS/ARCH + build_plan Plan shape
  • VC-14: ci.yml validates via lint_manifest.py CLI
  • VC-15: RUNNER_OS/ARCH token mapping matches KNOWN_OS/KNOWN_ARCH
  • VC-16: cache key hashFiles both files
  • VC-17/VC-23: hardensetup enumerate/materialize/prune/fail-closed sequence
  • VC-18: hardensetup gate + BASE_REF derivation match baseprovision
  • VC-19: vendor-slice.sh copies manifest + marker
  • VC-20/VC-40: install-state.json digests match HEAD; generate-install-state.py --check passes
  • VC-21: SCRIPTS_DIR resolved once and exported (no mismatch)
  • VC-22: review tier (lint_mode none) never reaches the provision-gated cache step
  • VC-24: install.sh install_state.py build CLI surface accepted
  • VC-25: install.sh validates manifest, publishes only on exit 0
  • VC-26/VC-27: COMPONENTS tuple + build_state signature match
  • VC-28: prflow_version non-empty string guard
  • VC-29/VC-30: helper env-var names + error substrings align test↔helper
  • VC-31/VC-32: build_plan Plan shape + pinned URL/digest match manifest
  • VC-33: install_state API exercised (six-shape matrix, check_readiness) in test
  • VC-34/VC-35: run.sh pins (2 provision-gated steps, 5 BASE_REF fetch sites) match HEAD
  • VC-36: installer-wiring fixture copies manifest + marker
  • VC-37: install.sh publish nested in validation success branch
  • VC-38/VC-50: check_readiness fixtures cover first-install/backfill/version-skew/component-removed/manifest-missing
  • VC-39: git ls-files proves manifest + marker tracked
  • VC-42/VC-56: six fail-closed provisioning arms each name the tool
  • VC-43: coverage-map new entries valid (unmodularized owner + test_python_scripts.py focused_test)
  • VC-44/VC-45/VC-46/VC-47: lint_manifest.load_manifest contract (.established/.reason/.manifest, missing: prefix, artifact record shape)
  • VC-48: cache_key sha256: prefix normalization; both digest sources use sha256:<64hex>
  • VC-49: build_state raises before publish on unreadable component; check_readiness fail-closed
  • VC-51: cache_key includes all six fields
  • VC-52: platform matrix — manifest declares upstream-shipped tuples; unsupported → unsupported-lint-platform
  • VC-53: shellcheck (v-prefixed) / ruff (bare-version) upstream URL layouts correct
  • VC-54: version field regex-anchored + os/arch/archive_type closed vocab → no metacharacter reaches URL
  • VC-55: six-shape validate_state fail-closed matrix (object/array/scalar/valid-falsy/missing/wrong-type)
  • VC-57: check_readiness digests recorded runtime path relative to repo_root

Code Review Findings

🟠 Important / Major

  1. AC8 not literally met — the review runner still invokes two PR-head composite actions. The review job invokes ./.github/actions/read-project-config and ./.github/actions/vendor-plugin from the PR-head workspace ahead of the hardened setup-project-env; a PR editing either action's action.yml still executes its edited body in the read-only review job. This is a pre-existing, in-code-acknowledged residual (the PR does not regress it, and lint_mode: none keeps manifest-derived bytes out of the review job), but AC8 as written is absolute and the test's AC8 assertions pin only setup-project-env. Reconcile by either hardening those two actions the same way or narrowing the AC to the setup-project-env path this PR actually covers. (raised by 2/6 agents — final-pass reviewer + VC-6) [self-contradicting-diff carve-out: unknown] — note: filed as an unmet-AC / spec-coverage gap, not a diff-added falsehood.

  2. lint_mode closed-set dispatch (none / unknown-refusal) has no behavioral coverage — only string-presence pins. The none no-op and the fail-closed *) refusal of an unknown lint_mode (AC6) live entirely in action.yml's inline case "$LINT_MODE". A regression making an unknown mode fall through to provisioning (fail-open) would not be caught; the inline snippet is extractable and runnable the same way _run_helper_1388 runs the sibling script. (raised by 1/6 agents — pr-test-analyzer) [suspected over-grade: shape 3 — single-source from pr-test-analyzer; advisory only, does not change the verdict]

  3. The base-ref hardening step — a security boundary — is verified only by string-presence pins, not behavior. The tests pin the step name, ordering, and presence of the git ls-tree/git show FETCH_HEAD lines, but nothing exercises that the prune loop actually removes a PR-added file, or that the step fails closed when the base ref carries no action.yml. For a supply-chain isolation control, presence pins do not prove the guarantee holds. (raised by 1/6 agents — pr-test-analyzer) [suspected over-grade: shape 3 — single-source from pr-test-analyzer; advisory only, does not change the verdict]

  4. Plan does not enforce its payload-per-status invariant, unlike its sibling StateResult introduced in the same diff. Plan.__init__ takes untyped **kw and defaults every payload field to None, so Plan("established") with no url/digest constructs successfully. The contract is upheld only by prose plus downstream shell ([ -n "$url" ]) and CI (assert plan.url and plan.digest) checks. Mirror StateResult's constructor guard: require the payload for established and a reason for unsupported/unestablished. (raised by 1/6 agents — type-design-analyzer)

🟡 Suggestion / Minor

  1. VC-41 residual — exact-URL correctness for linux/arm64 and macos/x86_64 is unpinned. CI exercises the full product for no-crash + URL-presence, but a typo in a _RUFF_TARGET triple or the _SHELLCHECK_OS/arch map for those two tuples would pass every test and silently point provisioning at a nonexistent artifact (fail-closed at runtime as a network failure). Add one exact-URL assertion per remaining established tuple, or pin the _RUFF_TARGET/_SHELLCHECK_OS maps directly. (raised by 2/6 agents — pr-test-analyzer + VC-41)

  2. install.sh marker-publish failure discards the underlying error. The manifest validation and install_state.py build run under >/dev/null 2>&1, so a failed publication shows only the generic "could not publish" warning. install_state.py build already prints a specific build failed: <reason> to stderr — routing that into the install log makes a failed publication actionable without changing behavior. (raised by 1/6 agents — silent-failure-hunter)

  3. The logging-only cache-key computation can abort provisioning. In _provision_one the lint_provision.py cache-key result is used only in printf log lines, yet a failure of that call triggers _die and aborts the whole pass. Fail-closed direction is safe, but failing a real run because a cosmetic log string could not be computed is stricter than necessary. (raised by 1/6 agents — silent-failure-hunter)

  4. The cache_key() tuple never keys any real cache. The actions/cache key is hashFiles-based (covering the tuple transitively); the helper's per-tool cache_key() value is emitted only into log lines. The two coexisting key derivations are mildly confusing — document that cache_key() is diagnostic-only, or drop it. (raised by 2/6 agents — silent-failure-hunter + final-pass)

  5. Three workflow/YAML comment blocks exceed the three-line prevention-only standard (action.yml cache-step ~10 lines and provision-step ~9 lines; devflow-runner.yml hardening ~11 lines). Each is accurate but spends most of its length on derivation/security narrative; the hardening rationale is already stated canonically in docs/internal/cloud-setup.md (added in this same diff). (raised by 1/6 agents — comment-analyzer)

  6. Readiness allows a ready=True verdict to carry a stale failure reason, and ready is not type-checked — a minor asymmetry versus StateResult's crisp XOR; low risk given two internal call sites. (raised by 1/6 agents — type-design-analyzer)

over-grade annotation: findings #2 and #3 flagged shape 3 (advisory only — the verdict is unchanged by these annotations).
truthfulness sweep: no finding promoted — the comment-analyzer and code-reviewer found no diff-added prose false against HEAD; the "digest bound into cache key" comment is transitively true (the manifest hashed into the key carries the digest).
intra-diff contradiction scan: no contradiction found.

Verdict Criteria applied

  • Rule 1 (any verification checklist FAIL → REJECT): VC-6 and VC-41 are stored FAILs → REJECT. VC-6 is the load-bearing blocker (an explicit acceptance criterion is not met); VC-41 is a genuine but narrower test-coverage FAIL.
  • Verdict threshold: critical (default). No Phase-3 finding is Critical, so rule 3 does not independently fire; the four Important findings reinforce the concern but do not drive the verdict at this threshold.
  • No deferrals block present in the PR body; no demotions applied.

What clears the REJECT

The single required change is to reconcile AC8: either extend the base-ref hardening to read-project-config and vendor-plugin (closing the residual), or narrow AC8's wording to the setup-project-env path this PR actually isolates and update the AC8 test assertions accordingly. Strongly recommended alongside: add a behavioral test for the lint_mode unknown-value refusal and the hardening prune/fail-closed logic (Important #2, #3), since those are the load-bearing security-boundary behaviors and are currently pinned only by string presence.

Verdict

REJECT — see above.

@prflow-reviewer prflow-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REJECT — full report in PR comment

The complete review report (checklist results, findings, details) is in the
PRFlow Review progress comment on this PR.

@The01Geek

Copy link
Copy Markdown
Owner

/prflow:review

@github-actions

Copy link
Copy Markdown
Contributor

PRFlow Review — PR #1963

Status: ❌ Review failed

Review run failed: claude step failure — https://github.com/The01Geek/prflow/actions/runs/32897245966

@prflow-implementer

Copy link
Copy Markdown
Author

DevFlow review stall backstop — this cloud review ended with no verdict for 9d522ea8f55f9e84144ba098c3c8e9078d99bd40. Auto-resume attempt 1:

/devflow:review

@The01Geek

Copy link
Copy Markdown
Owner

Reception pass — round 3 disposition (review @ dbf1330, fixes in 64c4be4)

Important

  • I-1 (hardensetup structural-pins-only): fixed, without relocating the security control — the SHIPPED devflow-runner.yml step body is now extracted from the YAML and executed end-to-end against fixture git repos (lib/test/test_python_scripts.py, #1388 hardensetup: block): a PR-head EDIT is overwritten by base bytes, a PR-head ADDED file is pruned, and both abort arms (base ref without the action dir; unfetchable base ref) fail closed. Because the test runs the workflow's own bytes, there is no helper mirror to drift, and the trusted-source bootstrap question an extraction would raise does not arise.
  • I-2 (source-digest components depend on byte-identical copies): acknowledged, prevention comment added in install.sh §4b naming the wrong change (any templating/newline transform on that copy path → consumer-wide digest-mismatch refusal).

Suggestions

  • S-3 (rc-3 conflates unknown tool with unsupported platform): fixedunknown-lint-tool is a distinct reason with CLI exit 4; the helper fails closed on it (even with a PATH candidate present) while the platform gap keeps degrading. CLI + helper arms added.
  • S-4 (find -print -quit): fixed-print | head -1 (a missing head fails closed into the member-not-found refusal).
  • S-5 (digest-skew refusal names no remedy): fixed — the readiness refusal now appends "remedy: re-run the PRFlow installer (install.sh)…"; asserted.
  • S-6 (ManifestResult not uniform; Readiness.ready untyped): fixed — both-direction XOR + post-init freeze on ManifestResult; Readiness.ready must be a bool. Deep-freeze of the referenced dicts remains declined (shared-shape churn, no observed defect).
  • S-7 (comment discipline): fixed — the URL-block comment now points at lint_manifest._VERSION_RE instead of transcribing a divergent regex; the AC ordinal left the cache_key docstring; the helper header dropped its sibling/provenance narrative.
  • S-8 (lib/generate-install-state.py mode): fixed — executable bit restored.

Verification: PYTHON_COLORS=0 python3 lib/test/test_python_scripts.py → 5945 passed / 29 failed (exactly the known #1655 environment-only baseline); lib/generate-install-state.py --check green; shellcheck/ruff clean on every edited file; a REAL provisioning pass (downloads + digest verify) run locally for macos/arm64 — both tools installed and version-verified, live-proving the two digests the linux-only CI exercise cannot reach.

@prflow-implementer

Copy link
Copy Markdown
Author

/prflow:review

@prflow-reviewer

prflow-reviewer Bot commented Aug 26, 2026

Copy link
Copy Markdown

PRFlow Review — PR #1963

Status: ❌ Review failed
Diff profile: (pending Phase 0.5)
Run: View run
Reviewed HEAD: (set at Phase 4)
Last updated: (pending)

Blueprint

  • Classify diff (Phase 0.5)
  • Generate verification checklist (Phase 1)
  • Verify checklist (Phase 2)
  • Review agents (Phase 3)
  • Aggregate & verdict (Phase 4)
  • Run complete — everything this run owed

Findings (live)

(Phase-3 findings appear here as each agent returns.)

Verdict

(pending)

Review run failed: claude step failure — https://github.com/The01Geek/prflow/actions/runs/32923574003

@prflow-implementer

Copy link
Copy Markdown
Author

DevFlow review stall backstop — this cloud review ended with no verdict for 64c4be4d16043e3e9b15f99035b8e6f17ab56edd. Auto-resume attempt 1:

/devflow:review

@github-actions

Copy link
Copy Markdown
Contributor

PRFlow review: no verdict-post receipt was found for this run.

  • Actions run id: 32923574003
  • Pull-request head SHA this step resolved: 64c4be4d16043e3e9b15f99035b8e6f17ab56edd

No run-scoped verdict-post receipt was found for this run: either Phase 4.4's
verdict emitter did not run, or it ran and could not write its receipt (look for a
could not write the verdict-post receipt breadcrumb in the job log).

No review authored by this run's reviewer identity is recorded in the reviews
API for the head above, so this run left the reviews API and reviewDecision untouched.
It recorded no verdict anywhere.

Any verdict text this run published OUTSIDE the emitter carries no producer-emitted verdict marker,
and the verdict-derivation consumers do not read it as a verdict.

This comment is a record of that gap. It is not a verdict, and it neither approves nor
rejects this pull request.

@github-actions

Copy link
Copy Markdown
Contributor

PRFlow Review — PR #1963

Status: ❌ Review failed

Review run failed: claude step failure — https://github.com/The01Geek/prflow/actions/runs/32924266453

@prflow-implementer

Copy link
Copy Markdown
Author

DevFlow review stall backstop — this cloud review ended with no verdict for 64c4be4d16043e3e9b15f99035b8e6f17ab56edd. Auto-resume attempt 2:

/devflow:review

@github-actions

Copy link
Copy Markdown
Contributor

PRFlow review: no verdict-post receipt was found for this run.

  • Actions run id: 32924266453
  • Pull-request head SHA this step resolved: 64c4be4d16043e3e9b15f99035b8e6f17ab56edd

No run-scoped verdict-post receipt was found for this run: either Phase 4.4's
verdict emitter did not run, or it ran and could not write its receipt (look for a
could not write the verdict-post receipt breadcrumb in the job log).

No review authored by this run's reviewer identity is recorded in the reviews
API for the head above, so this run left the reviews API and reviewDecision untouched.
It recorded no verdict anywhere.

Any verdict text this run published OUTSIDE the emitter carries no producer-emitted verdict marker,
and the verdict-derivation consumers do not read it as a verdict.

This comment is a record of that gap. It is not a verdict, and it neither approves nor
rejects this pull request.

@prflow-reviewer prflow-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE with notes (all acceptance criteria satisfied and cross-file contracts verified against the shipped bytes; one Important test-coverage gap and several refinement suggestions, all below the merge-gating threshold)

Issue Compliance

Reviewed against issue #1388: Lint provisioning: installer digest-tuple + before-Claude tool install + setup lint_mode + review-runner isolation — criteria from the /prflow:implement workpad comment; scope unchanged. Requirement-based checklist items are included in the verification results below.

  • Criteria came from the /prflow:implement workpad comment — this run's authoritative set, possibly narrowed from the issue.
  • No divergence between the workpad's criteria and the issue body's.

All twelve acceptance criteria were checked against the shipped code (read at head 64c4be4d, since the review checkout sits on the default branch) and each is satisfied:

  • Digest-bound tuple published last (install.sh §4b validates the staged manifest, then install_state.py build publishes .prflow/install-state.json — ordering driven end-to-end by new installer-wiring.sh arms). ✓
  • Fail-closed fixtures (first-install / backfill / missing-marker / version-skew / interrupted-publication) resolve deterministically before any tool work via check_readiness. ✓
  • .gitignore re-includes !/.prflow/install-state.json (and the manifest); both files tracked at head. ✓
  • Closed lint_mode input; provision/none accepted, any other value ::error::+exit 1. ✓
  • provision installs run-local (no sudo), digest-before-extract and whole-token version verified, cache re-verified. ✓
  • none short-circuits before any manifest read. ✓
  • devflow.yml/devflow-implement.yml/devflow-runner.yml pass none/provision/none (pinned by count). ✓
  • Review-runner isolation: hardensetup materializes the whole setup-project-env dir from trusted BASE_REF FETCH_HEAD, prunes PR-added files, fails closed, and precedes the uses: step; paired with lint_mode: none. ✓
  • Platform matrix declared; unsupported-lint-platform for the one unsupported tuple. ✓
  • Six integrity arms each fail closed naming the tool. ✓
  • CI lint-manifest job validates+exercises the manifest under contents: read, no App token. ✓
  • Uniquely-named changeset with bump: patch frontmatter added. ✓

Verification Checklist Results

59 verification items (deduped from 3 generator batches) — 0 failed, 0 inconclusive. The cross-file contracts (helper CLI signatures, exit-code conventions, component-name sets, the validate_state fail-closed matrix, check_readiness fixtures, the MISSING_FILE_REASON equality distinction, the URL-metachar safety invariant, the .gitignore/vendor-slice shipping, run.sh pin counts, and each acceptance criterion) were confirmed against the shipped bytes by the Phase-3 agents and by direct git show probes at head, and the green CI (all five suite shards, lib + python tests, lint, and the lint-manifest validate+exercise job) corroborates the digest-tuple, drift-gate, and manifest-matrix items.

Engine ground truth for reviewed commit 64c4be4d: lib + python tests: success, lint (shellcheck + actionlint + ruff): success, lint-manifest: success, and all five shards success.

Code Review Findings

🟠 Important / Major

  1. Artifact-URL and pinned-digest correctness is exercised against upstream for only the linux/x86_64 tuple (the CI lint-manifest job downloads and digest-verifies that one); the other 8 tuples' os/arch/target-triple mappings (_SHELLCHECK_OS/_SHELLCHECK_ARCH/_RUFF_TARGET) and their pinned digests carry no string-equality or real-download assertion. A transcription error in one of those mappings or digests produces a plausible-but-wrong URL (404) or a digest mismatch that no test catches — and because a mismatch/404 on a declared tuple is a hard _die (exit 1) in an un-continue-on-error step, it aborts the whole implement job at setup on that platform (a hard abort, not the unsupported-lint-platform warn-and-continue degrade) — precisely the paid-turn failure this feature exists to remove. A CI matrix that resolves+downloads each declared artifact (or per-tuple URL-string assertions) would catch it before merge. (raised by 2/6 agents — Important from pr-test-analyzer, Suggestion from the final-pass reviewer.) Below the default critical threshold, so it does not block; worth addressing.

🟡 Suggestion / Minor

  1. provision-lint-tools.sh hard-aborts (_die, exit 1) on a missing extraction primitive (unzip/tar) for a supported tuple rather than degrading — in tension with the graceful-degradation convention this PR adds to CLAUDE.md. A deliberate, tested choice (integrity failure vs. unsupported-platform degrade), but a Windows consumer lacking unzip would see a hard implement-run abort; worth a conscious confirmation. (code-reviewer, sub-threshold confidence)
  2. install.sh §4b silently skips the marker-publish block (no log line) when its source preconditions are absent, unlike every other §4b arm; the fail-closed consequence is correct but undiagnosed for the operator. (silent-failure-hunter)
  3. check_readiness digest-checks each recorded component but does not assert the recorded component set is complete — a trust-only defense-in-depth gap, not a live fail-open (the marker is trusted committed/base-ref output). (silent-failure-hunter)
  4. StateResult.state stores the raw parsed dict directly, so the frozen wrapper's immutability is shallow — a caller could mutate sr.state[...] and invalidate the validated guarantee. Advisory; current callers don't mutate. (type-design-analyzer)
  5. Plan lacks the .established boolean discriminator its siblings (StateResult.established, Readiness.ready) expose, forcing stringly-typed status == "established" compares at call sites. (type-design-analyzer)
  6. Plan.__init__ mandates completeness only for _RESOLVED_FIELDS; the identity fields tool/os/arch (read by the cache-key path) may be None on an established plan. Latent — build_plan always supplies them. (type-design-analyzer)
  7. Plan's __setattr__/__init__ comments say "defeat the XOR" though Plan is a three-way discriminated union, not an XOR — the comment misdescribes the invariant it guards. (type-design-analyzer)
  8. Plan.__init__ silently ignores unknown keyword arguments (**kw), so a typo'd kwarg surfaces as the less-direct "missing resolved fields" error; its sibling types use explicit named parameters. (type-design-analyzer)
  9. The devflow-runner.yml hardensetup rationale comment block (~11 lines) exceeds the three-line prevention-comment cap and carries mechanism derivation duplicated in docs/internal/cloud-setup.md; the load-bearing "fail closed / no PR-head fallback" prohibition is the keeper. Demotable (undecidable-load-bearing carve-out plausibly applies). (comment-analyzer)
  10. Test refinements: the version-skew "either direction" arm is exercised by a single mutation; the unwritable-target arm no-ops (false pass) under root; the full platform matrix and real downloads run only in CI, not the offline suite. (pr-test-analyzer)

over-grade annotation: no finding flagged.
truthfulness sweep: no finding promoted — all six agents verified the doc/comment/release-note claims (the review-runner isolation claim, the "no shell metacharacter reaches the URL" invariant, the CLAUDE.md convention addition) against the shipped code and found no documented falsehoods.
intra-diff contradiction scan: no contradiction found.

Prompt-surface edit routing gate: the diff touches CLAUDE.md but no writing-skills trigger glob (skills/*/SKILL.md, skills/*/phases|references/*.md, .prflow/prompt-extensions/*.md) — the gate draws no finding this run.

Notes

  • No self-contradicting-diff / documented-falsehood findings; no Phase 0.6 STALE findings (the stale-prose lint reported only UNRESOLVABLE/coverage-drop informational rows, expected because the review checkout resolves --rev HEAD on the default branch where the PR's new files do not yet exist).
  • This is an exceptionally well-tested, security-conscious change. The type-design (both-direction invariants + frozen __setattr__), the fail-closed provisioning arms (each driven end-to-end), and the read-only review-runner isolation are all correct. The single Important item is a coverage gap, not a demonstrated defect.

@github-actions

Copy link
Copy Markdown
Contributor

PRFlow Review — PR #1963

Status: ❌ Review failed

Review run failed: claude step success but the run wrote no verdict (engine reported no error) — https://github.com/The01Geek/prflow/actions/runs/32924350684

@prflow-implementer

Copy link
Copy Markdown
Author

/prflow:review

@github-actions

Copy link
Copy Markdown
Contributor

PRFlow Review — PR #1963

Status: ❌ Review failed

Review run failed: claude step cancelled — https://github.com/The01Geek/prflow/actions/runs/32925877001

@github-actions

Copy link
Copy Markdown
Contributor

PRFlow review: no verdict-post receipt was found for this run.

  • Actions run id: 32925877001
  • Pull-request head SHA this step resolved: 743cdef0bc98ab5afe117e73c757910df434d78b

No run-scoped verdict-post receipt was found for this run: either Phase 4.4's
verdict emitter did not run, or it ran and could not write its receipt (look for a
could not write the verdict-post receipt breadcrumb in the job log).

No review authored by this run's reviewer identity is recorded in the reviews
API for the head above, so this run left the reviews API and reviewDecision untouched.
It recorded no verdict anywhere.

Any verdict text this run published OUTSIDE the emitter carries no producer-emitted verdict marker,
and the verdict-derivation consumers do not read it as a verdict.

This comment is a record of that gap. It is not a verdict, and it neither approves nor
rejects this pull request.

@The01Geek
The01Geek marked this pull request as draft August 26, 2026 03:20
The01Geek and others added 2 commits August 25, 2026 22:06
Critical — install.sh section 4b published a marker no consumer could satisfy.
The marker digested SOURCE bytes for the four copy-loop components while
recording consumer runtime paths, but install_managed PRESERVES a locally
modified artifact and the tier1_rc arm skips the workflow copy. check_readiness
then returned digest-mismatch forever and provision-lint-tools.sh _die'd the
whole implement job, with a re-run-the-installer remedy that reproduced the
identical marker. The marker now describes the target tree; the new
--digest-root carries the one real exception (the vendor-fetched readers, absent
at install time). Proven end-to-end through the real installer in
installer-wiring: a preserved local edit now converges to READY on re-run.

Critical — devflow-runner.yml's hardensetup displaced PR-head bytes for the
setup-project-env action but published no displaced_paths and was absent from
displaced_join, so the reviewing agent read base-ref bytes as head on exactly
the file a PR editing that action is under review for. It now discloses what it
displaced and the join consumes it.

Also: build_state validates its own output before publishing; Plan rejects
unknown keywords instead of composing a "None" cache key; TOOLS is derived from
the validated manifest rather than a third hardcoded copy; the SCRIPTS_DIR
fallback probes both helpers and refuses when neither candidate carries them;
the prune loop's membership test uses a bash builtin instead of grep (a missing
grep pruned every trusted file it had just materialized); the base-ref fetch
keeps its own diagnostic; _die no longer leaks its work directory;
generate-install-state.py refuses an unrecognized argument instead of writing;
4b's outer guard says why it refused; __delattr__ is guarded on the immutable
result types.

Tests: the two Criticals, the GITHUB_PATH export, tar.xz extraction (the archive
type every real shellcheck artifact uses), the drift gate's RED direction, and a
readiness — not merely structural — positive control against the consumer tree.
The COMPONENTS tuple's two transcriptions are now reconciled both ways round.
Replaced a wording-only pin over log prose with a structural one; guarded a
permission fixture on uid; trimmed the diff's over-cap comment blocks.

Pushed back: the "hand-edited digest" Critical was refuted upstream — the new
pins are correct and main's shellcheck pin was wrong. The missing-unzip/tar
fail-closed arm is AC10's stated behavior, not a defect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The01Geek and others added 2 commits August 25, 2026 22:26
The new hardensetup disclosure published a second `displaced_paths<<` heredoc,
which broke #504's uniqueness pin on harden_hooks' own output. Re-wording that
pin is not available here — the #810 classifier refuses a changed literal, and
re-adjudicating it cannot share a branch with the change that needs it. Give the
new producer its own output key instead (`displaced_setup_paths`): the two
producers disclose different path sets, so distinct names are right on their own
merits, and harden_hooks' literal stays unique and unedited.

The new #1963 assertions also added label 1963 to installer-wiring, which
test_coverage_map_guard.py pins as an exact set — that one pinned set is what
turned #591 and, through it, #1055 red on three shards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The three new #1963 readiness assertions moved the module's count, and the floor
is an exact-policy coupled pair — the registry literal and run.sh's must move in
the same commit or python-pool goes red on the mismatch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@The01Geek
The01Geek marked this pull request as ready for review August 26, 2026 04:59
# Conflicts:
#	docs/external/release-notes.md
@prflow-implementer

Copy link
Copy Markdown
Author

/prflow:review

@prflow-reviewer

prflow-reviewer Bot commented Aug 26, 2026

Copy link
Copy Markdown

PRFlow Review — PR #1963

Status: ✅ APPROVE with notes
Diff profile: engine_self_modifying (+ has_new_types + detect_all_audit) → full checklist + completeness-critic pass
Run: View run
Reviewed HEAD: f8a3e84
Last updated: Phase 4

Blueprint

  • Classify diff (Phase 0.5)
  • Generate verification checklist (Phase 1)
  • Verify checklist (Phase 2)
  • Review agents (Phase 3)
  • Aggregate & verdict (Phase 4)
  • Run complete — everything this run owed

Verdict: APPROVE with notes (correct, thoroughly-tested lint-provisioning change; CI green; only sub-threshold suggestions and one non-blocking test-coverage note)

Issue Compliance

Reviewed against issue #1388: [#1276 follow-up] Lint provisioning — criteria from the /prflow:implement workpad comment (this run's authoritative set); scope unchanged. No divergence between the workpad's criteria and the issue body's. All 12 acceptance criteria were verified satisfied against the shipped code at HEAD (digest-bound tuple + publish-after-validate; fail-closed readiness fixtures; .gitignore re-include + both ship; closed lint_mode refusing unknowns; provision-before-Claude run-local no-sudo with digest+version verify and cache re-verification; none-mode no-op; the none/provision/none workflow wiring; review-runner base-ref isolation with a PR-head-edit fixture proven unreachable; the platform matrix + unsupported-lint-platform; the six named fail-closed arms; the no-write-creds CI manifest job; the bump: changeset). Requirement-based checklist items are included in the verification results below.

Verification Checklist Results

61 passed, 0 failed, 0 inconclusive — 4 via lite probe, 57 via agent.

Three items passed with a wording-only claim inaccuracy (the shipped code implements the targeted property correctly; only the checklist-generator's incidental locators were off):

  • VC-39: NORMALIZED (wording-only) — install.sh 4b DOES emit the three log substrings; the claim's separate assertion that installer-wiring.sh greps them was inaccurate [install.sh].
  • VC-42: NORMALIZED (wording-only) — the AC re-verification property holds (digest+version verified before readiness; cache restores re-verified via the helper's [ -x dest ] version re-check; the actions/cache hashFiles(manifest,install-state) key transitively covers the six-field tuple). The claim over-specified that lint_provision.cache_key's log-only six-field string IS the actions/cache key [scripts/lint_provision.py].
  • VC-54: NORMALIZED (wording-only) — the test's expected installer-wiring labelset (incl. 1388/1963) is present and passes; the claim misdescribed the derivation source (module text, not coverage-map.json) [lib/test/test_coverage_map_guard.py].

Note: an initial verification pass mis-graded VC-27/VC-51/VC-52 as FAIL; a focused re-verification confirmed each is a clean PASS (the fail-closed fixtures do live in installer-wiring.sh; run.sh pins the provision_env gate count as 2 and the BASE_REF/git-fetch counts as 5/5, all matching the shipped workflow — consistent with the green lib + python tests check).

✅ Passed items (58 of 61) — click to expand

All checklist items resolved PASS. Highlights verified against PR-head content (git show f8a3e84…):

  • Cross-file contracts: install_state.py verify/build arg + NOT-READY prefix; lint_provision.py plan/cache-key field order & exit codes (2/3/4); ManifestResult/Readiness/StateResult APIs; build_state signature; MISSING_FILE_REASON equality discriminator.
  • Digest tuple: install-state.json's 7 component sha256 digests all match the PR-head file bytes; install.sh 4b --component/--digest-root/--record-path operands reconcile both-ways with generate-install-state.py COMPONENTS; drift --check exit codes 0/1/2.
  • Wiring & isolation: action.yml closed lint_mode refusal; RUNNER_OS/ARCH mapping; hardensetup base-ref materialization + prune + displaced_setup_paths join; vendor-slice + .gitignore shipping both files; CI lint-manifest job under contents: read.
  • Tests: platform matrix, validate_state six-shape matrix, check_readiness states, the six provision fail-closed arms, hardcoded tool versions matching the manifest, the helper env contract.

Code Review Findings

🟠 Important / Major

  1. The composite action's embedded provisioning bash in .github/actions/setup-project-env/action.yml (the RUNNER_OS→TARGET_OS / RUNNER_ARCH→TARGET_ARCH translation, the two-helper SCRIPTS_DIR probe, and the none/unknown-lint_mode control flow) is presence-pinned via substring assertions rather than executed end-to-end. provision-lint-tools.sh is driven with those env vars pre-set, so a mis-map (e.g. macOS→linux), an ||-vs-&& slip in the two-helper probe, or a broken none arm would pass every test. The hardensetup step in this same PR demonstrates the executable-extraction pattern that would close it. (raised by 1/6 agents) [suspected over-grade: shape 3 — single-source pr-test-analyzer test_gap; observable fail-direction is a coverage gap in a thin translation layer whose mappings this review verified correct at HEAD, milder than the Important label — advisory only, does not change the verdict]

🟡 Suggestion / Minor

  1. Fail-closed-vs-degrade calibration: provision-lint-tools.sh hard-fails the whole pre-model step on a network failure / missing installer primitive (curl/tar/unzip), which sits in tension with this PR's own new CLAUDE.md convention ("reserve fail-closed for integrity checks; degrade on an uncovered platform"). This is a deliberate, defensible calibration — AC10 explicitly requires those six arms to fail before model execution naming the tool, and the degrade clause is scoped to platform coverage, not availability — but a retry-once-then-degrade on transient network/CDN flakiness would better match the stated convention. Worth a conscious decision. (raised by 2/6 agents)
  2. hardensetup's PR-added-file prune loop is not emptiness-guarded like its materialization loop; if git ls-files errored it would prune nothing. Blast radius is small (every base file is already overwritten and the trusted action.yml references no PR-added helper), but asserting the enumeration ran would restore symmetry with the step's fail-closed posture. (raised by 1/6 agents)
  3. Three inline comment blocks exceed the 3-line prevention-only standard by mixing prevention with derivation: install.sh §4b (~L1531-1540), .github/workflows/devflow-runner.yml hardensetup rationale (~7 lines), and two adjacent action.yml notes (borderline). The derivation is already restated in docs/internal/. (raised by 1/6 agents)
  4. Type design: Plan (scripts/lint_provision.py) lacks the established predicate its three sibling result types carry, forcing bare magic-string status comparisons (plan.status == "established") at call sites; and StateResult.state / ManifestResult.manifest expose the validated JSON dict by live reference (acceptable for the trusted single-process callers, but a MappingProxyType view would close it). (raised by 1/6 agents)
  5. lint_provision.py's cache-key CLI subcommand is not exercised via subprocess (only the plan subcommand's exit codes are). A direct assertion would close the last untested CLI branch. (raised by 1/6 agents)

ℹ️ Notes

  • Completeness-critic pass (Phase 3.1.5, forced by detect_all_audit): the digest-bound COMPONENTS tuple was independently re-enumerated from the provisioning runtime dependency chain (what provision-lint-tools.sh execs/reads: install_state.py, lint_provision.py, lint_manifest.py, the manifest; what action.yml invokes; and the one workflow that provisions, devflow-implement.yml). That independent set equals the 7 declared COMPONENTS exactly — the audit is a superset of the independent signal, i.e. complete with respect to it (not a proof of exhaustiveness).
  • over-grade annotation: one finding flagged (the Important test-gap above, shape 3 — advisory, verdict unchanged).
  • intra-diff contradiction scan: no contradiction found (the new fail-closed convention and the helper's fail-closed network/primitive arms are scoped-distinct — platform coverage vs availability/integrity — not a self-contradiction).
  • truthfulness sweep: no finding promoted (no diff-added doc line, comment, example, or command-form was demonstrated false against HEAD; code-reviewer and comment-analyzer both explicitly found no documented_falsehood).
  • Prompt-surface edit routing evidence gate (repo policy): not triggered — the diff touches no skills/**/agents/**/.prflow/prompt-extensions/** trigger path.
  • Verification-evidence marker advisory (repo policy, non-blocking): informational only for this standalone review; the PR was produced by a cloud /prflow:implement run.

Verdict Basis

  • No verification-checklist FAIL or INCONCLUSIVE (rule 1/2 clear).
  • No self-contradicting-diff finding (carve-out clear).
  • No finding at or above the critical verdict threshold (rule 3 clear); the one Important finding is a non-blocking test-coverage gap.
  • All 6 Phase-3 agents returned (no partial-coverage caveat); checklist ran in full.
  • CI ground truth for f8a3e84: lib + python tests = success, lint (shellcheck + actionlint + ruff) = success, all four shards + manifest jobs = success.
    → Phase 4.2 rule 6: APPROVE with notes.

Run telemetry

Per-phase call/token/wall-clock figures were not established this run → unavailable. Durable persistence (--persist) is gated out on the read-only cloud review profile; this comment is the surface.

Subagent effectiveness trace

Iteration 1

  • Diff profile: engine_self_modifying + has_new_types (+ detect_all_audit)
  • Phase 3 agents dispatched: 6
  • Checklist verifiers: 4 lite, 57 agent.
  • Effectiveness signal: verdict contribution (standalone review applies no fixes) — 4 of 6 agents contributed
  • Agent verdicts:
    • prflow:code-reviewer — null (no findings)
    • prflow:comment-analyzer — unique-effective
    • prflow:pr-test-analyzer — unique-effective
    • prflow:requesting-code-review — null (recommended APPROVE, no distinct blocking finding)
    • prflow:silent-failure-hunter — corroborating
    • prflow:type-design-analyzer — unique-effective

@prflow-reviewer prflow-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE with notes — full report in PR comment

The complete review report (checklist results, findings, details) is in the
PRFlow Review progress comment on this PR.

@The01Geek
The01Geek merged commit a8ad7b7 into main Aug 26, 2026
11 checks passed
@The01Geek
The01Geek deleted the issue-1388-1276-follow-up-lint-provisioning-installer-digest branch August 26, 2026 05:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[#1276 follow-up] Lint provisioning: installer digest-tuple + before-Claude tool install + setup lint_mode + review-runner isolation

1 participant