Skip to content

feat: allow tasks to wait for multiple prerequisites - #1509

Closed
ivankuznetsov wants to merge 22 commits into
mainfrom
support-multiple-dependencies-per-task-260928-b4f8
Closed

ivankuznetsov wants to merge 22 commits into
mainfrom
support-multiple-dependencies-per-task-260928-b4f8

Conversation

@ivankuznetsov

@ivankuznetsov ivankuznetsov commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Tasks can now wait for every declared prerequisite, so clearing one dependency cannot release work that still depends on others.

  • A scalar keeps the configured dependency gate and existing branch-stacking policy. Arrays, including singleton arrays, use the default branch and require every prerequisite at 9-done. Repeated --depends-on flags create an array and explain the base/gate transition.
  • Admission checks the dependency graph, reports cycles with a path, and exposes every unmet prerequisite across CLI, daemon, TUI, and task detail. Status includes each blocker's required gate and the resolved dependency_base_mode.
  • Public status advances to hive-status.v9 and hive-operational-status.v5; superseded schemas are retired. Array singular blocker fields are null; scalar singular fields are also null when only transitive blockers remain. Upgrade and restart all readers before writing arrays, and avoid downgrade while arrays remain.

Planning carries the user-directed decision that arrays supply no implicit Git base. List eligibility uses the coding workflow's merge-before-9-done contract; admission does not independently verify merge ancestry. Draft-PR workflow restrictions remain in place.

The branch also adjusts the advisory incident aggregate timing budget from 32 to 40 seconds for hosted-runner variance, with a superseding historical note.

Test plan

Recorded implementation validation (not rerun during description refresh):

  • Dependency-focused suites: 1,463 runs, 6,582 assertions passed, covering repeated CLI flags, shape preservation, fan-in, status refresh, cycles, and default-base behavior.
  • Rails model/browser suites: 18 runs, 155 assertions passed, including task-detail dependency relationships.
  • RuboCop: 51 files, no offenses; schema checks and retired live-schema references checked during implementation.
  • Broad bin/test --all checkpoint executed. Four failures attributed in the implementation record to host contention passed immediate serial reruns (37 runs, 324 assertions); the combined broad run was not fully green.
  • Hosted CI coverage and dedicated merge gates: still running when this description was refreshed.

Outcome-evidence capture is recorded as unavailable (outcome_evidence_review_blocked); no demo or screenshot is claimed.

Review summary

Two review passes are complete. All recorded findings were fixed or explicitly resolved without a fix; no user questions remain in the escalation records. Fix sentinels identify 262af0f5af and final head 06a00001908ff0ad944ae0384c123091d5bf1ce8.

Fixes address cycle traversal cost and route-dependent diagnostics, malformed declaration reporting, declaration-sensitive fingerprints, bounded dependency visualization, shared branch/config observations, creation-error preservation, and missing shape/freshness regressions. The supplied review record includes independent graph and fail-closed checks; it does not establish completion of the current hosted CI run.

Linked task

Hive task 43367: support-multiple-dependencies-per-task-260928-b4f8 — Support multiple task dependencies (U1–U6).

@ivankuznetsov
ivankuznetsov force-pushed the support-multiple-dependencies-per-task-260928-b4f8 branch from 13d8f40 to 93a77f7 Compare October 2, 2026 11:46
@ivankuznetsov

Copy link
Copy Markdown
Owner Author

Reviewer: codex-ce-code-review - Pass 01

High

  • Continue cycle discovery after an unresolved sibling (lib/hive/dependency_admission.rb:194): With existing app:b -> a, creating app:a with [missing, b] returns dependency_task_missing before visiting b; Commands::New#validate_creation_dependency_cycle! ignores that error and publishes the resolvable a -> b -> a cycle, violating U3; preserve unresolved-reference creation while checking the remaining edges for cycles.
  • Include proposed cross-project edges in repository-identity discovery (lib/hive/dependency_snapshot.rb:180, :197): The context initializes live identities before overlaying the proposed declaration, so existing other:b -> app:a plus new app:a -> [other:b] yields an ignored dependency_repository_identity_missing instead of rejecting the cycle when no existing edge targets other; initialize identities for the proposed targets before traversing the overlay.
  • Add the required refreshed-default checkout proof (test/unit/stages/execute_test.rb:1415, test/integration/dependency_admission_test.rb:148): U5/U6 and AE2 require prerequisite commits merged into updated default before 9-done, followed by a real list-dependent checkout containing those commits; the added tests only assert FakeWorktree arguments or move task folders, leaving this explicit verification requirement unimplemented; add the fixture without introducing production ancestry enforcement.

Medium

  • Report the effective branch fallback in dependency_base_mode (lib/hive/dependency_snapshot.rb:118-146): Resolving prerequisite metadata always reports stacked, even after both prerequisite branch refs disappear, while execute falls back through Worktree#override_local_or_default and open-PR checks remote branch availability; use the shared effective selection policy and existing branch observations so both status surfaces and fingerprints report default in this required KTD7/U4 case.
  • Qualify transitive blockers when crossing project boundaries (lib/hive/dependency_admission.rb:299-303, :539): For app:root -> [other:b, third:b] with both b tasks at 9-done and each locally waiting on base, admission emits two indistinguishable reference: base, blocked_by: base records; preserve qualified blocker identities when projecting child results so status identifies other:base and third:base separately.
  • Match dependency-component blockers by qualified target identity (lib/hive/task_workspace/dependency_component.rb:227-230): For alpha:root -> [shared, beta:shared], local shared at 4-execute and beta:shared at 9-done, the bare-slug match incorrectly labels the satisfied beta:shared edge blocking at 4-execute; resolve each blocker to its project-qualified identity before matching sibling edges.

Nit

No findings.

Validation: source review and read-only in-memory Ruby reproductions; no full test suite or filesystem-writing integration tests run under the single-output-file constraint.

@ivankuznetsov

Copy link
Copy Markdown
Owner Author

Reviewer: claude-opus-5-ce-code-review - Pass 01

ce-code-review — support-multiple-dependencies-per-task-260928-b4f8 (pass 1)

Scope: git diff origin/main..HEAD (base e15aff34b7, head 8b6de99e4a), 72 files / 2299+ / 368-.
Reviewers: correctness, adversarial, api-contract, reliability, performance, testing, maintainability,
project-standards. Local verification: rubocop clean on touched lib files; all touched unit and
integration suites green except test/integration/cli_usage_error_json_test.rb, which fails
identically (29F/1E, child exit 1) on the merge-base commit — environmental, not a finding.

Graph semantics were probed directly against the new traversal and are correct: cycle verdicts do
not leak to an acyclic root sharing a node (both evaluation orders), a shared target reached through a
scalar and a list parent retains the stronger 9-done gate (both orders, and still unmet at
8-finalize), direct blockers precede transitive ones with each edge carrying its declaring parent's
gate, array singular fields are null while scalar ones are populated, and a waiting first edge does not
hide a cancelled or gate-unreachable second edge. The gaps below are coverage, not behavior.

High

  • lib/hive/daemon/dispatcher.rb:3810 emits condition["dependencies"] for a multi-blocker wait, but Condition in schemas/hive-one-shot.v1.json is additionalProperties: false and has no such key: any task blocked by 2+ prerequisites — the headline case of this feature — produces a one-shot payload that fails its own closed schema (flagged independently by four reviewers, two at full confidence).
  • lib/hive/daemon/dispatcher.rb:1086 puts row.depends_on straight into "dependency" => of an operator_action condition, but that property is typed {"type": "string"} — an unresolved list declaration now serializes an Array into a string-typed schema field.
  • lib/hive/commands/new.rb:685 (and :249) pass a raw Array as value:, which new.rb:165 copies verbatim into the --json error envelope, while schemas/hive-new.v1.json types value as ["string","integer"] — hive new --depends-on a --depends-on BAD --json (or any list on a draft-PR workflow) emits an envelope the plan explicitly pins as unchanged.
  • dependency_base_mode is resolved by two divergent prerequisite lookups inside base_selection (lib/hive/dependency_snapshot.rb:119 admission_context.task_snapshot vs :130 base_branch_for over tasks(project_root)): the first consults the archive fallback layer and the second scans live stage dirs only, so a task whose prerequisite has been archived publishes "stacked" from the full status scan and "default" from the bounded path at commands/status.rb:1828 (base_context: nil), with admission_fingerprint keyed on a third reading.

Medium

  • lib/hive/task_meta.rb:352 falls back to normalize_string(value) in the tolerant read path, turning a malformed array into the literal Ruby-inspect string "[\"a\", \"\"]" (verified by probe) — exactly the bracketed-Array output U4 forbids in text/TUI, and the opposite of normalized_data's preserve_invalid: true; pass preserve_invalid: true here too.
  • lib/hive/operational_status.rb:1028 dependency_wait? now returns true whenever unmet_dependencies is non-empty, and apply_dependency_verdict populates that collection on error verdicts too, so an admission-error row emits a dependency_wait reason alongside its error — admission errors are supposed to retain priority over ordinary waits.
  • lib/hive/task_workspace/dependency_component.rb:201 matches an unmet record by entry["blocked_by"] == target["slug"] as a fallback, so for depends_on: ["dup", "other:dup"] the satisfied cross-project edge inherits the same-project blocker's record and renders as blocking.
  • lib/hive/dependency_snapshot.rb:310 raises DependencyWaitError naming only unmet_dependencies.first, so hive run / approve / act on the AE1 fixture report 1 of 9 outstanding prerequisites even though R3 asks status to name every one; route the message through Dependencies.blocked_label.
  • Three historical append-only changelog fragments (wiki/log.d/20260810T231926Z-…, 20260811T160000Z-…, 20260909T211600Z-…) were rewritten to replace accurate past version names with "its then-current schema", apparently to satisfy U6's rg gate — that gate targets live references; de-versioning history destroys the record of which schema each past change shipped.
  • lib/hive/commands/status.rb:1828 passes base_context: nil on the bounded/incremental path, so base_selection falls through to tasks(task.project_root) and performs a full stage-tree meta.yml scan per dependent row — in the one code path whose purpose is to avoid full scans.
  • lib/hive/commands/status.rb:2007 rescues StandardError and returns "default", silently publishing a wrong base mode for a genuinely stacked task where every sibling failure path is loud.
  • lib/hive/dependency_admission.rb:404 builds plan_dependency_mismatch's offending_ref with task.plan_dependency.to_s, so a list declaration renders as Ruby Array inspect (["a", "b"]) in hive status --json and in enforce_admission!'s operator-facing message — the same bracketed-inspect output U4 forbids in text and TUI.
  • lib/hive/dependency_snapshot.rb:180 proposed_task_verdict builds the fleet-wide admission_context (every enrolled project, archives included) for the creation cycle check, and task_capture.rb runs that validator inside Hive::Lock.with_commit_lock — every hive new with a dependency now holds the commit lock across a full-fleet scan; seed targeted_admission_context from the declaration's references instead.
  • No test covers the cycle-cache invariant U3 calls out by name: a node shared by cyclic and acyclic traversals must not inherit a root-relative cycle verdict, asserted in both evaluation orders, with a root that really reaches the cycle still failing (test/unit/dependency_admission_test.rb:216 uses only a->b->a and queries two cyclic roots). Behavior verified correct by probe — this is pure regression exposure.
  • No test covers the shared scalar+list target retaining the stronger required_gate in the list-first declaration order or after the target reaches 8-finalize (dependency_admission_test.rb:186 fixes one order and one stage).
  • No test covers a gate-satisfied first declaration with a transitive blocker plus a directly unmet second declaration, asserting direct-before-transitive ordering and null array singular fields, with a scalar twin pinning the first-direct-edge rule.
  • dependency_admission_test.rb:147 proves a waiting first edge cannot hide only a missing second edge; cancelled, gate-unreachable, malformed, and cross-project-mismatch second edges are untested.
  • test/unit/dependency_snapshot_test.rb:239 pins admission_fingerprint with a one-element list, so U4's "editing only the second dependency or advancing only its stage refreshes status" is unproven.
  • test/integration/new_test.rb:344 captures stderr for the duplicate-flag case but never asserts the required notice, and no test asserts the absence of a notice for zero or one occurrence.
  • StatusCache::SCHEMA_VERSION was bumped to 2 but no test asserts a v1 record is rejected (the new rejection test at test/unit/daemon/operational_snapshot_test.rb:336 only varies the payload).
  • dependency_base_mode status coverage is "default"-only: no cross-project scalar, no remote-branch disappearance fallback, no "stacked" operational row, and no scalar-to-list edit asserting mode and gate flip on an unchanged target.
  • The list base-selection tests (test/unit/stages/open_pr_test.rb:612, test/unit/stages/execute_test.rb:1415) assert only assert_nil, which the pre-change rescue InvalidReference path also satisfied — they pass against reverted production code; assert base_selection(...).mode == "default" or assert_empty the warning instead.
  • No test covers enforce_admission! raising DependencyWaitError for a list with multiple unmet prerequisites — the verdict.blocked_by == nil path that the .first projection above actually exercises.
  • No test asserts dependency_base_mode agreement between the admission-context path and the disk-scan path for the same task, which is what would have caught the divergence above.
  • No deep-chain test pins the iterative traversal; a 2000-node chain was verified by probe to avoid SystemStackError, but nothing in the suite would catch a recursive rewrite.

