Skip to content

Lex restore-reachable SQL function calls - #1159

Open
KeenWill wants to merge 1 commit into
agent/daemon-live-redacted-tool-recoveryfrom
agent/daemon-live-restore-call-lexing
Open

Lex restore-reachable SQL function calls#1159
KeenWill wants to merge 1 commit into
agent/daemon-live-redacted-tool-recoveryfrom
agent/daemon-live-restore-call-lexing

Conversation

@KeenWill

Copy link
Copy Markdown
Owner

Outcome

Restore-reachability validation now lexes SQL function bodies instead of relying on one regular expression. It recognizes quoted function identifiers and comments between a function name and its opening parenthesis, while excluding aliases and call-shaped text inside comments or string literals. Fixed-point closure still finds functions reached through multiple body hops.

The persistence contract's verified-against reference advances with the implementation, and focused straight-line tests cover each lexical boundary.

Meaningfully changed lines: 392 (325 additions, 67 deletions; no lockfiles).

Validation

  • cargo fmt --all -- --check
  • workspace check and warning-denied Clippy
  • workspace doctests, warning-denied rustdoc, and metadata
  • full ignored PostgreSQL persistence integration suite: green, including both restore-reachability tests
  • documentation bar: green except the inherited carrier-history check for live-but-unmerged Make numeric-bound declarations fail closed #1125
  • workspace unit suite: changed targets green; unrelated local environment failures remain in the host timezone-alias and trusted-socket-ancestor fixtures
  • release signalboxd is byte-identical to the deployed parent artifact

Numeric-bound ceilings added by this slice: none.

Absorption ledger

Copilot AI lite review requested due to automatic review settings August 23, 2026 05:18
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: adf2047a-ce94-4402-8e27-d079bc75f6be

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Warning

Your free Security trial is over. An organization admin can activate billing to continue.


Comment @coderabbitai help to get the list of available commands.

@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: 6541cd65ad

