Skip to content

Bridge: a blank or invisible-character destination record ID settles nothing (stacked on #131) - #157

Merged
sykosyber merged 1 commit into
feat/bridge-next-after-127from
feat/bridge-next-after-131
Nov 4, 2026
Merged

sykosyber merged 1 commit into
feat/bridge-next-after-127from
feat/bridge-next-after-131

Conversation

@sykosyber

Copy link
Copy Markdown
Member

Stacked on #131 (feat/bridge-next-after-127, which now includes origin/main after #156 by plain merge). It closes the first item #131's NOTES list under "Not judged": a record ID that is a nonempty string but names no record.

The gap

Both reconcile paths copy a destination's record ID into the external_id of a verified reconciled proof, and both accepted any nonempty string:

So " ", "\t", "SHIP-1​" (zero-width space), "‮1-PIHS" (right-to-left override) or "SHIP-1\r\nSHIP-2" met acceptance and settled the effect. A blank ID names no record. An ID with an invisible character renders as an ID it is not (in the console, the dashboard, logs), or splits into two lines.

Before / after (same tests on each tree)

external_id before (#131 tip) this PR
" ", " " verified, reconciled, acceptance met (both paths) Work: unverified / destination_record_mismatch. Session: unverified / external_id_missing. Unresolved, retry blocked
tab, newline, CRLF inside, NUL, DEL, C1 \x85 verified same as above
U+200B, U+202E, U+FEFF (format) verified same as above
U+00A0, U+3000 (non-ASCII space), U+2028, U+2029 verified same as above
lone surrogate Session: verified. HTTP: already destination_record_ambiguous (strict parser) Session: external_id_missing. HTTP: unchanged
"SHIP-1", "a b", "ü-ä", "日本-7", "x" verified verified (positive controls, unchanged)
"", non-string refused (#131 / existing) unchanged

Change

Cases (tests/test_bridge_record_id.py, new, 6 tests)

The file is outside the tests/test_bridge_redteam*.py glob and adds no CASES tags, so G2 and G3 counts are unchanged. It reuses the loopback destination from tests/test_bridge_destination_ack.py and the LostWrite executor from tests/test_session_applied_proof.py. It imports the first as a module, so none of that file's tests are collected twice.

Test What it pins
RecordIdPredicate.test_unusable_ids_are_refused 16 unusable strings
RecordIdPredicate.test_usable_ids_and_non_strings 8 usable IDs (including non-ASCII). "", None, numbers, bool, list, dict and bytes are refused
StatusLookupRecordId.test_status_record_with_an_unusable_id_is_a_mismatch 15 IDs on a record that matches key and digest. Each gives destination_record_mismatch with the effect unresolved, commit refused (effect_claimed) and a fresh proposal blocked. The history is schema-valid, there is 1 POST, and there is 1 lookup per check. Positive: a usable ID then reconciles once, and a second reconcile is already reconciled
StatusLookupRecordId.test_usable_ids_reconcile Each usable ID reconciles, and the proof cites it verbatim
SessionRecordId.test_applied_proof_with_an_unusable_id_is_unverified 16 IDs give the exact external_id_missing body. Nothing settles, the history is valid, the chain is valid and the retry is blocked. Positive: "v1.4.0" settles once
SessionRecordId.test_a_non_ascii_visible_id_settles "日本-7" settles (false-positive control)

Mutation check

record_id → isinstance(value, str) and bool(value) (= the old rule): all 3 refusal tests fail, with 20 failures and 29 errors across subtests. The first blank ID reconciles, and every later subtest then hits already reconciled. The 3 positive tests still pass.

NOTES (claim ceiling)

Local runs (Linux, Python 3.13.5, venv with hypothesis 6.170.14, no Postgres)

CI links follow in a comment once the runs finish. Those runs are evidence for this tree only and do not change required checks.

🤖 Generated with Claude Code

…nothing

Work.reconcile (http status lookup) and Session.reconcile (bound executor
applied proof) accepted any nonempty string as the destination record ID
that a verified reconciled proof cites. An ID that is only spaces, or that
carries a control, format, separator or surrogate character (NUL, newline,
U+200B, U+202E, U+00A0...), now names no record: the status lookup records
destination_record_mismatch and the Session records external_id_missing,
as each already does for an empty ID. New predicate
syberlabs.evidence.record_id; cases in tests/test_bridge_record_id.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T02:10:11.937827Z 86df060 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sykosyber

Copy link
Copy Markdown
Member Author

CI on 86df060: ci (pull_request) https://github.com/SyberLabs/SyberWork/actions/runs/41263947211 · ci (push) https://github.com/SyberLabs/SyberWork/actions/runs/41263943341. These were in progress when this comment was posted.

@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: 86df060e46

ℹ️ 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 syberlabs/evidence.py
Comment on lines +90 to +91
and not any(unicodedata.category(char) in INVISIBLE_CATEGORIES
or (char != " " and unicodedata.category(char) == "Zs") for char in value))

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 Reject default-ignorable marks in record IDs

When a status record or executor proof contains an invisible combining mark such as U+034F, this predicate accepts it because its Unicode category (Mn) is not checked, so Work.reconcile or Session.reconcile can still append a verified reconciled event for an ID that renders indistinguishably from another ID. Reject these characters too so the guard prevents the ambiguous IDs it is intended to catch.

Useful? React with 👍 / 👎.

@sykosyber
sykosyber merged commit 5f2e542 into feat/bridge-next-after-127 Nov 4, 2026
25 checks passed
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