Nit

  • lib/hive/dependency_admission.rb:252 seeds a frame's references: with EMPTY_UNMET_DEPENDENCIES — a correctly-frozen array under a name that describes a different collection.
  • lib/hive/dependency_admission.rb:338 gate_strength derives ordering from DEPENDENCY_GATE_STAGES.index, so reordering that constant silently inverts which gate wins during blocker dedup; make the strength explicit.
  • lib/hive/schemas.rb:135 still says "intentionally independent from fleet-wide status v8" after the v9 bump.
  • lib/hive/dependencies.rb:65 parse_optional_reference has no remaining lib/web caller after the declaration migration.
  • lib/hive/dependencies.rb:189 return default_branch if list_declaration?(depends_on) is unreachable from base_selection, which already returns for lists at dependency_snapshot.rb:106 — two owners for one rule.
  • lib/hive/dependencies.rb:173 reaches unmet records through field(entry, :reference) although UnmetDependency#to_h and every JSON payload are string-keyed only; the indirection hides rather than resolves the key-shape question.
  • commands/status.rb:1850 uses row[:dependency_base_mode] ||= while :1961/:1984 use =, so the two hold paths can publish different modes for the same row.
  • lib/hive/dependency_admission.rb:194 completes a frame as soon as any edge yields an admission error, so a cycle reachable only through a later sibling is reported as the earlier sibling's error instead — admission still blocks the task, but the creation-time overlay (which consumes only dependency_cycle/dependency_self_reference) lets that creation through and the first diagnosis names the wrong problem. The plan only mandates that an ordinary wait not hide siblings, so this is diagnostic quality rather than a contract break.
  • lib/hive/daemon/dispatcher.rb:3799 calls Hive::Dependencies.field with no require "hive/dependencies" in the file; it resolves transitively today but is fragile to require-graph changes.
  • lib/hive/dependencies.rb:180 blocked_label's no-unmet fallback renders only Array(depends_on).first, silently truncating a list. Unreachable from today's producers (wait verdicts always carry a non-empty unmet set) but a latent truncation for any new caller.

@ivankuznetsov
ivankuznetsov force-pushed the support-multiple-dependencies-per-task-260928-b4f8 branch 3 times, most recently from 37b85f0 to 944071d Compare October 3, 2026 20:00
Hive-Execute-Complete: 40bcaaa57bd722c76888bd904b91957502daf7076b62c1fcbd08666bf3975020
Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci
Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci
Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci
@ivankuznetsov
ivankuznetsov force-pushed the support-multiple-dependencies-per-task-260928-b4f8 branch from 944071d to 3ecde2f Compare October 4, 2026 00:33
Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Findings: 34
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix
@ivankuznetsov

Copy link
Copy Markdown
Owner Author

Reviewer: codex-ce-code-review - Pass 02

High

Medium

  • Detect cycles even when an edge has an unreachable gate (lib/hive/dependency_admission.rb:255-258): Creating A with [B] succeeds when B depends on A but lacks 9-done, because gate validation skips traversal and creation ignores the resulting gate error; inspect the graph for cycles before accepting the candidate.
  • Report the actual upstream self-reference (lib/hive/commands/new.rb:729-731): When existing B depends on itself, creating A depending on B incorrectly reports A -> A in both message and structured value; preserve and qualify the offending task’s identity.
  • Pass the effective default branch when resolving status base mode (lib/hive/commands/status.rb:2016-2019, lib/hive/dependency_snapshot.rb:423-426): With default branch develop and a scalar prerequisite named develop, passing nil makes status and its fingerprint report stacked although execution resolves default; use the same default-branch input as execution.

Nit

Traverse dependency edges even when their gates are invalid so creation
cannot accept a resolvable cycle. Preserve qualified upstream self-reference
paths and align status and fingerprint base selection with project config.

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 02
Hive-Fix-Findings: 3
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: codex-ce-code-review
Hive-Fix-Phase: fix
Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Stage: 4-execute
Hive-Auto-Commit: residue
Hive-Auto-Commit-Reason: stage_exit
Exercise the real CLI process boundary for repeated dependency creation,
idempotent replay, and invalid later values. Keep JSON, text, and TUI blocker
projections aligned as individual prerequisites reach the list gate.

Hive-Execute-Complete: 40bcaaa57bd722c76888bd904b91957502daf7076b62c1fcbd08666bf3975020
Use race-tolerant temporary directory cleanup and disable Git auto-maintenance
before fixture commits can spawn detached maintenance processes.

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci
Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 02
Hive-Fix-Phase: ci
Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Findings: 20
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix
@ivankuznetsov

Copy link
Copy Markdown
Owner Author

Reviewer: claude-opus-5-ce-code-review - Pass 02

High

  • Restore coverage for the branches orphaned by the deleted Dependencies.resolve tests (lib/hive/dependencies.rb:215,217,239,241, plus 208,210): commit 262af0f5af removed 13 tests covering the retired resolve/parse_optional_reference, but task_id's rescue ArgumentError, TypeError warn arm and field's "unrecognized task shape" else arm stayed in lib/, and no test anywhere references either warn string (grep -rn "unrecognized task shape\|is not an integer" test/ web/test/ → no matches). Measured with Ruby's Coverage against the current test: UNCOVERED=16 [99, 100, 111, 112, 114, 115, 116, 117, 119, 121, 208, 210, 215, 217, 239, 241] — 99/100 and 111-121 are covered by sibling suites, but 208/210/215/217/239/241 have no reaching caller because TaskMeta.normalize_id coerces ids to Integer|nil and every production caller passes a Hash or a TaskSnapshot. The gate is exact (test/support/coverage.rb:7 DEFAULT_MIN_LINE_PERCENT = 100.0, enforced by line_coverage_ok?'s if minimum == 100.0 branch), so CI's coverage job fails. Fix: either exercise the arms through Dependencies.same_task? (a non-integer string id warns and returns false; an Object.new task warns "unrecognized task shape"; a numeric-string id for line 210) or delete the now-dead rescue/else arms. Independently raised by the testing lane and measured by the orchestrator.

