Context
src/orb/ingest.ts's per-event validation/insert loop for orb_signals caps every other string field it writes: repo_hash/pr_hash at MAX_HASH_CHARS (128), gate_reasoncode_bucket at MAX_BUCKET_CHARS (64), and instance_id at MAX_INSTANCE_ID_CHARS (64) elsewhere in the same file. gate_verdict, however, is inserted with only a type check and no length cap:
typeof event.gate_verdict === "string" ? event.gate_verdict : null,
The column is documented in migration 0060_orb_fleet_collector.sql (line 20) as a low-cardinality enum-shaped field ('merge' | 'close' | 'hold'), matching every sibling string field's tight cap in intent — but nothing in the ingest path actually enforces a size limit on it. test/integration/orb-ingest.test.ts has explicit oversized-value rejection tests for instance_id (line 29), repo_hash/pr_hash (lines 35, 38), and gate_reasoncode_bucket (line 63) — but only a plain string-vs-null test for gate_verdict (lines 42-46), confirming no cap was ever added for this field even though its siblings all have one.
Since this collector accepts anonymized batches from self-hosted instances (see the file's own header comment: "Accepts anonymized, reversal-aware outcome batches from self-hosted instances"), an unbounded string field is an easy way for a malformed or hostile instance to push oversized rows into orb_signals.
Requirements
- Add a
MAX_VERDICT_CHARS constant (32 is a reasonable ceiling given the three documented enum values) alongside the file's existing MAX_HASH_CHARS/MAX_BUCKET_CHARS/MAX_INSTANCE_ID_CHARS constants.
- Gate the
gate_verdict bind the same way gate_reasoncode_bucket already is: typeof event.gate_verdict === "string" && event.gate_verdict.length <= MAX_VERDICT_CHARS ? event.gate_verdict : null.
- Do not change validation for any other field in this loop.
Deliverables
Test Coverage Requirements
This repo's Codecov patch gate requires 99%+ coverage of changed lines and branches. Cover both the within-cap (value stored) and over-cap (coerced to null) branches of the new condition.
Expected Outcome
gate_verdict is bounded the same way every sibling string field in the orb_signals insert already is — an oversized value from a malformed or hostile self-hosted instance is coerced to null instead of being written unbounded.
Links & Resources
src/orb/ingest.ts — the orb_signals insert loop, gate_verdict bind around line 166-170; existing caps MAX_HASH_CHARS/MAX_BUCKET_CHARS/MAX_INSTANCE_ID_CHARS near the top of the file
migrations/0060_orb_fleet_collector.sql — line 20, gate_verdict column documentation
test/integration/orb-ingest.test.ts — existing oversized-value tests, lines 29, 35, 38, 63; existing gate_verdict string-vs-null test, lines 42-46
Context
src/orb/ingest.ts's per-event validation/insert loop fororb_signalscaps every other string field it writes:repo_hash/pr_hashatMAX_HASH_CHARS(128),gate_reasoncode_bucketatMAX_BUCKET_CHARS(64), andinstance_idatMAX_INSTANCE_ID_CHARS(64) elsewhere in the same file.gate_verdict, however, is inserted with only a type check and no length cap:The column is documented in migration
0060_orb_fleet_collector.sql(line 20) as a low-cardinality enum-shaped field ('merge' | 'close' | 'hold'), matching every sibling string field's tight cap in intent — but nothing in the ingest path actually enforces a size limit on it.test/integration/orb-ingest.test.tshas explicit oversized-value rejection tests forinstance_id(line 29),repo_hash/pr_hash(lines 35, 38), andgate_reasoncode_bucket(line 63) — but only a plain string-vs-null test forgate_verdict(lines 42-46), confirming no cap was ever added for this field even though its siblings all have one.Since this collector accepts anonymized batches from self-hosted instances (see the file's own header comment: "Accepts anonymized, reversal-aware outcome batches from self-hosted instances"), an unbounded string field is an easy way for a malformed or hostile instance to push oversized rows into
orb_signals.Requirements
MAX_VERDICT_CHARSconstant (32 is a reasonable ceiling given the three documented enum values) alongside the file's existingMAX_HASH_CHARS/MAX_BUCKET_CHARS/MAX_INSTANCE_ID_CHARSconstants.gate_verdictbind the same waygate_reasoncode_bucketalready is:typeof event.gate_verdict === "string" && event.gate_verdict.length <= MAX_VERDICT_CHARS ? event.gate_verdict : null.Deliverables
MAX_VERDICT_CHARSconstant added tosrc/orb/ingest.ts.gate_verdict's bind expression updated to enforce the new cap, following the exact pattern already used forgate_reasoncode_bucket.test/integration/orb-ingest.test.tsmirroring the existing oversized-value tests (e.g. line 63'sgate_reasoncode_buckettest) forgate_verdict: an event with agate_verdictlonger thanMAX_VERDICT_CHARSis accepted into the batch but stored asnullfor that field (matching the existing coercion behavior for other capped fields), not rejected outright.Test Coverage Requirements
This repo's Codecov patch gate requires 99%+ coverage of changed lines and branches. Cover both the within-cap (value stored) and over-cap (coerced to null) branches of the new condition.
Expected Outcome
gate_verdictis bounded the same way every sibling string field in theorb_signalsinsert already is — an oversized value from a malformed or hostile self-hosted instance is coerced tonullinstead of being written unbounded.Links & Resources
src/orb/ingest.ts— theorb_signalsinsert loop,gate_verdictbind around line 166-170; existing capsMAX_HASH_CHARS/MAX_BUCKET_CHARS/MAX_INSTANCE_ID_CHARSnear the top of the filemigrations/0060_orb_fleet_collector.sql— line 20,gate_verdictcolumn documentationtest/integration/orb-ingest.test.ts— existing oversized-value tests, lines 29, 35, 38, 63; existinggate_verdictstring-vs-null test, lines 42-46