Skip to content

fix(devops): transport exact pre-push evidence - #493

Open
qnbs wants to merge 2 commits into
mainfrom
reconstruct-pr491-s3-transport
Open

fix(devops): transport exact pre-push evidence#493
qnbs wants to merge 2 commits into
mainfrom
reconstruct-pr491-s3-transport

Conversation

@qnbs

@qnbs qnbs commented Aug 24, 2026

Copy link
Copy Markdown
Owner

User description

Purpose

This replacement slice reconstructs the outgoing pre-push range and lossless
change-evidence transport from the frozen PR #491 source:

9bbeded78f1032a5e74aa370ef7ca158628ad784

PR #492 exposed the caller/evidence boundary defect: pre-push stdin was consumed
by outgoing signature verification before local admission could receive the same
ref-update evidence.

Scope

This PR owns only:

  • one canonical pre-push update parser and serialized handoff;
  • shared evidence input for outgoing signature verification and local admission;
  • fail-closed ref/range/object resolution for branch, new-branch, deletion, tag,
    multiple-update, malformed-input, and Git-failure cases;
  • NUL-safe changed-path discovery and lossless consumer transport.

The S1 classifier corrections remain deferred until this transport contract is
available. S3b exact-tree dependency compatibility proof, node_modules
mirroring, exact-tree TypeScript execution, and broad localSha execution
deduplication remain separate slices.

Validation

  • tests/unit/signing.test.ts: 14/14 passed;
  • Biome on all six touched files: passed;
  • git diff --check: passed;
  • pnpm run signing:doctor: passed;
  • local SSH-signed commit verified;
  • pnpm run ci:prepush reached the existing release/doc truth gate after
    typecheck and i18n passed, then reported the pre-existing README metric drift
    (6954 vs expected 6958 tests). No documentation change is included here.

Non-goals

Summary by Sourcery

Preserve exact pre-push ref and path evidence across signing and local admission checks, failing closed when it cannot be resolved.

New Features:

  • Share a single canonical pre-push evidence stream between outgoing signature verification and local admission.
  • Resolve branch, new-branch, deletion, tag, multiple-ref, and changed-path push evidence with lossless filenames.

Bug Fixes:

  • Prevent malformed input, unavailable Git objects, and changed-path resolution failures from allowing incomplete pre-push checks.

Enhancements:

  • Refactor outgoing verification and low-end pre-push checks to consume serialized structured update evidence.

Documentation:

  • Synchronize README test-count metrics with the updated test suite.

Tests:

  • Add coverage for serialized evidence transport, ref dispositions, lossless changed paths, and fail-closed resolution.

CodeAnt-AI Description

Preserve and validate exact pre-push evidence across signing and local checks

What Changed

  • Pre-push updates are captured once and shared with outgoing signature verification and local admission.
  • Branch updates, new branches, deletions, tags, multiple refs, and changed paths are resolved explicitly.
  • Invalid input, missing Git objects, and changed-path lookup failures now stop the push instead of allowing incomplete checks.
  • Changed paths remain intact when filenames contain newlines, tabs, or non-ASCII characters.
  • Added coverage for evidence transport, ref handling, lossless paths, and fail-closed behavior.

Impact

✅ Consistent signing and local admission results
✅ Fewer pushes accepted with incomplete evidence
✅ Reliable handling of unusual filenames

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai

codeant-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 7517c4a Aug 24, 2026 · 21:36 21:41

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@sourcery-ai sourcery-ai 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.

Sorry @qnbs, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 9 hours and 52 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@codeant-ai

codeant-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
worldscript-studio Ready Ready Preview Aug 24, 2026 9:47pm

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 11 minutes.

View limit details

Limit details: You’ve used the included review currently available. Your 115 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c8e36c25-d133-4181-afee-efb564675b39

📥 Commits

Reviewing files that changed from the base of the PR and between 5806bd7 and 3a4e5f9.

📒 Files selected for processing (7)
  • README.md
  • scripts/ci-prepush-lowend.mjs
  • scripts/hooks/pre-push.mjs
  • scripts/signing/signing-core.d.mts
  • scripts/signing/signing-core.mjs
  • scripts/signing/verify-outgoing.mjs
  • tests/unit/signing.test.ts

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.

