Plates as an internal correlation key only: HMAC match key from spoken plates, 2h TTL, no search/watchlist/display (CISO ruled) #248

Open
opened 2026-10-05 02:18:54 -04:00 by logan · 1 comment
Owner

What (narrowed by owner, 2026-10-05; replaces the original broad proposal)

Licence plates spoken on the radio are extracted from transcripts and used only as an internal correlation key. The same plate heard across talkgroups or calls links those calls into one incident or pursuit path. No plate search, no watchlist, no alerts, no display on any surface (UI, Discord, webhooks, exports, summaries, public tier). Not LPR and not camera-derived (that stays under #200). Roadmap: #247 (NEXT).

The original proposal (structured plates on the call doc, search, watchlist, map pins) is withdrawn. CISO position on that version is HOLD; it needs a new ruling plus E&O, #43, #13, #44 and #47.

Design as ruled (CISO ruling comment, 2026-10-05)

  • Normalise the plate (letters and digits, uppercase) using _PLATE_READ (intelligence.py:529) as a pre-filter plus a phonetic-alphabet normaliser.
  • Store only a keyed HMAC-SHA256 (16 bytes), per-org derived key (HKDF of a c2-core secret PLATE_HMAC_KEY + org_id). Never plaintext, never partial or n-gram hashes.
  • Own server-only collection plate_keys (org_id, hmac, call_id, incident_id, seen_at, expires_at) with an explicit deny rule. Nothing plate-derived on call or incident docs (browser reads Firestore directly; #13 rules not verifiably live).
  • Lifetime is the correlation window: expires_at = last_seen + 2h, code-enforced ceiling 6h, not org-configurable. In-process sweep loop plus a Firestore TTL policy.
  • No route or tool that accepts a plaintext plate and queries the store.
  • corr_debug and logs: match boolean, count, and at most a 4-hex-char HMAC prefix. Never plaintext, normalised plate, span or full hash. Remove vehicle lists from the INFO line at incident_correlator.py:1950-1952. Required caplog and corr_debug tests.
  • Scrub plates from vehicles (prompt + deterministic) and from AI summaries. Add plates as a redaction class in #43's write-time redacted copy.
  • Plate match corroborates, never links alone; review with drb-correlation-review.

Status

Ruling: ship-with-conditions (see comment). Needs CEO acceptance. Nothing in the narrowed build waits for E&O, Gate A or Gate B.

Related: #200 (camera frames, unruled), #43 (name/plate redaction), #44 (retention), #13 (rules), #47 (ToS/Privacy), #96 (corr_debug taint), #170 (replay dumps), #247 (roadmap).

## What (narrowed by owner, 2026-10-05; replaces the original broad proposal) Licence plates spoken on the radio are extracted from transcripts and used **only as an internal correlation key**. The same plate heard across talkgroups or calls links those calls into one incident or pursuit path. **No plate search, no watchlist, no alerts, no display on any surface** (UI, Discord, webhooks, exports, summaries, public tier). Not LPR and not camera-derived (that stays under #200). Roadmap: #247 (NEXT). The original proposal (structured plates on the call doc, search, watchlist, map pins) is **withdrawn**. CISO position on that version is HOLD; it needs a new ruling plus E&O, #43, #13, #44 and #47. ## Design as ruled (CISO ruling comment, 2026-10-05) - Normalise the plate (letters and digits, uppercase) using `_PLATE_READ` (`intelligence.py:529`) as a pre-filter plus a phonetic-alphabet normaliser. - Store only a **keyed HMAC-SHA256** (16 bytes), per-org derived key (HKDF of a c2-core secret `PLATE_HMAC_KEY` + org_id). Never plaintext, never partial or n-gram hashes. - Own server-only collection `plate_keys` (`org_id`, `hmac`, `call_id`, `incident_id`, `seen_at`, `expires_at`) with an explicit deny rule. Nothing plate-derived on call or incident docs (browser reads Firestore directly; #13 rules not verifiably live). - Lifetime is the correlation window: `expires_at = last_seen + 2h`, code-enforced ceiling 6h, not org-configurable. In-process sweep loop plus a Firestore TTL policy. - No route or tool that accepts a plaintext plate and queries the store. - `corr_debug` and logs: match boolean, count, and at most a 4-hex-char HMAC prefix. Never plaintext, normalised plate, span or full hash. Remove vehicle lists from the INFO line at `incident_correlator.py:1950-1952`. Required `caplog` and `corr_debug` tests. - Scrub plates from `vehicles` (prompt + deterministic) and from AI summaries. Add plates as a redaction class in #43's write-time redacted copy. - Plate match corroborates, never links alone; review with `drb-correlation-review`. ## Status Ruling: ship-with-conditions (see comment). Needs CEO acceptance. Nothing in the narrowed build waits for E&O, Gate A or Gate B. Related: #200 (camera frames, unruled), #43 (name/plate redaction), #44 (retention), #13 (rules), #47 (ToS/Privacy), #96 (corr_debug taint), #170 (replay dumps), #247 (roadmap).
logan added the
type
feature
area/ai-pipelinearea/map
priority
p2
status
needs-decision
labels 2026-10-05 02:18:54 -04:00
logan changed title from Plate tracking from transcripts: extract spoken plates, search, watchlist, show where mentioned (needs CISO ruling) to Plates as an internal correlation key only: HMAC match key from spoken plates, 2h TTL, no search/watchlist/display (CISO ruled) 2026-10-05 02:21:37 -04:00
Author
Owner

CISO ruling, 2026-10-05: plates as an INTERNAL correlation key only (narrowed scope)

Verdict: SHIP WITH CONDITIONS. I did not post a ruling on the broad version (search, watchlist, alerts, map pins). Nothing here supersedes an earlier ruling. My position on the broad version, for the record, is HOLD: it needs E&O, #43, #13, #44 and #47 first, and I would want a fresh ruling before it is picked up again. The CEO can overrule any of this.

Position

Hash-only, same-org, short-lived matching keys create no new disclosure. They have no reader and no display. That can ship ahead of every gate. The honest limit is that it cannot make the existing plate exposure go away. Raw plates already sit in transcripts, and probably in vehicles, on browser-readable docs. See finding 2.

Findings (ranked by blast radius if we get it wrong; all cited from origin/main a75149b)

  1. The key store must be server-only and must not depend on Firestore rules. Browsers read calls and incidents directly, and the rules allow any org member, whatever their role, to read those docs (infra/firestore/firestore.rules docInMyOrg()). Rules cannot mask fields. #13 was reopened 2026-09-13 because CI never actually deploys them, and I found no later comment saying that is fixed. So the live rules are not verifiable from here. Nothing plate-derived goes on a call or incident doc.
  2. "No display on any surface" is not true today, and this feature does not change that.
    • vehicles is free text from the extraction prompt (intelligence.py:58). It is rendered at drb-frontend/app/incidents/[id]/page.tsx:217-221 and substring-searched client-side at incidents/page.tsx:43. If Gemini puts a plate in it, a plate is searchable by any org user right now. I could not verify from here whether it does.
    • Transcripts show raw in the ticker and call views.
    • corr_debug copies the raw scene transcript onto the call doc (incident_correlator.py:1546-1562).
    • _snippet_allowed (alerter.py:97) is closed by default, and that holds.
  3. The only deletion code the company has is none. There is no TTL or sweep anywhere in c2-core (#44). Plate keys need a real expiry or they become the retention problem #44 describes.
  4. Existing third-party flow. llm_correlator.py:101,110 sends the transcript (700 chars) and vehicles to Gemini. Plates already leave to Google today. This is not new, but I cannot verify the key's data-handling tier from here.
  5. INFO logs already print vehicle lists. incident_correlator.py:1950-1952 logs inc_vehicles and call_vehicles at INFO. Any plate that reaches vehicles lands in container logs. This is the recurring "library or app logs the sensitive thing" class.

Rulings

1. HMAC vs plaintext: HMAC. Plaintext is not allowed.

  • Use HMAC-SHA256 over the normalised plate (letters and digits only, uppercase), truncated to 16 bytes.
  • Use a per-org derived key, HKDF(master secret, org_id). Hashes are then useless for joining across tenants even if a table is dumped.
  • The master is a new c2-core secret (PLATE_HMAC_KEY) in the ansible vault, read into the process only. Never logged, never in .env.example.
  • An unkeyed hash is just plaintext. NY plates have roughly 1.8e8 to 1e11 possible values, so a bare SHA-256 can be enumerated in minutes. The HMAC only helps while the key stays secret. If c2-core env and the key store are both taken, the hashes are enumerable, which is why the lifetime below is short.
  • Match on the full normalised plate only. No prefix, suffix or n-gram hashes. Partial matching would make enumeration cheaper and would be search by another name.
  • Store the keys in their own collection plate_keys (org_id, hmac, call_id, incident_id, seen_at, expires_at). Add an explicit allow read, write: if false rule for it. It is also a server-only decision (finding 1), so it never depends on a rule being deployed.
  • No route, no admin tool and no script that accepts a plaintext plate and queries the store. That is the dropped search feature again. plate_key(org, plate) is private to the pipeline.

Lifetime: the correlation window, not a retention period.

  • Longest auto-resolve is 90 min (config.py:119). Set expires_at = last_seen + 2h, with a code-enforced ceiling of 6h. It is not org-configurable.
  • Lookups must also ignore anything outside the candidate incident's idle window, whether or not it has been swept yet.
  • Deletion has two parts: a Firestore TTL policy on expires_at (set in the console or gcloud, not verifiable from this repo) AND an in-process sweep loop in c2-core. Do not rely on Firestore TTL alone, because it deletes lazily.
  • A plate key never survives incident resolution plus 2h.
  • Rotating PLATE_HMAC_KEY is cheap and safe, since keys are only 2h old. Rotate yearly or on any c2-core env exposure.

2. Redaction of raw plates in transcripts and summaries.

  • Build now: strip plate spans from vehicles before write. Add a prompt instruction not to put plates in vehicles, plus a deterministic scrub. Reuse the _PLATE_READ pattern (intelligence.py:529).
  • Build now: scrub plates from AI-generated incident summaries (summarizer.py) after generation. Summaries are our own first-party content, so a plate next to an incident is our own assertion.
  • Do not use redaction on the raw transcript to gate this feature. The key creates no new disclosure. But plates become a redaction class in #43's write-time redacted copy (GATE-B3-SCOPE.md design), alongside names. Display-layer redaction is cosmetic while the browser reads the raw doc.
  • Every outbound or unauthenticated surface must carry zero plates: the Public tier, Discord posts, alert payloads and snippets, webhooks, exports and the share card. Today those are either unbuilt or closed (#85). Treat that as a standing rule, and any new surface needs a plate-scrub test before it merges.
  • The owner's "no display on any surface" claim stays unverified until #43's plate class lands and the vehicles leak is checked against live data.

3. Logging and corr_debug.

  • Allowed in corr_debug and logs: corr_plate_match: true|false, a count, and a 4-hex-char prefix of the HMAC at most. The prefix is for disambiguating two plates in one incident during debugging.
  • Never allowed: plaintext, normalised plate, raw span, full hash, or any partial plate string. Also never in exceptions, f-strings at any level, or the replay and eval dumps (#170). The admin dump (admin.py:107-120) uses a field whitelist. Keep it that way. Do not add a plate field to it.
  • Fix as part of this work: drop call_vehicles and inc_vehicles from the INFO line at incident_correlator.py:1950-1952, or log counts only.
  • Do not send plates or hashes to the LLM correlator as extra fields.
  • Required test: a caplog test plus a corr_debug schema test asserting a known test plate never appears in either.
  • Measurement caveat: corr_debug is last-write-wins on multi-scene calls (#96). Any plate-match path stat read from it is tainted for those calls.
  • Existing exposure, not new: unversioned corr and replay dumps at the project root hold transcripts. I did not open them. Keep them out of any git repo.

4. DPPA, E&O and legal. I am not counsel. Counsel confirmation goes in #47.

  • DPPA (18 U.S.C. §2721) regulates personal information drawn from state DMV motor-vehicle records. A plate overheard on radio and hashed is not DMV-sourced. A plate number alone is not in the statute's enumerated list. I see no DPPA exposure in hash-only matching. Exposure would start if we ever join a plate to a registrant name or address. Dispatch reads DMV returns on air ("registered to ..."), so the plate normaliser must not capture registrant returns, and names stay suppressed under #43. Never add a DMV or registration lookup. This must be a written non-goal.
  • NY. I know of no NY statute governing plate retention by a private entity. This is unverified. The NY Shield Law (amended; includes ALPR-sharing and out-of-state-subpoena limits for protected health-care activity) is aimed at ALPR operators and in-state entities. We are neither, but the policy signal is clear. A plate-to-location index is exactly the data those laws target. Hash-only, 2h and no query surface leave little for a subpoena to reach. The real subpoena surface is still the transcript store, which has no retention (#44). Do not assume the press shield law (Civil Rights Law §79-h) covers a SaaS vendor's stored data.
  • E&O and defamation. Internal matching with no display, no name and no plate shown adds no incident-level assertion about a person. Residual risk is a false link from a Whisper-misheard plate merging two incidents. That is a quality risk and belongs to drb-correlation-review. A plate match should corroborate, not stand alone, and "orphan in doubt" still applies. Any display, search, watchlist or alert changes this and needs E&O first.
  • Privacy and ToS (#47). Add a data-inventory line: plate-derived match keys, 2h, hashed, never displayed. The page at drb-frontend/app/privacy/page.tsx:23 is still a TODO(legal).

5. Gates.

  • Nothing in the narrowed build must wait for E&O, Gate A or Gate B. It publishes nothing, charges nothing and displays nothing.
  • Gate B items it touches: #43 (add the plate class), #44 (the 2h TTL is a real enforced retention fact, usable as the model for the sweep), #13 (not depended on).
  • Must wait for a new CISO ruling, E&O bound, #43, #13, #44 and #47: any search, watchlist, alert, map pin, export, webhook, Discord, summary or public mention of plates. The broad version remains HOLD.
  • The comped (friends and family) users are live now. So the finding 2 scrub applies on merge, not at Gate B.

Could not verify from here

  • Whether the live Firestore rules match the repo (#13 state after 2026-09-13).
  • Whether the live vehicles field and transcripts already carry plates, and whether summaries do.
  • Whether the Firestore TTL policy can be created and is applied to the c2-server database.
  • Gemini and OpenAI data-handling terms for the keys in use.
  • NY statutory position on private plate retention, and the DPPA reading, until counsel confirms.
  • #200 (the camera-frame ruling) is still open and unruled. It is not precedent for this, only the same data class.

Recommendation (smallest fix first)

  1. CTO: drop vehicle lists from the INFO log line (incident_correlator.py:1950-1952) and add the caplog plus corr_debug tests. Smallest and no dependencies.
  2. CTO: plate scrub on vehicles and summaries (intelligence.py, summarizer.py) and add the plate class to #43's redaction pass.
  3. CTO: plate_key() (HMAC, per-org HKDF), plate_keys collection with a deny rule, 2h TTL with a 6h clamp and a sweep loop. Review with drb-correlation-review before anything uses it as a link signal.
  4. CIO: add PLATE_HMAC_KEY to vault and env templates without printing it; create the Firestore TTL policy.
  5. Owner: privacy and ToS line (#47).

Needs a CEO ruling

  • Accept hash-only internal use ahead of the gates, with plates scrubbed from vehicles and summaries on merge?
  • Accept that the "no display" claim is not true of raw transcripts until #43 lands?
  • Confirm the broad version (search, watchlist, alerts, map) stays HOLD.

Status label: status/needs-decision is kept. The conditions above need CEO acceptance. No code is in progress.

## CISO ruling, 2026-10-05: plates as an INTERNAL correlation key only (narrowed scope) **Verdict: SHIP WITH CONDITIONS.** I did not post a ruling on the broad version (search, watchlist, alerts, map pins). Nothing here supersedes an earlier ruling. My position on the broad version, for the record, is HOLD: it needs E&O, #43, #13, #44 and #47 first, and I would want a fresh ruling before it is picked up again. The CEO can overrule any of this. ### Position Hash-only, same-org, short-lived matching keys create no new disclosure. They have no reader and no display. That can ship ahead of every gate. The honest limit is that it cannot make the existing plate exposure go away. Raw plates already sit in transcripts, and probably in `vehicles`, on browser-readable docs. See finding 2. ### Findings (ranked by blast radius if we get it wrong; all cited from origin/main a75149b) 1. **The key store must be server-only and must not depend on Firestore rules.** Browsers read `calls` and `incidents` directly, and the rules allow any org member, whatever their role, to read those docs (`infra/firestore/firestore.rules` `docInMyOrg()`). Rules cannot mask fields. #13 was reopened 2026-09-13 because CI never actually deploys them, and I found no later comment saying that is fixed. So the live rules are not verifiable from here. Nothing plate-derived goes on a call or incident doc. 2. **"No display on any surface" is not true today, and this feature does not change that.** - `vehicles` is free text from the extraction prompt (`intelligence.py:58`). It is rendered at `drb-frontend/app/incidents/[id]/page.tsx:217-221` and substring-searched client-side at `incidents/page.tsx:43`. If Gemini puts a plate in it, a plate is searchable by any org user right now. I could not verify from here whether it does. - Transcripts show raw in the ticker and call views. - `corr_debug` copies the raw scene transcript onto the call doc (`incident_correlator.py:1546-1562`). - `_snippet_allowed` (`alerter.py:97`) is closed by default, and that holds. 3. **The only deletion code the company has is none.** There is no TTL or sweep anywhere in c2-core (#44). Plate keys need a real expiry or they become the retention problem #44 describes. 4. **Existing third-party flow.** `llm_correlator.py:101,110` sends the transcript (700 chars) and `vehicles` to Gemini. Plates already leave to Google today. This is not new, but I cannot verify the key's data-handling tier from here. 5. **INFO logs already print vehicle lists.** `incident_correlator.py:1950-1952` logs `inc_vehicles` and `call_vehicles` at INFO. Any plate that reaches `vehicles` lands in container logs. This is the recurring "library or app logs the sensitive thing" class. ### Rulings **1. HMAC vs plaintext: HMAC. Plaintext is not allowed.** - Use HMAC-SHA256 over the normalised plate (letters and digits only, uppercase), truncated to 16 bytes. - Use a per-org derived key, HKDF(master secret, org_id). Hashes are then useless for joining across tenants even if a table is dumped. - The master is a new c2-core secret (`PLATE_HMAC_KEY`) in the ansible vault, read into the process only. Never logged, never in `.env.example`. - An unkeyed hash is just plaintext. NY plates have roughly 1.8e8 to 1e11 possible values, so a bare SHA-256 can be enumerated in minutes. The HMAC only helps while the key stays secret. If c2-core env and the key store are both taken, the hashes are enumerable, which is why the lifetime below is short. - Match on the full normalised plate only. No prefix, suffix or n-gram hashes. Partial matching would make enumeration cheaper and would be search by another name. - Store the keys in their own collection `plate_keys` (`org_id`, `hmac`, `call_id`, `incident_id`, `seen_at`, `expires_at`). Add an explicit `allow read, write: if false` rule for it. It is also a server-only decision (finding 1), so it never depends on a rule being deployed. - No route, no admin tool and no script that accepts a plaintext plate and queries the store. That is the dropped search feature again. `plate_key(org, plate)` is private to the pipeline. **Lifetime: the correlation window, not a retention period.** - Longest auto-resolve is 90 min (`config.py:119`). Set `expires_at = last_seen + 2h`, with a code-enforced ceiling of 6h. It is not org-configurable. - Lookups must also ignore anything outside the candidate incident's idle window, whether or not it has been swept yet. - Deletion has two parts: a Firestore TTL policy on `expires_at` (set in the console or gcloud, not verifiable from this repo) AND an in-process sweep loop in c2-core. Do not rely on Firestore TTL alone, because it deletes lazily. - A plate key never survives incident resolution plus 2h. - Rotating `PLATE_HMAC_KEY` is cheap and safe, since keys are only 2h old. Rotate yearly or on any c2-core env exposure. **2. Redaction of raw plates in transcripts and summaries.** - Build now: strip plate spans from `vehicles` before write. Add a prompt instruction not to put plates in `vehicles`, plus a deterministic scrub. Reuse the `_PLATE_READ` pattern (`intelligence.py:529`). - Build now: scrub plates from AI-generated incident summaries (`summarizer.py`) after generation. Summaries are our own first-party content, so a plate next to an incident is our own assertion. - Do not use redaction on the raw transcript to gate this feature. The key creates no new disclosure. But plates become a **redaction class in #43's write-time redacted copy** (GATE-B3-SCOPE.md design), alongside names. Display-layer redaction is cosmetic while the browser reads the raw doc. - Every outbound or unauthenticated surface must carry zero plates: the Public tier, Discord posts, alert payloads and snippets, webhooks, exports and the share card. Today those are either unbuilt or closed (#85). Treat that as a standing rule, and any new surface needs a plate-scrub test before it merges. - The owner's "no display on any surface" claim stays unverified until #43's plate class lands and the `vehicles` leak is checked against live data. **3. Logging and `corr_debug`.** - Allowed in `corr_debug` and logs: `corr_plate_match: true|false`, a count, and a 4-hex-char prefix of the HMAC at most. The prefix is for disambiguating two plates in one incident during debugging. - Never allowed: plaintext, normalised plate, raw span, full hash, or any partial plate string. Also never in exceptions, f-strings at any level, or the replay and eval dumps (#170). The admin dump (`admin.py:107-120`) uses a field whitelist. Keep it that way. Do not add a plate field to it. - Fix as part of this work: drop `call_vehicles` and `inc_vehicles` from the INFO line at `incident_correlator.py:1950-1952`, or log counts only. - Do not send plates or hashes to the LLM correlator as extra fields. - Required test: a `caplog` test plus a `corr_debug` schema test asserting a known test plate never appears in either. - Measurement caveat: `corr_debug` is last-write-wins on multi-scene calls (#96). Any plate-match path stat read from it is tainted for those calls. - Existing exposure, not new: unversioned corr and replay dumps at the project root hold transcripts. I did not open them. Keep them out of any git repo. **4. DPPA, E&O and legal.** I am not counsel. Counsel confirmation goes in #47. - **DPPA** (18 U.S.C. §2721) regulates personal information drawn from state DMV motor-vehicle records. A plate overheard on radio and hashed is not DMV-sourced. A plate number alone is not in the statute's enumerated list. I see no DPPA exposure in hash-only matching. Exposure would start if we ever join a plate to a registrant name or address. Dispatch reads DMV returns on air ("registered to ..."), so the plate normaliser must not capture registrant returns, and names stay suppressed under #43. Never add a DMV or registration lookup. This must be a written non-goal. - **NY.** I know of no NY statute governing plate retention by a private entity. This is unverified. The NY Shield Law (amended; includes ALPR-sharing and out-of-state-subpoena limits for protected health-care activity) is aimed at ALPR operators and in-state entities. We are neither, but the policy signal is clear. A plate-to-location index is exactly the data those laws target. Hash-only, 2h and no query surface leave little for a subpoena to reach. The real subpoena surface is still the transcript store, which has no retention (#44). Do not assume the press shield law (Civil Rights Law §79-h) covers a SaaS vendor's stored data. - **E&O and defamation.** Internal matching with no display, no name and no plate shown adds no incident-level assertion about a person. Residual risk is a false link from a Whisper-misheard plate merging two incidents. That is a quality risk and belongs to `drb-correlation-review`. A plate match should corroborate, not stand alone, and "orphan in doubt" still applies. Any display, search, watchlist or alert changes this and needs E&O first. - **Privacy and ToS (#47).** Add a data-inventory line: plate-derived match keys, 2h, hashed, never displayed. The page at `drb-frontend/app/privacy/page.tsx:23` is still a TODO(legal). **5. Gates.** - **Nothing in the narrowed build must wait for E&O, Gate A or Gate B.** It publishes nothing, charges nothing and displays nothing. - **Gate B items it touches:** #43 (add the plate class), #44 (the 2h TTL is a real enforced retention fact, usable as the model for the sweep), #13 (not depended on). - **Must wait for a new CISO ruling, E&O bound, #43, #13, #44 and #47:** any search, watchlist, alert, map pin, export, webhook, Discord, summary or public mention of plates. The broad version remains HOLD. - The comped (friends and family) users are live now. So the finding 2 scrub applies on merge, not at Gate B. ### Could not verify from here - Whether the live Firestore rules match the repo (#13 state after 2026-09-13). - Whether the live `vehicles` field and transcripts already carry plates, and whether summaries do. - Whether the Firestore TTL policy can be created and is applied to the `c2-server` database. - Gemini and OpenAI data-handling terms for the keys in use. - NY statutory position on private plate retention, and the DPPA reading, until counsel confirms. - #200 (the camera-frame ruling) is still open and unruled. It is not precedent for this, only the same data class. ### Recommendation (smallest fix first) 1. CTO: drop vehicle lists from the INFO log line (`incident_correlator.py:1950-1952`) and add the `caplog` plus `corr_debug` tests. Smallest and no dependencies. 2. CTO: plate scrub on `vehicles` and summaries (`intelligence.py`, `summarizer.py`) and add the plate class to #43's redaction pass. 3. CTO: `plate_key()` (HMAC, per-org HKDF), `plate_keys` collection with a deny rule, 2h TTL with a 6h clamp and a sweep loop. Review with `drb-correlation-review` before anything uses it as a link signal. 4. CIO: add `PLATE_HMAC_KEY` to vault and env templates without printing it; create the Firestore TTL policy. 5. Owner: privacy and ToS line (#47). ### Needs a CEO ruling - Accept hash-only internal use ahead of the gates, with plates scrubbed from `vehicles` and summaries on merge? - Accept that the "no display" claim is not true of raw transcripts until #43 lands? - Confirm the broad version (search, watchlist, alerts, map) stays HOLD. Status label: `status/needs-decision` is kept. The conditions above need CEO acceptance. No code is in progress.
logan added the area/legal label 2026-10-05 02:21:38 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/server-26#248