Skip to content

ORB-155 gate ledger child filing threshold - #650

Merged
thomasluizon merged 11 commits into
mainfrom
feature/orb-155-give-the-ledger-a-filing-threshold
Jul 29, 2026
Merged

ORB-155 gate ledger child filing threshold#650
thomasluizon merged 11 commits into
mainfrom
feature/orb-155-give-the-ledger-a-filing-threshold

Conversation

@thomasluizon

@thomasluizon thomasluizon commented Jul 29, 2026

Copy link
Copy Markdown
Owner

What changed

  • Replaced the one-child-per-ledger-entry rule with a filing threshold of three occurrences or a run-blocking defect.
  • Kept sub-threshold evidence in the parent run ticket until accumulated evidence earns a child.
  • Added the fixed child line Ledger occurrence: <count>; blocked: no|<what it blocked> and required blocking values to state an affirmative blocked outcome at every occurrence count.
  • Limited ledger validation to children whose parent carries the fixed Harness defect ledger marker. Complete Orca parent data is authoritative; only an actual but incomplete parent signal uses a bounded read-only Linear lookup.
  • Added checker coverage for threshold passes and failures, malformed or missing lines, ordinary children, nonblocking descriptions, affirmative blocking forms, Orca-only validation, and fallback success and error branches.

Why

ORB-155 stops unattended orchestration from turning every transient ledger observation into backlog growth. Repeated defects and defects that actually stop a run remain independently repairable, while the parent stays the durable record for measured evidence.

Acceptance evidence

Current head: 5d592cb9.

$ node tools/test-tools.mjs
PASS check-ticket.mjs: a ledger child with 7 occurrences passes
PASS check-ticket.mjs: a ledger child at the threshold of 3 occurrences passes
PASS check-ticket.mjs: a non-blocking ledger child below the threshold fails with the count and threshold
PASS check-ticket.mjs: nonblocking aliases and descriptions cannot bypass the threshold
PASS check-ticket.mjs: the defect could not block the run cannot bypass the threshold
PASS check-ticket.mjs: the defect could not have blocked the run cannot bypass the threshold
PASS check-ticket.mjs: the merge sweep was blocked by nothing cannot bypass the threshold
PASS check-ticket.mjs: prevented the merge sweep from being blocked cannot bypass the threshold
PASS check-ticket.mjs: a below-threshold ledger child passes when it names what blocked the run
PASS check-ticket.mjs: a bare blocking claim is rejected below and above the threshold
PASS check-ticket.mjs: exact active and passive affirmative blocking claims pass below the threshold
PASS check-ticket.mjs: a ledger child with no occurrence line fails
PASS check-ticket.mjs: a recorded non-ledger child ticket is unaffected
PASS check-ticket.mjs: an unparseable ledger occurrence line fails
PASS check-ticket.mjs: an Orca parent relation validates without a separate Linear key
PASS check-ticket.mjs: an Orca ledger parent relation still requires the occurrence line
PASS check-ticket.mjs: a standalone Orca issue validates without a separate Linear key
PASS check-ticket.mjs: a partial Orca parent relation uses the bounded Linear fallback
PASS check-ticket.mjs: a Linear parent GraphQL error exits with a tool error
ORBIT TOOLS GATE OK

The integrated branch also passed:

npm run lint: exit 0
npm run type-check: exit 0
npm run test: exit 0
node .claude/hooks/test-hooks.mjs: ORBIT HOOK PARITY OK
node tools/check-dashes.mjs --files .claude/skills/orchestrate/SKILL.md tools/check-ticket.mjs tools/test-tools.mjs: exit 0
git push origin feature/orb-155-give-the-ledger-a-filing-threshold: protect-main passed

Review fixes

  • e5f4cedf restricts ledger validation to children with a marked ledger parent and adds an ordinary-parent regression.
  • 6c108bff rejects nonblocking sentinel aliases below the threshold and normalizes punctuation.
  • 2b684972 preserves Orca-only issue validation by consulting returned parent data first.
  • 8de52269 replaces the nonblocking denylist with an affirmative blocking grammar and documents that grammar in the skill.
  • 7c40ae8e makes no-parent Orca results authoritative, bounds the partial-parent fallback to 5 seconds, covers GraphQL success and error paths, and validates blocking values above the threshold.
  • 9d2ddf9f anchors the grammar to affirmative active or passive blocked outcomes and adds regressions for negated inability claims.
  • 5d592cb9 rejects whole-claim negation and inversion after the blocking verb, restricts accepted claims to the two documented blocked shapes, and covers both reported suffix bypasses.
  • The PR diff remains limited to the three ORB-155 files.

Decisions taken unattended

  • Standardized the checker and skill on one exact occurrence line.
  • Required a fixed parent marker so ordinary child issues are unaffected.
  • Treat complete Orca parent information, including no parent, as authoritative. Use the direct Linear lookup only when Orca returned a parent signal that lacks classification fields.
  • Require one of two exact affirmative blocking shapes and reject negators anywhere in the claim.
  • Integrated advancing main with ordinary merge commits and preserved every mainline harness case.
  • Kept the pull request ready while owning the automated review cycle required by the finishing contract.

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
orbit-ui-mobile-web Ignored Ignored Jul 29, 2026 4:53am

Request Review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f6567a23a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/check-ticket.mjs Outdated

Copy link
Copy Markdown
Owner Author

@codex review

Please review the current head e5f4ced.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: e5f4cedf0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Owner Author

@codex review

Please review the final integrated head 398e822.

Copy link
Copy Markdown
Owner Author

Automated activity reconciliation for current head 398e822ff3efbadd445d9a7f2b015c627023efb1:

  • IC_kwDOR5Siws8AAAABMKrETA: No code change required. Vercel reports a skipped web deployment, which is expected because this PR changes only the orchestration skill and repository ticket tooling. Evidence: commit 398e822ff3efbadd445d9a7f2b015c627023efb1; the PR diff contains only .claude/skills/orchestrate/SKILL.md, tools/check-ticket.mjs, and tools/test-tools.mjs.
  • IC_kwDOR5Siws8AAAABMKx4-w: No code change required. The automated review explicitly found no major issues on the previous implementation head. Evidence: commit 398e822ff3efbadd445d9a7f2b015c627023efb1 retains the reviewed three-file behavior after integrating current main, and the final full tools gate returned ORBIT TOOLS GATE OK.

Copy link
Copy Markdown
Owner Author

No code change required for automated activity IC_kwDOR5Siws8AAAABMLUwtQ: SonarQube reports that the quality gate passed with zero new issues, zero accepted issues, and zero security hotspots. Evidence: PR commit 398e822ff3efbadd445d9a7f2b015c627023efb1.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 398e822ff3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/check-ticket.mjs Outdated
@thomasluizon

Copy link
Copy Markdown
Owner Author

@codex review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #650 — ORB-155 gate ledger child filing threshold

Scope: PR #650 in thomasluizon/orbit-ui-mobile (feature/orb-155-give-the-ledger-a-filing-thresholdmain)
Recommendation: NEEDS WORK

Summary