ℹ️ 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 on lines +310 to +311
if bytes[cursor] == b'\\' {
cursor = (cursor + 2).min(bytes.len());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Distinguish standard strings from escape strings

With PostgreSQL's default standard_conforming_strings=on, a plain literal such as '\' ends at the quote after the backslash, but this branch always treats that quote as escaped. A valid body such as SELECT '\', restore_probe_tail() is therefore consumed through EOF and the real call is omitted from the reachability closure, allowing an unpinned reachable function to pass INV-070. Only E'...' strings should apply backslash escaping.

AGENTS.md reference: AGENTS.md:L6-L10

Useful? React with 👍 / 👎.

Comment on lines +227 to +229
b'\'' => {
cursor = skip_single_quoted(bytes, cursor);
preceding_identifier = None;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve calls made through dynamic SQL

When a check-reachable PL/pgSQL function executes a literal query such as EXECUTE 'SELECT helper()', helper is genuinely reachable during restore, but this unconditional string skip removes it from the closure. The previous conservative regex did discover that call, so this change lets an unpinned dynamic callee pass the invariant; either recognize call-shaped names in EXECUTE query literals or explicitly reject dynamic application-function calls from reachable bodies.

AGENTS.md reference: AGENTS.md:L6-L10

Useful? React with 👍 / 👎.

Comment on lines +268 to +270
const fn is_identifier_start(byte: u8) -> bool {
byte.is_ascii_alphabetic() || byte == b'_'
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize non-ASCII unquoted identifiers

PostgreSQL permits letters with diacritics and non-Latin letters in unquoted identifiers, but this byte classifier accepts only ASCII. A valid call such as réachable() is tokenized as separate fragments and never compared with the catalogue name, so a migration using such a function can leave it unpinned while INV-070 still passes; scan identifiers as Unicode characters according to PostgreSQL's identifier rules rather than rejecting every non-ASCII byte.

AGENTS.md reference: AGENTS.md:L6-L10

Useful? React with 👍 / 👎.

Comment on lines +478 to +479
#[test]
fn quoted_function_identifier_is_a_call_edge() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Tag the lexical tests with INV-070

These seven new focused tests directly enforce the lexical clauses added to INV-070, but none of their names or attached doc comments carries the invariant identifier. The repository's invariant discovery intentionally recognizes only declaration-local test names and doc comments, so these enforcement points are undiscoverable as INV-070 coverage until the tag is added.

AGENTS.md reference: AGENTS.md:L231-L235

Useful? React with 👍 / 👎.

Comment on lines +240 to +243
b'"' => {
let (identifier, after) = quoted_identifier(source, cursor);
cursor = after;
preceding_identifier = identifier;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude quoted aliases with column lists

Quoted identifiers followed by ( are not necessarily calls: PostgreSQL permits constructs such as WITH "helper"(value) AS (...) and FROM table AS "helper"(value), where the parentheses introduce a column-alias list. If an unrelated schema function is also named helper, this lexer adds it to the reachable set and can fail the restore invariant solely because that unrelated function is unpinned; the classifier needs enough syntactic context to distinguish these aliases from quoted function invocations.

Useful? React with 👍 / 👎.

@KeenWill

Copy link
Copy Markdown
Owner Author

@codex review

Head: 6541cd6

@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: 6541cd65ad

ℹ️ 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".

cursor += 1;
continue;
}
identifier.push_str(&source[segment_start..cursor]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Decode Unicode escapes in quoted identifiers

When a function body calls a catalogue function through PostgreSQL's Unicode-escaped identifier syntax, such as U&"restore\005fprobe\005ftail"(), PostgreSQL resolves the decoded name restore_probe_tail, but quoted_identifier records the raw escape spelling. The name therefore never matches the catalogue entry, allowing that reachable function to remain unpinned without failing INV-070; recognize the U& prefix and decode its escapes, including an optional UESCAPE character.

AGENTS.md reference: AGENTS.md:L8-L10

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

Rust coverage (report only)

Report only. This measurement has no threshold, gates no merge, and
fails no check; it exists so untested code stays visible.

Measured suite Outcome
workspace (--all-targets --all-features) success
persistence PostgreSQL (--ignored) success
signalboxd PostgreSQL (--ignored) failure
terminal-client PostgreSQL (--ignored) success
What this number does not measure
  • Doctests. cargo llvm-cov --doctests needs a nightly
    toolchain; this workspace pins stable, so the compile-fail
    sealing proofs and every other doctest are outside the
    denominator.
  • Live smokes, which spend real network requests or
    credentials and are never run here: the tools-github and
    tools-web smokes stay --ignored, and the whole-daemon and
    real-provider terminal smokes are skipped by name above.
  • The Swift native client, which Xcode measures separately.
  • One environment-clearing test in signalbox-tools-exec,
    which instrumenting its supervisor process perturbs. The
    workflow comment on the workspace step states why; rust.yml
    runs that test uninstrumented and gates on it.
  • Dedicated test files. cargo-llvm-cov excludes tests/
    and benches/ targets and *tests.rs modules from the
    report by default, so that test code is counted neither
    covered nor uncovered here. Inline #[cfg(test)] modules
    inside a source file are the exception: they are
    instrumented, and they land on both sides. A test body
    that ran counts as covered, which makes every percentage
    below optimistic; the body of an #[ignore]d test no
    measured suite runs counts as uncovered, which puts test
    lines into the file ranking. Read both tables as close,
    not exact.
Measure Covered Total Percent
Lines 253984 311193 81.62%
Functions 20710 25164 82.30%
Regions 321748 402579 79.92%

Per crate, least-covered first

Crate Line % Lines Function % Region %
crates/program-runtime 7.57% 38/502 5.77% 6.13%
crates/runner-wire 60.41% 644/1066 62.67% 59.14%
apps/signalboxd 63.68% 35477/55715 70.84% 63.56%
crates/tools-sessions 65.06% 378/581 63.29% 61.68%
apps/signalbox-runner 69.47% 1784/2568 70.93% 70.29%
crates/approval-judge-eval 73.87% 492/666 76.12% 76.41%
crates/tool-schema-derive 74.20% 279/376 88.00% 72.42%
crates/tools-basic 75.37% 771/1023 66.15% 79.16%
crates/model-runtime-claude-cli 76.51% 1655/2163 73.10% 77.37%
crates/tools-github 77.06% 2408/3125 74.19% 74.82%
crates/tools-exec 77.72% 5193/6682 74.88% 74.01%
apps/client 78.88% 13283/16840 89.58% 75.76%
crates/persistence 80.07% 53946/67376 78.01% 75.47%
crates/tools-code-host 80.39% 7089/8818 80.46% 76.60%
crates/blob-store-filesystem 80.50% 1371/1703 70.17% 80.45%
crates/model-provider-runtime 81.37% 2463/3027 85.71% 79.97%
crates/blob-store 82.31% 307/373 75.41% 83.49%
crates/tools-conversations 83.01% 508/612 83.78% 77.65%
crates/egress-transport 85.35% 134/157 83.33% 77.61%
crates/conversation-import-claude-code 85.45% 740/866 66.09% 84.67%
crates/tools-plan 85.71% 768/896 88.89% 81.74%
crates/conversation-import-codex 85.76% 873/1018 64.00% 83.56%
crates/tools-workspace 86.36% 2977/3447 82.14% 87.30%
crates/tools-web 86.59% 2978/3439 87.93% 85.03%
crates/tools-git 87.40% 8750/10011 86.50% 83.24%
crates/application 88.65% 19184/21640 87.86% 88.81%
crates/model-runtime-codex-cli 90.01% 1550/1722 90.98% 89.96%
crates/test-bin 90.91% 10/11 100.00% 70.00%
crates/domain 92.28% 60165/65196 91.37% 94.13%
crates/web-contract 92.31% 408/442 82.61% 82.09%
crates/conversation-import-json 92.51% 284/307 96.88% 91.36%
crates/model-runtime-openai 93.55% 3812/4075 97.50% 91.72%
crates/process-protocol 93.67% 8469/9041 95.89% 86.68%
crates/model-runtime-anthropic 93.88% 3910/4165 97.51% 91.26%
crates/model-runtime 93.96% 9221/9814 93.29% 94.67%
crates/expect-table 95.60% 977/1022 100.00% 95.75%
crates/tool-contract 97.18% 688/708 96.47% 95.54%

25 files with the most uncovered lines

File Uncovered lines Line %
apps/signalboxd/src/process_runtime.rs 6571 53.43%
apps/signalboxd/src/repo_watch_runtime.rs 2235 69.20%
apps/signalboxd/src/runner_protocol_runtime.rs 2212 34.75%
crates/persistence/src/submit_input.rs 2071 71.81%
apps/client/src/lib.rs 1697 79.51%
apps/signalboxd/src/review_orchestration_runtime.rs 1333 2.56%
apps/signalboxd/src/main.rs 1303 47.46%
crates/persistence/src/model_execution.rs 1237 84.68%
crates/domain/src/turn_eligibility.rs 1223 90.33%
crates/persistence/src/runner_protocol.rs 1157 82.16%
crates/tools-code-host/src/code_host/github.rs 1052 76.26%
crates/persistence/src/review_workflow.rs 997 77.35%
crates/tools-exec/src/bin/signalbox-exec-supervisor.rs 885 45.61%
crates/persistence/src/tool_loop.rs 749 76.07%
crates/tools-github/src/lib.rs 717 76.47%
apps/signalboxd/src/convergence_sweep_runtime.rs 683 22.21%
crates/persistence/src/process_read.rs 681 82.33%
apps/client/src/presentation.rs 680 80.55%
apps/signalboxd/src/lib.rs 647 72.20%
crates/domain/src/submit_input.rs 640 88.03%
apps/signalboxd/src/daemon_tools.rs 573 89.68%
crates/process-protocol/src/lib.rs 572 93.67%
crates/persistence/src/review_orchestration.rs 569 75.66%
crates/persistence/src/session_delegation.rs 543 77.63%
crates/application/src/model_execution.rs 541 86.15%

Measured at b6c1187010cd0072d3ba6e4271f5895e6e8797c4, the merge commit this pull request builds, whose head is 6541cd65ad96019f621d81c9b475906ed2eeb692, by run 32620026741, which uploads the HTML report and LCOV as an artifact.

@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.27%. Comparing base (cd06846) to head (6541cd6).

Additional details and impacted files
Flag Coverage Δ
rust 82.85% <ø> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants