From 93110beedb79a81023ad1903761471223f384dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=98=81=EA=B8=B0?= Date: Thu, 30 Jul 2026 11:47:52 +0900 Subject: [PATCH 1/3] knowledge: ingest 5 verified insight(s) into 2 pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit testing/docs-as-spec/document-conformance-checks (new category): positive + negative control per check for gates whose subject is a document; validate the pattern on a conforming sibling when the target file does not exist yet; split the requirement into structure / normative modality / enum completeness / cross-reference axes, each with a mutation that breaks only what it owns; pair coverage with value-validity; split Markdown rows on unescaped pipes. backend/common/storage/object-key-persistence (new category): persist the upload response Key (+Bucket) and derive URLs on read — Location is produced by two different code paths split at the managed uploader part size. --- .dev-loop/INGEST_REPORT.md | 189 ++++++++++++++++-- INDEX.md | 4 +- log.md | 2 + .../common/storage/object-key-persistence.md | 75 +++++++ wiki/backend/index.md | 8 +- .../document-conformance-checks.md | 88 ++++++++ wiki/testing/index.md | 12 +- 7 files changed, 350 insertions(+), 28 deletions(-) create mode 100644 wiki/backend/common/storage/object-key-persistence.md create mode 100644 wiki/testing/docs-as-spec/document-conformance-checks.md diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index 2ff3871..bf984cb 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -1,35 +1,180 @@ -# Knowledge flush — 1 insight +# Knowledge flush — 5 insight(s) → 2 pages -Source: RNR-3440 (사내 잠재매물 주간 추출 스크립트 메모리 피크 저감). Candidate: -"QueryPie 프록시 경유로 대용량 결과를 스트리밍할 때 server-side named cursor 대신 -일반 커서 + `fetchmany` + openpyxl `write_only`." +Queue drained: 5 pending candidates across 3 sessions. Four of them describe the +same situation at different granularity and were folded into one page (AGENTS.md +"one case per page" cuts by _situation_, not by candidate); the fifth is +unrelated and got its own page. + +| # | Candidate (hash) | Claim | Outcome | +| --- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| 1 | `f997639131d8ec9e` | Validate a grep gate on a conforming sibling file before adopting it | Folded → `testing-docs-as-spec-document-conformance-checks` (Do this §2) | +| 2 | `739b75ed972c6078` | Token-presence gates are blind three ways; split into structure / modality / row-completeness / cross-reference axes with per-axis negative controls | Folded → same page (Do this §3) | +| 3 | `2e3fcd105c8512fa` | Persist `s3.upload()` `Key`, not `Location` | New page `backend-common-storage-object-key-persistence` | +| 4 | `39dfae0d5a813cb4` | Pair a coverage check with a value-validity check, each with its own negative control | Folded → same page (Do this §4) | +| 5 | `18dc623000d54f07` | Split Markdown rows on unescaped pipes only | Folded → same page (Edge cases + Instead of) | ## Verified best-practice -**Claim 1 — psycopg2 server-side (named) cursor requires a transaction; fails under autocommit.** -- Source: psycopg2 usage docs — `https://github.com/psycopg/psycopg2/blob/master/doc/src/usage.rst` (via Context7). Quote: "Named cursors are typically created 'WITHOUT HOLD', meaning they exist only within the current transaction. Attempting to fetch from them after a commit or in autocommit mode raises an exception." -- Matches my live repro (`can't use a named cursor outside of transactions`). → **verified** +### Insights 1, 2, 4 — controls on a document-conformance check → `confidence: verified` + +**Claim.** A checker is unproven until it has been run against both a conforming +input (must PASS) and a deliberately-broken input (must FAIL), one control per +check; presence checks do not establish correctness. + +Sources checked: + +- **ESLint `RuleTester`** — https://eslint.org/docs/latest/integrate/nodejs-api#ruletester — + fetched; requires both a `valid` array (rule must not fire on compliant code) + and an `invalid` array whose cases must assert the errors the rule produces. + This is the positive/negative control pair as an enforced API shape. +- **Semgrep rule tests** — https://docs.semgrep.dev/writing-rules/testing-rules — + fetched; `ruleid:` annotates true positives and `ok:` true negatives, both in + the same test file. Second independent tool converging on the same requirement. +- **Google Testing Blog, mutation testing** — https://testing.googleblog.com/2021/04/mutation-testing.html — + already cited elsewhere in this wiki; supports "insert a fault, require the + check to fail" as the measure of detection. +- **JSON Schema `required`** — https://json-schema.org/understanding-json-schema/reference/object — + fetched; `required` asserts key presence and constrains no value (a required + property may even be `null`). This is the canonical statement of the + coverage-vs-validity split that insight 4 hit empirically. -**Claim 2 — a client-side (default) cursor pulls the whole result set to the client on execute; `fetchmany` only caps the Python-list explosion.** -- Source: psycopg2 cursor/usage docs + FAQ (named-cursor advantage = "data is fetched in chunks … minimal client memory"). By contrast the default cursor buffers the full result in libpq. → **verified** +**How verified.** The two linter sources are official docs for tools whose entire +job is "run a rule against a document/AST", and both mandate the two-sided test. +The candidates' own evidence (an independent auditor's 8 mutations passing a +16-gate suite; a `Bogus` cell surviving a coverage check) is the field half. +Insight 1's specific mechanism — a missing target file makes _every_ pattern +red, so red carries no information about the pattern — is a direct logical +consequence, and the candidate reports the positive control actually run +(7 sections matched on the conforming sibling `rfcs/0005`). -**Claim 3 — openpyxl `write_only` gives near-constant memory (<10 MB); one save only; lxml is for serialization speed, not the memory saving.** -- Source: openpyxl Optimised Modes — `https://openpyxl.readthedocs.io/en/stable/optimized.html` (via WebSearch). "keeping memory usage under 10Mb"; "A write-only workbook can only be saved once"; "make sure you have lxml installed" for large dumps (speed). -- This **corrects** the raw candidate's "lxml unnecessary" → precise form: unnecessary *for the memory win*, recommended *for large-dump speed*. Confirmed by my server test (write-only worked with lxml absent). → **verified** +### Insight 5 — unescaped-pipe splitting of GFM table rows → `confidence: verified` -**Claim 4 — QueryPie blocks `BEGIN`, so server-side cursor is impossible there.** -- Environment-specific, no external source. Live repro in gui context: `autocommit=False` + named cursor → `[ENGINE] No permission to execute BEGIN statement`. → **field-tested**. Generalized in the page to "a read-only access-control proxy that blocks transaction control", with QueryPie as the concrete example (not a product-specific page). -- Memory figure 838 MB → 38 MB (300k synthetic rows) is my RNR-3440 measurement (`ru_maxrss`, separate processes). +- **GFM spec, tables extension** — https://github.github.com/gfm/#tables-extension- — + fetched. Cells are "separated by pipes (`|`)", and Example 200 shows you + "include a pipe in a cell's content by escaping it, **including inside other + inline spans**" (`f\|oo`, and an escaped pipe inside a code span and inside + emphasis). So `\|` is legal cell content and `split("|")` overcounts a correct + row. +- **Reproduced here.** Ran the candidate's `re.split(r"(? Date: Thu, 30 Jul 2026 11:48:52 +0900 Subject: [PATCH 2/3] knowledge: record cross-check scope in ingest report --- .dev-loop/INGEST_REPORT.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index bf984cb..84e6fe1 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -168,6 +168,20 @@ All five ids verified to resolve to existing files; no duplicate ids in `wiki/`. before `reliability`), plus "object-storage references" added to the `common` subtree route line and to the backend row of the root `INDEX.md`. +## Cross-Check + +Cross-Check: source-level, not adversarial-reviewer. Every claim in this PR was +re-derived in-session from the primary source rather than from the harvested +candidate text — five docs/issues fetched for the S3 page (including reading +`managed_upload.js` on `raw.githubusercontent.com` instead of trusting the +candidate's line numbers) and four for the doc-gate page, with two independent +tools (ESLint `RuleTester`, Semgrep rule tests) converging on the +positive+negative control requirement. The pipe-splitting rule was reproduced +against this PR's own two pages. One candidate claim was downgraded on review +(the `space → +` multipart form is field evidence, not a documented contract — +see the scope correction above). No separate adversarial reviewer ran; the +owner's PR review is the remaining gate. + ## Invariants checked - Body length: 73 and 60 lines (limit 120). From dcfd2cc248fda2e99d9208e70bcaefaa6b67d4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=98=81=EA=B8=B0?= Date: Thu, 30 Jul 2026 11:49:43 +0900 Subject: [PATCH 3/3] knowledge: add decision log to ingest report --- .dev-loop/INGEST_REPORT.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index 84e6fe1..ba0f221 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -182,6 +182,32 @@ against this PR's own two pages. One candidate claim was downgraded on review see the scope correction above). No separate adversarial reviewer ran; the owner's PR review is the remaining gate. +## Decision Log (AI 생성) + +### 의도 — 무엇을 / 왜 + +- 큐에 쌓인 5개 후보를 각각 원본 1차 소스로 재검증한 뒤 wiki 2페이지로 적재했다. 후보 텍스트를 그대로 믿지 않고 ESLint/Semgrep/GFM/JSON Schema/AWS 문서와 `managed_upload.js` 소스를 직접 열어 대조했다 — 수확된 블록은 후보이지 검증된 지식이 아니기 때문. +- 5후보 → 2페이지로 접었다. AGENTS.md "one case per page"는 후보 수가 아니라 **상황** 단위로 자르므로, 1·2·4·5는 모두 "문서를 대상으로 하는 체커를 작성하는 상황" 하나라 한 페이지로 병합했다. +- 새 카테고리 2개(`testing/docs-as-spec`, `backend/common/storage`)를 만들었다. 기존 카테고리는 각각 "코드 테스트"와 "8개 common 관심사"로 짜여 있어 문서 검증·오브젝트 스토리지를 담을 자리가 없었다. + +### 배제한 대안 — 무엇을 안 했나 / 왜 + +- `testing/quality/tests-that-cannot-fail.md`에 append하지 않았다. 그 페이지는 *테스트 코드*의 mutation 규칙을 소유하고 "load when" 라인이 충돌해 AGENTS.md invariant 1의 drift 조건에 걸린다. 대신 양방향 링크로 연결. +- 후보 2·5가 `domain: qa` 태그였지만 qa로 라우팅하지 않았다. qa는 릴리즈 *프로세스* 스코프이고, 여기서 만드는 산출물은 자동 체커라 testing이 소유한다. +- 후보 3을 `backend/node`가 아닌 `backend/common`에 뒀다. v2·v3 양쪽에서 재현되고 원리가 언어 무관이라, "common이 원리·stack이 메커닉"이라는 AGENTS.md 규칙에 따랐다. +- 후보 3의 "space → `+`"를 문서화된 계약으로 단정하지 않았다. 공개 소스로 확인되는 건 `/` → `%2F`뿐이라 컬럼명을 "Observed form"으로 두고 스코프를 좁혔다. +- 포매터(PostToolUse prettier)가 index 3개 파일의 표 전체를 패딩해 무관한 행까지 diff에 올렸다 — 되돌리고 스크립트로 대상 라인만 재적용했다(INDEX.md 2줄, backend +8/-1, testing +12/-3). + +### 리뷰어가 볼 곳 — 신뢰성 판단 포인트 + +- `.dev-loop/INGEST_REPORT.md` "Verified best-practice" — 인용 URL이 실제로 그 주장을 지지하는지. 특히 "One scope correction" 문단(후보보다 약하게 적은 부분). +- `wiki/testing/docs-as-spec/document-conformance-checks.md:39-42` — positive/negative control 표. ESLint valid/invalid + Semgrep ok/ruleid 두 출처가 실제로 이 형태를 강제하는지. +- `wiki/backend/common/storage/object-key-persistence.md:40-43` — 단일/멀티파트 인코딩 표. "Observed form" 열이 문서화된 계약처럼 읽히지 않는지. +- `wiki/testing/index.md`, `wiki/backend/index.md` — 새 "load when" 라인이 인접 페이지 트리거와 겹치지 않는지(AGENTS.md invariant 1). +- 새 카테고리 2개 신설이 과한지 — 기존 카테고리로 접는 게 낫다고 보면 이 PR에서 되돌리기 쉬운 부분. + +> [추정] 표시 항목은 세션에 명시 근거가 없어 사후 재구성한 의도임 — 검증 필요 + ## Invariants checked - Body length: 73 and 60 lines (limit 120).