The PR replaces the "one child per ledger entry" repair rule with a 3-occurrence-or-blocking threshold, adds a fixed Ledger occurrence: <count>; blocked: no|<what it blocked> line to the ticket template contract, and teaches tools/check-ticket.mjs to detect ledger children (via issue.parent, a relations entry, or a new live Linear API fallback) and validate that line. The diff is scoped to three files: .claude/skills/orchestrate/SKILL.md, tools/check-ticket.mjs, tools/test-tools.mjs — no apps/*, orbit-api, or packages/shared surface is touched. Two concrete defects survived an adversarial skeptic pass: the new occurrence-threshold check silently stops validating the blocked claim once the count reaches 3, and the new live-Linear-API fallback path ships with zero automated test coverage despite being the PR's stated central mechanism.

Findings

Critical

None.

High

[High] validateLedgerOccurrence never validates the blocked claim once the occurrence count reaches the threshold

  • dimension: 1. Correctness
  • location: tools/check-ticket.mjs:191-212
  • issue: if (occurrenceCount >= LEDGER_OCCURRENCE_THRESHOLD) return (line 203) exits before the blockingClaim bare-claim check (lines 204-208) ever runs. That check only fires when occurrenceCount < 3.
  • risk: A ledger child stating Ledger occurrence: 5; blocked: yes (or blocked: with a stray character) passes check-ticket.mjs even though blocked is neither the literal no nor a real description — violating the fixed line's own no|<what it blocked> contract, which the PR description itself states applies unconditionally ("checker failures for missing, malformed, below-threshold, and bare blocking claims"). None of the 8 new tests exercise a bare claim at or above the threshold. Confirmed via an independent skeptic pass: no other code path validates blocked content once the threshold check returns.
  • fix: Validate blockingClaim (reject bare/empty values) unconditionally, before or independent of the threshold early-return; use occurrenceCount only to decide whether blocked: no alone is sufficient.
  • reference: PR body's own stated contract ("bare blocking claims" as unconditional); .claude/skills/orchestrate/SKILL.md's "a blocking value must name … that could not proceed."

[High] The new Linear-parent network fallback and relations-based parent detection ship with zero harness coverage

  • dimension: 15. Harness changes need EXECUTED evidence
  • location: tools/check-ticket.mjs:145-189 (isLedgerChild's relations branch, readLinearParent's HTTPS/GraphQL call and its error/missing-key throws); tools/test-tools.mjs:4742-4787 (the 8 new cases)
  • issue: Every new test sets issue.parent directly and mocks relations: []. VALID_ISSUE (test-tools.mjs:3265) has no id field, so readLinearParent's first guard (!issue.id || Object.hasOwn(issue, "parent")) returns early before the HTTPS call in every test. No test omits parent while supplying an id, and none supplies a parent via relations instead of issue.parent.
  • risk: The actual network call (success path), the "fail closed" error-exit path, the missing/empty API-key-file throws, and the relations-array detection branch are all unexercised by tools/test-tools.mjs. The PR's acceptance evidence proves this mechanism only via a one-off manual run against a live ticket (ORB-154), not via the automated Harness Execution gate. Confirmed via an independent skeptic pass.
  • fix: Add cases that (a) supply an id with no parent and stub/inject the HTTPS call to prove the success path, (b) prove the error path exits 2 with the expected stderr, (c) prove isLedgerChild detects a parent via a relations entry.
  • reference: rubric dimension 15 ("a new decision path … needs its own case"; "'Verified' without an execution is itself a finding").

Medium

[Medium] New raw Node https Linear client, no timeout, diverges from the repo's sanctioned curl idiom, fires on every ticket check

  • dimension: 5. No-workaround / root-cause
  • location: tools/check-ticket.mjs:154-189
  • issue: readLinearParent hand-rolls an https.request POST to api.linear.app/graphql with no timeout. This repo already has a sanctioned, gate-recognized way to make this exact call — curl -H "Authorization: $(cat ~/.linear-api-key)" against api.linear.app/graphql, documented in .claude/hooks/_lib/rules-linear.mjs:67-68 and referenced from .claude/skills/orchestrate/SKILL.md (D36) — and the PR introduces a second, novel mechanism instead, with no WHY-with-URL comment justifying the divergence (CLAUDE.md rule 1 / rubric dimension 5).
  • risk: Per the PR's own evidence, the installed Orca build omits parent on every issue payload, so this fallback fires on every --issue check, not only ledger children, making a live Linear round-trip + ~/.linear-api-key read a hard dependency of the whole ticket-validation gate. With no timeout, a stalled connection blocks check-ticket.mjs (and anything shelling out to it) indefinitely with no diagnostic.
  • fix: Add an explicit request/socket timeout with a clear error, and either reuse the existing curl idiom for consistency or add a one-line WHY-with-URL comment.
  • reference: CLAUDE.md rule 1; rubric dimension 5; .claude/hooks/_lib/rules-linear.mjs:67-68.

Low / Info

None posted (signal gate).

Subagents

Agent Verdict
parity-checker N/A — no apps/web/**/apps/mobile/** changed
i18n-syncer N/A — no user-facing strings/i18n JSON changed
contract-aligner N/A — no packages/shared/src/types/*/endpoints.ts, no orbit-api change
security-reviewer N/A — no orbit-api code changed
design-reviewer N/A — no UI files changed

Validation

Check Result
Lint NOT RE-RUN this session (sandbox blocked); PR body reports npm run lint exit 0 at head 398e822f
Type check NOT RE-RUN this session; PR body reports npm run type-check exit 0
Tests NOT RE-RUN this session; PR body reports npm test, node tools/test-tools.mjs, node .claude/hooks/test-hooks.mjs all exit 0
Build (api) N/A — orbit-api not touched

