docs: point the README at action.yml and the logged remedy #34

Merged
jercik merged 1 commit from docs/readme-input-and-remedy-pointers into main 2026-10-05 09:31:27 +00:00
Owner

Replaces the action input table and the recovery table with pointers to action.yml and the remedy on the failure log line, as the review of #32 asked (claims 01M44DMTYMV2XX9RFN5A2MCZCP and 01M44DNK5MWG0DWZ49QPQ50Q54).

🤖 Generated with Claude Code

Replaces the action input table and the recovery table with pointers to `action.yml` and the remedy on the failure log line, as the [review of #32](https://code.j4k.dev/j4k-oss/review-wrapper/pulls/32#issuecomment-122860) asked (claims `01M44DMTYMV2XX9RFN5A2MCZCP` and `01M44DNK5MWG0DWZ49QPQ50Q54`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
docs: point the README at action.yml and the logged remedy
All checks were successful
commit-msg / commitlint (pull_request) Successful in 23s
Checks / quality-checks (pull_request) Successful in 41s
Review / Review (pull_request_target) Successful in 10m40s
5a6debdac2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Review 01M44EGC4KRKD9TTZRKSQTATG0 — head 5a6debdac2ed2bbc24f0486148fddbaccf046af0

Review — j4k-oss/review-wrapper @ b6f65f5ce7

Scope: diff against base tree e69943819640
Status: dispatched — coverage complete (5/5 slots terminal)
Facts: current review-wide projection

Computed under:

{
  "abandonment": "abandonment-v1",
  "anchor_recipe": 1,
  "batch_policy": "batch-v1",
  "coverage": "coverage-v3",
  "dispatch_policy": "dispatch-v2",
  "grounder_version": 1,
  "grounding_read_rule": "grounding-read-v1",
  "promotion_policy": "promotion-v1",
  "report": "report-v4",
  "tally": "tally-v1",
  "triage_settle": "triage-settle-v2"
}

Findings (6)

medium — Outcome table duplicates the code-defined outcome set and exit map

  • claim: 01M44ER8R4WSMZGXPXSBXDM0ZN
  • anchor: README.md (snippet)
## Outcomes and exit codes

| Row | Outcome               | Behaviour                                                                                                                                                     | Exit |
| --- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| 1   | `fork-skip`           | Explicit "not reviewed" block; no `/v1` call precedes it.                                                                                                     | 0    |
| 2   | `service-unreachable` | No comment — there is nothing to reconcile.                                                                                                                   | 1    |
| 3   | `auth-failed`         | Names the `REVIEW_CAPABILITY_TOKEN` rotation.                                                                                                                 | 1    |
| 4   | `forge-host-mismatch` | Quotes the service's problem detail.                                                                                                                          | 1    |
| 5   | `ask-conflict`        | Names the two real causes of an ask-key conflict.                                                                                                             | 1    |
| 6   | `stuck`               | Reconciles what stands, then fails (includes a stalled triage). Job log: `review did not settle (<kind>):`.                                                   | 1    |
| 7   | `partial-coverage`    | Full reconcile, then fails. Names the lossy slots and a dispatch re-ask from `main`. Job log: `review wrapper (partial-coverage):`.                           | 1    |
| 8   | `grounding-pending`   | Full reconcile, then fails.                                                                                                                                   | 1    |
| 9   | `clean-zero-findings` | Full reconcile with an explicit zero block.                                                                                                                   | 0    |
| 10  | `clean-findings`      | Full reconcile.                                                                                                                                               | 0    |
| 11  | superseded            | A flag, not an outcome; it does not itself change the exit code. Coverage and report accounting stay pinned; triage, claims, and findings remain review-wide. | —    |
| 12  | `head-moved`          | No PR comment or conversation-resolution writes; the newer run owns the comment surface. See the timing below.                                                | 0    |
  • lens: writing-quality · arm: default
  • verdicts: 1 valid / 0 invalid / 0 uncertain
    • pass 01M44EYYY88QXSWAJX4VDVC1GR · valid: The exact grounded README table enumerates the outcomes and exit values. The reviewer reports that src/contract/types.ts defines OUTCOMES and OUTCOME_EXIT with the same outcome members and values; superseded is separately labeled a flag. This is a second, currently matching copy of a code-defined set and map, so it can silently stale. Point to the definitions and retain behavior details without a complete member list or row numbering. Medium severity fits because no current disagreement is shown.
  • disposition: none

The outcome table is a second definition of the wrapper's outcomes and exit codes. A future change to the code can leave this operational reference stale without affecting the build. The table also numbers the outcomes and inserts superseded as a numbered row even though the text correctly calls it a flag, so later prose depends on table positions rather than stable concepts.

src/contract/types.ts defines OUTCOMES and OUTCOME_EXIT. Its outcome members are fork-skip, service-unreachable, auth-failed, forge-host-mismatch, ask-conflict, stuck, partial-coverage, grounding-pending, clean-zero-findings, clean-findings, and head-moved. The README covers those same members, plus the separate superseded flag; none of the outcome members differs at this revision. The table's exit values agree with OUTCOME_EXIT.

Replace the table with a pointer to OUTCOMES and OUTCOME_EXIT, and refer to outcomes by name instead of row number. Keep the operational details readers need, such as head-move timing and pinned-pass behavior, in the focused prose below or beside the relevant source definitions. This follows the writing standard's One Idea, One Place guidance and preserves the behavior while removing a set that must be updated in two places.

I read the full README, src/contract/types.ts, src/wrapper/orchestrator.ts, and src/wrapper/classify-outcome.ts; this is a source comparison, not a run. A generated README whose outcome table is itself consumed as the source of truth would refute the duplication, but the reviewed action reads the TypeScript definitions.

medium — Required environment variable list duplicates the code reads

  • claim: 01M44ERZV3461Q6CTWXGBKQ621
  • anchor: README.md (snippet)
The forge and runner variables — `FORGEJO_TOKEN`, `GITHUB_API_URL`, `GITHUB_EVENT_*`,
`GITHUB_RUN_ID`, `GITHUB_RUN_ATTEMPT`, `GITHUB_REPOSITORY`, `GITHUB_SERVER_URL` — are
required on every path.
`REVIEW_SERVICE_URL` and `REVIEW_CAPABILITY_TOKEN` are required only on the
same-repository path: a fork pull request returns at the fork gate without opening a
service session, so neither is ever read. Where a variable is required, a missing one is
a configuration defect and fails the run immediately rather than sending an
empty-string request.
  • lens: writing-quality · arm: default
  • verdicts: 1 valid / 0 invalid / 0 uncertain
    • pass 01M44EYYY88QXSWAJX4VDVC1GR · valid: The exact grounded paragraph presents the always-required environment variables as a complete list. The reviewer's specific trace names the reads in src/credentials.ts, src/wrapper/event-context.ts, and src/main.ts and says the code-defined members currently agree, including the two variables abbreviated by GITHUB_EVENT_*. The paragraph therefore restates that set. Point to those reads while retaining the fork-gate and failure behavior. Medium severity fits the matching copy.
  • disposition: none

The paragraph copies the complete set of variables required on every run. Adding or removing a required environment read can leave the README silently out of sync with the workflow's actual contract, even though the surrounding fork-gate explanation remains useful.

The source reads these always-required members: FORGEJO_TOKEN and GITHUB_API_URL in forgeCredentials; GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REPOSITORY, GITHUB_RUN_ID, GITHUB_RUN_ATTEMPT, and GITHUB_SERVER_URL in readWrapperInputs or main.ts. The README's GITHUB_EVENT_* covers the two event variables and otherwise names the same eight members; none differs at this revision. reviewCredentials separately reads REVIEW_SERVICE_URL and REVIEW_CAPABILITY_TOKEN only after the fork gate, as the README says.

Point to the environment reads in src/credentials.ts, src/wrapper/event-context.ts, and src/main.ts instead of repeating the complete list. Keep the fork-gate distinction and the fail-fast behavior in this paragraph. This follows One Idea, One Place while preserving the condition a workflow author needs.

I compared the full README with the environment reads in those three files and the gate in src/wrapper/orchestrator.ts; this is static source tracing. A separate generated configuration consumed by the action could make the README the source, but no such read appears in this path.

medium — Composition paragraph restates the reconciler set

  • claim: 01M44ESFQDXQ48MJPBXF6AWNQ8
  • anchor: README.md (snippet)
`src/main.ts` is the only place the modules meet. It reads the forge credentials and
`GITHUB_REPOSITORY`, builds the forge client, derives the run's inputs from the event
payload, and hands the orchestrator its collaborators: the wait protocol, the clock, and
the three reconcilers (summary, inline, dispositions). The orchestrator's exit code
becomes the process exit code, so the job's conclusion is the outcome table above.
  • lens: writing-quality · arm: default
  • verdicts: 1 valid / 0 invalid / 0 uncertain
    • pass 01M44EYYY88QXSWAJX4VDVC1GR · valid: The exact grounded architecture paragraph counts and names all three reconcilers. The reviewer reports that WrapperDeps in src/contract/types.ts defines exactly summary, inline, and dispositions and that src/main.ts supplies them. That is a complete copy of the contract-defined reconciler set; the useful wiring explanation can instead point to WrapperDeps. No member difference is reported, so medium severity fits.
  • disposition: none

This architecture paragraph counts and names the complete set of reconcilers that main.ts passes to the orchestrator. A new reconciler can be added to the dependency contract while this count and list stay unchanged, giving maintainers a false map of the composition point.

WrapperDeps in src/contract/types.ts defines the reconciler members summary, inline, and dispositions; the README covers the same three, with no difference at this revision. src/main.ts supplies those fields to runWrapper.

Replace the count and names with a pointer to WrapperDeps, while retaining that main.ts wires the dependencies and that the orchestrator's exit code becomes the job conclusion. The following paragraph should keep the session-thunk and fork-gate reason. This follows the writing standard's One Idea, One Place guidance without losing the useful architecture and security explanation.

I read the complete README, src/main.ts, and src/contract/types.ts; this is a direct source comparison. The claim would be refuted if the README were the consumed definition of the reconciler set, but the reviewed program uses the TypeScript interface and main.ts call.

medium — Fork-gate text overstates when credentials are read

  • claim: 01M44EV6MF7NQX2DJET42P268A
  • anchor: README.md (snippet)
- Forgejo does pass secrets to fork `pull_request_target` runs, so the real boundary is
  code-path ordering: the fork gate is evaluated from the event payload **before** the
  credentials module is ever called. On the fork path the capability token environment
  variable is never read — enforced by a static isolation test and an orchestrator unit
  test.
  • lens: writing-quality · arm: default
  • verdicts: 1 valid / 0 invalid / 0 uncertain
    • pass 01M44EYYY88QXSWAJX4VDVC1GR · valid: The exact grounded README sentence says the fork gate precedes any call to the credentials module. The reviewer's concrete call trace says main.ts invokes forgeCredentials() before deriving isFork, while reviewCredentials() is deferred through openSession and bypassed on the fork branch. Thus the broad sentence is false even though the capability-token isolation it seeks to explain holds. Naming reviewCredentials() is the precise correction.
  • disposition: none

This security explanation says the fork gate runs before the credentials module is ever called. In fact, src/main.ts calls forgeCredentials() before deriving inputs.isFork, so a fork run reads the forge task token before the gate. A reader auditing fork exposure could therefore infer a broader isolation guarantee than the code provides.

The intended and implemented narrower guarantee is that the capability token is not read on a fork run. main.ts passes openSession as a thunk; runWrapper returns from the fork branch before review() calls it; only that thunk invokes reviewCredentials().

Replace “before the credentials module is ever called” with “before reviewCredentials() is called.” Keep the surrounding explanation that Forgejo exposes secrets to fork-triggered jobs and that the capability token is isolated by code-path ordering. This follows the writing standard's Precise Language guidance and preserves the security boundary that is actually enforced.

I traced src/main.ts, src/credentials.ts, and src/wrapper/orchestrator.ts and read the full README; this is static call-order reasoning. A different bundle entry point could change the order, but the reviewed action names dist/index.mjs built from main.ts.

low — Service URL rule omits the rejected trailing-slash form

  • claim: 01M44EVPVQHRSTQHN71HRTHTSK
  • anchor: README.md (snippet)
| `REVIEW_SERVICE_URL`                 | Base URL of the review service (an org-managed variable). Must parse as an `https:` URL and must not end with `/v1`, which the client appends. |
  • lens: writing-quality · arm: default
  • verdicts: 1 valid / 0 invalid / 0 uncertain
    • pass 01M44EYYY88QXSWAJX4VDVC1GR · valid: The exact grounded README rule excludes a URL ending in /v1 but does not describe /v1/. The reviewer reports that serviceOrigin in src/credentials.ts checks the parsed pathname with a regex matching both suffixes and rejects either before opening a session. A plausible workflow author can supply the omitted trailing-slash form and get a configuration failure. State that /v1 cannot be the final path segment with or without a trailing slash.
  • disposition: none

A workflow author could read “must not end with /v1” and configure an HTTPS base ending in /v1/, a common URL form, expecting it to pass. The action rejects that value before opening a service session, so the stated validation rule omits a case that changes whether the job runs.

serviceOrigin in src/credentials.ts tests the parsed pathname with /\/v1\/?$/u, which matches both final forms. It throws before REVIEW_CAPABILITY_TOKEN is read when either matches.

Say that /v1 cannot be the final path segment, with or without a trailing slash, because the client appends the API prefix. This is the smallest addition that prevents the likely configuration mistake and keeps the current rationale. It follows the writing standard's Precise Language guidance.

I read the README row, serviceOrigin, and the reviewCredentials call path; this is a static regex and call-order check, not a workflow run. A direct reviewCredentials test with https://example.test/v1/ would establish the same rejection, while a changed suffix validator would refute this reading.

low — Environment table overstates which PR inputs come from the payload

  • claim: 01M44EX41M27Y04WKDRRFP5MMH
  • anchor: README.md (snippet)
| `GITHUB_EVENT_*`, `GITHUB_RUN_ID`, … | Standard runner event environment; the wrapper's PR inputs come from the event payload.                                                        |
  • lens: writing-quality · arm: default
  • verdicts: 1 valid / 0 invalid / 0 uncertain
    • pass 01M44EYYY88QXSWAJX4VDVC1GR · valid: The exact grounded table row says the wrapper's PR inputs come from the event payload without limiting the event type. The reviewer's branch trace says workflow_dispatch obtains only pr_number there and fetches head, branches, repository identity, and state through forge.getPull, while pull_request_target uses payload details. The later README note about dispatch does not make this row accurate; scope the payload statement to event-triggered PRs and describe the dispatch source.
  • disposition: none

This row says the wrapper's PR inputs come from the event payload without limiting that statement to event-triggered pull requests. A manual-dispatch reader could assume the payload contains the head and branch state used for review, although the wrapper fetches that state from the forge before it decides whether the pull request is a fork.

In readWrapperInputs, the pull_request_target arm reads PR details from the payload. The workflow_dispatch arm reads only pr_number from the payload, then calls forge.getPull(prNumber) for head SHA, branches, repository identity, and open/merged state. The README's later Notes section also says dispatch reads PR state from the forge.

Narrow the row to say that the event variables locate and identify the payload, and that PR state for manual dispatch comes from the forge. This follows the writing standard's Precise Language guidance, preserves the payload source for event runs, and removes an internal contradiction.

I read the entire README and traced both branches of src/wrapper/event-context.ts; this is static source comparison. A different dispatch input derivation would refute the claim, but this file is what main.ts calls.

Other claims

  • grounding-pending (0)
  • ungrounded (0)
  • rejected (0)
  • duplicate-of (0)
  • unadjudicated (0)

Coverage

Coverage pass: 01M44EGC7WHKVPV9866S8AKJ9J
Accounting: complete
Slot health: healthy

lens part arm unit status runs loss
general-bug whole default no-claims 1 no
writing-quality whole default claims-emitted 1 no
test-trimming whole default no-claims 1 no
restated-sets whole default no-claims 1 no
project-docs whole default no-claims 1 no
<!-- review:summary --> **Review** `01M44EGC4KRKD9TTZRKSQTATG0` — head `5a6debdac2ed2bbc24f0486148fddbaccf046af0` # Review — j4k-oss/review-wrapper @ b6f65f5ce707 Scope: diff against base tree `e69943819640` Status: dispatched — coverage complete (5/5 slots terminal) Facts: current review-wide projection Computed under: ```json { "abandonment": "abandonment-v1", "anchor_recipe": 1, "batch_policy": "batch-v1", "coverage": "coverage-v3", "dispatch_policy": "dispatch-v2", "grounder_version": 1, "grounding_read_rule": "grounding-read-v1", "promotion_policy": "promotion-v1", "report": "report-v4", "tally": "tally-v1", "triage_settle": "triage-settle-v2" } ``` ## Findings (6) ### medium — Outcome table duplicates the code-defined outcome set and exit map - claim: `01M44ER8R4WSMZGXPXSBXDM0ZN` - anchor: `README.md` (snippet) ``` ## Outcomes and exit codes | Row | Outcome | Behaviour | Exit | | --- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | | 1 | `fork-skip` | Explicit "not reviewed" block; no `/v1` call precedes it. | 0 | | 2 | `service-unreachable` | No comment — there is nothing to reconcile. | 1 | | 3 | `auth-failed` | Names the `REVIEW_CAPABILITY_TOKEN` rotation. | 1 | | 4 | `forge-host-mismatch` | Quotes the service's problem detail. | 1 | | 5 | `ask-conflict` | Names the two real causes of an ask-key conflict. | 1 | | 6 | `stuck` | Reconciles what stands, then fails (includes a stalled triage). Job log: `review did not settle (<kind>):`. | 1 | | 7 | `partial-coverage` | Full reconcile, then fails. Names the lossy slots and a dispatch re-ask from `main`. Job log: `review wrapper (partial-coverage):`. | 1 | | 8 | `grounding-pending` | Full reconcile, then fails. | 1 | | 9 | `clean-zero-findings` | Full reconcile with an explicit zero block. | 0 | | 10 | `clean-findings` | Full reconcile. | 0 | | 11 | superseded | A flag, not an outcome; it does not itself change the exit code. Coverage and report accounting stay pinned; triage, claims, and findings remain review-wide. | — | | 12 | `head-moved` | No PR comment or conversation-resolution writes; the newer run owns the comment surface. See the timing below. | 0 | ``` - lens: writing-quality · arm: default - verdicts: 1 valid / 0 invalid / 0 uncertain - pass `01M44EYYY88QXSWAJX4VDVC1GR` · valid: The exact grounded README table enumerates the outcomes and exit values. The reviewer reports that src/contract/types.ts defines OUTCOMES and OUTCOME_EXIT with the same outcome members and values; superseded is separately labeled a flag. This is a second, currently matching copy of a code-defined set and map, so it can silently stale. Point to the definitions and retain behavior details without a complete member list or row numbering. Medium severity fits because no current disagreement is shown. - disposition: none > The outcome table is a second definition of the wrapper's outcomes and exit codes. A future change to the code can leave this operational reference stale without affecting the build. The table also numbers the outcomes and inserts `superseded` as a numbered row even though the text correctly calls it a flag, so later prose depends on table positions rather than stable concepts. > > `src/contract/types.ts` defines `OUTCOMES` and `OUTCOME_EXIT`. Its outcome members are `fork-skip`, `service-unreachable`, `auth-failed`, `forge-host-mismatch`, `ask-conflict`, `stuck`, `partial-coverage`, `grounding-pending`, `clean-zero-findings`, `clean-findings`, and `head-moved`. The README covers those same members, plus the separate `superseded` flag; none of the outcome members differs at this revision. The table's exit values agree with `OUTCOME_EXIT`. > > Replace the table with a pointer to `OUTCOMES` and `OUTCOME_EXIT`, and refer to outcomes by name instead of row number. Keep the operational details readers need, such as head-move timing and pinned-pass behavior, in the focused prose below or beside the relevant source definitions. This follows the writing standard's One Idea, One Place guidance and preserves the behavior while removing a set that must be updated in two places. > > I read the full README, `src/contract/types.ts`, `src/wrapper/orchestrator.ts`, and `src/wrapper/classify-outcome.ts`; this is a source comparison, not a run. A generated README whose outcome table is itself consumed as the source of truth would refute the duplication, but the reviewed action reads the TypeScript definitions. ### medium — Required environment variable list duplicates the code reads - claim: `01M44ERZV3461Q6CTWXGBKQ621` - anchor: `README.md` (snippet) ``` The forge and runner variables — `FORGEJO_TOKEN`, `GITHUB_API_URL`, `GITHUB_EVENT_*`, `GITHUB_RUN_ID`, `GITHUB_RUN_ATTEMPT`, `GITHUB_REPOSITORY`, `GITHUB_SERVER_URL` — are required on every path. `REVIEW_SERVICE_URL` and `REVIEW_CAPABILITY_TOKEN` are required only on the same-repository path: a fork pull request returns at the fork gate without opening a service session, so neither is ever read. Where a variable is required, a missing one is a configuration defect and fails the run immediately rather than sending an empty-string request. ``` - lens: writing-quality · arm: default - verdicts: 1 valid / 0 invalid / 0 uncertain - pass `01M44EYYY88QXSWAJX4VDVC1GR` · valid: The exact grounded paragraph presents the always-required environment variables as a complete list. The reviewer's specific trace names the reads in src/credentials.ts, src/wrapper/event-context.ts, and src/main.ts and says the code-defined members currently agree, including the two variables abbreviated by GITHUB_EVENT_*. The paragraph therefore restates that set. Point to those reads while retaining the fork-gate and failure behavior. Medium severity fits the matching copy. - disposition: none > The paragraph copies the complete set of variables required on every run. Adding or removing a required environment read can leave the README silently out of sync with the workflow's actual contract, even though the surrounding fork-gate explanation remains useful. > > The source reads these always-required members: `FORGEJO_TOKEN` and `GITHUB_API_URL` in `forgeCredentials`; `GITHUB_EVENT_NAME`, `GITHUB_EVENT_PATH`, `GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, `GITHUB_RUN_ATTEMPT`, and `GITHUB_SERVER_URL` in `readWrapperInputs` or `main.ts`. The README's `GITHUB_EVENT_*` covers the two event variables and otherwise names the same eight members; none differs at this revision. `reviewCredentials` separately reads `REVIEW_SERVICE_URL` and `REVIEW_CAPABILITY_TOKEN` only after the fork gate, as the README says. > > Point to the environment reads in `src/credentials.ts`, `src/wrapper/event-context.ts`, and `src/main.ts` instead of repeating the complete list. Keep the fork-gate distinction and the fail-fast behavior in this paragraph. This follows One Idea, One Place while preserving the condition a workflow author needs. > > I compared the full README with the environment reads in those three files and the gate in `src/wrapper/orchestrator.ts`; this is static source tracing. A separate generated configuration consumed by the action could make the README the source, but no such read appears in this path. ### medium — Composition paragraph restates the reconciler set - claim: `01M44ESFQDXQ48MJPBXF6AWNQ8` - anchor: `README.md` (snippet) ``` `src/main.ts` is the only place the modules meet. It reads the forge credentials and `GITHUB_REPOSITORY`, builds the forge client, derives the run's inputs from the event payload, and hands the orchestrator its collaborators: the wait protocol, the clock, and the three reconcilers (summary, inline, dispositions). The orchestrator's exit code becomes the process exit code, so the job's conclusion is the outcome table above. ``` - lens: writing-quality · arm: default - verdicts: 1 valid / 0 invalid / 0 uncertain - pass `01M44EYYY88QXSWAJX4VDVC1GR` · valid: The exact grounded architecture paragraph counts and names all three reconcilers. The reviewer reports that WrapperDeps in src/contract/types.ts defines exactly summary, inline, and dispositions and that src/main.ts supplies them. That is a complete copy of the contract-defined reconciler set; the useful wiring explanation can instead point to WrapperDeps. No member difference is reported, so medium severity fits. - disposition: none > This architecture paragraph counts and names the complete set of reconcilers that `main.ts` passes to the orchestrator. A new reconciler can be added to the dependency contract while this count and list stay unchanged, giving maintainers a false map of the composition point. > > `WrapperDeps` in `src/contract/types.ts` defines the reconciler members `summary`, `inline`, and `dispositions`; the README covers the same three, with no difference at this revision. `src/main.ts` supplies those fields to `runWrapper`. > > Replace the count and names with a pointer to `WrapperDeps`, while retaining that `main.ts` wires the dependencies and that the orchestrator's exit code becomes the job conclusion. The following paragraph should keep the session-thunk and fork-gate reason. This follows the writing standard's One Idea, One Place guidance without losing the useful architecture and security explanation. > > I read the complete README, `src/main.ts`, and `src/contract/types.ts`; this is a direct source comparison. The claim would be refuted if the README were the consumed definition of the reconciler set, but the reviewed program uses the TypeScript interface and `main.ts` call. ### medium — Fork-gate text overstates when credentials are read - claim: `01M44EV6MF7NQX2DJET42P268A` - anchor: `README.md` (snippet) ``` - Forgejo does pass secrets to fork `pull_request_target` runs, so the real boundary is code-path ordering: the fork gate is evaluated from the event payload **before** the credentials module is ever called. On the fork path the capability token environment variable is never read — enforced by a static isolation test and an orchestrator unit test. ``` - lens: writing-quality · arm: default - verdicts: 1 valid / 0 invalid / 0 uncertain - pass `01M44EYYY88QXSWAJX4VDVC1GR` · valid: The exact grounded README sentence says the fork gate precedes any call to the credentials module. The reviewer's concrete call trace says main.ts invokes forgeCredentials() before deriving isFork, while reviewCredentials() is deferred through openSession and bypassed on the fork branch. Thus the broad sentence is false even though the capability-token isolation it seeks to explain holds. Naming reviewCredentials() is the precise correction. - disposition: none > This security explanation says the fork gate runs before the credentials module is ever called. In fact, `src/main.ts` calls `forgeCredentials()` before deriving `inputs.isFork`, so a fork run reads the forge task token before the gate. A reader auditing fork exposure could therefore infer a broader isolation guarantee than the code provides. > > The intended and implemented narrower guarantee is that the capability token is not read on a fork run. `main.ts` passes `openSession` as a thunk; `runWrapper` returns from the fork branch before `review()` calls it; only that thunk invokes `reviewCredentials()`. > > Replace “before the credentials module is ever called” with “before `reviewCredentials()` is called.” Keep the surrounding explanation that Forgejo exposes secrets to fork-triggered jobs and that the capability token is isolated by code-path ordering. This follows the writing standard's Precise Language guidance and preserves the security boundary that is actually enforced. > > I traced `src/main.ts`, `src/credentials.ts`, and `src/wrapper/orchestrator.ts` and read the full README; this is static call-order reasoning. A different bundle entry point could change the order, but the reviewed action names `dist/index.mjs` built from `main.ts`. ### low — Service URL rule omits the rejected trailing-slash form - claim: `01M44EVPVQHRSTQHN71HRTHTSK` - anchor: `README.md` (snippet) ``` | `REVIEW_SERVICE_URL` | Base URL of the review service (an org-managed variable). Must parse as an `https:` URL and must not end with `/v1`, which the client appends. | ``` - lens: writing-quality · arm: default - verdicts: 1 valid / 0 invalid / 0 uncertain - pass `01M44EYYY88QXSWAJX4VDVC1GR` · valid: The exact grounded README rule excludes a URL ending in /v1 but does not describe /v1/. The reviewer reports that serviceOrigin in src/credentials.ts checks the parsed pathname with a regex matching both suffixes and rejects either before opening a session. A plausible workflow author can supply the omitted trailing-slash form and get a configuration failure. State that /v1 cannot be the final path segment with or without a trailing slash. - disposition: none > A workflow author could read “must not end with `/v1`” and configure an HTTPS base ending in `/v1/`, a common URL form, expecting it to pass. The action rejects that value before opening a service session, so the stated validation rule omits a case that changes whether the job runs. > > `serviceOrigin` in `src/credentials.ts` tests the parsed pathname with `/\/v1\/?$/u`, which matches both final forms. It throws before `REVIEW_CAPABILITY_TOKEN` is read when either matches. > > Say that `/v1` cannot be the final path segment, with or without a trailing slash, because the client appends the API prefix. This is the smallest addition that prevents the likely configuration mistake and keeps the current rationale. It follows the writing standard's Precise Language guidance. > > I read the README row, `serviceOrigin`, and the `reviewCredentials` call path; this is a static regex and call-order check, not a workflow run. A direct `reviewCredentials` test with `https://example.test/v1/` would establish the same rejection, while a changed suffix validator would refute this reading. ### low — Environment table overstates which PR inputs come from the payload - claim: `01M44EX41M27Y04WKDRRFP5MMH` - anchor: `README.md` (snippet) ``` | `GITHUB_EVENT_*`, `GITHUB_RUN_ID`, … | Standard runner event environment; the wrapper's PR inputs come from the event payload. | ``` - lens: writing-quality · arm: default - verdicts: 1 valid / 0 invalid / 0 uncertain - pass `01M44EYYY88QXSWAJX4VDVC1GR` · valid: The exact grounded table row says the wrapper's PR inputs come from the event payload without limiting the event type. The reviewer's branch trace says workflow_dispatch obtains only pr_number there and fetches head, branches, repository identity, and state through forge.getPull, while pull_request_target uses payload details. The later README note about dispatch does not make this row accurate; scope the payload statement to event-triggered PRs and describe the dispatch source. - disposition: none > This row says the wrapper's PR inputs come from the event payload without limiting that statement to event-triggered pull requests. A manual-dispatch reader could assume the payload contains the head and branch state used for review, although the wrapper fetches that state from the forge before it decides whether the pull request is a fork. > > In `readWrapperInputs`, the `pull_request_target` arm reads PR details from the payload. The `workflow_dispatch` arm reads only `pr_number` from the payload, then calls `forge.getPull(prNumber)` for head SHA, branches, repository identity, and open/merged state. The README's later Notes section also says dispatch reads PR state from the forge. > > Narrow the row to say that the event variables locate and identify the payload, and that PR state for manual dispatch comes from the forge. This follows the writing standard's Precise Language guidance, preserves the payload source for event runs, and removes an internal contradiction. > > I read the entire README and traced both branches of `src/wrapper/event-context.ts`; this is static source comparison. A different dispatch input derivation would refute the claim, but this file is what `main.ts` calls. ## Other claims - grounding-pending (0) - ungrounded (0) - rejected (0) - duplicate-of (0) - unadjudicated (0) ## Coverage Coverage pass: 01M44EGC7WHKVPV9866S8AKJ9J Accounting: complete Slot health: healthy | lens | part | arm | unit status | runs | loss | | --- | --- | --- | --- | --- | --- | | general-bug | whole | default | no-claims | 1 | no | | writing-quality | whole | default | claims-emitted | 1 | no | | test-trimming | whole | default | no-claims | 1 | no | | restated-sets | whole | default | no-claims | 1 | no | | project-docs | whole | default | no-claims | 1 | no |
Author
Owner

Replying to comment #123243

All six findings are summary-only, and none of them is about the two spots this PR changes, so each goes to the PR that owns that text.

  • 01M44ER8R4WSMZGXPXSBXDM0ZN ("Outcome table duplicates the code-defined outcome set and exit map") is valid. #30 already replaces the table with a pointer to OUTCOMES and OUTCOME_EXIT in src/contract/types.ts.
  • 01M44ERZV3461Q6CTWXGBKQ621 ("Required environment variable list duplicates the code reads") and 01M44ESFQDXQ48MJPBXF6AWNQ8 ("Composition paragraph restates the reconciler set") are valid. #32 already replaces both lists: the variables with a pointer to forgeCredentials, readWrapperInputs and src/main.ts, and the reconcilers with a pointer to WrapperDeps. It is stacked on #30.
  • 01M44EV6MF7NQX2DJET42P268A ("Fork-gate text overstates when credentials are read") is valid. #33 already corrects it: the gate guards only reviewCredentials(), and FORGEJO_TOKEN is read first on every path.
  • 01M44EVPVQHRSTQHN71HRTHTSK ("Service URL rule omits the rejected trailing-slash form") and 01M44EX41M27Y04WKDRRFP5MMH ("Environment table overstates which PR inputs come from the payload") are valid. serviceOrigin rejects a path ending in /v1 or /v1/, and the workflow_dispatch arm of readWrapperInputs reads only pr_number from the payload. Tracked in #37, which corrects both rows. It targets main.
> Replying to comment #123243 All six findings are summary-only, and none of them is about the two spots this PR changes, so each goes to the PR that owns that text. - `01M44ER8R4WSMZGXPXSBXDM0ZN` ("Outcome table duplicates the code-defined outcome set and exit map") is valid. https://code.j4k.dev/j4k-oss/review-wrapper/pulls/30 already replaces the table with a pointer to `OUTCOMES` and `OUTCOME_EXIT` in `src/contract/types.ts`. - `01M44ERZV3461Q6CTWXGBKQ621` ("Required environment variable list duplicates the code reads") and `01M44ESFQDXQ48MJPBXF6AWNQ8` ("Composition paragraph restates the reconciler set") are valid. https://code.j4k.dev/j4k-oss/review-wrapper/pulls/32 already replaces both lists: the variables with a pointer to `forgeCredentials`, `readWrapperInputs` and `src/main.ts`, and the reconcilers with a pointer to `WrapperDeps`. It is stacked on #30. - `01M44EV6MF7NQX2DJET42P268A` ("Fork-gate text overstates when credentials are read") is valid. https://code.j4k.dev/j4k-oss/review-wrapper/pulls/33 already corrects it: the gate guards only `reviewCredentials()`, and `FORGEJO_TOKEN` is read first on every path. - `01M44EVPVQHRSTQHN71HRTHTSK` ("Service URL rule omits the rejected trailing-slash form") and `01M44EX41M27Y04WKDRRFP5MMH` ("Environment table overstates which PR inputs come from the payload") are valid. `serviceOrigin` rejects a path ending in `/v1` or `/v1/`, and the `workflow_dispatch` arm of `readWrapperInputs` reads only `pr_number` from the payload. Tracked in https://code.j4k.dev/j4k-oss/review-wrapper/pulls/37, which corrects both rows. It targets `main`.
jercik merged commit 9b43c40520 into main 2026-10-05 09:31:27 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
j4k-oss/review-wrapper!34
No description provided.