Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 160 additions & 22 deletions .dev-loop/INGEST_REPORT.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ follow the cross-pointers in their index or take the next matching seeded domain
| [frontend](wiki/frontend/index.md) | **seeded** | Web UI code: state placement, rendering performance, in-UI data fetching (races, infinite scroll), auth token handling, forms, XSS-safe output, accessibility |
| [infrastructure](wiki/infrastructure/index.md) | **seeded** | CI/CD pipelines, secrets in build/deploy, container image builds, rollout/rollback strategy, observability (logs/metrics/alerting) |
| [testing](wiki/testing/index.md) | **seeded** | Writing or structuring automated tests: level choice, cases/assertions, test data, mock decisions, flaky tests (release-process quality → qa) |
| [qa](wiki/qa/index.md) | **seeded** | Release-quality process: release gates, regression scoping, bug reports, severity/priority triage, exploratory testing (writing automated test code → testing) |
| [qa](wiki/qa/index.md) | **seeded** | Release-quality process: release gates, regression scoping, bug reports, severity/priority triage, exploratory testing, automated verification of document deliverables (spec/RFC gates) (writing automated test code → testing) |
| [debugging](wiki/debugging/index.md) | **seeded** | Diagnosing a failure — finding what is wrong and why: reproducing, bisection, hypothesis testing, traces/logs, intermittent failures (fixing the diagnosed fault → its owning domain) |
| [security](wiki/security/index.md) | **seeded** | Trust-boundary decisions: input validation, session-vs-token auth choice, per-resource authorization (IDOR), secrets hygiene, dependency trust, PII handling (XSS rendering → frontend; CI secrets → infrastructure; JWT implementation → backend/frontend auth) |
| [platforms](wiki/platforms/index.md) | **seeded** | OS-level differences breaking code across macOS/Linux/Windows: shell portability, BSD-vs-GNU CLI, filesystem case/line endings, background services/cron, toolchain version pinning |
| [platforms](wiki/platforms/index.md) | **seeded** | OS-level differences breaking code across macOS/Linux/Windows: shell portability, BSD-vs-GNU CLI, filesystem case/line endings, Unicode normalization in text/file-name matching, background services/cron, toolchain version pinning |
| [mobile](wiki/mobile/index.md) | **seeded** | App-side iOS/Android/cross-platform: process death/state survival, offline-first sync, mobile-network calls, store rollout/hotfix strategy, startup time |

