[verified] Add fail-closed rework provider readback adapter
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# Canonical PR rework completion readback
|
||||
|
||||
## Scope and source
|
||||
|
||||
Canonical entry point: `<HARNESS_ROOT>/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": "<independently-observed-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
|
||||
`<HARNESS_ROOT>/repositories/<owner>/<repository>/worktrees/issue-<issue>/runtime/<run_id>`.
|
||||
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_ROOT>/harness/agents/impl/skills/gitea-issue-rework/scripts/rework_comment.py snapshot \
|
||||
--expected <RUNTIME>/expected.json \
|
||||
--provider-contract <TRUSTED_PROVIDER_CONTRACT> \
|
||||
--provider-contract-sha256 <INDEPENDENT_PROVIDER_SHA256> \
|
||||
--actor-id <INDEPENDENT_IMPL_ACCOUNT_ID> \
|
||||
--output <RUNTIME>/rework-baseline.json
|
||||
```
|
||||
|
||||
After the authorized write or existing-comment reuse, through `terminal`:
|
||||
|
||||
```sh
|
||||
python3 -B <HARNESS_ROOT>/harness/agents/impl/skills/gitea-issue-rework/scripts/rework_comment.py verify-live \
|
||||
--expected <RUNTIME>/expected.json \
|
||||
--provider-contract <TRUSTED_PROVIDER_CONTRACT> \
|
||||
--provider-contract-sha256 <INDEPENDENT_PROVIDER_SHA256> \
|
||||
--actor-id <INDEPENDENT_IMPL_ACCOUNT_ID> \
|
||||
--baseline <RUNTIME>/rework-baseline.json \
|
||||
--baseline-sha256 <INDEPENDENT_BASELINE_SHA256> \
|
||||
--action create \
|
||||
--output <RUNTIME>/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.
|
||||
Reference in New Issue
Block a user