Venue data — evidence and owner verification (canon)
Status: Active · Ratified: 2026-09-03 (owner: "Naver is the more commonly used map provider. So Naver it is.") Last reviewed: 2026-09-06 (owner: reliable information may come from one source family; reserve 인증됨 for verified owner claims.)
Public venue identity
Reliable information does not require several source families. One source that reliably identifies the actual court can be enough. Its evidence must belong to that venue; a provider name or a duplicated observation is not proof. Facts that the source does not establish stay unknown, and conflicts still require field-level resolution under the precedence rules below.
Ordinary venues display without a trust badge, dimming, or a source-count ranking penalty. Do not display 미확인 or 확인됨, or offer a "confirmed venues only" filter. Normal browse, search and counts remain inclusive under their existing location, facility and closure filters; this policy change does not authorize silently removing legacy venues with incomplete evidence. This inclusive policy applies identically to VenueFinder's pick intent (wizard/session venue selection) and its explore intent — neither mode threads a trust filter or shows a 미확인 badge (closes the 2026-09-01 "미확인 filter exposure in pick mode" open question).
인증됨 certifies ownership. Migrations 00621–00628 make an accepted, active venue_owner_claims tenure the authority; venue_admins is its scoped projection. New claims require independent platform review with a recorded business check. A legacy official or host_verified status, an accepted correction, and any source count do not establish ownership. Complete historical grants import once with their missing original proof explicitly marked; invalid historical pairs cannot revive through raw verifier/timestamp edits. Anonymizing a historical reviewer removes that person's evidence and identity without revoking another active owner's tenure.
Public certification reads disclose only venue ID, the current ownership boolean and a server check time. The app integration must obtain fresh certification through get_venue_owner_certifications, hide it on failed/expired checks and avoid persisting it in venue selections. Existing directory flags remain compatibility projections. The ownership workflow and integration audit distinguish the implemented server contract from the remaining app integration.
00602 adds an optional internal reliable query mode: an active, non-merged venue with a valid name/pin needs either a verified owner claim or one active source whose raw identity passes the attachment guard. Classification-only audits and picker markers are not identity proof. Missing legacy government/Place identity evidence stays a review backlog; this mode is not the public default. source_count remains provenance metadata, with Naver Local Search and Naver Place counted as one family.
This policy supersedes the August trust-tier plan. The implementation and release boundary records what has been verified locally and what still requires deployment.
The law
A venue fact has exactly one leading source and any number of corroborating sources. Naver leads; everything else corroborates.
| Rank | Provider | Role |
|---|---|---|
| 1 | host, manual (a person) | overrides everything, never re-derived |
| 2 | naver_place — the Naver Place page, name-verified by the scraper | leads every fact the page publishes: name, coordinates, road address, phone, category, hours, fees (priced menu), amenities, capabilities, photos, reviews, payment, links |
| 3 | naver — Naver local search, strict name+proximity match | leads when the page is absent (phone, category, homepage) |
| 4 | kakao, tmap, gov_std, gov_kspo, gov_eshare, gov_overpass | corroborate only: agreement raises confidence; they fill a fact only when no Naver source has it |
facility_kind_source = 'audit' (a double-confirmed audited label) is pinned like a host value.
The one stated exception
court_count: the Naver page publishes no structured court count. The scraper records only unambiguous inventory wording (총 N면, 코트 수: N면, N면 운영) and treats it as corroboration, never the originating canonical value. Booking units, numbered court labels, bare N면 mentions, and conflicting component counts are rejected. An explicit government roster (gov_kspo, gov_std) leads; the bulk writer accepts a scalar only with kspo/std_data field provenance. A venue_courts roster, when present, derives the count and overrides all of it. Every canonical path is bounded to 1–40 courts (00529/00587/00592).
The Naver-seeded legacy public_courts.court_count stays raw evidence and never becomes the canonical scalar. The club bridge has one narrow creation exception: only a bounded 1–40 count on a court_venues row whose venue_id is null, and whose bridge actually creates a new canonical venue, may seed that new venue's scalar with manual field provenance. Once a club row is linked, its count and later edits are club-local; canonical changes go through an approved venue correction or an explicit venue_courts roster. The creation exception does not manufacture an observation or a second identity-source family. Structured rosters stamp their own provenance when they derive the scalar. Existing rosters created by the legacy 00338 scalar expansion are not retroactively relabelled as host evidence.
Intake identity and edit authority (00603–00605)
A picker result is a user's proposed location. Caller-supplied provider IDs stay lookup hints until an independently fetched source passes the attachment guard; they cannot reserve a canonical provider identity or create provider evidence. Legacy picker-generated map observations are not reliable source proof. One actual matching source still suffices for internal reliability, and neither path grants owner certification.
Adoption and the authenticated club bridge validate meaningful names and finite Korean pins before linking a venue. Reusing an existing venue requires matching identity and availability; an ambiguous or conflicting identity needs review. New club-local entries without a usable pin stay local until resolved. A successful asynchronous adoption keeps its continuation after the picker closes, while request correlation prevents stale or repeated completions from changing a newer selection.
Government ingestion retains each source's own resource ID, name, pin and facts. Geography narrows candidates; it cannot merge different indexed courts or nearby facilities. Re-ingestion preserves an unambiguous existing venue UUID and import key. Ambiguous matches are reported as held work. Display cleaning keeps branch names and court indices on future writes. The routine ingest reset endpoint is retired; exceptional data repairs use reviewed recovery procedures.
Venue review authority requires either an active platform administrator or an active accepted ownership tenure for that specific venue. The client reads this through can_review_venue_corrections, revalidates cached permissions and hides an unvalidated grant. Every mutation checks current server authority. Classification edits preserve operating state; closing and reopening require their own accepted corrections.
Search results carry a canonical ID only after unambiguous name, pin and open state checks. Provider outages, configuration failures and exhausted budgets remain distinguishable from successful searches with no matches. The venue finder retains its independently loaded directory results and offers a retry. These prepared changes and their verification are tracked in the venue pipeline implementation audit.
Where it is enforced
public.resolve_venue_facts— per-fact resolution with this precedence (00572).public.apply_display_name— display name = Naver Place page name (00570/00571); previous name kept infield_provenance.name_original; other providers' names remain searchable aliases (00569).- Observation attachment is strict everywhere: a provider record attaches only on meaningful normalized-name agreement and finite proximity (150 m; 300 m only when the canonical coordinate came from
kspo,eshare,std_data, oroverpass). Generic labels, a parent complex without a tennis identity, ancillary POIs, competing sports, and a different indexed court fail.providers.mjs, every enrichment/crawl producer, the nightly corroborator, the privileged observation endpoint, and therecord_venue_observationsdatabase boundary enforce the same rule. The emitted observation retains the provider's own name and pin so the decision stays auditable; identity is never copied from the canonical venue into a provider payload. Likewise,data_source = 'naver'seeds require a raw Naver name and pin, whilecross_verifiedrequires separate raw Naver and Kakao names and pins. The private-court crawler also keeps each provider's phone, address, category, and homepage with that provider; Naver supplies the canonical name, pin, phone, address, category, and homepage when it publishes a value. Those canonical seed fields must equal the retained raw Naver fields; Kakao may fill a phone or address only when Naver omitted it (00594). A valid re-ingest replaces that provider's prior snapshot without adding a source-family vote; an invalid refresh rolls back both canonical and observation changes. Client and service roles cannot write the observation table directly, and an archived map row must pass the same current attachment check before exact restoration. Record, quarantine, and restore writers lock every affected venue first (UUID order for batches), then validate and mutate observations, so canonical facts cannot drift between attachment validation and persistence. - The
import_hashmerge applies this hierarchy independently to name, pin, telephone, address, Naver category, homepage, andfacility_kind. A weaker maintenance/government row may fill an empty field and retain unrelated metadata, but it cannot overwrite a populated human, Naver Place, or Naver Local fact—or relabel that fact's provenance. Recognized government kinds are rank-zero gap fills, so a governmentpracticeclassification still reaches the court-count consistency guard without outranking Naver or a person. A Naver Local refresh must carry the provider's guarded raw identity and field value; a Naver provenance marker has no authority unless an active observation contains the same normalized canonical value. Provider categories are persisted only with their matching raw provider record. Human rows (host,manual,user_pick,club_pick) remain protected even when an older path created no observation. Same-hash imports and observation mutations serialize with the canonical row, so the evidence used for a precedence decision cannot change underneath the merge (00595). - The legacy
enrich_venues_batchmarker rail cannot write resolved facts or classifications. Place/POI IDs and provider categories require the exact active observation for that venue; each rating additionally must be domain-valid and exactly equal the value on that same active Naver Place observation and Place ID. A matching human observation protects the coupled venue kind, capabilities, closure state, and any independently authoritative court count even when a legacy row-level kind marker saysnameorresolved. Direct kind, capability, phone, hours, fee, and indoor assertions are stripped. Administrative fills andnaver_scraped_atremain bookkeeping, while the earlier court-count and homepage guards still run in the delegated writer (00596). - Legacy Naver Place rows are not silently presented as fully attachment-audited. The old scraper kept the Place ID and usually the page name, but not the page pin. Missing or generic Place identities are quarantined for refetch; name-agreeing legacy rows remain active as an explicit refetch cohort. New/refreshed Place rows must retain and pass both name and pin.
- Legacy Kakao/Naver rows with the same canonical name, byte-identical long-decimal centroid, and identical timestamp are one historical merged-seed projection, not independent corroboration. Those exact captured rows are quarantined for provider-specific refetch;
data_sourceand coordinate confidence are downgraded to the surviving evidence. An unsupported historical pin remains only as a low-confidence refetch anchor, without invented coordinate provenance. No family vote survives solely because one seed was duplicated into two provider payloads (00593). source_countcounts distinct source families as internal evidence metadata; Naver alone is one family, a quarantined observation proves nothing, and a new row starts at zero until an active observation proves a family. A club picker may retain a Naver Place ID as an identity marker, but its caller-supplied fields do not create a Naver observation; a guarded refetch must establish that provider vote.- Court inventory is fail-closed at every write boundary: the correction validator and roster trigger reject more than 40, generic enrichment/seed edge routes reject scalar
court_count, the Naver legacy bridge cannot originate it, andingest_venues_batchaccepts it only with bounded government field provenance. The app read/write schemas and club editor enforce the same 1–40 interval. Deployment also aborts if an existing active roster conflicts with a non-full_courtkind, leaving that legacy evidence for explicit adjudication instead of silently promoting either side (00592).
Why
Kakao, government databases and OpenStreetMap disagree with Naver on names, pins and phones for the same place, and a vote among them let a neighbour's facts win (00568). Korean users search Naver; the venue must be findable and consistent with what Naver shows. Other providers can corroborate individual facts and reveal conflicts, but they do not overwrite Naver or confer ownership certification.
Address evidence vetoes a court verdict (00574)
A full-size court cannot exist on a building floor. When the address (Naver's road address first, ours second) shows basement, 2층 and up, a 3–4 digit 호 unit, or a multi-storey building word (상가/오피스텔/빌딩/타워/프라자/스퀘어), the row is a practice facility, whatever its name says — unless the page names a real court (풀코트, 정규 코트, 야외/실외 코트, 하드코트/클레이/인조잔디) or the row is a rooftop (옥상/루프탑). A studio's own 코트 대관 menu line does not rebut it (it rents a mini bay).
Government rosters, human rows (manual/user_pick/club_pick), public-facility names (경기장/체육/공원/학교/대학/선수촌…) and public categories (공유누리/부속시설/체육관) are exempt. A bare 1층 is deliberately weak — standalone halls list it.
One predicate, private.venue_court_veto(venue_id), is consulted everywhere a kind is decided: the classifier (derive_facility_kind(…, p_address) rule R3c, source address), the resolver (a scraped full_court capability under veto becomes practice and the capability is dropped), enrichment and the review-profile RPC (the veto joins the lesson demotion and blocks promotion). The JS mirrors live in scripts/lib/venue-parse.mjs (hasFloorEvidence, hasRealCourtWords), used by the Naver scraper and enrich-venues.mjs. Locked by supabase/tests/venue_floor_evidence.test.sql.
Why: the name token 테니스장 alone made 78 commercial studios (VTG실내테니스장 B1, 반포실내테니스 4,5층, 탑테니스 옥수점 상가 지하1층 …) full courts, through two independent rules that never looked at the address.
Regions come from the pin, not the club
A venue's 시/도·시/군/구 is resolved from its address prefix, and when there is no address, from its coordinates (scripts/audit-venue-regions.mjs sweeps the whole corpus against Kakao coord2regioncode and emits the adm2 fills). Seeded venues take the district from their own address, never from the club that seeded them (2026-09-04: 올림픽공원 테니스장 showed under 강남구 because a 강남구 club seeded it).
Row invariants and record hygiene (00575, 00577)
private.venue_row_invariants() runs BEFORE every insert/update on venues: kind ⇄ capabilities (full_court ⇒ the capability; shop ⇒ none), adm1 = parent(adm2), a valid KR phone or NULL, a pin inside Korea or NULL, fee lines ≤ 2,000,000 KRW, court_count 1–40, and — unless a person set them — display-name cleaning and address-prefix cleaning (private.clean_venue_name, private.clean_address_prefix). Locked by supabase/tests/venue_invariants.test.sql.
Display cleaning is not identity proof. Retain original branch/court indices in source evidence; nearby numbered resources can represent separate booking units. Do not collapse them into a whole-facility identity or infer inventory from their labels. A physical polygon/roster count requires its own provenance and scope adjudication. The September 5 review withdraws four earlier merge proposals for precisely this reason. 00589 now names only seven reviewed pairs and its regression test excludes the ambiguous units. A road-sign, evacuation or parent-complex POI does not prove a tennis identity; 00598 records bounded corrections.
Merged aliases retain their records. 00597 resolves explicit successors only at new bookmark and club-venue reference writes (maximum eight hops), without moving existing user history; the club bridge preserves an unknown court count as NULL. Detail reads and dependent court/source/ correction queries use the resolved identity. Bulk reads retain their exact-ID draft contract. These source changes require the documented migration/client rollout; they are not a live-state claim.
Independent audit (scripts/audit)
The guarded partial refresh (00600) requires the exact current Place anchor, raw source name and a stricter 150 m pin bound even for government-seeded venues. It changes only supported business fields, never kind, court inventory, closure or successor state. Missing amenities do not prove false: an old unsupported Naver false may become unknown only with exact old-payload proof and no independent boolean contributor or human protection. Original source timestamps remain intact for unrefreshed fields. See the September 5 metadata handoff for the incomplete capture boundary and fixed identity cohort; prepared migrations are not a hosted deployment receipt.
The corpus is re-audited with methods that share no code with the rules: check-facts invariants, blind LLM judges on raw dossiers (first pass over every open venue, second judge on every disagreement, corrections only when both agree at ≥ medium and the row is not host/audit-pinned), and a live Naver re-fetch on a stratified sample for per-field accuracy. See scripts/audit/README.md. Every finding ends as a trigger, a test, or a parser fix.
Editable metadata per kind (00573)
Every kind has a common editable set; court facts exist only where courts exist. The server (submit_venue_correction) is the authority; the app mirrors it from EDITABLE_FIELDS_BY_KIND.
| Field | full_court | practice / screen | shop |
|---|---|---|---|
| name · address · facility_kind · telephone · location · operating_hours · website_url · booking_url · amenities · has_parking · closed | ✓ | ✓ | ✓ |
| capabilities · has_indoor · has_shower · fee_schedule | ✓ | ✓ | — |
| court_count · courts (roster) · has_lighting | ✓ | — | — |
The current contribution flow stages all selected fields into one evidence-backed request. One supported submission can be accepted by platform staff or a currently verified, venue-scoped owner. Ownership certification remains a separate claim. The legacy correction RPC retains two-account agreement for its low-risk fields, except court_count and courts, which always need explicit review after 00606. New requests use the contribution envelope below; old correction history and review remain readable.
Approved facts retain human precedence through the existing correction/host-observation mechanisms. A later explicit reviewer decision outranks an older matching-vote pile for telephone, scalar court count, indoor availability and capabilities (00609). A real court roster still owns its derived count. Fact changes do not establish identity merely by accumulating a name and pin in the host observation.
Hours and fees use the shared lossless contract (00599): omitted groups and fee units are unknown; explicit closed/holiday schedules, menu labels, notes and exact zero prices survive editing. Empty fees mean unknown, not free. Raw Naver schedules retain breaks and exceptions when the coarse weekday/weekend projection cannot express them. Both submission and approval validate, including old drafts; the latest actual approval wins over older popularity for these two fields.
Tracked venue contributions (00606–00610)
Missing venues, fact corrections and confirmations, individual-court changes, operating-state changes and investigation reports have durable receipts. Every submission has a request UUID, an authenticated author and supporting information. Repeating an unchanged request returns the same receipt; changing its content requires a new UUID. Pending submissions do not change the public directory. A new-venue identity requires platform review; a verified owner can review contributions only for their own venue. Review authority is checked again at decision time, including after revocation.
States are pending, accepted, rejected, needs_information, withdrawn and superseded. Decisions require an explanation and preserve their history. Authors can withdraw an undecided request or replace it with a linked new submission. An unreplaced stale receipt can also be replaced using current facts while retaining its original decision history. Accepted fact changes apply together. Review compares the relevant initial facts with current facts under locks; a conflicting change supersedes the proposal without applying stale values. Unrelated refresh timestamps and court metadata do not invalidate a telephone edit. One receipt-level notification links to the decision; notification delivery is separate from the authoritative stored receipt.
Court operations address stable UUIDs: add, update or retire. They retain unspecified metadata, parent links and historical rows. Explicit null means unknown and false remains false. The active count is derived and bounded to 40; retiring the last court restores unknown count without reclassifying the venue. Legacy anonymous roster replacement can initialize an empty roster only; it cannot replace existing or retired court history.
Temporary closure, permanent closure, relocation and reopening are explicit reviewed actions. Relocation links to a distinct existing destination and preserves the old venue and its history. Accepting an investigation report alone does not close a venue or invent a corrected fact. Unresolved legacy reports join the inbox with their original date and author; absent evidence remains absent. Resolving an incident permits a later report about a new incident.
Supporting notes, author linkage and decision explanations are available only to the author and currently authorized reviewers. Raw table writes are denied. These queries are excluded from persistent device storage and revalidate access when opened. Submitted receipts survive app restart; unsent drafts and offline delivery are not durable in this version.
Observation dates have Korean calendar-day precision. Text bounds count Unicode characters as PostgreSQL does. Public references currently accept HTTP(S) with ASCII DNS hostnames, alphabetic top-level domains and valid ports; IP/local, Unicode and punycode hosts are outside this input contract. Storing a URL does not claim it was fetched or its contents verified.
The implementation audit records source verification separately from hosted migrations, provider freshness and owner onboarding.
Durable provider refresh (00611–00615)
Kakao refresh schedules usable, open venue identities by evidence age, regardless of source count. Thirty days is a freshness target, not a coverage guarantee. The shared budget allows at most 60 reserved requests per Korean calendar day; misses, failures and held identities remain overdue. Operational reads distinguish those outcomes from fresh evidence and saved matches waiting for application.
Only the privileged worker can prepare, claim, reserve, finish or apply refresh work. Each dispatch requires a current lease and a new durable reservation. Replaying a reservation never permits another dispatch; a lost permission response still consumes that reservation. Results persist before application. A separate lease can retry the saved result without calling the provider again. Exhausted budgets and provider pauses do not prevent already saved matches from reaching application. The HTTP endpoint still requires its configured provider and database credentials before starting work.
Application locks the venue and compares its captured identity, lifecycle, provider IDs and source revision. Changed targets become superseded work. Attachment validation and fact resolution run in one transaction; any failure rolls back public changes and retains a recoverable receipt. Source freshness uses the captured fetch time. Existing human, audit and Naver authority, provenance and stable court history stay protected. Unsupported richer source snapshots are held rather than erased. A provider miss cannot close a venue, retire a court or create an owner certification.
This protocol currently covers the Kakao corroboration worker. Government hours have the separate maintenance contract below. Existing collectors and Naver scraping do not acquire durable capture or atomic application merely by using legacy writers.
Maintenance extraction and receipts
Naver review statistics, photos and blog previews must follow the exact Place detail's captured references. A foreign global Apollo entity is not evidence for the requested venue. Unpublished fields remain unknown; broken or ambiguous linked entities hold the snapshot and cannot stamp a successful scrape. Bounded previews retain their supplied and total counts in the extraction result; they are not a complete provider inventory.
Legacy maintenance accepts one explicit operation with bounded input and valid, distinct target identities. Each posting batch requires a valid receipt confirming the transmitted row count. A short or unreadable result stops the caller and never triggers an automatic retry. Resolver completion is checked before profile/name stages; zero changed rows can be valid for those later stages. Earlier legacy stages may already be committed when a later stage fails. This receipt validation does not give legacy callers the captured, atomic maintenance contract below.
Ingestion counts confirmed row operations, not distinct newly created venues. A short count stops subsequent batches; a missing, malformed or failed acknowledgement leaves the dispatched batch uncertain. Neither case triggers individual-row or batch replay. Earlier confirmed operations remain confirmed. A separate inventory-count failure does not erase that receipt. A requested collector exception makes the overall run fail, even if another source completed; errors contain bounded diagnostic codes. A collector that internally swallows failure as an empty array remains a separate repair.
Captured government-hours maintenance (00616–00620)
The service-only gov_std_hours_v1 contract prepares existing target identities and source revisions before a fetch, retains one immutable capture, then applies it under a separate current lease. Request receipts make retries explicit; applying a saved capture never grants another provider request. Refused inputs remain visible without inventing a preparation or capture. Changed identity, lifecycle, provider references or source revisions supersede the old work; callers cannot rebase that capture.
The server reconstructs weekday/weekend hours and rest-day text from raw government fields and explicit coverage. Client previews are not evidence. A raw record must pass identity attachment checks; syntactically valid hours alone do not make it applicable. Missing records in an incomplete collection do not prove absence. This first contract rejects government no_match and never infers closure, court retirement or certification. The name/address resource key is derived, not a provider-issued identifier.
Unreported fields and existing human, Naver, inventory and lifecycle authority remain protected. Partial updates retain the source row's original observation time and track newly captured fields separately. Older unscoped fields retain an unknown capture time; the source's reference date is separate from fetch time. Atomic application saves the decision while rolling back public/source/provenance changes on failure. Saved captures can be retried under a new lease, with bounded attempts before a visible hold.
Once a venue is enrolled, legacy public write RPCs and ingestion check enrollment after locking its parent row. Sealed delegates support reviewed corrections and existing refresh/quarantine flows. Legacy database-owner SQL retains its existing access but must pass enrollment guards. This boundary does not revoke existing privileged direct table-write grants. New maintenance RPCs separately require service-role access.
Preparation accepts at most 50 targets and has a larger request-receipt budget than capture/apply receipts. Oversized legacy baselines become explicit compact holds. Detailed history reads include retained snapshots and need their own transport limits; the per-receipt limits do not bound the entire history response.
The raw-row parser and SQL contract are local source work. The collector still needs a verified response envelope, prepare-before-fetch integration, durable capture recovery and an audited caller cutover before it is enabled.
Maintenance transport
The dedicated venue-maintenance Edge function accepts seven fixed operations: prepare, refusal, capture, claim, apply, get and list. Every request needs the private ingest token before body parsing or database access. Its custom authentication has a per-function verify_jwt = false setting; existing functions retain their settings. Gateway publishable keys use apikey; runtime service credentials never enter the operator's request body. The function makes one named RPC, with no retry or provider request, and returns the validated original SQL response bytes.
Requests and responses have separate operation limits. Preparation supports 50 explicit venues and a 576 KiB response; list requires an explicit limit of 1–5 and allows 1 MiB; get allows 2 MiB. Capture, claim and apply responses allow 96 KiB. Actual streamed bytes control admission, and the RPC connection plus body share a 20-second deadline. Oversized detail is an explicit failed read, never truncated history. Original mutation replay and bounded list reads remain available independently of detail size.
The Node client transmits already-serialized original requests once. Request UUID, target, worker, capture, lease and cursor checks prevent another operation's response from acknowledging work. Apply receipts must agree with their source changes, stage outcomes and rollback state. An unchanged canonical venue can still have a valid new source observation under stronger existing authority. Invalid replies and lost mutation responses leave completion unknown; only a recognized rejection establishes rejection. The client requires Node 22.22 or newer for shared TypeScript contract loading.
This transport does not itself persist operator requests, schedule work, renew leases or enable a collector. Local recovery journals and bounded operator orchestration are separate integration work. No existing hours caller is switched by this source change.