Medium

  • Stop reusing cycle-derived evaluations inside one walk (lib/hive/dependency_admission.rb:207,252): the pass-01 fix added walk_cache[frame.identity] = evaluation on the line directly above @verdict_cache[frame.identity] = evaluation unless evaluation.cycle_derived, and line 252 consults it (cached = @verdict_cache[target_identity] || walk_cache[target_identity]), so a node aborted on a cycle anchored to one route is replayed verbatim on a second route where those anchors are no longer active. Two independent differential fuzzers against a walk_cache-free reference agree on the phenomenon and bound it: 65/20,004 and 56/24,000 divergences, all inside graphs that genuinely contain a cycle, none changing the verdict state — what rotates is the cycle path, the dependency_cycle vs dependency_self_reference reason code, and the unmet_dependencies collection attached to the error verdict. Reproducing graph: beta:b1 -> [alpha:a3, beta:b6], alpha:a3 -> beta:b3, beta:b3 -> beta:b6, beta:b6 -> [b5, beta:b3]. Cross-root caching is clean (24,014 two-project queries with independent dependency_gate_stage values, cancelled tasks and gate-unreachable workflows showed zero shared-vs-fresh Context divergence), so this is diagnostic nondeterminism, not a dispatch-safety hole — but the unmet set feeds admission_fingerprint, so an unchanged broken graph can churn the attempt generation token. Fix: record the cycle anchor identity on Frame#fail!(..., cycle_derived: true), propagate it through absorb_child onto Evaluation, and reuse a cycle-derived walk_cache entry only while every recorded anchor is still in active_index; that keeps O(V+E) fan-in for acyclic graphs and recomputes only route-dependent nodes. The reviewers disagreed on whether this is a defect (adversarial: P2/100) or acceptable (correctness: residual risk, "verdict-safe"), so the conservative route is recorded here.
  • Keep a non-JSON depends_on out of the snapshot (lib/hive/task_meta.rb:313): normalize_dependency(..., preserve_invalid: true) is the first path that lets a non-String depends_on reach TaskSnapshot#depends_on (lib/hive/dependency_snapshot.rb:816), and DependencySnapshot.semantic_fingerprint JSON-generates it (dependency_snapshot.rb:43,56). Executed against this worktree: a meta.yml with depends_on: .nan survives YAML.safe_load as Float::NAN, is preserved verbatim by the invalid path, and raises JSON::GeneratorError: NaN not allowed in JSON; .inf behaves identically. In Tui::StateSource#publish_active_snapshot that raise lands between @active_snapshot = … and @current = … inside @publication_mutex (lib/hive/tui/state_source.rb:344-355), so the source is left half-updated and @mtime_fingerprint never advances — refresh_once re-raises every tick and the TUI is frozen on its last snapshot until the file is fixed; TaskWorkspace::DependencyComponent degrades the whole dependency panel to unavailable for every task in the context. Admission itself still reports dependency_reference_invalid, so the damage is confined to the observability channel this PR widened. Fix: preserve_invalid && value.is_a?(Array) ? value : normalize_string(value), which keeps the array-aware diagnostics and restores the pre-diff String guarantee for scalars.
  • Stop paying config and git cost per task in admission_fingerprint (lib/hive/dependency_snapshot.rb:425,427): the base version of this method hashed only the verdict — git show fa96ebf16b:lib/hive/dependency_snapshot.rb has no Config.load and no base-mode resolution. It now runs Hive::Config.load(task.project_root) unconditionally (even for a dependency-free task, before base_selection returns "default" for a nil declaration) and Hive::Worktree.branch_refs (an Open3.capture3 git subprocess) for every scalar-dependency task. admission_fingerprint is reached from Attempts::Generation.artifact_token, which runs per task on the attempts dispatcher (attempts/dispatcher.rb:89,630), the recovery coordinator (daemon/recovery_coordinator.rb:1562, which threads a shared admission context so the verdict lookup itself is ~0.001 ms) and the event publisher (modules/event_publisher.rb:31). Measured with a shared context: 2.6 ms per no-dependency task, 14.2 ms per scalar-dependency task. Fix: compute default_branch and available_branch_refs only when task.depends_on is a scalar.
  • Guard the declaration parse inside admission_fingerprint (lib/hive/dependency_snapshot.rb:417,441): the pass-01 fix added Hive::Dependencies.normalize_declaration(task.depends_on) to both payloads, but that helper raises InvalidReference for exactly the values the other pass-01 fix funnels into Task#depends_on. Probed directly: TaskMeta.read turns ["a", "!!"] into the String "[\"a\", \"!!\"]" and {"a"=>"b"} into "{\"a\" => \"b\"}", and normalize_declaration on either raises depends_on must be a task slug, numeric id, or project:slug. The method-level rescue StandardError at :446 then returns hexdigest(["hive-dependency-admission-v2", "unreadable", e.class.name, e.message]) — a constant that ignores the verdict, the project and the slug, so the fingerprint stops honouring its own documented contract ("must not mask a later wait->clear change") and is identical for every corrupt-metadata task. On the base commit base_selection rescued InvalidReference internally and the fingerprint stayed verdict-derived. Fix: rescue Hive::Dependencies::InvalidReference around the two calls and hash task.depends_on.to_s instead. Raised independently by the reliability and adversarial lanes.
  • Restore per-source identity for invalid-declaration placeholders (lib/hive/task_workspace/dependency_component.rb:202-203): the pass-01 fix replaced reference_value.to_s with a constant invalid_label = "invalid dependency declaration" and then feeds that constant into Digest::SHA256.hexdigest(invalid_label), so target_id no longer depends on the node or the declaration — every task in the context with a malformed depends_on collapses into one shared placeholder node and one indistinguishable diagnostic, making the rendered graph claim unrelated tasks share a prerequisite. Fix: keep the constant operator-facing label but hash the source identity (hexdigest("invalid:#{node.fetch('id')}")) and emit diagnostic("dependency_reference_invalid", node.fetch("id")).
  • Finish the pre-application abort guard (lib/hive/command_operation.rb:235-241): pass 01 wrapped abort_pre_submission so a store failure cannot replace the operator-facing InvalidDependencyError cycle message, but the rescue arm calls persist_uncertainty, which itself makes three unguarded calls (:348-:359) against the same store that just failed — so a store-level failure still escapes the rescue Exception and masks the cycle error. The arm also drops the original error, making retryable_pre_submission_contention?(nil) false. Fix: guard persist_uncertainty too (or inline a store-failure-tolerant uncertainty write) and keep the original error in scope.
  • Revert the rewritten historical changelog fragments (wiki/log.d/20260810T231926Z-provider-circuit-operations.md:18, wiki/log.d/20260811T160000Z-operational-attempt-layout-v4.md:7, wiki/log.d/20260909T211600Z-active-row-reuse-integration.md:3): three dated entries describing what shipped on 2026-08-10/08-11/09-09 were reworded from hive-operational-status.v4 / "schema v8" to "version 4 (the current operational-status schema on 2026-08-10)" and similar, solely so the plan's rg -n 'hive-status\.v8|hive-operational-status\.v4' wiki/ lib/ test/ check returns clean. CLAUDE.md treats wiki/ as required project context and the changelog as append-only; editing history to satisfy a lint makes dated entries describe a schema state that did not exist then. The new wiki/log.d/20261002T095924Z-multiple-task-dependencies.md fragment already records the v8→v9 and v4→v5 bump, so the history edits buy nothing. Fix: git checkout fa96ebf16b -- <the three paths> and, if the grep must stay clean, scope it to lib/ test/ schemas/ plus non-historical wiki pages.
  • Bound list-edge expansion in the dependency view (lib/hive/task_workspace/dependency_component.rb:184-200): deadline_exhausted!(diagnostics) is checked once per node in the outer nodes.values.each, but the new references.each inner loop checks neither the deadline nor an expansion budget, so one wide declaration allocates every edge, missing-node placeholder and diagnostic before the later selection caps apply, and the missing-reference diagnostics escape the node/edge byte cap. Measured per-edge lookup cost is quadratic in list width (0.023 s at 250 references, 0.6 s at 2000); nothing in TaskMeta, PlanFrontmatter or the v9 schema caps depends_on length, so an agent-authored plan list is adopted verbatim. Fix: check the deadline and an edge budget inside the reference loop and emit a truncation diagnostic. Raised independently by the sibling codex reviewer and the performance lane.
  • Do not let a transient git failure rewrite base mode fleet-wide (lib/hive/commands/status.rb:1890, lib/hive/worktree.rb:679): dependency_branch_refs rescues to Set.new.freeze and Worktree.branch_refs returns an empty frozen Set whenever git for-each-ref exits non-zero, after which stacked_branch_available? is false for every ref and every scalar same-project row publishes dependency_base_mode: "default". That value is hashed by admission_fingerprint (dependency_snapshot.rb:430-441), which feeds Attempts::Generation.artifact_token, so one failed git invocation churns the generation-bound progress token for every stacked task in the project at once rather than degrading a single row. Fix: distinguish "no refs" from "ref inventory unavailable" and keep the previous mode (or skip the evidence check) on inventory failure.