@sourcery-ai

sourcery-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes pre-push evidence loss by parsing stdin once, handing a validated serialized update stream to both outgoing signature verification and local admission, and resolving changed paths fail-closed with NUL-safe Git discovery across supported ref-update scenarios.

Sequence diagram for shared pre-push evidence transport

sequenceDiagram
    actor Git
    participant PrePush as pre-push hook
    participant Signing as verify-outgoing
    participant Admission as ci-prepush-lowend
    participant Core as signing-core

    Git->>PrePush: send pre-push ref-update stdin
    PrePush->>Core: parsePrePushInput(input)
    Core-->>PrePush: RefUpdate[]
    PrePush->>Core: serializePrePushUpdates(updates)
    Core-->>PrePush: serialized updates
    PrePush->>Signing: run with WORLD_SCRIPT_PREPUSH_UPDATES
    Signing->>Core: parseSerializedPrePushUpdates(serialized)
    Core-->>Signing: RefUpdate[]
    Signing->>Core: verifyOutgoingUpdates(updates)
    Signing-->>PrePush: signing result
    PrePush->>Admission: run with WORLD_SCRIPT_PREPUSH_UPDATES
    Admission->>Core: parseSerializedPrePushUpdates(serialized)
    Core->>Core: resolvePushEvidence(updates)
    Core-->>Admission: resolved or invalid evidence
    Admission-->>Git: accept or fail closed
Loading

Flow diagram for fail-closed push evidence resolution

flowchart TD
    A[Serialized pre-push updates] --> B[parseSerializedPrePushUpdates]
    B --> C[resolvePushEvidence]
    C --> D{Ref update valid?}
    D -- No --> X[INVALID evidence and reject]
    D -- Yes --> E{Deletion or tag?}
    E -- Yes --> F[Record disposition]
    E -- No --> G[Validate local and remote objects]
    G --> H{Objects available?}
    H -- No --> X
    H -- Yes --> I[changedFilesBetween with NUL-safe Git paths]
    I --> J{Git resolution succeeds?}
    J -- No --> X
    J -- Yes --> K[RESOLVED evidence for local admission]
Loading

File-Level Changes

Change Details Files
Introduced a canonical, lossless pre-push evidence pipeline shared by signing verification and local admission.
  • Parse and validate the hook’s complete ref-update stream once.
  • Serialize updates through an environment handoff and consume the same structured updates in both downstream checks.
  • Resolve branch, new-branch, deletion, and tag evidence with fail-closed object and Git error handling.
  • Discover changed paths with NUL-delimited Git output while preserving unusual filenames and deduplicating paths.
scripts/hooks/pre-push.mjs
scripts/signing/verify-outgoing.mjs
scripts/signing/signing-core.mjs
scripts/signing/signing-core.d.mts
scripts/ci-prepush-lowend.mjs
Expanded signing-core APIs and tests to cover the new transport and evidence-resolution contract.
  • Add parser, serializer, deserializer, evidence types, and resolver declarations.
  • Allow outgoing verification to accept either serialized input, parsed updates, or legacy line arrays.
  • Test round-trip transport, dirty-worktree-independent path resolution, multiple update dispositions, malformed input, missing objects, and Git failures.
scripts/signing/signing-core.d.mts
scripts/signing/signing-core.mjs
tests/unit/signing.test.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 24, 2026
@codeant-ai

codeant-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: 3a4e5f94
Scan Time: 2026-08-24 21:47:06 UTC

✅ Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets ✅ PASSED 0 secrets found, 1 false positive secret suppressed
Duplicate Code ✅ PASSED 0.0% duplicated
SAST ✅ PASSED No security issues
Bugs ✅ PASSED Rating B: 2 bugs (2 medium)
IAC ✅ PASSED No IAC issues

View Full Results

@amazon-q-developer amazon-q-developer 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 Summary

This PR successfully implements the pre-push evidence transport mechanism that addresses the caller/evidence boundary defect identified in PR #492. The implementation correctly ensures that pre-push stdin is consumed once and then shared between outgoing signature verification and local admission processes.

Key Strengths

