# Devkit review bundles and issue evidence Gitea remains the canonical backlog and review state log. These commands assemble guidance and append evidence; they do not create a second progress tracker, close issues, rewrite issue bodies, tick checkboxes, or certify a module review. ## Prepare a module review ```sh ./devkit review campaign ./devkit review campaigns --json --output /tmp/campaign-review.json ./devkit review campaign --evidence RUN_ID ``` Repository names, repository aliases and scope IDs from the [issue discovery inventory](../project/ui-review-issue-inventory.json) are accepted. `review bundle campaign` is an equivalent spelling. A bundle contains the current source inventory and fingerprint, canonical module/central issue links, the Core principle revision and content hash, applicable **planned** check stages, and a manual checklist. No checks or application servers are started. The optional output file is a local snapshot, not an authoritative state record. The filename inventory is a starting point, not proof that all runtime surfaces were found. It does not import application manifests or optional modules. Review routes, panes, dialogs, settings, widgets, public forms, contributed/headless interfaces, missing-module behavior, permissions, English/German and narrow/wide layouts manually. Missing source, no UI files or no planned checks is never an automatic pass or N/A. Record exceptions and remaining work in the module issue. Apply [UI-01 and UI-02, and the other Core principles](../../../govoplan-core/docs/UI_DESIGN_PRINCIPLES.md): books belong beside meaningful visible text; normal screens display compact data, with intentional scoped dialog editing. A bulk editing mode needs a documented reason. Record new principles and their back-propagation to previously reviewed modules through the [central review program](../project/UI_REVIEW_PROGRAM.md). An explicit portable `--project project.json` does not inherit GovOPlaN issue links or principles. Its optional configuration is: ```json { "review": { "issue_inventory": "meta/docs/review-issues.json", "principles": "core/docs/UI_DESIGN_PRINCIPLES.md" } } ``` Paths are relative to `--workspace-root` and cannot escape it or traverse symlinks. The inventory uses schema version 1, `issues[]` with `scope_id`, `repository`, `number`, `url`, and optional `name`/`kind`; an optional `epic` contains `repository`, `number`, `url`. Snapshot issue-state fields are not reported as current state. Custom checks come from the project's selected profile (`--profile ui` by default). ## Preview and append evidence ```sh ./devkit issues note --root govoplan-campaign --issue 103 \ --evidence RUN_ID --summary 'Targeted checks passed.' \ --next 'Manual module review and remaining fixes are still required.' --json ./devkit issues note --root govoplan-campaign --issue 103 \ --evidence RUN_ID --summary 'Targeted checks passed.' \ --next 'Manual module review and remaining fixes are still required.' \ --env-file /private/gitea.env --apply ``` `--root` is resolved relative to the selected **workspace root**, not the shell's current directory. In the normal siblings workspace, use `--root govoplan-campaign` or an absolute checkout path; do not use a path escaping the workspace. The default is a completely offline dry run; it does not load credentials or contact Gitea. `--apply` explicitly authorizes comment creation. Tokens are read from `GITEA_TOKEN` or the explicit `--env-file` (environment wins); there is no implicit `.env` search. The credential file is parsed, never sourced, and is not printed or uploaded. Git's selected remote (default `origin`) determines the exact target: ambient `GITEA_OWNER`, `GITEA_REPO` and `GITEA_URL` cannot retarget the request. Credential-bearing HTTP Git remote URLs are rejected. Use repeatable `--summary`/`--next`, `--body-file` for Markdown, or `--note-file` with structured JSON `{ "summary": ["..."], "next": ["..."], "body": "..." }`. Do not put secrets in notes, paths, check arguments or logs. Display redaction is best-effort protection, not permission to include confidential data. For a bounded multi-issue operation, replace `--root`/`--issue` with `--target-plan targets.json`: ```json { "schema_version": 1, "targets": [ { "root": "govoplan-campaign", "issue": 103, "url": "https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/103" } ] } ``` Targets must be unique, inside the workspace and bound exactly to their Git remote/issue URLs, all on one exact Gitea base URL. Optional `issue_id` binds Gitea's immutable numeric ID as well. Every target, complete comment history, marker collision and local journal binding is checked before the first POST. Comments are then posted serially and read back against the exact issue. A closed issue stays closed; existing bodies, comments and checklists are untouched. Each note has a stable hidden marker binding its issue, `--key` purpose (`verification` by default), and evidence run ID. Identical already-published notes are read back instead of reposted; changed content with the same marker is a collision, not an update. Use a distinct intentional `--key` for a separate follow-up. Without a receipt, the note's structured content determines identity. A lost POST response is **not** retried automatically. The publisher scans all comment pages and reads back any matching comment. If it cannot confirm the result, it records `uncertain`, stops later posts, and exits nonzero. The next invocation reconciles again without replaying the POST. Only after inspecting the issue and local result, use `--apply --retry-uncertain` to permit one new attempt when the marker is still absent. Local journals and OS-released locks live in the workspace-scoped devkit state directory; do not delete them to bypass uncertainty. These are retry receipts, not issue state. Independent machines or different state directories cannot share a lock: coordinate publishers, because Gitea comment creation does not provide an atomic idempotency key. ## What a receipt does and does not prove `--evidence RUN_ID` reads the workspace-scoped runner receipt and checks local integrity. `--evidence /path/to/receipt.json` accepts bounded validated external receipt metadata, explicitly labeled `external-unverified`. A self-consistent digest is not a signature or independent verification. Foreign-workspace, malformed and inconsistent successful receipts are rejected. Receipt-provided commands are never run; log files are not opened or uploaded, only referenced. Successful receipts must explicitly record a verified source snapshot; an external file's flag is still a reported claim, not an independent attestation. An aggregate containing a skipped stage cannot claim success. New versioned receipts additionally require independent verified checkpoints for passed phases. Failed/interrupted/stale aggregates remain labeled as such even when they contain reusable successful phases; reuse creates a new run and preserves the original. Stage coverage limitations are validated and retained in the note, including checks omitted by scoped profiles. Compact review output shows the first eight limitations and points to the complete list in its JSON bundle. A passing stage never implies that omitted compiler, browser, permutation or manual checks ran. Documentation audit limitations are saved with their stage receipts, so later review bundles and issue notes retain the same scope as the original command. Managed inventory collectors use the explicit selected workspace throughout; an incomplete checkout never borrows sources or missing module imports from a fuller default workspace. Missing required Core/parser dependencies fail the audit. Legacy direct inventory commands without `--workspace-root` retain their discovery behavior; use the explicit option for reproducible evidence. The note distinguishes `matches-current`, `historical-source-differs`, `different-project-not-compared` and unavailable comparisons. Matching source is a comparison at bundle/note preparation time, not a live test run or proof that a person completed review. If the source or environment has changed, run the appropriate checks again and keep historical evidence labeled honestly. Versioned repository-input receipts compare the recorded repository scope, not a legacy whole-workspace hash. Partial scopes remain explicitly labeled in evidence notes. This comparison reads source only; receipt commands are never executed. Legacy receipts retain their original comparison format. A matching source scope does not prove that ignored build outputs, the environment or live services match. Fixtures cover dry-run isolation, exact target bindings, capped/repeated pagination, collisions, append-only idempotency, uncertain POST reconciliation, secret-safe errors, receipt validation, portable projects and optional-module review boundaries: ```sh python3 -m pytest -q tests/test_devkit_issues.py tests/test_devkit_review.py ```