Nit

  • Update the two docs that still name the retired schema versions in prose (wiki/commands.md:81 "selects the v4 agent workflow document", docs/implementation/cli-usage-contracts-baseline.md:66-68 hive-operational-status / 4 and two hive-status / 8 rows): the literal $id strings are gone everywhere, but these prose/table references now describe versions this branch deleted.
  • Give malformed declarations a readable rendering instead of Ruby inspect text (lib/hive/task_meta.rb:356, lib/hive/dependency_admission.rb:638): the tolerant read arm falls back to normalize_string(value), so [a, {k: v}] becomes the String "[\"a\", {\"k\" => \"v\"}]" and travels into status, TUI, operational status, dispatcher conditions and blocked_label as a bogus scalar; admission_error similarly funnels an Array offending_ref through .to_s, producing hash-rocket inspect text in a schema String field for plan_dependency_mismatch and dependency_reference_invalid. Schema-valid and fail-closed (and it matches base behaviour), but pass 01 already fixed this class in dependency_component.rb. Fix: promote New#dependency_error_value's JSON rendering into Hive::Dependencies as a shared display helper and use it in both places.
  • Gate the incremental base context on the same scalar predicate as the ref inventory (lib/hive/commands/status.rb:1866): return [ nil, true ] unless rows.any? { |row| row[:depends_on] } still builds a second targeted_admission_context when the only changed rows declare lists or cross-project scalars, and base_selection returns "default" for those before it ever consults the context. Measured 26-35 ms warm (256 ms cold) per bounded status call on a 381-task project. Fix: reuse the declaration && !list_declaration? predicate that dependency_branch_refs already applies three lines below.
  • Collapse the triplicated base-mode/row-hold assignment in status (lib/hive/commands/status.rb:2022, and the same block in the apply_dependency_verdict rescue and hold_incremental_dependency): the pass-01 fix added row[:dependency_base_mode] = if base_projection_available … else "default" end plus the surrounding nil/empty row assignments in three places with identical bodies, so the next status-row field needs three synchronized edits. Fix: extract project_dependency_base_mode(row, …) and hold_dependency_row!(row, error, …); roughly -20 net lines.
  • Refresh the Dependencies.field comment again (lib/hive/dependencies.rb:222): pass 01 corrected it to mention TaskSnapshot readers, but it still claims "Most production callers pass symbol-keyed Hashes" and frames the string-key arm as legacy compatibility, while the dominant new callers (blocker_names, blocked_label) receive string-keyed hashes from UnmetDependency#to_h (dependency_admission.rb:36) and from JSON payloads — the string-key arm is now a primary path.
  • Document the scalar transitive-wait singular-field change: for a scalar depends_on whose direct prerequisite has reached the gate but is itself blocked, blocked_by/dependency_stage are now null where the base commit returned the transitive task's slug and stage (confirmed against the base implementation and pinned by test/unit/commands/status_test.rb:1399). This is what KTD5/U3 ask for ("derive scalar singular compatibility fields from the first unmet direct declaration"), but plan line 152 and the v9 upgrade notes read the other way, so a v9 consumer upgrading on explicit schema skew is not told. Fix: add the case to wiki/modules/task_dependencies.md and wiki/commands/status.md.
  • Close the remaining plan-mandated test gaps: no case asserts that a matching list plan assertion passes admission (test/unit/dependency_admission_test.rb:94 has the only list plan_dependency fixture and expects a mismatch, so list-vs-list equality, [a]-vs-scalar-a mismatch and [a,b]-vs-[b,a] order are all unpinned); the pass-01 shared-node cycle test (dependency_admission_test.rb:325) never queries the loop root, so a root-relative cached cycle verdict would still pass it; nothing pins the blocker collection for a node reachable by both a cyclic and an acyclic route in one walk, admission_fingerprint against a corrupt declaration, or distinct placeholder nodes for two distinct malformed declarations.
  • Separate the e2e timing-budget loosening from this feature (test/e2e/lib/incident_budget.rb:8): DEFAULT_AGGREGATE_SECONDS was raised twice on this branch (32.0 → 36 → 40.0, commits d3c103ea5f and 3ecde2f07a) to unblock an advisory hosted-runner job, which the plan does not list in scope; the first fragment (wiki/log.d/20261003T005846Z-incident-budget-hosted-variance.md) is now stale — it states "The aggregate advisory budget is now below 36 seconds" while the constant reads 40.0. Fix: at minimum correct the superseded fragment; preferably land the budget change on its own.
  • Drop the glyph-stripping coupling in operational status (lib/hive/operational_status.rb:1042): .delete_prefix("⏸ ") depends on a decoration Dependencies.blocked_label happens to add, so changing the shared label's prefix silently changes the operational reason text. Fix: split blocked_label into blocker_summary(...) plus a glyph wrapper and call the former here.
  • Share the two predicates the fix pass re-encoded: the cycle-class reason-code test %w[dependency_cycle dependency_self_reference].include?(error.reason_code) appears in lib/hive/commands/new.rb:729 and again in Frame#fail! (lib/hive/dependency_admission.rb:86) — add DependencyAdmission::CYCLE_REASON_CODES and AdmissionError#cycle?; and the "scalar declaration needs ref evidence" test task.depends_on && !list_declaration?(task.depends_on) appears in lib/hive/dependency_snapshot.rb:427 and lib/hive/commands/status.rb:1883 — add a single DependencySnapshot.branch_refs_for(project_root, declarations) used by both.

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 02
Hive-Fix-Findings: 20
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix
@ivankuznetsov
ivankuznetsov marked this pull request as ready for review October 15, 2026 02:26
…b4f8