All ten domains are seeded. New categories grow via `skills/wiki-ingest/SKILL.md`.
2 changes: 2 additions & 0 deletions log.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ Append-only. Format: `## [YYYY-MM-DD] <ingest|revise|lint|gap|contradiction|drif
## [2026-07-12] revise | security/secrets-in-code +1 edge case: third-party HTTP client (httpx/requests) logs the full request URL — including a query-param API key — at INFO, so root/DEBUG logging leaks it; keep the client logger above INFO. Found when a standalone sync process set logging.basicConfig(INFO) and httpx wrote the data.go.kr serviceKey to the log file. last_verified bumped to 2026-07-12.
## [2026-07-13] ingest | databases +1 (query-optimization): streaming-large-result-sets — memory-bounded export of a huge single-query result. Client-side cursor pulls the whole set to libpq on execute (fetchmany caps only the Python-list explosion); only a server-side/named cursor truly streams but needs a transaction, so it fails under autocommit or a proxy that blocks BEGIN → fall back to client-side fetchmany + disk spool + openpyxl write_only (measured 300k rows 838MB→38MB). Derived from RNR-3440 (potential-listing weekly extract memory peak); QueryPie BEGIN-block generalized to "read-only access proxy", field-tested. Sources: psycopg2 usage/cursor docs (named-cursor WITHOUT HOLD + autocommit exception), openpyxl optimized-modes (write-only near-constant memory, lxml=speed-not-memory).
## [2026-07-23] ingest | databases +2: schema-design/online-schema-changes (ACCESS EXCLUSIVE lock avoidance — non-volatile default fast path, ADD CONSTRAINT NOT VALID + VALIDATE at SHARE UPDATE EXCLUSIVE, CHECK-NOT-NULL trick, CREATE INDEX CONCURRENTLY, expand-and-contract to decouple DB migration from app deploy, lock_timeout for lock-queue pile-up) + operations/autovacuum-and-wraparound (NEW category operations: per-table scale_factor/cost_limit tuning for hot tables, age(datfrozenxid)/relfrozenxid + n_dead_tup monitoring, wraparound read-only cliff and superuser VACUUM recovery, VACUUM FULL vs pg_repack). Derived from the Hatchet "Postgres survival guide"; both cross-checked against PostgreSQL official docs (sql-altertable, routine-vacuuming).
## [2026-07-30] ingest | qa +1 (new category document-verification): document-verification/spec-document-gates — automated gates that decide whether a spec/RFC meets its requirements. Positive control on a conforming sibling before adopting a pattern (an absent target fails every pattern, so Red proves nothing), negative control per gate kept as a permanent mutant self-test, four check axes (table structure / sentence-scoped modality+polarity / closed-set exact count / cross-reference recomputation), fail-closed on a missing anchor. Derived from RFC-authoring sessions where an independent auditor's 8 tampered copies passed a 16-gate token-existence checklist (5/8 fully); after the axes, 32/32 mutants failed and the intact doc passed 62/62. Mechanisms sourced to ESLint RuleTester (valid+invalid required), Google mutation testing, RFC 2119 (MUST vs SHOULD), Vale conditional/occurrence checks, markdownlint MD056 + issue #1206.
## [2026-07-30] ingest | platforms +1 (environment): environment/unicode-text-matching — non-ASCII grep/regex patterns. Precomposed syllables are single code points so a stem prefix is not a substring of its inflected form (`아니` not in `아닌`), and grep applies no canonical equivalence so an NFC pattern scores 0 on NFD data; enumerate surface forms, normalize both sides on write, assert expected hit counts, normalize file-name lists before cross-OS diffs. Reproduced on macOS 15/APFS 2026-07-30 (code-point dump + 4 grep runs); sourced to UAX #15, Unicode core spec §3.12 (11,172 precomposed Hangul syllables from U+AC00), APFS FAQ (preserving + normalization-insensitive; HFS+ stores a normalized form), POSIX grep.
2 changes: 1 addition & 1 deletion wiki/platforms/environment/timezone-and-locale.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ sources:
- https://unicode.org/faq/casemap_charprop.html
- https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap07.html
last_verified: 2026-07-10
related: [databases-schema-design-column-data-types, platforms-processes-background-services]
related: [databases-schema-design-column-data-types, platforms-processes-background-services, platforms-environment-unicode-text-matching]
---

# Timezone and Locale as Hidden Inputs to Date and Text Code
Expand Down
68 changes: 68 additions & 0 deletions wiki/platforms/environment/unicode-text-matching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
id: platforms-environment-unicode-text-matching
domain: platforms
category: environment
applies_to: [general]
confidence: verified
sources:
- https://unicode.org/reports/tr15/
- https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/
- https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/APFS_Guide/FAQ/FAQ.html
- https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html
last_verified: 2026-07-30
related: [platforms-environment-timezone-and-locale, platforms-filesystems-paths-case-and-line-endings, qa-document-verification-spec-document-gates]
---

# Matching Non-ASCII Text with grep and Regex

## When this applies

You are writing a grep/regex pattern that must match non-ASCII text (Korean,
Japanese, accented Latin, emoji) in files, log lines, or file names; a pattern that
looks correct returns zero hits on text you can see on screen; a search works on one
machine and misses on another after the file crossed an OS, archive, or editor boundary.

## Do this

| Case | Do |
|------|----|
| Writing a literal pattern | Copy the exact substring out of the file rather than typing a stem you expect to be a prefix. In a precomposed script each syllable is a single code point: `아니` (`U+C544 U+B2C8`) is not contained in `아닌` (`U+C544 U+B2CC`), so a stem-prefix pattern returns 0 hits on a line that plainly contains the word |
| The word occurs in several inflected forms | Enumerate the forms as alternatives — `(아닌\|아니다\|아니라)` — one alternative per surface form present in the text |
| Pattern and data can come from different producers (editor, export, archive, another OS) | Normalize both sides to the same form before comparing (NFC for stored text), because `grep` matches code-unit sequences and applies no canonical equivalence: an NFC pattern scores 0 against the same word stored as NFD |
| The match result gates a build, release, or verification checklist | Assert an expected non-zero count rather than "no error", so a normalization or stem mistake surfaces as a failed count ([qa-document-verification-spec-document-gates]) |
| Comparing file names collected on macOS and Linux | Normalize both name lists in your code before diffing: APFS preserves the normalization it was given and looks up either form, HFS+ stores its own normalized form, and Linux filesystems store the bytes given — so the same name reaches your comparison in different forms |

Measured on macOS 15 / APFS, 2026-07-30 (`grep` 2.6.0-FreeBSD, Python 3.13):

```
grep -c '아니' <NFC file> → 0 # same line contains 아닌
grep -c '아닌' <NFC file> → 1
grep -c -f <NFC pattern> <NFD file> → 0 # no normalization by grep
grep -c -f <NFD pattern> <NFD file> → 1
len('아닌') NFC = 2 code points, NFD = 5 # jamo L+V+T decomposition
```

## Edge cases

| Case | Then |
|------|------|
| The pattern must survive both normalization forms | Match on a substring that contains no combining sequence (an ASCII token, an id, a number), or normalize the input through a filter before grep |
| A pattern with a character class or quantifier over non-ASCII text | Test the exact pattern against a known-matching line first: BSD and GNU regex engines differ in multi-byte class handling, so a class that works on one userland can misfire on the other ([platforms-tools-bsd-vs-gnu-cli]) |
| Zero hits and the cause is unclear | Print the code points of both the pattern and the target line (`python3 -c "print([hex(ord(c)) for c in open(f).read()])"`) before concluding the text is missing — it separates "word absent" from "different code points" |
| The text is user-supplied and used as a key or a dedup identifier | Normalize to NFC at the trust boundary on write, so later equality and search compare one form |
| The search happens inside a database rather than a file | Normalization is applied by the writer, not the engine — same rule: normalize on write, search the stored form |

## Instead of

| If you are about to | Do this instead | Why |
|---------------------|-----------------|-----|
| Write a stem prefix (`아니`) expecting it to match its inflections | Copy the literal form present in the text, or enumerate the alternatives | Precomposed syllables are distinct code points, so the stem is not a substring of the inflected word |
| Read a 0-hit grep as "the requirement is absent from the document" | Compare the code points of the pattern and the line before acting | 0 hits also means "different normalization form" or "different syllable" — an absence conclusion from that is a false negative |
| Compare two file-name lists byte-for-byte across machines | Normalize both lists to NFC in code, then diff | Producers store different forms of the same name; APFS lookup hides this locally but a byte diff does not |

## Sources

- https://unicode.org/reports/tr15/ — normalization forms; Hangul syllables have special full-decomposition rules; canonically equivalent strings have different binary representations unless normalized
- https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/ — §3.12 Conjoining Jamo Behavior: 11,172 precomposed Hangul syllables from `SBase = U+AC00` decompose algorithmically into L/V/T jamo
- https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/APFS_Guide/FAQ/FAQ.html — APFS preserves the file name's normalization and is normalization-insensitive via hashes of the normalized form; HFS+ stores the normalized form
- https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html — grep matches patterns against input lines by the specified regular-expression rules; no canonical-equivalence folding is specified
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sources:
- https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
- https://git-scm.com/docs/gitattributes
last_verified: 2026-07-10
related: [platforms-shells-portable-shell-scripts]
related: [platforms-shells-portable-shell-scripts, platforms-environment-unicode-text-matching]
---

# Files That Break When a Repo Moves Between macOS, Windows, and Linux
Expand Down
6 changes: 4 additions & 2 deletions wiki/platforms/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
Route here for: OS-level differences that break code and scripts moving between
macOS, Linux, and Windows — shell portability, BSD-vs-GNU CLI flags, filesystem
case/line-ending/path behavior, file permissions and exec bits across
git/archives/containers, hidden environment inputs (timezone/locale, per-context
PATH resolution), keeping processes alive as services or scheduled jobs, and
git/archives/containers, hidden environment inputs (timezone/locale, Unicode
normalization form in text and file names, per-context PATH resolution), keeping
processes alive as services or scheduled jobs, and
pinning toolchain versions across machines. Application logic stays in backend;
SQL stays in databases.

Expand All @@ -27,6 +28,7 @@ Match your situation to a "load when" line; load only matching pages.
| Page | Load when |
|------|-----------|
| [timezone-and-locale](environment/timezone-and-locale.md) | Date/time or text-processing code behaves differently across machines (passes locally, fails in CI or vice versa); a cron/scheduled job fires at the wrong hour or double-fires/skips around DST; reviewing code that formats, parses, or compares dates or strings; writing tests that touch time; building case-insensitive keys, sorted output, or number parsing that must agree across machines |
| [unicode-text-matching](environment/unicode-text-matching.md) | A grep/regex pattern over non-ASCII text (Korean/Japanese/accented Latin/emoji) returns zero hits on text you can see; writing a pattern that must match an inflected or precomposed word; a search or name comparison works on one machine and misses after the file crossed an OS/archive/editor boundary; deciding where to normalize (NFC/NFD) user-supplied text used as a key |
| [path-resolution](environment/path-resolution.md) | "command not found" though the tool is installed; a different version runs than the one installed; sudo/CI/cron/GUI apps/ssh can't find a command the interactive shell finds; two installations of the same tool conflict; deciding how a script should locate its correctness-critical tools |

## filesystems
Expand Down
Loading
Loading