Backlog dependency reconciliation

The backlog dependency reconciler reads issue state and proposes readiness-label changes. Dry-run mode never edits an issue, changes a label, closes work, or interprets pull-request merge state. A dependency is satisfied only when the exact referenced GitHub issue is closed.

Dependency grammar

An automatically reconciled issue has one exact second-level section:

## Backlog Dependencies

Mode: automatic
Dependencies:
- #525
- honua-io/honua-site#120

Use #N only for an issue in the owning repository. Cross-repository dependencies must use owner/repo#N. Full URLs, ranges, prose qualifiers, trailing comments, duplicate references, pull-request numbers, and self-references are invalid. The dependency list is bounded to 20 entries by default.

An issue with no prerequisites uses this explicit form:

## Backlog Dependencies

Mode: automatic
Dependencies: none

Epics and intentionally manual sequencing use a validated opt-out rather than prose dependencies:

## Backlog Dependencies

Mode: manual
Reason: Umbrella epic; executable child issues own readiness.

The section allows no extra or blank lines. A manual reason is required and is limited to 240 characters. Specifica issues in automatic mode must contain exactly one visible ## Specifica section with exactly one canonical Type: Feature declaration. Epics, missing or ambiguous types, and noncanonical type declarations must use manual sequencing or be corrected before automation. Dependency headings and type declarations inside valid Markdown fences, CommonMark raw HTML blocks, or HTML comments are ignored. Manual reasons are validated but never emitted in human reports, JSON reports, or errors.

Dry run

Run against current GitHub metadata with a read-only token:

GITHUB_TOKEN=... npm run backlog:dependencies:dry-run -- --repository honua-io/honua-sdk-js

Add --json for the versioned machine-readable report. Bounds can only be positive integers:

npm run backlog:dependencies:dry-run -- \
  --repository honua-io/honua-sdk-js \
  --max-pages 2 \
  --max-issues 200 \
  --max-dependencies 20 \
  --concurrency 4 \
  --json

The reader loads a bounded open-issue inventory, follows only exact dependencies, and then re-reads every accessible issue before planning. Body, labels, state, updated_at, or accessibility drift produces a fail-closed drift disposition. Drift, missing sections, malformed bodies, and manual opt-outs never propose a label change. A valid automatic issue that is marked ready-to-start is proposed for demotion to blocked when an exact dependency is inaccessible, resolves to a pull request, or participates in a dependency cycle. Those unsafe dispositions never propose promotion to ready-to-start.

Token-bearing requests are origin- and path-locked to the configured HTTPS GitHub API root, use GET with redirect refusal and a 15-second request timeout, require JSON, and apply fixed byte and chunk-count bounds while streaming at most 16 MiB per response. Plain HTTP is accepted only for a loopback test server. The total readable plus inaccessible graph is bounded by --max-issues; labels, bodies, pages, dependency fan-out, and concurrency have hard ceilings. Response bodies, request failures, manual reasons, unavailable reasons, labels, tokens, and filesystem paths are never copied into reports or errors. Explicit exhausted-rate-limit headers, retry requests, HTTP 429 responses, and malformed rate metadata stop reconciliation before another read or write is admitted.

For deterministic offline inspection, pass a stabilized JSON snapshot:

npm run backlog:dependencies:dry-run -- \
  --repository honua-io/honua-sdk-js \
  --metadata test/fixtures/backlog-dependencies/stable-snapshot.json \
  --json

The report always says mutationsPerformed: false. An offline snapshot must be a non-symlink regular UTF-8 file no larger than 16 MiB, and the reader rejects size or identity drift while reading it.

Trusted apply

Apply mode accepts live GitHub metadata only:

GITHUB_TOKEN=... npm run backlog:dependencies:apply -- --repository honua-io/honua-sdk-js --json

The apply path consumes the same pure planner as dry-run mode. Before the first write, it loads and double-reads each candidate's exact transitive dependency graph and requires the targeted plan, issue body, labels, and dependency states to match the stabilized inventory. The graph fingerprint includes every reachable issue body, state, pull-request identity, stability result, and inaccessible member, so an unchanged direct disposition cannot hide transitive dependency drift. Immediately before each write it repeats that targeted double-read. Any drift, ambiguous labels, unreadable metadata, rate limit, degraded response, or changed disposition stops the run.

An admitted transition replaces the issue's label set in one bounded PATCH, changing exactly one of blocked and ready-to-start while preserving every unrelated label. The returned issue and a post-write double-read must prove the expected stable postcondition. The command never edits issue bodies, priority, phase, roadmap, effort, parentage, state, or comments. A second run is a no-op once the planned readiness transition has landed.

The Backlog dependency reconciliation workflow runs scheduled applies every six hours and offers explicit manual dry-run and apply modes. It checks out only the repository default branch into trusted-policy, disables checkout credentials, pins every action by commit, and never runs on pull-request events. The dry-run job has contents: read and issues: read; only the apply job receives issues: write. Both jobs write the bounded JSON report to the Actions log and job summary without adding issue comments. CI allowlists the exact action identities and commits and rejects additional action, remote-fetch, package-manager, interpreter, or pull-ref execution surfaces in this trusted workflow.

Admission evidence

On 2026-07-15 HST, two consecutive read-only runs against honua-io/honua-sdk-js from trunk base abf3128c2e6ca7905ef2eb62571b4c2322619075 produced the same report SHA-256: 56c6672769dda2ba0c11a43adefa4cef9c8420ad634de9412ef5503b9338657a. Both reports observed 58 open target issues, 58 stabilized metadata records, 58 missing dispositions, zero other dispositions, and mutationsPerformed: false. The terminal adversarial hardening pass reproduced the same pair of hashes and zero-mutation aggregates.

The zero-mutation result is intentional and fail-closed: the existing backlog had not yet adopted the exact ## Backlog Dependencies section. Maintainers must groom those issues to the documented automatic or manual form; the reconciler never invents dependencies from prose.