Keep main's 36-second incident aggregate budget and drop this branch's
unrelated e2e budget loosening plus its three changelog fragments.
Renumber the dependency ADR to ADR-056 after main claimed 053-055, and
regenerate the OpenClaw skill projection.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ivankuznetsov and others added 3 commits October 16, 2026 00:30
Pass 02 stopped a non-finite scalar depends_on from reaching the
admission snapshot, but an invalid list such as [a, .nan] was still
preserved verbatim, so DependencySnapshot.semantic_fingerprint raised
JSON::GeneratorError and froze TUI/Web dependency projections. Keep the
list shape for diagnostics while rendering non-finite floats as text at
any depth.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t stack

Cross-project and invalid scalars always resolve the default base, so
neither status nor admission_fingerprint needs a git for-each-ref
subprocess or targeted base context for them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ivankuznetsov

Copy link
Copy Markdown
Owner Author

Verification of the unreviewed fix pass 02 (06a0000)

Fix pass 02 landed without a re-review or CI gate (the review-loop bug fixed on main in #1537). I checked every pass-02 finding against the code at the new head, 88fd8456a1.

Merged main (no rebase) in c39de28:

  • I removed this branch's unrelated e2e incident-budget loosening. incident_budget.rb, its test and scenarios/README.md now match main's 36 s aggregate cap from chore(deps): batch dependency bumps, babysitter CI fixes, and 0.8.0 changelog notes #1533. I also deleted the three budget fragments (20261003T005846Z/20261003T122231Z-incident-budget-hosted-variance.md, 20261015T120000Z-incident-budget-40-seconds.md) and the "40 seconds" wording in wiki/e2e.md, wiki/testing.md and wiki/gaps.md. git diff origin/main...HEAD -- test/e2e wiki/e2e.md is now empty.
  • Main took ADR-053 through ADR-055, so the dependency ADR is now ADR-056. I regenerated the OpenClaw skill projection and resolved the wiki conflicts by keeping both sides.

Fixed in this pass:

  • Medium, NaN/Inf depends_on (pass 02 only partly fixed it): a non-finite scalar was handled, but an invalid list such as depends_on: [a, .nan] was still copied as-is into TaskSnapshot#depends_on. DependencySnapshot.semantic_fingerprint then still raised JSON::GeneratorError. 25fe58b keeps the list's shape for diagnostics and turns non-finite floats into text at any depth. New regression tests in task_meta_test and dependency_snapshot_test fail without the fix.
  • Nit, the scalar predicate for ref inventory and base context: pass 02 still ran git for-each-ref and built the targeted base context for cross-project and invalid scalars. 6764652 limits branch_refs_for to valid same-project scalars and adds a test.
  • 88fd845 fixes a rubocop offense in that new test.

Already fixed by pass 02, checked:

  • High, orphaned Dependencies.task_id / field arms: tests now cover both warning messages through same_task?, and the lines are covered.
  • Medium, walk_cache reuse for cycle-derived results: reuse is now limited to entries whose recorded anchors are still active. I ran a differential fuzzer: about 80k root queries on random 3–8-node graphs, compared against a reference that never reuses a cycle-derived result.
    • State, reason code and the unmet_dependencies set matched the reference in every query. Those are the inputs to the fingerprint and the admission decision.
    • A shared context queried in random order matched a fresh context in all 44k comparisons.
    • About 3.6% of queries still report a different but valid cycle path than the reference. The path is the same for every query of the same graph and root, so it does not change the fingerprint between scans.
  • Medium, the remaining four:
    • admission_fingerprint loads config and git only when they are needed.
    • Malformed declarations get a guarded fingerprint (fingerprint_declaration).
    • Invalid-declaration placeholders are keyed per source task.
    • The edge loop checks the deadline and the edge cap.
  • Medium, pre-application abort guard: persist_uncertainty_without_masking is in place.
  • Medium, history: the three historical log.d fragments are reverted and now match main.
  • Medium, git failure: a failed branch inventory keeps the base mode instead of reporting every row as default.
  • Codex pass 02: a cycle hidden behind an unreachable gate is still detected, the upstream self-reference names the real task, and status and the fingerprint use the configured default branch. Each has tests.
  • Nits: these are done:
    • The docs no longer name schema v4/v8.
    • Malformed values render as JSON through display_declaration.
    • The base-mode code is collapsed into project_dependency_base_mode and hold_dependency_row!.
    • The field comment is refreshed.
    • The transitive scalar singulars are documented.
    • The plan-assertion, root-order and placeholder tests are added.
    • blocker_summary replaces the glyph stripping.
    • CYCLE_REASON_CODES, AdmissionError#cycle? and branch_refs_for are shared.

Removed: the e2e aggregate budget increase (32 → 36 → 40 s) and its fragments, as listed above.

CI: run 38826068700 on 88fd8456a1 passed. The branch is one docs-only commit behind main (#1540, changelog notes) and merges without conflicts.

Not merged.

ivankuznetsov added a commit that referenced this pull request Oct 16, 2026
* feat(dependencies): U1 preserve dependency declaration shape

* feat(cli): U2 capture repeated task dependencies

* feat(dependencies): U3 evaluate dependency graphs

* feat(dependencies): U5 keep lists on the default branch

* feat(status): U4 expose all dependency blockers

* refactor(dependencies): reuse admission snapshot projections

* docs(dependencies): U6 prove and document fan-in

Hive-Execute-Complete: 40bcaaa57bd722c76888bd904b91957502daf7076b62c1fcbd08666bf3975020

* fix(dependencies): cover multi-dependency CI branches

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci

* fix(ci): restore incident aggregate timing headroom

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci

* fix(ci): allow hosted incident timing variance

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci

* feat(runtime): U1 inspect read-only state storage safely

* fix(status): U2 carry read-only inspection through CLI

Attempt the ordinary writable activation and registration path first, then
scope a Linux/procfs-confirmed operational inspection to the one CLI
invocation. Preserve typed storage failures through attempt and status
boundaries while keeping other status schemas unchanged.

* test(status): U3 prove read-only mounts in Docker

Exercise clean, paired-WAL, and unpaired-sidecar state through the candidate
CLI on real read-only binds and a read-only container root. Register the proof
as a required, isolated CI merge gate and retain its scenario evidence.

* fix(runtime): preserve inspection component contracts

Keep operational route parsing inside the low-level inspection component so
runtime database consumers do not load CLI agent dependencies. Update the
quiescence status contract to assert the intentional inaccessible-storage
classification instead of legacy corruption guidance.

* docs(status): U4 document read-only operational audits

Describe writable-first fallback eligibility, Linux/procfs and owning-uid
limits, the three-way sidecar policy, typed guidance, error envelopes, and the
permanent Docker proof with its recorded successful AE1 and AE4 outcomes.

Hive-Execute-Complete: 3dbbf1dc3e3d4bfc4b08cd48101049d1d8289636fe7c84e5b01d2dff5f28d3ad

* fix(coverage): exercise operational storage fallbacks

Hive-Task-Slug: make-hive-status-operational-work-261001-7e85
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci

* fix(test): stabilize changed-runner git fixture cleanup

Use the shared race-tolerant temporary directory cleanup and disable Git
auto-maintenance before fixture commits can spawn background maintenance.

Hive-Task-Slug: make-hive-status-operational-work-261001-7e85
Hive-Fix-Pass: 02
Hive-Fix-Phase: ci

* fix(task-meta): fence updates to the observed task (U1)

Separate creation from update-only persistence and preserve directory identity
across legitimate moves while rejecting missing or replacement task folders.

* fix(task-meta): reject stale updates at caller boundaries (U2)

Carry transient directory identity through generation, review, migration,
completion, and automatic rollback so stale work cannot report success or
mutate a same-path replacement.

* test(task-meta): prove stale update boundaries (U3)

Cover deletion and same-path replacement races through every live metadata rewrite caller, update the managed wiki contracts, and keep the Patrol Fix test double aligned with observation-aware completion snapshots.

Hive-Execute-Complete: ba02aee95a7f131b12764a659badff46b33597b0a3f8b0939721470536632441

* fix(coverage): exercise stale task custody branches

Hive-Task-Slug: patrol-id-backfiller-can-resurrect-a-dropp-coding-c6a9d445
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci

* fix(task-id): require allocation before task capture

Fail closed and roll back task candidates when the runtime control plane
cannot allocate an ID, avoiding id-less tasks that durable attempt admission
cannot run.

Hive-Task-Slug: patrol-id-backfiller-can-resurrect-a-dropp-coding-c6a9d445
Hive-Fix-Pass: 02
Hive-Fix-Phase: ci

* fix(ci): restore incident aggregate headroom

Keep the advisory timing signal above observed hosted-runner variance while
retaining the existing per-scenario ceiling and functional E2E gates.

Hive-Task-Slug: patrol-id-backfiller-can-resurrect-a-dropp-coding-c6a9d445
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci

* fix(attempts): U1 fail closed on torn tails

* test(attempts): U2 prove StreamLog contention recovery

* test(attempts): U3 pin tail recovery consumers

* refactor(attempts): simplify StreamLog snapshot state

* docs(attempts): U4 document tail recovery contract

Hive-Execute-Complete: 50e4c12223b5609bc3ee72bd2abc2b2dabca3a708c76600645b911e5867771f2

* fix(ci): restore incident aggregate headroom

Keep the advisory timing signal above observed hosted-runner variance while
retaining the strict per-scenario ceiling and functional E2E gate.

Hive-Task-Slug: patrol-streamlog-append-after-a-torn-tail-coding-3fc93742
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci

* fix(dependencies): close multi-dependency review gaps

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Findings: 34
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix

* fix(attempts): harden stream log recovery

Hive-Task-Slug: patrol-streamlog-append-after-a-torn-tail-coding-3fc93742
Hive-Fix-Pass: 01
Hive-Fix-Findings: 19
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix

* fix(ci): remove unrelated incident budget change

Hive-Task-Slug: patrol-streamlog-append-after-a-torn-tail-coding-3fc93742
Hive-Fix-Pass: 01
Hive-Fix-Findings: 1
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix

* fix(status): harden read-only operational inspection

Hive-Task-Slug: make-hive-status-operational-work-261001-7e85
Hive-Fix-Pass: 01
Hive-Fix-Findings: 25
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix

* fix(task-custody): close review race gaps

Fence completion, rollback, migration, and plan-review mutations to the observed task identity while preserving legacy allocation and metadata compatibility.

Hive-Task-Slug: patrol-id-backfiller-can-resurrect-a-dropp-coding-c6a9d445
Hive-Fix-Pass: 01
Hive-Fix-Findings: 28
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix

* fix(dependencies): close graph and base-mode review gaps

Traverse dependency edges even when their gates are invalid so creation
cannot accept a resolvable cycle. Preserve qualified upstream self-reference
paths and align status and fingerprint base selection with project config.

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 02
Hive-Fix-Findings: 3
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: codex-ce-code-review
Hive-Fix-Phase: fix

* fix(attempts): align stream log recovery cleanup

Keep strict reconciliation in parity with public replay and ensure every
constructor or descriptor-replacement failure releases retained resources.

Hive-Task-Slug: patrol-streamlog-append-after-a-torn-tail-coding-3fc93742
Hive-Fix-Pass: 02
Hive-Fix-Findings: 3
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: codex-ce-code-review
Hive-Fix-Phase: fix

* fix(test): qualify read-only recovery evidence

Hive-Task-Slug: make-hive-status-operational-work-261001-7e85
Hive-Fix-Pass: 02
Hive-Fix-Findings: 2
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: codex-ce-code-review
Hive-Fix-Phase: fix

* fix(daemon): emit one resume failure envelope

Preserve parseable lifecycle errors on schema-skewed daemon resume and align
the publication-race test with the lstat-based initial storage probe.

* fix(task-custody): close remaining stale-task races

Hive-Task-Slug: patrol-id-backfiller-can-resurrect-a-dropp-coding-c6a9d445
Hive-Fix-Pass: 02
Hive-Fix-Findings: 4
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: codex-ce-code-review
Hive-Fix-Phase: fix

* test(runtime): preserve unreadable lifecycle contract

* chore(4-execute): commit residual worktree changes

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Stage: 4-execute
Hive-Auto-Commit: residue
Hive-Auto-Commit-Reason: stage_exit

* test(dependencies): prove terminal fan-in outcomes (U2/U4/U6)

Exercise the real CLI process boundary for repeated dependency creation,
idempotent replay, and invalid later values. Keep JSON, text, and TUI blocker
projections aligned as individual prerequisites reach the list gate.

Hive-Execute-Complete: 40bcaaa57bd722c76888bd904b91957502daf7076b62c1fcbd08666bf3975020

* fix(test): stabilize changed-runner fixture cleanup

Use race-tolerant temporary directory cleanup and disable Git auto-maintenance
before fixture commits can spawn detached maintenance processes.

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Phase: ci

* fix(coverage): exercise dependency fallback branches

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 02
Hive-Fix-Phase: ci

* fix(dependencies): harden multi-dependency review paths

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 01
Hive-Fix-Findings: 20
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix

* fix(dependencies): harden multi-dependency review findings

Hive-Task-Slug: support-multiple-dependencies-per-task-260928-b4f8
Hive-Fix-Pass: 02
Hive-Fix-Findings: 20
Hive-Triage-Bias: courageous
Hive-Reviewer-Sources: claude-opus-5-ce-code-review,codex-ce-code-review
Hive-Fix-Phase: fix

* fix(attempts): skip non-object stream log records in replay and reconciliation

A newline-terminated record that parses as non-object JSON or carries an
out-of-range sequence raised NoMethodError/FloatDomainError out of
StreamLog.read, the constructor, and every later append, so one foreign
line made the log permanently unappendable. Treat these records as
malformed in both the public and strict parsers.

Also pin Base64 padding-bit parity between strict reconciliation and
Base64.strict_decode64 with an exhaustive final-quantum test and a
reopen-path regression.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): cover read-only storage fallbacks and repin evidence upload

Cover the four lib lines the exact-100% gate reported uncovered: a
partially unparseable mountinfo table and a failed post-fork reset both
leave read-only storage unconfirmed, and a non-storage Unavailable during
attempt-repository initialization stays a RepositoryError. Repin the
read-only evidence upload to the repository-wide upload-artifact revision
that main moved to, which the CI partition contract enforces.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(decide): restore return-to-stage rollback under commit custody

Approve releases project and task custody before it raises, so decide's
return-to-stage rollback restored decision files with no lock that a
supported drop respects. The restore now re-enters the project commit
lock, and both return and repeat rollbacks raise RollbackFailed when a
stale or replaced task blocks restoration instead of silently skipping.

Drop the rollback_failed error-kind mapping: hive-decide.v1 has no such
enum value, so a --json rollback failure would fail envelope validation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(task-custody): share exclusive cross-device relocation

Approve and the managed-workflow migrator carried three copies of the
cp_r/handle/cleanup sequence, and the copy-failure path still removed
the destination unconditionally. TaskMeta.relocate_across_devices! now
creates the destination with an exclusive mkdir and holds its handle
before copying, so no cleanup path can delete a pre-existing or
replaced destination; an existing destination surfaces as a collision.

Approve also revalidates the observation before renaming, so a same-path
replacement is never moved into the destination stage, and its two
state-file rollback helpers share one guarded restore.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(task-custody): cover lease custody callers and guard branches

Pin that run and plan-review claim observed leases under project commit
custody, and exercise the stale, invalid-metadata, and git-failure
branches the coverage gate reported as uncovered.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(dependencies): keep invalid list evidence JSON-safe

Pass 02 stopped a non-finite scalar depends_on from reaching the
admission snapshot, but an invalid list such as [a, .nan] was still
preserved verbatim, so DependencySnapshot.semantic_fingerprint raised
JSON::GeneratorError and froze TUI/Web dependency projections. Keep the
list shape for diagnostics while rendering non-finite floats as text at
any depth.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(dependencies): skip branch inventory for declarations that cannot stack

Cross-project and invalid scalars always resolve the default base, so
neither status nor admission_fingerprint needs a git for-each-ref
subprocess or targeted base context for them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* style(test): drop spaces inside Set reference brackets

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(plan): pass the observation keyword in the preflight review test

Main's headless-Claude refactor added a start_plan_review call that
predates the PR's required observation keyword; the merge combined
cleanly but the test raised ArgumentError in coverage shard 4/6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(task-custody): cover pin revalidation and restage git failures

The pin-revalidation test passed a nil observation, so it raised
StaleTask before ever reaching the changed-pin branch; give it a real
observation and assert the pin message. Cover the tolerated git failure
when restaging restored decision files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(babysitter): skip repairs for Actions billing holds

* test(plan): adopt multi-dependency plans through the observed helper

#1509's adoption tests called adopt_plan_dependency! directly; #1514 made
the observation keyword required, so they go through adopt_dependency.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ivankuznetsov

Copy link
Copy Markdown
Owner Author

Merged as part of #1545, which batched the finished PRs (including this PR's head) into one CI run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant