Avoid leaking CJ contact details in published tracking examples

The first delivery-tracking draft exposed too much of the raw CJ event payload in the public examples. This follow-up keeps the examples useful for status checks while normalizing them to non-PII fields only, and locks that expectation in the docs regression suite so the raw  field is not reintroduced.

Constraint: Must preserve the verified official CJ flow while removing avoidable personal-contact leakage from the shipped examples
Constraint: Follow-up had to stay docs-first and scoped to the existing feature/#4 PR branch
Rejected: Keep raw CJ payload output and rely on users to ignore  | published examples should not leak names/phone numbers unnecessarily
Rejected: Drop CJ example detail entirely | loses practical verification value for the main carrier path
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any future CJ example update must continue to avoid printing raw  or other contact-bearing fields in public docs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 normalized CJ official endpoint verification for 1234567890
Tested: python3 우체국 follow-up verification for 1234567890123
Not-tested: Real non-placeholder customer waybills with different event shapes
This commit is contained in:
Jeffrey (Dongkyu) Kim 2026-03-27 00:48:02 +09:00
commit e257ab39a0
3 changed files with 46 additions and 4 deletions

View file

@ -137,7 +137,6 @@ print(json.dumps({
"status": status_map.get(latest.get("crgSt"), latest.get("scanNm") or "알수없음"),
"timestamp": latest.get("dTime"),
"location": latest.get("regBranNm"),
"message": latest.get("crgNm"),
"event_count": len(events),
}, ensure_ascii=False, indent=2))
PY
@ -147,7 +146,7 @@ rm -f "$tmp_body" "$tmp_cookie" "$tmp_json"
추가 smoke test 로는 `000000000000` 도 사용할 수 있다.
CJ 응답은 `parcelResultMap.resultList` 가 비어 있어도 `parcelDetailResultMap.resultList` 쪽에 이벤트가 들어올 수 있으므로, 상세 이벤트 배열을 우선 본다.
CJ 응답은 `parcelResultMap.resultList` 가 비어 있어도 `parcelDetailResultMap.resultList` 쪽에 이벤트가 들어올 수 있으므로, 상세 이벤트 배열을 우선 본다. 예시 출력은 `crgSt` / `scanNm` / `dTime` / `regBranNm` / 이벤트 수처럼 비식별 필드만 요약하고, 담당자 이름·연락처가 섞일 수 있는 `crgNm` 원문은 그대로 보여주지 않는다.
### 2. 우체국: official HTML flow

View file

@ -64,14 +64,38 @@ curl -sS -L -b "$tmp_cookie" \
python3 - <<'PY' "$tmp_json"
import json
import sys
status_map = {
"11": "상품인수",
"21": "상품이동중",
"41": "상품이동중",
"42": "배송지도착",
"44": "상품이동중",
"82": "배송출발",
"91": "배달완료",
}
payload = json.load(open(sys.argv[1], encoding="utf-8"))
print(json.dumps(payload["parcelDetailResultMap"]["resultList"][-1], ensure_ascii=False, indent=2))
events = payload["parcelDetailResultMap"]["resultList"]
if not events:
raise SystemExit("조회 결과가 없습니다.")
latest = events[-1]
print(json.dumps({
"carrier": "cj",
"invoice": payload["parcelDetailResultMap"]["paramInvcNo"],
"status_code": latest.get("crgSt"),
"status": status_map.get(latest.get("crgSt"), latest.get("scanNm") or "알수없음"),
"timestamp": latest.get("dTime"),
"location": latest.get("regBranNm"),
"event_count": len(events),
}, ensure_ascii=False, indent=2))
PY
rm -f "$tmp_body" "$tmp_cookie" "$tmp_json"
```
CJ는 JSON 응답이므로 `parcelDetailResultMap.resultList` 를 기준으로 상태를 읽는 편이 가장 안정적이다.
CJ는 JSON 응답이므로 `parcelDetailResultMap.resultList` 를 기준으로 상태를 읽는 편이 가장 안정적이다. 문서 예시는 `crgSt` / `scanNm` / `dTime` / `regBranNm` / `event_count` 만 정리하고, 담당자 이름이나 휴대폰 번호가 포함될 수 있는 `crgNm` 원문은 그대로 출력하지 않는다.
## 우체국 예시

View file

@ -186,3 +186,22 @@ test("delivery-tracking skill documents official CJ and ePost flows with extensi
assert.match(featureDoc, /JSON/);
assert.match(featureDoc, /HTML/);
});
test("delivery-tracking docs avoid raw CJ personal fields in published examples", () => {
const skill = read(path.join("delivery-tracking", "SKILL.md"));
const featureDoc = read(path.join("docs", "features", "delivery-tracking.md"));
assert.doesNotMatch(skill, /"message":\s*latest\.get\("crgNm"\)/);
assert.doesNotMatch(
featureDoc,
/print\(json\.dumps\(payload\["parcelDetailResultMap"\]\["resultList"\]\[-1\],\s*ensure_ascii=False,\s*indent=2\)\)/,
);
for (const doc of [skill, featureDoc]) {
assert.match(doc, /"status_code":\s*latest\.get\("crgSt"\)/);
assert.match(doc, /"status":\s*status_map\.get\(latest\.get\("crgSt"\),/);
assert.match(doc, /"timestamp":\s*latest\.get\("dTime"\)/);
assert.match(doc, /"location":\s*latest\.get\("regBranNm"\)/);
assert.match(doc, /"event_count":\s*len\(events\)/);
}
});