Repository navigation
Added
DatasetRef.to_dict()(#784): a dataset reference serialises to a JSON-safe dict with a stable set of keys — identity,status(#783),query_support,license,request_parameters,applicationandverified_at.dataclasses.asdict(ref)raisedTypeErroronraw_metadata, so a consumer had to readraw_metadata, which carries no stability promise. Every key is always present;Nonemeans nothing is declared. The key list is indocs/compatibility.md§3.kpubdata datasets list --format jsonanddatasets show --format jsonprint this dict:listentries gain the new keys next toid,name,providerandoperations, andshowkeepscapabilitiesandraw_metadata_keysbeside them.kpubdata.SENSITIVE_PARAM_KEYS(#782): the names kpubdata masks as credentials —servicekey,service_key,api_key,apikey,token,authorization,secret,password,key,oc,accesstoken,consumer_key,consumer_secret— are importable fromkpubdataandkpubdata.transport. They lived only in the privatekpubdata.transport._sensitive, so Builder kept its own list, and that list lackskey,oc,consumer_keyandconsumer_secret. Names are casefolded and matched exactly; a release only adds names. Value-based masking stays private.DatasetRef.status(#783): a dataset reference says how far the dataset has been verified, as aDatasetStatus(now exported fromkpubdata). A dataset awaiting an application, one checked against fixtures only and one verified against the live API used to look the same at run time; the levels lived only inSUPPORTED_DATA.md, which is not in the wheel.scripts/gen_dataset_status.pywritessrc/kpubdata/dataset_status.jsonfrom that document's level column (throughSUPPORTED_DATA_LEVELS, with a spec'sstatus:override applied), andtests/unit/test_dataset_ref_status.pyfails when the two differ. Of the 155 datasets the table and the runtime share: 94application_required, 36fixture_verified, 24live_verified, 1unstable(datago.ocean_buoy, by its spec's override);datago.genericis not in the table and reportsNone. The status is the recorded one, not a live check — that isClient.probe.- Evidence bound to the spec, the commit and the run (#522, #713): a recorded fixture's
meta.jsonnow carriesspec_sha256, a digest of the spec file's pipeline-relevant content (kpubdata.core.spec.spec_file_digest, which normalises away thelast_verifiedline the recorder itself rewrites), andrecord_commit/run_reffrom the CI environment (GITHUB_SHA/GITHUB_RUN_ID) when present.make verifygains a spec-binding step: a fixture whose recorded digest no longer matches its spec is void — the spec changed after recording — and fails with re-record guidance. Fixtures recorded before the digest existed are reported as legacy evidence rather than failed — since #717, only those on a frozen baseline. - Public probe API (#694):
Client.probe(dataset_id) -> ProbeResult | NoneandClient.probe_all(*, provider=None) -> list[ProbeResult]classify reachability with the client's own keys, andProbeResultandPROBE_STATUSES(the ADR 0005ProbeStatusvocabulary) are exported fromkpubdata. Probing keeps its fast-fail transport (PROBE_TIMEOUT_SECONDS,PROBE_RETRIES, no cache), and a run shares one transport that is closed at the end — previously every dataset opened its own and none was closed.kpubdata probenow goes through these methods; its output and report are unchanged, and it now honors the global--provider-keyoption, which it silently ignored before. - Explicit-keys-only mode (#694):
Client(..., env_keys=False)(backed byKPubDataConfig.env_fallback) uses onlyprovider_keysand never reads a credential from the environment — notKPUBDATA_<P>_API_KEY,<P>_API_KEYorKPUBDATA_SGIS_CONSUMER_SECRET. A service probing with a user's key no longer has a missing entry silently filled with the operator's key. With no key the probe makes no call and reportsauth_unknown. The default (env_keys=True) is unchanged. - R3 review gate (#722, kpubdata-builder#905): a pull request labelled
review:R3now needs an approval from someone other than its author before it can merge.scripts/r3_review.py, wrapped by the.github/actions/r3-reviewcomposite action, is the one implementation all four repositories call; theR3 reviewworkflow runs it on every pull request (passing when it is not R3) and again on label changes, pushes and review submissions or dismissals, withpull-requests: readonly. Each reviewer's latest approving or change-requesting review decides; approvals on an older head count, as with branch protection's stale-approval dismissal off; the author, bots and accounts without write access do not count. POLICY 14.1 and AGENTS.md describe it.R3 reviewbecomes a required status check next toCI gate; the approval count in branch protection stays at 0. - Probe evidence fields (#625, #710):
ProbeResultcarrieshttp_status(200 on success — the executor raises on anything else — and the raised error's status otherwise),result_code(read off the raised error; a successful query does not parse the envelope),latency_ms,schema_hash(a fingerprint of the returned field names — data changes daily, field names are the contract) andclassification(theDriftClassificationthis outcome feeds the dataset status machine), eachNonewhere it could not be observed — never guessed. The report envelope records who ran the probe (runner; the nightly workflow passesgithub-actions). - Nightly drift detection (#625, #711):
scripts/drift_detect.pyruns the dataset status machine over two consecutive probe reports — a differingschema_hashemitsSCHEMA_CHANGEDitself (it is not a probe outcome), transient failures move nothing before the third consecutive night, restores restoreprevious_statusand file nothing,RETIREDmoves nothing but files. The live-probe workflow grows a drift job that chains the previous night's artifacts, keeps streak state in adrift-stateartifact, and files one[drift]issue per transition that needs a person — skipping datasets that already have an open one, with thestatus_history.jsonentry riding in the issue body. The exit status is always 0: an upstream failure is a drift event, not a CI failure. - Production-grade gate (#463, #711):
scripts/check_production_grade.pyevaluates the machine-checkable criteria ofdocs/production_grade.yaml— source and listing gate every spec; a recorded fixture (any*.meta.json: the recorder names fixtures after the example,tour_kor_arearecordsseoul_restaurants.meta.json) and an example script gate the verified tier only (an in-progress row gates nothing — ocean_buoy's missing fixture is #627, not a surprise);fields_declaredis reported, not gated. Its unit test is the CI gate: every spec dataset in the repository must pass. - Example recency windows (#734, #750): a spec can declare
params[].max_age_days, andmake verifyfails an example outside the window before the live call does — naming the cause (expired, or a future issue that does not answer yet — the #731 morning trap) and the refresh route. Parameters without a window emit nothing. The relative-time representation the issue considered first was rejected: replay matches on recorded literals, and issue-slot granularity would leak provider specifics into the generic schema. - Recorder run reference (#523, #729, #739): the evidence-authorship gate grows a second layer — a changed
meta.jsonby a recorder name must also carryrecord_commitandrun_ref, andrun_refmust resolve to a run of this repository's Build Dataset workflow whose head SHA is exactly that commit (gh run view; any lookup failure fails closed). A local tool committing under the recorder's name — the #728 path — no longer passes.ci.ymlgainsactions: readand the job token for the lookup. - Unconfirmed terms are not redistributable (#732): a spec whose licence says
redistribution: allowedmust carry theattributiontext that proves the terms were confirmed from the provider page (#525); terms nobody checked sayredistribution: unknown— the two claims #728 wrongly shipped (bus_arrival, social_enterprise) were fixed to unknown first (#740, #744), because Builder's publish gate (kpubdata-builder#892) reads exactly this flag.make verifyenforces the rule with a shrink-only baseline,scripts/unconfirmed_terms_baseline.txt, frozen at the 16 legacy violators — mirroring the legacy evidence ratchet (#717): a listed spec passes while its terms stay unconfirmed, an unlisted violation fails verify, and a stale entry (terms confirmed, or the claim withdrawn) must be removed from the list. - PR title blocks the merge (#741):
PR titlejoinedCI gateandR3 reviewas a required status check in all four product repositories, so a failing title check now closes the merge button instead of only showing a red X. The check re-runs on title edits (titles.yml), whichci.ymlcannot see, so it is registered in branch protection rather than added to the aggregate gate's needs.scripts/check_required_checks.pyverifies the required list against the workflows (the studio#416 lesson), and POLICY 14.2 records the rule. - Unjustified plain http is refused (#738): a spec whose
endpoint.base_urlsayshttp://must carryendpoint.insecure_http_reason— the service key rides the query string, and over plain http anyone on the network path reads it.make verifygains a transport-security step enforced with a shrink-only baseline,scripts/insecure_http_baseline.txt, frozen at the 23 legacy http specs — all of them onapis.data.go.kr, which answers https with identical envelopes (the reproducible record — commands and raw envelopes for every service path behind the baseline — sits as a comment on #738), so each entry leaves the list by switching schemes (a switch moves the spec digest, so the fixtures are re-recorded through the Build Dataset workflow). - The agent PR may shrink the insecure-http baseline, and nothing else (#738): the Build Dataset commit step stages
scripts/insecure_http_baseline.txtnext to the spec and the fixtures, because an https conversion must land with its baseline line removed ormake verifyfails the ratchet.scripts/check_baseline_shrink.pyguards the staging: the diff may delete exactly the converting dataset's line, add nothing, and only when that spec'sbase_urlnow sayshttps://— an addition, a swap or an unearned removal stops the run withneeds-human, so the agent cannot widen a count-only ratchet through its own pull request. Negative tests pin each refusal; the base-branch comparison those ceilings really want is #766.
Changed
- A request that carries a credential is not redirected (#816, decided in #812): the transport followed every redirect, so a request with the key in its query string was re-sent — key and all — to wherever the answer pointed, and over
http://anyone on the path can write that answer. Such a request is now sent without following redirects; a 3xx answer raisesTransportErrornaming the target host (never the location, which can carry the key). A request without a credential is redirected as before. Behaviour change: a provider that redirects a keyed request in normal operation now fails;TransportConfig(follow_credentialed_redirects=True)restores following for it. No provider was checked live for this. - A release pull request no longer fails the compatibility check it cannot satisfy (#780).
tests/unit/test_compatibility_json.pyrequired the package's version to be inside thesupportedrange ofcompatibility.json. That row is Builder's pin, and Builder pins only a released kpubdata, so raising the version to 0.9.0 failed the test unless the row claimed a pin Builder does not have yet. The test now holds what can be true at every point: the version is never behind the supported range.compatibility.jsonsays when the row moves — with the Builder and Studio release, asdocs/compatibility.md§5.1 already orders it — where itsupdate_policysaid "on every kpubdata release". - Unconfirmed terms are reported as
unknown(#813, decided in #812): a spec that saysredistribution: allowedwithout theattributiontext that shows its terms were confirmed reportedallowedon itsDatasetRef. It now reportsunknown— onref.license,ref.to_dict()andraw_metadata["license"]. Behaviour change: the 16 specs frozen inscripts/unconfirmed_terms_baseline.txtno longer read as redistributable to a direct user of kpubdata; the three with attribution (datago.apt_rent,datago.apt_trade,datago.village_fcst) are unchanged.find_spec(...).licensestill returns what the spec file declares. list_all()on a spec dataset holds one page in memory, not the whole result (#789). Global column casting (#481) needs every page before any is cast, and the fetched pages waited in a list, so memory grew with the row count: 40 pages of 500 rows peaked at 16.5 MB against 1.7 MB for 4 pages. Each page now goes to a temporary file and only its evidence for the casting decision is kept; the pages are read back, cast and yielded one at a time once the last is in — 1.3 MB for 4 pages and for 40. The casting rule, the batches and their reports are unchanged, andSpecExecutor._finalize_castingnow uses the same decision code. What a failing page does is now documented, not changed: the exception propagates, no batch is yielded and the earlier pages are discarded.- The package metadata names its repository (#791):
[project.urls]gives the homepage, documentation, repository, issue tracker and changelog underkpubdata-lab/kpubdata, so the PyPI page links back to the source. There was no[project.urls]at all. compatibility.jsonsays what it is (#788). Its description claimed "Builder and Studio CI read this file to check their kpubdata dependency range"; no workflow or script in kpubdata, kpubdata-builder or kpubdata-studio reads it. The sentence is replaced: the file is a record for people and release notes.tests/unit/test_compatibility_json.pychecks what can be checked without another repository — the file's shape, that exactly one range issupportedand the package's own version is inside it, and that the range is the pindocs/compatibility.mdstates.- Only a dataset that declares its terms can be published publicly (#785). 25 of the 156 datasets declare a
license— exactly the spec-backed ones — and Builder's publish gate readslicense.redistribution, so the other 131 gave it nothing to read. The decision, recorded indocs/policy/terms-matrix.md: they are not filled in from a provider-level default,DatasetRef.licensestaysNone(unknown) for them, Builder refuses a public publish of such a source withredistribution_unknown, and a dataset becomes publishable when it is migrated to a spec with terms confirmed from its provider page.tests/unit/test_declared_terms_decision.pyholds that every dataset with terms is spec-backed and no catalogue-only dataset has any. - HTTP 401 raises
AuthErrorand HTTP 503 raisesServiceUnavailableError(#786). Both used to leave the transport as a plainTransportError, told apart only bystatus_code; 429 was alreadyRateLimitError. Behaviour change:AuthErroris not aTransportError, soexcept TransportErrorno longer catches a 401 — catchAuthError(orPublicDataError). A 503 is still aTransportError(its subclass), still retried, and typed once the retries run out. ThekpubdataCLI exits3instead of4on a 401.Client.probereports a 401 asauth_unknown, as before. Every error now has a stablecode(auth_error,service_unavailable,rate_limited, …) andto_dict(), a JSON-serialisable dict ofcode,type,message,provider,dataset_id,operation,status_code,provider_codeandretryable, so a consumer maps an error without comparing message text; the table is inAPI_SPEC.md§7. - Breaking:
Clientrefuses an unknown keyword argument (#781).Client(**extra)accepted any keyword and dropped it without an error or a warning —Client(timeoutt=5)built a client with the default timeout, andenv_keys=Falsepassed to 0.8.0, which does not have it, left the environment keys in use (#780). It is now aTypeErrornaming the argument.Client.from_env(extra=...), whose dict was forwarded into the same channel, is removed with it;from_envtakesprovider_keys,timeout,max_retries,cacheandcache_ttl_seconds. Nothing in the library read the dropped values, so a caller passing only documented options is unaffected. - The repository moved from
yeongseon/kpubdatatokpubdata-lab/kpubdata. Links, the documentation site (https://kpubdata-lab.github.io/kpubdata/) and the shared GitHub Actions references now use the new owner. - A pull request title now blocks the merge when it breaks POLICY 2.1.3 (#741). On top of #742's Hangul and inline issue-number rules,
scripts/conventional_title.py --pull-requestrefuses issue and pull request URLs and titles over 100 characters. GitHub'sRevert "…"title is now exempt before any rule, as it should have been: it quotes the reverted squash commit, whose title ends in its PR number, so undoing a merge with GitHub's button used to fail the check. ThePR titlecheck reads the title from the API instead of the event payload, and becomes a required check in all four repositories; it is not folded intoCI gateso that a title edit does not re-run the whole CI. The squash commit body is now the PR body (2026-10-01), so POLICY 2.1.3, AGENTS.md and CONTRIBUTING say commit titles (= PR titles) are English and commit bodies (= PR bodies) are free (#743). - A required check that failed no longer keeps a pull request blocked after a later run of the same check passes (#759). Each event starts its own check suite, and branch protection read a failed suite as failing even after another passed, so
R3 reviewstayed blocked after an approval until someone re-ran the run the label had failed.required-check-refresh.ymlre-runs the failed and cancelled runs ofR3 reviewandTitleson the same head once one passes; both read the pull request live. - CODEOWNERS covers every ratchet baseline with one pattern (#764):
/scripts/*_baseline.txtreplaces the single legacy-evidence entry, so the unconfirmed-terms (#732) and insecure-http (#738) baselines sit behind an owner too — a count-only ratchet cannot stop one entry being swapped for another, which is why #723 put the first baseline there.tests/unit/test_codeowners.pychecks the reverse direction as well: everyscripts/*_baseline.txtfile must match an owned pattern, so a baseline added without coverage fails a test instead of silently missing review. - The baseline ratchets compare sets against the base branch, not counts against frozen ceilings (#766): a count-only ratchet lets a shrunk list grow back toward its ceiling — filling the freed slots with exactly the violations it exists to stop — and a one-for-one swap never moved the count at all; #765 gives the dataset agent a path to try exactly that.
verify_spec.pyreads each baseline at an explicit ref —KPUBDATA_BASELINE_BASE: the pull request's base branch inci.yml,HEADon the Build Dataset runner (it verifies uncommitted edits on main),origin/mainas the Makefile's local default — fails any entry the ref does not carry, and fails closed when the ref is missing or unreadable; there is no implicitHEADfallback, which in a pull request's CI would compare the PR against itself. A baseline the ref does not carry reads as the empty set, so a creating change may only land it empty — freezing existing violations is the failure that names its human-reviewed moment. The frozen-size constants (LEGACY_CEILINGand friends) and their count tests are gone — the ref's entry set is the ceiling, and an after-shrink addition or a swap now fails for all three baselines (#717, #732, #738). - The https evidence is recorded, and the transport rule runs outside make verify (#767): the insecure-http gate's citations pointed at "the keyless probe in the issue", but the issue held a results table without commands or raw envelopes, and 10 of the 16 service paths behind the baseline — ocean_buoy, the three localdata specs, six of the seven RTMS trade services — had never been probed at all. A reproducible record now sits on #738: the exact request per path over both schemes, byte-identical envelopes for all 16 (code 30 from ten real services, code 12 at the gateway for the rest), and no service needing
insecure_http_reason. The CHANGELOG, the baseline header and the verify comment point at it, and the rule moved tokpubdata.core.spec.insecure_http_problem— shared byverify_spec.pyandvalidate_spec.pythe way #725 shares the licence contradictions — so an unjustifiedhttp://base_url failsvalidate_spec(the CI spec validation) and not onlymake verify, with the same shrink-only baseline exemption on both paths. - CODEOWNERS covers the evidence path (#715):
scripts/record.py,scripts/verify_spec.py,scripts/check_fixture_authorship.py, the recorded fixtures and the wholesrc/kpubdata/specs/tree (previously onlyschema.json) now need a code owner's review, because whoever changes them changes what "verified" means.tests/unit/test_codeowners.pychecks glob patterns too: a wildcard entry must match at least one file, since GitHub silently ignores a pattern that matches nothing. Enforcing code-owner review in branch protection stays a person's decision (POLICY 14). - Release rule (#685): kpubdata now releases on demand — when a downstream repository is blocked, for a security fix, or for accumulated changes — at most once every seven days, and is no longer part of the monthly release week, which stays for kpubdata-builder and kpubdata-studio.
scripts/release_window.py, wrapped by the.github/actions/release-windowcomposite action, is the one implementation:on-demandrefuses a release less than seven days after the last final GitHub Release,monthlyrefuses outside the Monday-to-Sunday week holding the month's last Thursday, both in KST, and a critical patch passes either only when it names its issue (critical_patch/critical_issueinputs, or aCritical-Patch: #Nline in the release pull request).release.ymlruns it first on both the prepare and the release path; a dry run reports the decision without stopping.docs/compatibility.md§5.1, AGENTS.md and POLICY say the same. - bus_arrival and social_enterprise migrated to specs (#409, #728): both leave the catalogue for spec YAML with recorded live evidence. User-visible:
datago.bus_arrivalserves over https and reports no pagination (a station query is one page — previously offset-style, max 1000), anddatago.social_enterprisecapspage_sizeat 100 (previously 1000) and declares its PII columns (CEO name, phone, fax) per #693. - ultra_srt examples renamed with a fresh issue slot (#731, #736):
seoul_1530/seoul_1500→seoul_0600(base 2026-10-01 06:00) — the KMA APIs answer only recent issues, so the old example dates had drifted outside the window and every live call reportedparams_invalid. Replay matching and the example scripts follow the names; the legacy evidence baseline shrank 35 → 32.
Fixed
- Every provider request passes its credential's value for masking (#810): after #805, seven adapters (
kipris,korean,kosis,law,lofin,neis,sgis) still relied on the parameter name alone. Their names are all on the sensitive list, so nothing leaked; they now pass the value too, and a test fails when a transport request underproviders/orcore/is added without it. list_allno longer fails on a lone surrogate (#804): pages are spooled to a temporary file as JSON lines (#789), and a row whose text held a lone surrogate — what a broken character in a provider's response decodes to — serialised but could not be written, so the whole read raisedUnicodeEncodeErrorwherelistwould have returned the page. The spool is now written and read withsurrogatepass, and the rows come back unchanged.- A key sent under an unlisted parameter name is masked (#805): the spec executor and the legacy
datagoadapter relied on the parameter's name alone, so a key sent under a name outsideSENSITIVE_PARAM_KEYS— a spec's ownauth.param_name, ordatago.generic's_service_key_param— appeared in the traceback of a failed request. Both now pass the key's value to the transport, asbok,fdsandseouldo, and so does the sharedlocaldata/semasrequest. - A total count of zero is
0, notNone(#806): the hand-written adapters reportedRecordBatch.total_countasNonewhenever the provider's count was zero, so "the provider said there are no rows" could not be told from "the provider did not say".datago, the datago family (localdata,semas),lofin,law,bok,korean,neisandfdsnow report0when the response carries a zero andNoneonly when it carries no count (or one that is not a number). Behaviour change: code that teststotal_count is Noneto detect an empty result must testnot batch.itemsortotal_count == 0instead. Paging is unchanged, and the spec-based path already kept the two apart. API_SPEC.md's examples fordatago.apt_tradepassLAWD_CDandDEAL_YMD, the names the spec declares (#790). They passedlawd_codeanddeal_ym, which the library sends to the provider verbatim, so the documented call could not work.tests/unit/test_doc_example_params.pyreads the Python examples ofAPI_SPEC.md, the READMEs anddocs/quickstart.mdand fails when a call on a spec-backed dataset passes a filter its spec does not declare.- NODATA is an empty result on every path (#787): data.go.kr's
resultCode03 (NODATA_ERROR, no record matched the request) came back as an empty batch from localdata and semas (#470) but raisedProviderResponseErrorfrom the datago envelope parser (the standard,gyeonggi_msgandits_flatenvelopes) and from every spec dataset, sincecheck_payload_errorhad no branch for it and no spec lists it inok_values. All of them now return an empty result —items == [], andtotal_countas the response states it. Behaviour change: code that caughtProviderResponseErrorwithprovider_code == "03"to detect an empty answer now gets the empty batch instead; every other code raises as before. - The
R3 reviewfailure message cites POLICY 14.1, the section that defines the gate, instead of sections 14 and 25 (#722). - A KOGL licence may no longer waive attribution (#725): every KOGL type requires it, so
attribution_required: falseunder공공누리_1유형–4유형now fails to load, like the commercial-use and modification contradictions #719 added. The rule now lives in one public function,kpubdata.core.spec.licence_conflicts, which the loader andscripts/validate_spec.pyboth call — before,validate_spec.pychecked only the schema and field contradictions, so an external spec author saw a KOGL contradiction only when the spec was loaded. datago.air_quality's licence contradicted itself: its attribution andredistribution: forbiddenfollow the source's KOGL type 3 (attribution, no modification), but it saidtype: 공공누리_1유형andmodification_allowed: true. It now says공공누리_3유형andmodification_allowed: false(#719). A spec whose KOGL type contradicts an explicitcommercial_use: true(types 2 and 4) ormodification_allowed: true(types 3 and 4) now fails to load, so a consumer reading the type and one reading the flag cannot be told two different things.- A missing or null spec digest no longer passes as legacy evidence (#717). #713 (#522) let any fixture whose meta had no non-empty
spec_sha256throughmake verifyas legacy, so deleting the key or setting it tonull/""unbound a fixture from its spec in one edit, andscripts/record.pywrote"spec_sha256": nullwhen it could not read the spec. Legacy is now the frozen baselinescripts/legacy_evidence_baseline.txt— the 28 fixtures tracked without a digest — and only a listed fixture whose meta has nospec_sha256key passes unbound; any other missing,nullor""digest fails the spec-binding step. The baseline is a ratchet: it fails when it grows past 28, repeats an entry, or keeps an entry that no longer exists or now carries a digest.scripts/record.pystops before calling or writing anything when it cannot compute the digest. The baseline is code-owned like the recorder and verifier. - The release workflows after #683 (ref validation):
publish-pypi.yml's tag pattern was written\\., which grep reads as a literal backslash, so no version tag matched and every dispatched publish failed — it is one pattern now,v0.8.0andv0.7.1a1match andv0.8does not. The ref is validated on therelease: publishedpath too, reaches the shell through the environment instead of${{ inputs.ref }}, must be a tag in this repository and must be onmain(git merge-base --is-ancestor). Inrelease.ymlthe merge path only fires for pull requests intomain, a re-run after the tag was pushed butgh release createfailed finishes the release when the tag is on the same commit instead of stopping at "tag must not exist", and thereleaseconcurrency group moved from the workflow to the two jobs, so ordinary pull requests closing no longer queue in it and cannot cancel a waiting release. #683 itself had no CHANGELOG entry: it makes a release run only frommainand validates the publish ref. datago.bus_arrival's licence and code columns (#732, #733, via #740): the licence drops its unconfirmed공공누리_1유형type and saysredistribution: unknownuntil the terms are confirmed from the provider page, and six ID columns (stationId,routeId,routeDestId,vehId1,vehId2,routeTypeCd) are text withsemantic_kind: code— a column named like a request parameter is not a number (ADR 0006).
Security
Secret scan (gitleaks)no longer fails on another branch's commits (#774). Without--log-opts, gitleaks runsgit log --all, which — combined withfetch-depth: 0pulling in every remote branch — walked unmerged PR branches too, so a synthetic test key on someone else's open branch turned this job, whichCI gateneeds, red for runs that never touched that branch. The job now scopes--log-optsto the run's own commits: a pull request scansbase..head, a push tomainscansbefore..sha(or justshafor a brand-new branch, wherebeforeis all zeros), andschedule/workflow_dispatchscan the default branch's full history only — never another branch..gitleaksignoreis unaffected. Ported from kpubdata-studio#685/#686.- Configured keys are masked out of
ProbeResult.detailby value (every percent-encoded form) before the message is truncated, so a key cut at the boundary cannot survive as a prefix (#694). - httpx's own INFO line
HTTP Request: GET <url> ...carried the query string, and with it a data.go.krserviceKey, verbatim. A filter on thehttpxlogger now masks the credential parameters named inSENSITIVE_PARAM_KEYS(#694).
Documentation
- Docs follow the 2026-09-30 decisions (#695): the compatibility page names the three products and the one-way dependency (Studio → Builder → KPubData) and moves the
mainrow to kpubdata 0.8.0 with Builder's>=0.8.0,<0.9pin; TERMINOLOGY says Builder owns the Access vocabulary;RELEASE_POLICY.mdpoints to §5.1 for cadence, adds therelease-windowstep, and states the real latest releases; PACKAGING describes the window gate, the manual release-PR step while Actions cannot open PRs, and re-runs after #687; SECURITY no longer says versions and tags are being reconciled.
Full changelog: v0.8.0...v0.9.0