# Canonical PR rework completion readback ## Scope and source Canonical entry point: `/harness/agents/impl/skills/gitea-issue-rework/scripts/rework_comment.py`. The adjacent `rework_readback.py` derives readback inputs; `rework_readback_cli.py` provides the role-local GET-only transport and private artifact writer. Update these canonical files and this reference in place; do not copy them into another profile or use a review-role credential for impl delivery. `snapshot` and `verify-live` never create, edit, retry, or submit a Gitea comment. The `create` action verifies an already observed single comment addition; `reuse` verifies an unchanged collection with exactly one matching existing comment. An independently authorized publication workflow remains responsible for its single external write, uncertain-write reconciliation, Issue lease, and fresh pre-write delegation admission. ## Prerequisites and independent expectations - The canonical access contract is `harness/operations/gitea-access.md`. - Run in the impl role's own `HERMES_HOME`. Only its owned, non-symlink, mode-0600 `.env` file supplies `GITEA_TOKEN`. No ambient token or other-profile fallback. - Independently supply the expected stable impl account ID. The first API read must authenticate that ID and the `gitea-impl-agent` login. - Generate `expected.json` with the existing canonical renderer. Its exact target, final head, reviewed head, review ID, body, marker and digest bind the workflow. - Supply a reviewed provider contract and its independently expected SHA-256. The exact provider version is read from the server and must match the contract. A digest calculated from an untrusted delegate file is not independent evidence. - Own a private mode-0700 runtime directory. Inputs must be owned regular files, not group/world writable or symlinked. Output files use mode 0600; a failed verification does not replace an existing receipt. ## Provider contract Schema: `gitea-agent.rework-provider/v1`. The contract explicitly declares the aggregate semantics for PR and underlying PR-Issue `comments` fields. Each `components` array contains unique values from `top_level`, `reviews`, `inline` and must include `top_level`. Choose the reviewed deployment semantics, not the combination that happens to fit a current response. There is no guessed version or automatic capability downgrade. Shape example only, **not a claim about this deployment**: ```json { "schema": "gitea-agent.rework-provider/v1", "version": "", "aggregate": { "pr": {"field": "comments", "components": ["top_level"]}, "pr_issue": {"field": "comments", "components": ["top_level"]} }, "inline": { "mode": "per_review", "field": "review_comments", "components": ["inline"] } } ``` `per_review` requires every exact-review comment endpoint. Only the explicit `per_review_or_pr_count` capability permits endpoint-unavailable 404/405 fallback. Permission errors, redirects, malformed JSON and conflicting metadata never trigger fallback. Fallback uses a fresh PR count, the declared review/inline components and complete review `comments_count` records; disagreement is BLOCK. Counts-only fallback is recorded as such and is not proof of inline comment bodies or a complete inline-record inventory. Native wire-shape reference: the pinned Gitea `v1.24.6` serializers in `services/convert/pull.go` and `modules/structs/{issue,pull}.go` expose PR `url` and `html_url` as HTML URLs, and PR-Issue `pull_request.html_url` without a native `pull_request.url`. Require the actual native relation URL; do not invent a missing field. This source pin is fixture/protocol evidence, not a claim about the deployment version. The caller's independent provider contract still binds that version. In `per_review` mode only, an omitted `review_comments` is accepted as zero only when complete per-review records and their counters prove a zero projection. Explicit null, nonzero projection or counts-only fallback with a missing scalar is rejected. Raw pagination Link evidence is preserved and `first`, `prev`, `next`, `last` are checked against the declared total, including the empty sentinel page after an exact multiple of the page size. ## Commands and canonical artifact paths The caller's admitted Issue runtime is `/repositories///worktrees/issue-/runtime/`. Place the canonical `rework-baseline.json` and `rework-receipt.json` there, outside the product checkout. Preserve the baseline SHA-256 returned by its producer independently; never replace the baseline after the external write. Through `terminal`, before publication: ```sh python3 -B /harness/agents/impl/skills/gitea-issue-rework/scripts/rework_comment.py snapshot \ --expected /expected.json \ --provider-contract \ --provider-contract-sha256 \ --actor-id \ --output /rework-baseline.json ``` After the authorized write or existing-comment reuse, through `terminal`: ```sh python3 -B /harness/agents/impl/skills/gitea-issue-rework/scripts/rework_comment.py verify-live \ --expected /expected.json \ --provider-contract \ --provider-contract-sha256 \ --actor-id \ --baseline /rework-baseline.json \ --baseline-sha256 \ --action create \ --output /rework-receipt.json ``` Use `--action reuse` only for an unchanged scoped collection. Do not recapture a baseline or retry a write merely because readback fails. Exit 0 and a receipt are required; stdout reports only the artifact path, fingerprint and read-only flag. The existing offline `verify` command remains compatible, but manually supplied JSON/true flags or archived artifacts alone are not fresh provider readback. ## Verified invariants - Source Issue, PR, underlying PR-Issue, repository, exact source review and heads are independently bound to authenticated endpoint responses. - Pagination is bounded; duplicate IDs, contradictory totals, truncated pages, malformed metadata and off-target continuation links are rejected. - Genuine empty arrays without a count header are supported; null is not silently accepted as an empty collection. - Existing top-level comments, Source Issue fields/comments, native reviews and admitted inline evidence remain unchanged. Create admits exactly one matching impl-authored comment. Reuse admits no addition. - Only provider-declared count projections and the bounded PR/underlying-PR-Issue state-delta/exact-timestamp admission for that verified new comment are admitted; Source Issue metadata, including `updated_at`, is fully immutable. This admission is not proof of causal event attestation; the receipt records that limitation. - The adapter computes normalized inputs and passes them to the same verifier used by `rework_comment.py verify`, rather than accepting caller success flags. ## Limits and verification The CLI enforces verified HTTPS, no redirects, GET-only requests, a one-megabyte response cap, ten-second socket timeouts and a 120-second operation budget. Body budget checks occur between socket reads, so an already-blocking read may add its remaining socket-timeout allowance. There are no automatic retries. The authenticated transport and independently supplied actor/provider/baseline expectations are trust inputs, not a sandbox or proof that arbitrary caller code is honest. The adapter cannot prove missing inline bodies from scalar counts, and state deltas plus exact timestamps are not independent causal-event attestation. Run focused tests with `terminal`: ```sh python3 -B -m unittest harness.tests.impl.test_rework_readback harness.tests.impl.test_rework_readback_transport harness.tests.impl.test_rework_comment -v python3 -B harness/tests/run.py ``` Local synthetic transport and HTTP fixtures validate the executable paths; they do not constitute a live Gitea readback or an external-write completion record.