Deferred — N/A dimensions & files not verdicted

  • DESIGN.md / AI-slop (#8), Parity (#9), i18n (#10): N/A — no apps/* UI/i18n files changed.
  • Contract drift + backward-compat (#11) / Phase 5 guard, Security (#12 API)/Backend hard rules (#13): N/A — no packages/shared/src/types/* or orbit-api touched.
  • FEATURES.md parity (#14): N/A — internal orchestrator policy/gate change, not an in-app feature surface.
  • Validation re-execution: this review session's shell tool could not obtain approval to re-run npm run lint/type-check/tools/test-tools.mjs/test-hooks.mjs, so the validation table reflects the PR author's self-reported run, not an independent re-execution.
  • All three changed files (SKILL.md, check-ticket.mjs, test-tools.mjs) received a verdict.

What's good

  • The threshold logic's core shape is a clean, well-isolated addition; the pre-existing blockedBy/dependency-prose checks are untouched.
  • The "fails closed" intent for an unreadable parent relation (process.exit(2)) is the right default for a gate meant to catch defects before a worker burns a worktree.
  • Good test hygiene elsewhere: below-threshold, at-threshold, missing-line, unparseable-line, and non-ledger-parent cases are distinct, well-named, and assert on both stdout and stderr shape.
  • isLedgerChild correctly restricts ledger validation to children of a marked Harness defect ledger parent (the "Review fix" commit), so an ordinary child with an unrelated parent is unaffected.

Recommendation

Fix the two High findings before merge: (1) make the bare-blocking-claim check apply regardless of occurrence count, with a test at count ≥ 3, and (2) add real coverage (success path, error path, and the relations-based detection branch) for the new Linear fallback in tools/test-tools.mjs. Address the Medium finding (timeout + idiom consistency) in the same PR or an immediate fast-follow, since the fallback fires on every ticket check today.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 398e822ff3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/check-ticket.mjs Outdated

Copy link
Copy Markdown
Owner Author

@codex review

Please review current head 6c108bff6ff8f42b493fd311e82bdd0486eb42c1. The prior P1 is fixed in this head: sub-threshold nonblocking aliases are rejected, punctuation is normalized, and the full tools harness is green.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c108bff6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/check-ticket.mjs Outdated

Copy link
Copy Markdown
Owner Author

@codex review

Please review current head 2b684972a06b982da3c4e8d3f97beb5dddcd523d. Both prior P1 findings are fixed with focused regressions, and the full tools harness is green.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #650

Scope: PR #650 in thomasluizon/orbit-ui-mobileORB-155 gate ledger child filing threshold
Recommendation: NEEDS WORK

Summary

The PR replaces the one-child-per-ledger-entry rule with a filing threshold (3 occurrences or a
run-blocking defect), adds a fixed Harness defect ledger parent marker and Ledger occurrence: <count>; blocked: no|<what it blocked> child line, and extends check-ticket.mjs to enforce
both. The threshold/format logic itself is well-tested and correct. The new "prefer Orca parent
data, fall back to a direct Linear lookup" logic, however, misclassifies the common "this ticket
has no parent at all" case as "Orca's parent data is incomplete," so it fires a live, untested,
timeout-less network call on ordinary tickets rather than only on genuine ledger-parent stubs —
and this checker gates every worker launch in /orchestrate, not only ledger children.

Findings

Critical

None.

High

[HIGH] Parent-classification bug turns the universal ticket gate into a live, untested, timeout-less network dependency
· dimension: Correctness (#1)
· location: tools/check-ticket.mjs:145-154 (parentFromOrca), tools/check-ticket.mjs:160-195 (readLinearParent), tools/check-ticket.mjs:249-261 (the resolution block)
· issue: parentFromOrca returns undefined when Orca's issue payload has no parent key and no parent/childOf relation — i.e. "this ticket definitively has no parent," the same fact an explicit parent: null represents. But parentIsClassifiable (line 250) only treats null as classifiable; undefined falls through to readLinearParent, which fires a live HTTPS POST to https://api.linear.app/graphql whenever issue.id is set and $env:USERPROFILE\.linear-api-key exists — the exact personal-key file this same skill already establishes as Thomas's real dev-machine setup (.claude/skills/orchestrate/SKILL.md:61-62).
· risk: node tools/check-ticket.mjs --issue ORB-N is the universal ticket gate run before every worker launch (.claude/skills/orchestrate/SKILL.md:133), not just for ledger children. readLinearParent sets no timeout on its https.request (no .setTimeout(), no timeout option, no abort), so a network partial failure hangs the whole check indefinitely instead of failing fast; an outright API/network error exits 2 (documented as the tool's "usage error" code) for an entirely unrelated, otherwise-valid ticket. This isn't hypothetical: the PR's own new tests "an Orca parent relation validates without a separate Linear key" and "a standalone Orca issue validates without a separate Linear key" (tools/test-tools.mjs, new ledger block) both have to point USERPROFILE/HOME at a directory with no .linear-api-key to avoid hitting this exact branch — direct evidence it fires for ordinary, non-ledger tickets whenever the key is present. Verified independently by an adversarial skeptic pass, which confirmed the mechanism and the missing timeout.
· fix: Treat "no parent key and no parent-type relation at all" the same as an explicit parent: null — both mean "definitively no parent," so skip the Linear fallback for that case. Reserve the Linear fallback for when a parent signal exists (a non-null issue.parent, or a parent/childOf relation) but its object lacks title/description. Separately, add a timeout to the https.request call ({ timeout: <ms> } plus a req.on("timeout", () => req.destroy(new Error(...))) handler) so a network partial failure fails fast instead of hanging the process.
· reference: CLAUDE.md rule 1 (no defensive/fallback branch beyond what its trigger actually requires); rubric dimension 1; rubric dimension 15 (harness reliability)

Medium

[MEDIUM] Bare/vague blocked: claims escape validation once the occurrence count meets the threshold
· dimension: Correctness (#1) / Harness changes (#15)
· location: tools/check-ticket.mjs:197-219 (validateLedgerOccurrence)
· issue: The bare-claim check (rejecting blocked: yes / true / blocked with nothing named) only runs when occurrenceCount < LEDGER_OCCURRENCE_THRESHOLD (line 209 returns early once the threshold is met). The contract this enforces, per .claude/skills/orchestrate/SKILL.md ("Use no only for a non-blocking defect; a blocking value must name the run step, ticket, or operation that could not proceed"), is not threshold-conditional — it describes the fixed line's shape unconditionally.
· risk: Ledger occurrence: 5; blocked: true passes check-ticket.mjs silently even though it names nothing concrete, so a legitimately-filed high-occurrence child can still carry an unusable blocked value.
· fix: Run the bare-claim regex check regardless of occurrenceCount; only skip the "below-threshold-without-a-name" alias rejection once the threshold is met, not the bare-claim rejection.
· reference: CLAUDE.md rule 1; rubric dimension 15 ("a new decision path...needs its own case")

[MEDIUM] readLinearParent's actual network branches (success and API-error) have zero executed test coverage
· dimension: Harness changes need EXECUTED evidence (#15)
· location: tools/check-ticket.mjs:160-195; tools/test-tools.mjs (new ledger test block, ~lines 4907-4995)
· issue: Every new ledger test either supplies an already-classifiable parent (so readLinearParent is never entered) or points USERPROFILE/HOME at a key-less directory (so the function returns at the existsSync check before touching the network, tools/check-ticket.mjs:163). Neither the successful-fetch/parse branch nor the catchprocess.exit(2) branch is exercised anywhere in tools/test-tools.mjs.
· risk: The one new external-network code path this PR adds ships with no executed proof it parses a real Linear GraphQL response or handles a real API/network error correctly — exactly the class of gap rubric dimension 15 exists to catch ("a new decision path... needs its own case, not an extension of an existing assertion").
· fix: Add a fixture that stubs the request (e.g. a test-only injection point, or a tiny local HTTP(S) stub the test points readLinearParent at) to cover: (a) a successful GraphQL response resolving a parent stub to its full title, and (b) a non-2xx / GraphQL-error response driving the documented process.exit(2) path.
· reference: rubric dimension 15; TESTING.md

Low / Info

None posted (signal gate: Low/Info are not posted as PR-review findings).

Subagents

Agent Verdict
parity-checker N/A — no apps/web/** or apps/mobile/** file changed
i18n-syncer N/A — no user-facing strings or i18n JSON changed
contract-aligner N/A — no packages/shared/src/types/* / endpoints.ts, and only one repo touched
security-reviewer N/A — no orbit-api code changed
design-reviewer N/A — no apps/* or landing-page UI file changed

Validation

CI on PR #650 (gh pr view 650 --json statusCheckRollup) already ran the full gate set for this
head commit; local re-execution was unavailable in this review session (sandboxed, no shell-exec
permission), so these are the CI-reported results rather than a local re-run:

Check Result
Lint PASS (CI: Guards/PR Tests)
Type check PASS (CI: PR Tests → Type Check)
Tests (Unit) PASS (CI: PR Tests → Unit Tests)
Harness Execution (node tools/test-tools.mjs + node .claude/hooks/test-hooks.mjs) PASS (CI: Guards → Harness Execution)
Harness Lockstep PASS (CI: Guards)
Cross-Platform Parity PASS (CI: Guards — trivially, no apps/* touched)
Dash Ban / Copy Register / Context Budget / Suppressions Ratchet / Expo SDK Pin PASS (CI: Guards)
Build (api) N/A — orbit-api not touched

Deferred — N/A dimensions & files not verdicted

  • Dimension 8 (DESIGN.md/AI-slop), 9 (Parity), 10 (i18n), 11 (Contract drift), 13 (Backend hard
    rules): N/A — the diff touches only .claude/skills/orchestrate/SKILL.md, tools/check-ticket.mjs,
    and tools/test-tools.mjs; no apps/*, packages/shared/src/types/*, or orbit-api surface is
    in scope.
  • Dimension 14 (FEATURES.md parity): N/A — this changes internal dev-tooling behavior (the harness
    defect-ledger workflow), not a user-facing app feature, screen, tool count, or gating rule.
  • Dimension 12 (Security): reviewed the new readLinearParent network call (credential handling,
    error-message content) — no secret leakage or injection issue found; the personal-key convention
    it follows is pre-established elsewhere in this same skill.
  • Local /validate execution: not run in this session (sandbox blocked shell execution of node/
    chmod/mkdir); substituted with the PR's own CI check-run results, which were all green for
    this head commit at review time.
  • All three changed files (.claude/skills/orchestrate/SKILL.md, tools/check-ticket.mjs,
    tools/test-tools.mjs) received a verdict above; nothing in the diff was left unexamined.

What's good

  • The threshold/format logic itself (LEDGER_OCCURRENCE_THRESHOLD, the fixed-line regex, the
    alias rejection for false/none/n/a/no.) is precise and thoroughly tested — 15 new focused
    cases in tools/test-tools.mjs, each naming one exact decision path.
  • isLedgerParent correctly scopes the new validation to children of a marked ledger parent only,
    with an explicit regression ("a recorded non-ledger child ticket is unaffected") proving ordinary
    children are untouched.
  • The SKILL.md prose changes accurately describe the new threshold/marker contract and match the
    shipped code (the fixed line format, the threshold of three, the marker string).
  • PR body carries genuine executed evidence (the tools/test-tools.mjs PASS transcript, lint/
    type-check/test exit codes), which is exactly the standard rubric dimension 15 asks for.

Recommendation

Fix the parent-classification bug before merge (High): treat "no parent signal from Orca at all"
the same as parent: null, and add a timeout to the readLinearParent HTTPS request. This is the
blocking item — as written, it turns the gate run before every single worker launch into a
network-dependent, occasionally-hanging check for the common case of an ordinary, non-ledger
ticket. The two Medium findings (the threshold-conditional bare-claim gap, and the untested live
network branches) are good to fix in the same PR but would not block merge on their own.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 2b684972a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Owner Author

@codex review

Please review current head 8de522694c16331a77bac34ba0e784f5f95e7d70. All prior P1 findings are fixed with focused regressions, the blocking path now uses an affirmative grammar, and the full tools harness is green.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Complete

Scope: PR #650 in thomasluizon/orbit-ui-mobile (feature/orb-155-give-the-ledger-a-filing-thresholdmain), head commit 4cfdd06f
Recommendation: REQUEST CHANGES

Note: this review re-runs /pr-review against the current head. Two prior automated reviews on this PR already flagged the parent-classification/network-timeout issue below as High — it remains unresolved at the current head, so it is re-flagged (not a new nit). All three findings below were independently verified against the actual committed code (tools/check-ticket.mjs at current head) and the real PR diff (gh pr diff 650), not just the skill's report.

Severity Count
Critical 0
High 3
Medium 1
Low / Info 0

Findings

High

[HIGH] Standalone-ticket checks trigger an unwanted live Linear API call, contradicting the PR's own stated design
· dimension: Correctness (#1) / Harness reliability (#15)
· location: tools/check-ticket.mjs:147-156 (parentFromOrca), :247-257 (resolution block)
· issue: parentFromOrca returns undefined when Orca's issue payload has no parent key and no parent/childOf relation — i.e. "this ticket definitively has no parent," the same fact an explicit parent: null represents. But parentIsClassifiable (line 247) only treats null (or an object with a string title/description) as classifiable; undefined falls through to readLinearParent, firing a live HTTPS POST to Linear's GraphQL API whenever issue.id is set and ~/.linear-api-key exists — for every ordinary ticket, not just ledger children.
· risk: check-ticket.mjs --issue gates every worker launch in /orchestrate, not only ledger children. This turns a previously fully-local, deterministic checker into one with a hidden live external dependency for the common case. Flagged as High in two prior reviews on this PR already; still present at the current head.
· fix: Treat "no parent key and no parent-type relation at all" the same as an explicit parent: null (skip the Linear fallback). Reserve the fallback for when a parent signal exists but the object lacks title/description.

[HIGH] No timeout on the new outbound Linear HTTPS call — can hang check-ticket.mjs indefinitely
· dimension: Harness changes need EXECUTED evidence (#15) / Correctness (#1)
· location: tools/check-ticket.mjs:172-191 (readLinearParent)
· issue: The request() call sets no timeout option, no .setTimeout(), no abort signal. Node applies no default timeout, so a connect-then-hang or slow-drip response from Linear never resolves or rejects the promise.
· risk: Combined with the finding above, this is reachable from ordinary ticket checks. A stalled connection blocks check-ticket.mjs (and anything shelling out to it, including /orchestrate and /next) indefinitely with no diagnostic.
· fix: linearRequest.setTimeout(ms, () => linearRequest.destroy(new Error("Linear API timeout"))), treated as an ordinary fetch failure in the existing catch/exit-2 path.

[HIGH] Affirmative-blocking regex accepts a vague claim, undermining the threshold gate ORB-155 exists to add
· dimension: Correctness (#1) / harness decision-path soundness (#15)
· location: tools/check-ticket.mjs:47-48 (AFFIRMATIVE_BLOCKING_CLAIM); documented grammar at .claude/skills/orchestrate/SKILL.md:629-631
· issue: The first regex alternative — \b(?:could not|couldn't|cannot|can't|failed to|(?:was|were) unable to)\s+\S+ — matches could not <any single word> with no requirement that a named operation/subject precede it. Verified: "could not proceed" and "failed to run" both match. This is looser than the SKILL.md grammar this PR itself documents, which requires <operation> could not <action> (an operation named before the modal), blocked the <operation>, or the <operation> was blocked — all three of which require a named subject/operand.
· risk: Ledger occurrence: 2; blocked: could not proceed passes validateLedgerOccurrence, despite being exactly as content-free as the bare blocked: yes the test suite explicitly rejects ("a bare blocking claim does not bypass the threshold"). No test in tools/test-tools.mjs exercises a vague-but-modal-shaped claim like this, so the gap ships silently. Defeats the anti-vagueness purpose of the whole ticket.
· fix: Require the modal phrase to be preceded by a named subject, mirroring the other two patterns' (?:the|a|an)\s+\S+ requirement, and add a rejection test for "could not proceed" / "failed to run".

Medium

[MEDIUM] readLinearParent's network request/response code path has zero test execution
· dimension: Harness changes need EXECUTED evidence (#15)
· location: tools/check-ticket.mjs:172-197; tools/test-tools.mjs ledger test block
· issue: Every test reaching readLinearParent points USERPROFILE at a directory with no .linear-api-key file, so existsSync(keyPath) returns false and the function short-circuits before building the request, parsing a response, or exercising the HTTP-error/GraphQL-error branches.
· risk: The success path, the non-2xx branch, and the payload.errors branch are all unexecuted by the suite that reports "ORBIT TOOLS GATE OK" — a request-shape or response-parsing bug here would ship uncaught.
· fix: Add a test with a stub key file present and the network layer stubbed/injectable, covering success, HTTP-error, and GraphQL-error branches.

Low / Info

None.


Subagents

All five gated subagents were N/A — this diff touches only .claude/skills/orchestrate/SKILL.md, tools/check-ticket.mjs, tools/test-tools.mjs: no apps/web/**, apps/mobile/**, orbit-landing-page/src/**, packages/shared/src/types/*, endpoints.ts, or orbit-api files changed, and no user-facing i18n strings.

Agent Verdict
parity-checker N/A — no apps/* files touched
i18n-syncer N/A — no user-facing strings/i18n JSON touched
contract-aligner N/A — no shared types / endpoints / orbit-api touched
security-reviewer N/A — orbit-api not touched
design-reviewer N/A — no apps/* or landing-page UI touched

Validation

Sourced from live CI (gh pr view 650 --json statusCheckRollup).

Check Result
Lint PASS (CI)
Type check PASS (CI)
Tests PASS (CI)
Harness Execution PASS (CI, both matrix legs)
Cross-Platform Parity PASS (CI gate; no apps/* touched)
Contract Drift PASS (CI gate)
Build (api) N/A — orbit-api not touched

Deferred — N/A dimensions & files not verdicted

  • Dimensions 8-11, 13, 14 (DESIGN.md, parity, i18n, contract drift, backend hard rules, FEATURES.md parity): N/A — no apps/*, packages/shared/src/types, endpoints.ts, or orbit-api files touched.
  • Dimension 12 (Security): reviewed inline — no injection/authz/data-exposure surface beyond the reliability issues above (internal tooling reading a pre-existing sanctioned key-file convention over HTTPS).
  • Cross-model second opinion: not fired — scoped to Critical-only findings; none of the three survivors is Critical.
  • All three changed files received a full verdict; nothing left unexamined.

What's good

  • The core design — parent stays the durable record, a child is filed only for recurring (3+) or blocking defects — is a sound, well-motivated fix for the stated backlog-growth problem.
  • Test coverage for everything reachable without a live personal key is thorough and well-named: threshold boundary at exactly 3, below-threshold rejection with count+threshold in the message, six nonblocking aliases, both active and passive affirmative phrasings, malformed/missing occurrence line, and a non-ledger parent correctly left unaffected.
  • Preserving Object.hasOwn(issue, "parent") to honor an explicit parent: null from Orca (skipping an unnecessary lookup when Orca's answer is already complete) is the right instinct — the bug is that the same courtesy wasn't extended to "no parent relation found in a fetched relations array," which is the far more common shape of "complete negative answer."
  • SKILL.md prose and code agree on the marker string, the threshold number, and the field format everywhere except the one documented-vs-implemented gap in finding 3.

Recommendation

Fix the three High findings before merge. Findings 1 and 2 both concern the new live-Linear-lookup path becoming a hidden, unbounded dependency for ordinary ticket checks (not just ledger children) — a regression in the tool's reliability for the whole ticket-validation pipeline, already flagged twice in prior review rounds and still unresolved. Finding 3 is a real bypass of the exact validation this ticket exists to add. Add the missing coverage from the Medium finding alongside the fix for findings 1/2 (the fix will need its own test anyway).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8de522694c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/check-ticket.mjs Outdated

Copy link
Copy Markdown
Owner Author

Automated activity reconciliation for current head 7c40ae8e:

  • PRR_kwDOR5Siws8AAAABHlClVA: Findings addressed. Parent relation coverage and Orca-only cases landed in 2b684972; affirmative blocking validation landed in 8de52269; 7c40ae8e now skips the Linear lookup when Orca reports no parent signal, applies a 5-second request timeout, validates bare blocking values above the threshold, and executes hermetic GraphQL success and error cases. Full tools gate: ORBIT TOOLS GATE OK.
  • PRR_kwDOR5Siws8AAAABHlPg1w: Findings addressed in 7c40ae8e. No-parent tickets no longer become network dependent; only incomplete parent signals use the bounded fallback. The above-threshold bare-claim and live-network coverage findings also have dedicated passing cases.
  • IC_kwDOR5Siws8AAAABMKrETA: No code change required. Vercel reports a skipped web deployment, expected because current commit 7c40ae8e changes only the orchestration skill and repository tools.
  • IC_kwDOR5Siws8AAAABMLsARQ: No code change required. SonarQube reports a passed quality gate with zero new issues and zero security hotspots for this PR; current commit is 7c40ae8e.
  • IC_kwDOR5Siws8AAAABMLxzOw: No code change required. Codex found no major issues on 2b684972; current commit 7c40ae8e retains that reviewed behavior and adds the subsequently requested bounded fallback and unconditional blocking-value validation.

Copy link
Copy Markdown
Owner Author

@codex review

Please review current head 7c40ae8e1f97bc3452cd80e9efcdae28a0443aac. The remaining Claude findings are fixed: no-parent Orca results skip the network, partial-parent fallback is bounded and hermetically covered, and blocking values are validated above the threshold. Full tools harness is green.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7c40ae8e1f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/check-ticket.mjs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #650 — ORB-155 gate ledger child filing threshold

Recommendation: REQUEST CHANGES

Scope of this pass

This is a re-review focused on the diff since my last review (submitted 2026-07-29T04:06:58Z,
against head 4cfdd06f): the new tip commit 7c40ae8e ("ORB-155 bound parent fallback") and the
no-op merge commit dfd9d69a. I re-verified each of that review's three High findings against the
current committed code rather than trusting the PR author's summary comment.

What's fixed (not re-flagged)

  • Parent-classification bug (parentFromOrca returning undefined for "no parent at all"
    instead of null): fixed at tools/check-ticket.mjs:156 — now returns null, so
    parentIsClassifiable correctly short-circuits and standalone/non-ledger tickets no longer
    trigger the Linear network fallback. Confirmed via git diff 8de52269 7c40ae8e.
  • No timeout on the Linear HTTPS call: fixed — LINEAR_PARENT_TIMEOUT_MS = 5_000 is now passed
    as the request timeout option, with a timeout event handler that destroys the request and
    surfaces a clear error (tools/check-ticket.mjs:182,192-194).
  • Untested network branches: fixed — tools/test-tools.mjs's orca-shim.cjs now monkeypatches
    node:https.request when ORBIT_LINEAR_PARENT_STUB is set (a clean hermetic injection point,
    not a test-only branch in production code), and new cases exercise the success path, the
    requireTimeout assertion (proves the 5s timeout option is actually set), and the GraphQL-error
    process.exit(2) path.
  • Bare/vague blocked: claim escaping validation once the occurrence count reaches the
    threshold
    : fixed — validateLedgerOccurrence now runs AFFIRMATIVE_BLOCKING_CLAIM unconditionally
    whenever blockingClaim !== "no", regardless of occurrenceCount (tools/check-ticket.mjs:216-225).

Findings

High

[HIGH] AFFIRMATIVE_BLOCKING_CLAIM still accepts a subject-less vague claim, contradicting the grammar this same PR documents — carried over, unfixed

  • dimension: 1. Correctness
  • location: tools/check-ticket.mjs:48-49
  • issue: This is the same regex flagged in my prior review at this head's ancestor; git diff 8de52269 7c40ae8e -- tools/check-ticket.mjs shows it is byte-identical across both fix commits —
    it was not touched. The first alternative,
    \b(?:could not|couldn't|cannot|can't|failed to|(?:was|were) unable to)\s+\S+, requires only
    any single word after the modal phrase, with no requirement that an operation/subject precede
    it. "could not proceed" and "failed to run" both match. But .claude/skills/orchestrate/SKILL.md
    (edited by this same PR) documents the grammar as <operation> could not <action>, blocked the <operation>, or the <operation> was blocked — all three require a named operation. The
    implementation is a strict superset of the documented contract.
  • risk: Ledger occurrence: 2; blocked: could not proceed (or blocked: failed to run) passes
    check-ticket.mjs despite naming nothing concrete — exactly as content-free as the bare
    blocked: yes the test suite explicitly rejects ("a bare blocking claim does not bypass the threshold", tools/test-tools.mjs:6139-6143). This defeats the anti-vagueness purpose ORB-155
    exists to add. No test in tools/test-tools.mjs exercises a subject-less modal claim, so the gap
    ships silently; the two "affirmative claim passes" tests (tools/test-tools.mjs:6149-6157) both
    use claims with a named subject ("blocked the merge sweep", "the merge sweep was blocked by unavailable authentication"), so they don't cover this branch.
  • fix: Require the first alternative's modal phrase to be preceded by a named subject too, mirroring
    the other two patterns' (?:the|a|an)\s+\S+ requirement (e.g.
    \b(?:the|a|an)\s+\S+(?:\s+\S+){0,4}\s+(?:could not|couldn't|cannot|can't|failed to|(?:was|were) unable to)\s+\S+), and add a rejection test for "could not proceed" / "failed to run".
  • reference: .claude/skills/orchestrate/SKILL.md's own documented grammar (edited in this PR);
    rubric dimension 15 ("a new decision path…needs its own case").

Medium / Low / Info

None (signal gate).

Subagents

Agent Verdict
parity-checker N/A — no apps/web/**/apps/mobile/** changed
i18n-syncer N/A — no user-facing strings/i18n JSON changed
contract-aligner N/A — no packages/shared/src/types/*/endpoints.ts, no orbit-api change
security-reviewer N/A — no orbit-api code changed
design-reviewer N/A — no UI files changed

Validation

CI (gh pr view 650 --json statusCheckRollup) — Build, Unit Tests, SonarCloud are this PR's
required separate checks per the CI-adaptation instructions (Phase 6 /validate skipped here).
PR-body/comment evidence reports the full tools/test-tools.mjs + .claude/hooks/test-hooks.mjs
harness green at head 7c40ae8e.

Deferred — N/A dimensions

Dimensions 8-11, 13, 14 (DESIGN.md/AI-slop, parity, i18n, contract drift, backend hard rules,
FEATURES.md parity): N/A — diff touches only .claude/skills/orchestrate/SKILL.md,
tools/check-ticket.mjs, tools/test-tools.mjs. No orbit-api cross-repo dimension applicable
(sibling repo not checked out in this job, and none of the changed surfaces need it).

What's good

  • All three previously-flagged High findings and the accompanying Medium (untested network
    branches) are now genuinely fixed with real, hermetic test coverage — not just asserted fixed.
  • The hermetic https.request stub via orca-shim.cjs + ORBIT_LINEAR_PARENT_STUB is a clean
    injection point: it patches the shim process the tool runs under, not production code, so
    check-ticket.mjs itself carries no test-only branches.
  • parentFromOrca returning null (not undefined) for "definitively no parent" is the right fix
    and is exactly what the prior reviews asked for.

Recommendation

Fix the one remaining High finding — tighten AFFIRMATIVE_BLOCKING_CLAIM's first alternative to
require a named subject, matching the grammar .claude/skills/orchestrate/SKILL.md already
documents — before merge, with a regression test for a subject-less claim like "could not proceed". Everything else in this round of fixes is solid.

Copy link
Copy Markdown
Owner Author

@codex review

Please review current head 9d2ddf9f59817f3a6f2b41a6d877b4a64e71db5f. The remaining negated-inability bypass is fixed with anchored affirmative grammar and dedicated regressions. The full tools harness is green.

Copy link
Copy Markdown
Owner Author

Automated activity reconciliation for current head 9d2ddf9f59817f3a6f2b41a6d877b4a64e71db5f:

  • PRR_kwDOR5Siws8AAAABHlUj3w: Findings addressed. 7c40ae8e made no-parent Orca results authoritative, bounded the partial-parent fallback, added hermetic fallback coverage, and validates blocking claims at every count. 9d2ddf9f removes modal and vague blocking forms entirely in favor of anchored affirmative outcomes.
  • PRR_kwDOR5Siws8AAAABHlY7LQ: Finding addressed in 9d2ddf9f. The accepted grammar now has only complete active and passive affirmative forms, with negated-inability regressions. Full tools gate: ORBIT TOOLS GATE OK.
  • IC_kwDOR5Siws8AAAABMKrETA: No code change required. Vercel reports a skipped web deployment, expected because current head changes only the orchestration skill and repository ticket tooling.
  • IC_kwDOR5Siws8AAAABMMCacw: No code change required. SonarQube reports a passed quality gate with zero new issues and zero security hotspots. Current PR head is 9d2ddf9f59817f3a6f2b41a6d877b4a64e71db5f.

Copy link
Copy Markdown
Owner Author

No code change required for automated activity IC_kwDOR5Siws8AAAABMMOGRg. SonarQube reports that the quality gate passed with zero new issues, zero accepted issues, and zero security hotspots. Evidence: current PR head 9d2ddf9f59817f3a6f2b41a6d877b4a64e71db5f.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d2ddf9f59

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/check-ticket.mjs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #650

Scope: PR #650 in thomasluizon/orbit-ui-mobile (.claude/skills/orchestrate/SKILL.md, tools/check-ticket.mjs, tools/test-tools.mjs)
Recommendation: APPROVE

Summary

This PR replaces the orchestrator's one-child-per-ledger-entry rule with a filing threshold
(3 occurrences or a run-blocking defect), teaches check-ticket.mjs to enforce the new fixed
Ledger occurrence: <count>; blocked: no|<what it blocked> line on children of a
marker-tagged parent, and adds an affirmative-grammar check plus a bounded Linear-API fallback
for partially-described parent relations. The diff is tooling/skill-only (no apps/*,
packages/shared, or orbit-api surface touched), so parity, i18n, DESIGN.md, contract-drift
and security-reviewer gates are all N/A by scope. The core validation logic (threshold math, the
affirmative-blocking regex, marker-gated scoping to ledger parents only) is sound and is backed by
extensive, well-targeted test-tools.mjs cases, including the grammar's negated-inability edge
cases called out in the PR's own review-fix commits. One concrete test-coverage gap survives review.

Findings

Critical

None.

High

None. (One hypothesis — that readLinearParent's if (!issue.id) return null could silently
skip ledger validation when Orca returns a partially-described parent — was raised and then
refuted under adversarial review: the same issue object already supplies title/description/
labels unconditionally earlier in the --issue branch (check-ticket.mjs:249-251), which only
holds if Orca returns a fully-hydrated primary issue record, and Linear's Issue type carries id
and identifier together as base scalars. The wave-plan.mjs identifier ?? id pattern cited as
support turned out to apply to different, synthetically-built objects, not a fetched issue detail
record, so it doesn't transfer. Dropped per the verification protocol's default-to-refuted rule.)

Medium

[MEDIUM] New Linear-fallback failure branches ship with zero harness coverage
· dimension: 15 (Harness changes need EXECUTED evidence)
· location: orbit-ui-mobile/tools/check-ticket.mjs:166,168
· issue: `readLinearParent` adds two new decision paths — `if (!existsSync(keyPath)) throw new Error(...)`
  (missing `.linear-api-key`) and `if (!apiKey) throw new Error(...)` (empty key file) — introduced
  in this PR. No case in `tools/test-tools.mjs` drives either branch: every test that reaches the
  Linear fallback (`"a partial Orca parent relation uses the bounded Linear fallback"`,
  `"a Linear parent GraphQL error exits with a tool error"`) uses `linearKeyHome`, which always
  writes a non-empty `fixture-key` file first. The three cases using `noLinearKeyHome` (no key file)
  all use a fully-classifiable parent (`ledgerIssue(...)` / `ledgerParentRelation` with a `title`),
  so `readLinearParent` — and therefore the missing-key-file check — is never invoked in any of them.
· risk: A future refactor of the key-loading logic can silently break the "missing key" and "empty
  key" error paths (e.g. producing the wrong exit code, or an uncaught exception instead of the
  intended `check-ticket: could not read the Linear parent relation: …` message) with no test to
  catch the regression — exactly the class of gap dimension 15 exists to catch ("a new branch on an
  external tool's payload needs its own case, not an extension of an existing assertion").
· fix: Add a `checkIssue(...)` case combining `partialParentRelation` with `noLinearKeyHome` (asserts
  `status: 2` and a `missing .*\.linear-api-key` stderr match), and a second case using a fresh temp
  home with an empty `.linear-api-key` file (asserts `status: 2` and an `is empty` stderr match).
· reference: `.claude/skills/pr-review/rubric.md` dimension 15 ("A new tool or new branch on an
  external tool's payload needs its own case… Medium, and concrete: name the path and the missing case").

Low / Info

  • check-ticket.mjs's USAGE string documents exit code 2 as "usage error" only; this PR
    reuses 2 for a Linear GraphQL/network failure inside --issue mode too (check-ticket.mjs:265).
    No current caller in this repo branches on exit code specifically (they check exit 0 / exit 1
    only), so this is Info rather than an actionable Medium — worth a one-line USAGE update if a
    caller ever needs to distinguish "fix your ticket" from "Linear was unreachable," but not blocking.
  • The SKILL.md prose only spells out blocked the <operation> / the <operation> was blocked as
    the affirmative grammar, while AFFIRMATIVE_BLOCKING_CLAIM also accepts halted/stopped/
    prevented and the were passive form. The implementation is a strict superset of the documented
    grammar, so no worker following the doc can be rejected — non-blocking, Info only.

Subagents

Agent Verdict
parity-checker N/A — no apps/web/** or apps/mobile/** file changed
i18n-syncer N/A — no user-facing strings or i18n JSON changed
contract-aligner N/A — no packages/shared/src/types/* / orbit-api DTO changed, and orbit-api is not touched
security-reviewer N/A — no orbit-api code changed
design-reviewer N/A — no apps/* or orbit-landing-page UI file changed

Validation

Check Result
Lint N/A — not executable: this review session's sandbox blocks direct node/npm script execution (approval-gated, no interactive approver available)
Type check N/A — same restriction
Tests (node tools/test-tools.mjs) N/A — same restriction; relying on the PR body's self-reported run, which lists 18 PASS lines matching the exact test names added in this diff's tools/test-tools.mjs hunk 1:1, plus a clean ORBIT TOOLS GATE OK, and a separately reported npm run lint/type-check/test all exit 0 on the integrated branch
Build (api) N/A — orbit-api not touched

Deferred — N/A dimensions & files not verdicted

  • Dimensions 8 (DESIGN.md/AI-slop), 9 (Parity), 10 (i18n), 13 (Backend hard rules): N/A, diff never
    touches apps/* or orbit-api.
  • Dimension 14 (FEATURES.md parity): N/A, internal orchestration/tooling change with no user-facing
    feature surface.
  • Dimension 11 (Contract drift): N/A, no packages/shared/src/types/*, endpoints.ts, or orbit-api
    DTO in the diff — Phase 5 backward-compat guard has no candidate hunks to classify.
  • Whether Orca's real linear issue <id> --relations --json CLI output matches the shapes assumed
    by parentFromOrca/isLedgerParent/readLinearParent (e.g., whether a parent relation is
    actually modeled under relations with relationship: "parent", versus a top-level issue.parent
    field only) could not be verified: the real orca binary is a local Windows executable not present
    in this review environment, and no other file in the repo documents its exact JSON schema. The
    code's own primary path (Object.hasOwn(issue, "parent")) and its hermetic test stubs are
    internally consistent, so this is a documented gap, not a suspected defect.
  • All three changed files (.claude/skills/orchestrate/SKILL.md, tools/check-ticket.mjs,
    tools/test-tools.mjs) received a verdict; nothing in the changed-file inventory was skipped.

What's good

  • The threshold/marker design closes a real gap cleanly: LEDGER_PARENT_MARKER scopes the new
    validation strictly to children of a fixed-marker parent, so ordinary child tickets are provably
    unaffected ("a recorded non-ledger child ticket is unaffected" test).
  • The affirmative-blocking regex was hardened over several review-fix commits specifically against
    negated/inability phrasing ("could not block", "did not block", "could not have blocked") — the
    test list shows those exact adversarial cases were anticipated and locked down.
  • The Linear-fallback mechanism reuses the repo's existing, documented .linear-api-key convention
    (already used by /feature and /orchestrate for direct GraphQL calls) rather than inventing a
    new credential path, and bounds it to a 5s timeout with a real assertion (stub.requireTimeout)
    that the timeout option is actually wired into the https.request call, not just documented.
  • The orca-shim.cjs NODE_OPTIONS/syncBuiltinESMExports() technique correctly intercepts the
    ESM named import import { request } from "node:https" inside check-ticket.mjs's own process
    (not just the stubbed orca subprocess), keeping the new network path fully hermetic per
    tools/CONVENTIONS.md's "no network" gate contract.

Recommendation

Merge as-is. Optionally follow up with the two missing test-tools.mjs cases for the
missing/empty .linear-api-key branches (Medium finding above) — not blocking, since the branches
are defensive and the primary/common path is otherwise thoroughly covered.

Copy link
Copy Markdown
Owner Author

@codex review

Please review current head 5d592cb9f38d29e7291eaf9a006344617b0dd4b1. The remaining suffix-negation bypass is fixed by restricting claims to the two documented blocked shapes and rejecting negators across the complete claim. Both reported cases have regressions, and the full tools harness is green.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 5d592cb9f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: #650

Branch: feature/orb-155-give-the-ledger-a-filing-thresholdmain
Head: 5d592cb9f38d29e7291eaf9a006344617b0dd4b1
Recommendation: ✅ APPROVE

Severity Count
Critical 0
High 0
Medium 0
Low / Info 1

Scope

This review covers the diff since the last recorded review on this PR. The prior claude review already APPROVED at head 9d2ddf9f, leaving one non-blocking Medium open (untested .linear-api-key missing/empty error branches in readLinearParent, tools/check-ticket.mjs:166,168). The only commit since that approval is 5d592cb9 ("ORB-155 reject negated blocking claims"), touching tools/check-ticket.mjs, tools/test-tools.mjs, and .claude/skills/orchestrate/SKILL.md.

Findings

That commit:

  • Tightens AFFIRMATIVE_BLOCKING_CLAIM (tools/check-ticket.mjs:47-48) to accept only the two grammar shapes SKILL.md documents — blocked the <op> / the <op> was blocked — anchored so nothing may trail the word "blocked" (previously it also accepted halted/stopped/prevented and, per two earlier review rounds, a subject-less modal like "could not proceed").
  • Adds NEGATED_BLOCKING_CLAIM as a belt-and-suspenders pre-check rejecting any claim containing a negation token (not, never, n't, without, from being/getting blocked, etc.) anywhere in the value.
  • Adds two new regression tests for the closed bypasses and tightens two existing passing fixtures to the new anchored grammar.

Hand-traced both regexes against the diff's new/changed strings and several adversarial inputs; no surviving bypass found. This closes the exact High finding left open by the immediately-prior review round.

Low / Info (non-blocking)

  • NEGATED_BLOCKING_CLAIM's bare-word list can false-positive on a legitimate operation name that happens to contain a listed word (e.g. blocked: blocked the deploy without staging gets rejected because of \bwithout\b, even though it's a correctly-shaped affirmative claim). This only over-rejects — it can never let a vague/negated claim through — so it's cosmetic friction, not a correctness or security issue.

CI

Green on every completed check on the current head (Lint, Type Check, Unit Tests, Build, SonarCloud, Harness Lockstep, Contract Drift, Cross-Platform Parity, Dash Ban, Copy Register, Expo SDK Pin, Suppressions Ratchet). Harness Execution was still IN_PROGRESS at review time on both matrix legs.

Cross-repo note

The sibling orbit-api repo is not checked out in this job; no dimension in this diff required it (this PR touches only tools/ and .claude/skills/).


🤖 Generated with automated PR review (/pr-review)

@thomasluizon

Copy link
Copy Markdown
Owner Author

@codex review

@sonarqubecloud

Copy link
Copy Markdown

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: ce91fcd803

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Complete

Scope: PR #650 in thomasluizon/orbit-ui-mobile (feature/orb-155-give-the-ledger-a-filing-thresholdmain)
Recommendation: APPROVE

Severity Count
Critical (incl. ⚠️ old-client breaks) 0
High 0
Medium 3
Low / Info 0 posted (1 Info note only)

Summary

The diff is confined to tooling/skill docs — .claude/skills/orchestrate/SKILL.md, tools/check-ticket.mjs, tools/test-tools.mjs — replacing the one-child-per-ledger-entry rule with a 3-occurrence-or-blocking threshold, enforced via a fixed Ledger occurrence: <count>; blocked: no|<what it blocked> line and an affirmative-blocking grammar. No apps/*, orbit-api, or packages/shared files changed, so all five review subagents (parity, i18n, contract-aligner, security-reviewer, design-reviewer) and FEATURES.md parity are N/A. Static trace of the regex grammar, threshold logic, and parent-classification precedence against the diff's own 19 new test cases shows everything behaves as documented and the negation-bypass hardening is genuinely thorough. The findings below are about an unverified external-payload assumption and coverage gaps, not proven correctness bugs.

Findings

Critical / High

None.

Medium

[MEDIUM] Bounded Linear fallback keys off issue.id, a field this codebase treats as unreliable on Orca issue objects
· location: tools/check-ticket.mjs:166
· issue: readLinearParent only fires the Linear GraphQL fallback when issue.id is truthy; otherwise it silently returns null, treating the child as parentless and skipping ledger validation with no error. Every pre-existing "issue" fixture in tools/test-tools.mjs (VALID_ISSUE at line 3775, and linearIssueStub at 731-738, reused across many other tools' tests) carries identifier but never id. tools/wave-plan.mjs:246,276,287 defensively reads issue.identifier ?? issue.id in three places — evidence these two field names aren't reliably both present on the same "issue" shape elsewhere in this codebase. The new PR's own tests for this path only pass because they hand-inject id: "linear-partial-parent" / id: "linear-parent-error", a shape that doesn't match the established stub convention.
· risk: If the real orca linear issue <ORB-N> --relations --json payload doesn't carry a raw id, the fallback never fires in production, and a child with a genuinely shallow parent relation — the exact scenario this PR was built to handle — silently skips ledger-occurrence validation while check-ticket.mjs still reports "ticket ok".
· fix: Verify against a real orca linear issue --json output whether id is populated; if not, key the GraphQL variable and the guard off issue.id ?? issue.identifier, mirroring wave-plan.mjs.
· reference: CLAUDE.md rule 1; rubric dimension 15.

[MEDIUM] Three new readLinearParent decision paths have no dedicated test case
· location: tools/check-ticket.mjs:168-170 (missing/empty key file) and :194-196 (real timeout event)
· issue: Only "key present + GraphQL error" and "key present + success" are tested. The !existsSync(keyPath) branch, the empty-key-file branch, and the linearRequest.on("timeout", ...) handler actually firing (as opposed to just asserting the timeout: 5000 option was set) are all uncovered.
· risk: A regression in any of these three branches would ship silently.
· fix: Add three checkIssue cases exercising each branch directly.
· reference: rubric dimension 15 ("a new decision path... needs its own case").

[MEDIUM] tools/README.md's check-ticket.mjs catalog row is now stale
· location: tools/README.md:46
· issue: The row doesn't mention the new ledger-threshold gate or the direct Linear GraphQL fallback — the only tool in the catalog now making a raw network call outside the orca/gh stub surface.
· fix: Add one clause describing the ledger gate and the bounded Linear lookup.
· reference: root CLAUDE.md, Conventions & tooling.

Low / Info

None posted. (Info only: SKILL.md's documented grammar is a strict subset of what the regex accepts — a/an articles are also allowed — harmless, no action needed.)

Subagents

Agent Verdict
parity-checker N/A
i18n-syncer N/A
contract-aligner N/A
security-reviewer N/A
design-reviewer N/A

Validation

Check Result
Lint / Type check / Tests N/A — not independently executable in this session (sandbox denied); PR body cites all-PASS npm run lint/type-check/test and a full node tools/test-tools.mjs log naming every new ledger case
Build (api) N/A — orbit-api untouched
CI: Build / Unit Tests / SonarCloud Handled as separate required checks (this review skips /validate per the CI adaptation)

Deferred

Dimensions 8-14 (DESIGN.md, parity, i18n, contract drift, backend hard rules, FEATURES.md) are N/A — no apps/*, orbit-api, or packages/shared surface touched. Dimension 15's execution proof could not be independently re-run this session; cross-checked statically instead. All three changed files got a full-diff read and a verdict. Note: the PR branch's current head also carries a merge of main, which pulled in unrelated main-side work (tools/merge-sweep*.sh, tools/ai-quota.mjs, etc.) into the git history; the actual PR diff (base...head) remains the same 3 files reviewed here — confirmed against gh pr diff --name-only and against the prior approved review at commit 5d592cb9.

What's good

The affirmative/negated blocking-claim regex pair is well hardened (ten distinct bypass attempts tested and rejected). Parent-classification precedence (direct field → relations → bounded Linear fallback, with "no parent" authoritative) avoids unnecessary network calls on the common path. SKILL.md and the checker agree on the core contract.

@thomasluizon
thomasluizon merged commit 79552ec into main Jul 29, 2026
32 checks passed
@thomasluizon
thomasluizon deleted the feature/orb-155-give-the-ledger-a-filing-threshold branch July 29, 2026 05:11
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