Robust Architecture: The implementation establishes a clean separation of concerns with canonical parsing (parsePrePushInput), serialization (serializePrePushUpdates), and evidence resolution (resolvePushEvidence) functions. The evidence is captured once in the hook and then transported via environment variable to downstream consumers.

Comprehensive Error Handling: All functions implement fail-closed semantics with proper validation and error messages for malformed input, missing objects, and Git failures. The resolvePushEvidence function properly handles deletions, tags, new branches, and multiple updates.

Strong Test Coverage: The test suite validates all edge cases including deletions, new branches, malformed input, path resolution with special characters (newlines, Unicode, tabs), and round-trip serialization. Tests verify both local signing controls and GitHub API integration.

NUL-safe File Discovery: The implementation correctly uses -z flag in git diff to handle filenames with special characters and splits output on NUL bytes, ensuring lossless transport of changed paths.

Validation Results

All validation checks reported in the PR description have passed:

  • ✅ 14/14 unit tests passed
  • ✅ Biome checks passed on all files
  • git diff --check passed
  • pnpm run signing:doctor passed
  • ✅ SSH-signed commit verification working
  • ✅ Pre-push checks reaching existing gates

The code is ready for merge.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread scripts/signing/signing-core.mjs Outdated

export function resolvePushEvidence(input, cwd = process.cwd(), dependencies = {}) {
try {
const updates = Array.isArray(input) ? input : parsePrePushInput(input);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Type handling bug: resolvePushEvidence() doesn't properly handle string[] input despite accepting it in the type signature. When input is a string[], the code treats it as RefUpdate[] without parsing, which will cause runtime failures when accessing properties like update.localSha at lines 304-306.

The function should check if the array contains strings and parse accordingly:

const updates = Array.isArray(input)
  ? input.every((item) => typeof item === 'string')
    ? parsePrePushInput(input.join('\n'))
    : input
  : parsePrePushInput(input);

This matches the pattern used in verifyOutgoingUpdates() at lines 442-445 and aligns with the TypeScript signature that declares input: string | string[] | RefUpdate[].

Suggested change
const updates = Array.isArray(input) ? input : parsePrePushInput(input);
const updates = Array.isArray(input)
? input.every((item) => typeof item === 'string')
? parsePrePushInput(input.join('\n'))
: input
: parsePrePushInput(input);

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@qnbs

qnbs commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

S3a exact-head status (7517c4a2a1c3040e93133b1651ebb6f9fd602109):

  • S3a focused tests: 14/14 passed; Biome, diff check, signing doctor, local signature, CodeQL, Security, Verified Signatures, CodeAnt, Semgrep and dependency/security checks are green.
  • Review channels audited: GraphQL reviewThreads = 0; inline REST comments = 0; no material Codex/CodeRabbit/CodeAnt findings. CodeRabbit and Sourcery are rate-limited.
  • CI/CD is blocked by the existing README doc-metrics drift in both Node 22 and Node 24: README reports 6954 tests while the source set expects 6958. ✅ CI Success correctly propagates that quality failure.

This is outside S3a scope, so no remediation commit or push is being made. S3a remains frozen pending the baseline doc-metrics owner/fix.

Comment thread scripts/signing/signing-core.mjs Outdated
Comment on lines +320 to +321
const base = isZeroSha(update.remoteSha) ? EMPTY_TREE : update.remoteSha;
if (!isZeroSha(base) && !commitExists(base))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: EMPTY_TREE is a tree object, but the default commitExists check requires ${sha}^{commit}. Consequently, every new-branch update uses EMPTY_TREE as its base and is rejected as an unavailable remote base before changed paths can be resolved. Skip commit validation for the empty-tree base or validate it as a tree object. [logic error]

Severity Level: Major ⚠️
- ❌ New-branch pushes fail during local pre-push admission.
- ⚠️ Changed-path evidence is never resolved for new branches.
- ⚠️ The failure affects the hook's normal first-push workflow.

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** scripts/signing/signing-core.mjs
**Line:** 320:321
**Comment:**
	*Logic Error: `EMPTY_TREE` is a tree object, but the default `commitExists` check requires `${sha}^{commit}`. Consequently, every new-branch update uses `EMPTY_TREE` as its base and is rejected as an unavailable remote base before changed paths can be resolved. Skip commit validation for the empty-tree base or validate it as a tree object.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

for await (const chunk of process.stdin) input += chunk;
try {
const updates = parsePrePushInput(input);
process.env.WORLD_SCRIPT_PREPUSH_UPDATES = serializePrePushUpdates(updates);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Storing the complete update stream in WORLD_SCRIPT_PREPUSH_UPDATES puts the serialized data into the child processes' inherited environment. Large multi-ref pushes can exceed the operating system's environment/argument-size limit, causing spawnSync in runNodeScript to fail and rejecting an otherwise valid push. Use a bounded file or pipe-based handoff instead of environment transport. [possible bug]

Severity Level: Major ⚠️
- ❌ Large multi-ref pushes are rejected locally.
- ⚠️ Failure occurs before signing and admission checks run.

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** scripts/hooks/pre-push.mjs
**Line:** 10:10
**Comment:**
	*Possible Bug: Storing the complete update stream in `WORLD_SCRIPT_PREPUSH_UPDATES` puts the serialized data into the child processes' inherited environment. Large multi-ref pushes can exceed the operating system's environment/argument-size limit, causing `spawnSync` in `runNodeScript` to fail and rejecting an otherwise valid push. Use a bounded file or pipe-based handoff instead of environment transport.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@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: 7517c4a2a1

ℹ️ 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 +251 to +252
const lines = input.split(/\r?\n/).filter((line) => line.length > 0);
if (lines.length === 0) throw new Error('pre-push input is empty');

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 Accept empty pre-push streams for no-op pushes

When git push finds every requested ref already up to date, Git still invokes the pre-push hook but supplies an empty stdin stream (confirmed with Git 2.43 against a local bare remote). This new check therefore throws pre-push input is empty, turns an otherwise successful no-op push into a failure, and exits before the repository's mandatory quick pre-push gate runs; treat zero updates as a valid stream with no objects to verify.

AGENTS.md reference: AGENTS.md:L297-L303

Useful? React with 👍 / 👎.

Comment thread scripts/signing/signing-core.mjs Outdated

export function resolvePushEvidence(input, cwd = process.cwd(), dependencies = {}) {
try {
const updates = Array.isArray(input) ? input : parsePrePushInput(input);

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 Parse documented string-array evidence inputs

The new declaration explicitly permits resolvePushEvidence(string[]), but this branch treats every array as already-parsed RefUpdate objects. A typed caller passing valid pre-push lines consequently reads localSha and remoteSha from strings and returns evidenceState: 'INVALID'; distinguish string arrays and pass them through parsePrePushInput, as verifyOutgoingUpdates already does.

Useful? React with 👍 / 👎.

Comment thread scripts/signing/signing-core.mjs Outdated
Comment on lines +321 to +322
if (!isZeroSha(base) && !commitExists(base))
throw new Error(`remote base object is unavailable for ${update.remoteRef}`);

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 Skip commit validation for the synthetic empty-tree base

When creating a remote feature branch, Git supplies a zero remote SHA, so the preceding line substitutes EMPTY_TREE; this condition then calls the default commitExists, which runs git cat-file -e <empty-tree>^{commit} and fails because that object is a tree rather than a commit. Consequently resolvePushEvidence returns INVALID and the hook rejects every first push of a branch; skip commit-only validation for the synthetic base or validate it as a tree before diffing. This blocks the repository-required feature-branch PR workflow.

AGENTS.md reference: AGENTS.md:L407-L407

Useful? React with 👍 / 👎.

Comment on lines +442 to +445
updates =
Array.isArray(input) && input.every((item) => typeof item === 'string')
? parsePrePushInput(input.join('\n'))
: input;

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 Parse documented raw-string verification inputs

The updated declaration permits verifyOutgoingUpdates(string), but this branch parses only arrays whose elements are strings and otherwise assigns the raw value directly to updates. A typed caller passing one canonical pre-push stream is therefore rejected by the following Array.isArray check even though wrapping the same line in an array succeeds; route raw strings through parsePrePushInput as well.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant