leerness 1.36.12 → 1.36.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +4 -4
- package/bin/leerness.js +17 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.13 — 2026-07-13 — database 렌즈 8→10문항: 9라운드 dogfood playbook 역이식
|
|
4
|
+
|
|
5
|
+
- **왜**: stock-service 에서 코퍼스 9개 동시성 개념을 실구현-리뷰-재현 루프로 dogfood 한 결과(회고: db-safety-lens/review-playbook.md), 기존 8문항이 다루지 않는 결함 패밀리가 반복 확인됨 — 전부 "그린 테스트는 통과했는데 리뷰가 잡은" 실사건 기반.
|
|
6
|
+
- **추가 9번(KO+EN)**: 감싼 사용자 콜백의 async 이탈(await 강제/thenable 거부 — 아웃박스 sent=1 유실·펜스 밖 늦은 쓰기), 추적 구조의 등록-후-실행(동기 throw 가 반초기화 상태를 보면 원본 에러 삼킴·키 영구 오염), abort 의 완전 장벽(대기열 엔트리·표식·세대 카운터까지; 세대 재검사 없는 중단 획득은 abort 뒤에도 진행), 정리의 identity 가드.
|
|
7
|
+
- **추가 10번(KO+EN)**: 버전/토큰 불변식의 전-라이터 참여(INSERT OR REPLACE 버전 초기화 ABA), 토큰/키 식별자(무표식 LSN 교차-스토어 노출·객체 키 identity), 수량 경계 검증(음수 qty 의 가드 방향 역전), 테스트 정직성(자기보고 라벨 대신 분기 데이터 판별·no-op 변이 실행·활성성 timeout).
|
|
8
|
+
- **병합 cap 8→12**: 내장이 10문항이 되면서 기존 커스텀-병합 cap(8)이 내장 질문을 자르는 잠복 회귀를 함께 제거 (slice 2곳).
|
|
9
|
+
- **selftest**: database 렌즈 케이스를 10문항(KO=EN) + 신규 앵커(thenable/ABA, EN 동형) + "병합이 내장 10을 자르지 않음" 회귀 가드로 갱신.
|
|
10
|
+
- **검증**: selftest 286/286, `lens database` 실렌더 10문항 확인. 정직 한계: 신규 2문항은 단일 코드베이스 field log 에서 온 휴리스틱 질문이지 정적 감지가 아니다(렌즈 전체가 advisory).
|
|
11
|
+
|
|
3
12
|
## 1.36.12 — 2026-07-12 — UR-0009 완결: selftest 견고화 불변식 메타가드 + .gitattributes LF 강제 커밋
|
|
4
13
|
|
|
5
14
|
**저순위 백로그 UR-0009 (1.12.0 클린룸 exact-match 실패 후속) 3파트 완결.**
|
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ MIT
|
|
|
125
125
|
<!-- leerness:project-readme:start -->
|
|
126
126
|
## Leerness Project Harness
|
|
127
127
|
|
|
128
|
-
이 프로젝트는 Leerness v1.36.
|
|
128
|
+
이 프로젝트는 Leerness v1.36.13 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
129
129
|
|
|
130
130
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
131
131
|
|
|
@@ -179,7 +179,7 @@ leerness memory restore decision <date|title>
|
|
|
179
179
|
|
|
180
180
|
### MCP server (외부 AI 통합)
|
|
181
181
|
|
|
182
|
-
Leerness v1.36.
|
|
182
|
+
Leerness v1.36.13는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
183
183
|
|
|
184
184
|
```jsonc
|
|
185
185
|
// 카테고리별
|
|
@@ -200,7 +200,7 @@ Leerness v1.36.12는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
200
200
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
201
201
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
|
|
202
202
|
|
|
203
|
-
현재 누적: **70 라운드 (1.9.40 → 1.36.
|
|
203
|
+
현재 누적: **70 라운드 (1.9.40 → 1.36.13)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
204
204
|
|
|
205
205
|
### 성능 가이드 (1.9.140 측정)
|
|
206
206
|
|
|
@@ -238,6 +238,6 @@ leerness release pack --close --auto-main-push
|
|
|
238
238
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
239
239
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
240
240
|
|
|
241
|
-
Last synced by Leerness v1.36.
|
|
241
|
+
Last synced by Leerness v1.36.13: 2026-07-13
|
|
242
242
|
<!-- leerness:project-readme:end -->
|
|
243
243
|
|
package/bin/leerness.js
CHANGED
|
@@ -33,7 +33,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
33
33
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _TOOL_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 · 1.11.4 (UR-0007): _TOOL_CATALOG
|
|
34
34
|
const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
|
|
35
35
|
|
|
36
|
-
const VERSION = '1.36.
|
|
36
|
+
const VERSION = '1.36.13';
|
|
37
37
|
|
|
38
38
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
39
39
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -3894,13 +3894,17 @@ function _selfTestCases() {
|
|
|
3894
3894
|
&& eq(F([]), [])
|
|
3895
3895
|
&& eq(F(['a.css', 'b.md', 'c.js', 'd.test.js']).length, 2); // 최대 2개 클러터 방지
|
|
3896
3896
|
} },
|
|
3897
|
-
{ name: 'DB 안전성 렌즈 (코어
|
|
3897
|
+
{ name: 'DB 안전성 렌즈 (코어 승격 + 9라운드 playbook 역이식): database 도메인 10문항(KO=EN) + DB파일 매핑 인라인 + 비-DB 회귀0 (행위)', run: () => {
|
|
3898
3898
|
const F = _lensDomainsForFiles;
|
|
3899
3899
|
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
3900
3900
|
const d = LENS_CATALOG.database;
|
|
3901
|
-
const catOk = !!d && d.questions.length ===
|
|
3901
|
+
const catOk = !!d && d.questions.length === 10 && d.questionsEn.length === 10
|
|
3902
3902
|
&& d.questions[0].includes('lost update') && d.questions.some(q => q.includes('TOCTOU'))
|
|
3903
3903
|
&& d.questions.some(q => q.includes('in-doubt')) && d.questions.some(q => q.includes('SKIP LOCKED'))
|
|
3904
|
+
&& d.questions.some(q => q.includes('thenable')) // playbook 역이식 9번(콜백 async 이탈+등록/abort 장벽)
|
|
3905
|
+
&& d.questions.some(q => q.includes('ABA')) // playbook 역이식 10번(전-라이터 참여+토큰 식별자+테스트 정직성)
|
|
3906
|
+
&& d.questionsEn.some(q => q.includes('thenables')) && d.questionsEn.some(q => q.includes('ABA'))
|
|
3907
|
+
&& _mergeLensCatalog(LENS_CATALOG, {}).database.questions.length === 10 // 병합 cap 이 내장 10문항을 자르지 않음 (cap 8 회귀 방지)
|
|
3904
3908
|
&& d.affects.every(a => LENS_CATALOG[a]);
|
|
3905
3909
|
// DB 파일 → database 최우선(questions[0] 인라인 노출), DB+code → [database, code], 비-DB 코드 → 회귀 없음
|
|
3906
3910
|
const mapOk = eq(F(['db/migrations/001_init.sql']), ['database'])
|
|
@@ -4910,7 +4914,10 @@ const LENS_CATALOG = {
|
|
|
4910
4914
|
"이 불변식(잔액>=0, 유일성, NOT NULL, 참조무결성)이 DB 제약(CHECK/UNIQUE/FK/NOT NULL)으로 강제되는가, 아니면 앱 코드 검증에만 있는가? — 앱 검증은 동시 쓰기·다른 서비스·백필 스크립트·직접 SQL이 조용히 우회한다. 위반을 시도하는 테스트로 (a) 제약이 이 엔진/연결에서 실제로 켜져 있는지(예: SQLite는 연결마다 PRAGMA foreign_keys=ON; Postgres에서 NOT VALID로 추가한 FK는 신규 INSERT/UPDATE에는 그대로 적용되고 기존 행만 검증을 건너뛴다), (b) UNIQUE 컬럼의 NULL은 표준상 서로 구별돼 NULL 행이 무제한 허용된다는 함정을 확인했는가? — NULL이 유효하지 않으면 NOT NULL, NULL을 하나만 허용하려면 Postgres 15 NULLS NOT DISTINCT나 생성열/표현식 유니크로 풀어라(부분 인덱스 WHERE col IS NOT NULL은 NULL을 제약하는 게 아니라 오히려 무제한 허용한다 — non-null만 유니크할 때 쓰는 것).",
|
|
4911
4915
|
"이 트랜잭션의 격리 수준을 의식적으로 골랐고, 데드락·직렬화 실패에 유한 재시도 루프(재시도 안에서 최신 상태 재조회, 백오프/지터, 재시도되는 트랜잭션엔 외부 부수효과가 없어 두 번 실행돼도 안전할 것)를 넣었는가? — 기본값에 그냥 맡기면 non-repeatable read·phantom·write skew가 샌다(기본값은 엔진마다 다름: Postgres는 보통 READ COMMITTED, MySQL InnoDB는 보통 REPEATABLE READ). 재시도할 에러코드도 엔진별로 다르니 확인했는가(Postgres 데드락 40P01·직렬화 40001; MySQL 데드락 40001/1213·락 대기 타임아웃 1205 — 1205는 기본적으로 문장만 롤백하니 트랜잭션 전체를 BEGIN부터 재시도하거나 innodb_rollback_on_timeout을 켤 것; SQLite는 SQLITE_BUSY/BUSY_SNAPSHOT에 busy_timeout·쓰기 트랜잭션엔 BEGIN IMMEDIATE·필요 시 WAL로 대응). 그리고 COMMIT 도중 타임아웃/연결끊김으로 결과가 불확실(in-doubt)한 트랜잭션은 무조건 롤백으로 단정하지 말고 멱등키/트랜잭션ID/원본 재조회로 실제 반영 여부를 재확인한 뒤 재시도하는가? 스냅샷 격리/Postgres REPEATABLE READ만으론 write skew가 안 막히니(SERIALIZABLE 또는 명시적 잠금 필요) 합계·겹침 불변식엔 그 방어를 뒀고, 여러 행/테이블을 잠그는 경로는 늘 같은 순서로 잠그는가?",
|
|
4912
4916
|
"사용자에게 '성공'을 돌려준 이 쓰기가 실제로 확인된 COMMIT에 도달했고(커밋을 await하고 결과를 검사했는가), 그 ack가 메모리 버퍼가 아니라 안정 저장소(커밋 시 WAL/redo/journal이 flush됨)를 의미하는가? — autocommit을 끈 채 커밋을 빠뜨리면 커넥션 반납 시 열린 트랜잭션이 조용히 롤백된다. 내구성 설정은 엔진마다 다르니 데이터 등급에 맞게 확인했는가(예: Postgres synchronous_commit, MySQL innodb_flush_log_at_trx_commit=1 + sync_binlog=1, SQLite synchronous=FULL; Redis는 기본이 비내구적). 노드 손실까지 견뎌야 하는 데이터라면 쿼럼/동기 복제 ack(예: w:majority, 동기 스탠바이)로 한 노드가 죽어도 유실되지 않게 했고, failover RPO를 명시했는가?",
|
|
4913
|
-
"동시성 방어를 인프로세스 뮤텍스·싱글턴·인메모리 dedup에 기대거나, 여러 서비스에 걸친 작업을 하나의 DB 트랜잭션으로 감쌀 수 있다고 착각하거나, 방금 프라이머리에 쓴 값을 지연된 리드 레플리카에서 읽고 있지 않은가? — 인스턴스가 2개 이상이면 인프로세스 락은 무력하니 공유 지점으로 옮기고(DB UNIQUE 제약·행 잠금·advisory lock, 큐 소비는 FOR UPDATE SKIP LOCKED), 원자성은 프로세스/네트워크 경계를 못 넘으니 크로스서비스는 아웃박스+saga+보상 트랜잭션으로, at-least-once 전달은 멱등키(UNIQUE)로, 복제 지연이 깨는 read-your-writes는 프라이머리 라우팅/LSN 확인으로 옮겼는가? 분산 락을 쓴다면 리스 만료 후 늦게 깨어난 작업이 최신 작업을 덮어쓰지 못하게 단조 증가 펜싱 토큰(DB 시퀀스/행 버전)을 발급하고 하위 자원이 낮은 토큰을 거부하게 했는가?"
|
|
4917
|
+
"동시성 방어를 인프로세스 뮤텍스·싱글턴·인메모리 dedup에 기대거나, 여러 서비스에 걸친 작업을 하나의 DB 트랜잭션으로 감쌀 수 있다고 착각하거나, 방금 프라이머리에 쓴 값을 지연된 리드 레플리카에서 읽고 있지 않은가? — 인스턴스가 2개 이상이면 인프로세스 락은 무력하니 공유 지점으로 옮기고(DB UNIQUE 제약·행 잠금·advisory lock, 큐 소비는 FOR UPDATE SKIP LOCKED), 원자성은 프로세스/네트워크 경계를 못 넘으니 크로스서비스는 아웃박스+saga+보상 트랜잭션으로, at-least-once 전달은 멱등키(UNIQUE)로, 복제 지연이 깨는 read-your-writes는 프라이머리 라우팅/LSN 확인으로 옮겼는가? 분산 락을 쓴다면 리스 만료 후 늦게 깨어난 작업이 최신 작업을 덮어쓰지 못하게 단조 증가 펜싱 토큰(DB 시퀀스/행 버전)을 발급하고 하위 자원이 낮은 토큰을 거부하게 했는가?",
|
|
4918
|
+
// 9~10번: 9-라운드 현장 dogfood 회고(review-playbook)에서 역이식 — 그린 테스트가 놓치고 리뷰가 잡은 결함 패밀리.
|
|
4919
|
+
"트랜잭션·락·리스·single-flight 가 감싼 '사용자 콜백'이 promise 를 반환할 수 있지 않은가? — async 콜백은 첫 suspension 까지만 동기라, await 하지 않으면 그 뒤 continuation 이 COMMIT/해제 뒤 보호 밖에서 실행된다(아웃박스 릴레이가 async 발행 실패를 모른 채 sent=1로 마킹, 펜스 해제 뒤 늦은 쓰기). await 를 설계로 강제하거나 thenable 을 명시적으로 거부했는가? 진행 중 작업을 추적하는 구조(단일비행 맵·락 테이블·in-progress 레지스트리)는 사용자 코드가 실행되거나 동기 throw 하기 '전'에 등록을 마치는가 — 등록 전에 콜백이 던지면 실패 경로가 반초기화 상태를 보고 원본 에러가 삼켜지거나 죽은 엔트리가 키를 영구 오염시킨다. abort/해제는 그 작업의 흔적 전부(보유 락·대기열 엔트리·무효화 표식·세대 카운터)를 걷어내는 장벽인가 — 걷다 만 대기열 엔트리는 죽은 작업에 락을 승계시키고, 세대 재검사 없는 중단된 획득 루프는 abort 뒤에도 계속 획득한다. 그리고 정리는 identity 가드(맵의 엔트리가 여전히 '내 것'일 때만 삭제)로 후속 작업의 엔트리를 지우지 않는가?",
|
|
4920
|
+
"버전 컬럼·단조 토큰·워터마크가 지키는 불변식에 '모든' 라이터가 참여하는가 — upsert·마이그레이션·시드·관리자 경로 중 하나만 우회해도(INSERT OR REPLACE 가 version 을 기본값으로 초기화, 평범한 대입이 토큰을 되감음) 낡은 스냅샷의 CAS 가 통과한다(ABA). 그 토큰/키는 식별자를 갖는가 — 클러스터/스토어 표식 없는 맨 숫자 LSN 은 무관한 스토어의 레플리카와도 '같음'으로 비교돼 남의 데이터를 노출하고, 객체 키는 identity 비교라 같은 내용의 {id:1}도 서로 다른 키다. 수량/금액 파라미터는 경계에서 검증되는가 — 음수 qty 는 stock >= qty 가드를 '통과'하면서 이체 방향을 뒤집는다(역방향 절도). 마지막으로 그 방어의 테스트가 구현에 아부하지 않는가 — 구현이 스스로 보고하는 라벨(source:'replica')이 아니라 분기된 데이터로 판별하고, no-op/naive 변이를 실제로 돌려 그 테스트가 '실패함'을 확인했으며, '언젠가 완료/거부된다'류 활성성 검증엔 timeout 이 있어 감지기가 무력화된 변이에서 스위트가 행(hang)이 아니라 실패로 끝나는가?"
|
|
4914
4921
|
],
|
|
4915
4922
|
questionsEn: [
|
|
4916
4923
|
"Am I reading a value with SELECT, computing it in app code, then writing it back with UPDATE? Two requests reading the same row concurrently silently drop one write (lost update). Did I switch to an atomic conditional UPDATE (SET n = n + ?) or a version CAS (... WHERE version = ?, treating 0 rows affected as a conflict to retry, not as success)?",
|
|
@@ -4920,7 +4927,9 @@ const LENS_CATALOG = {
|
|
|
4920
4927
|
"Is this invariant (balance>=0, uniqueness, NOT NULL, referential integrity) enforced by a DB constraint (CHECK/UNIQUE/FK/NOT NULL), or does it live only in app-code validation? App validation is silently bypassed by concurrent writers, other services, backfill scripts, and direct SQL. Did I confirm, with a test that attempts a violation and asserts rejection: (a) the constraint is actually live on this engine/connection (e.g. SQLite needs PRAGMA foreign_keys=ON per connection; a Postgres FK added NOT VALID IS enforced for all new INSERT/UPDATE and only skips validating pre-existing rows), and (b) I'm not tripped by UNIQUE's NULL semantics — standard UNIQUE treats NULLs as distinct, so a 'unique' nullable column silently allows unlimited NULL rows? If NULL is invalid, use NOT NULL; if only one NULL is allowed, use Postgres 15 NULLS NOT DISTINCT or a generated/expression unique (a partial index WHERE col IS NOT NULL does NOT constrain NULLs — it leaves them unlimited; that's the tool for unique-when-present).",
|
|
4921
4928
|
"Did I consciously choose this transaction's isolation level and add a bounded retry loop for deadlock / serialization failure (re-read fresh state inside the retry, use backoff/jitter, and make sure the retried transaction is free of external side effects so re-running it is safe)? Inheriting an unexamined default can leak non-repeatable reads, phantoms, or write skew (defaults differ by engine — Postgres tends to READ COMMITTED, MySQL InnoDB tends to REPEATABLE READ). Did I use the RIGHT retry error codes (Postgres deadlock 40P01 / serialization 40001; MySQL deadlock 40001/1213 / lock-wait timeout 1205 — 1205 by default rolls back only the statement, so retry the WHOLE transaction from BEGIN or set innodb_rollback_on_timeout; SQLite: handle SQLITE_BUSY/BUSY_SNAPSHOT with busy_timeout, BEGIN IMMEDIATE for write txns, WAL where apt)? And if a COMMIT is left in-doubt by a timeout/dropped connection, do I NOT assume rollback but reconcile actual persistence via idempotency key / transaction id / source-of-truth read before retrying? Snapshot isolation / Postgres REPEATABLE READ does NOT stop write skew (needs SERIALIZABLE or explicit locking), so did I guard sum/overlap invariants that way — and do all paths that lock multiple rows/tables acquire them in the same order?",
|
|
4922
4929
|
"Did the write I returned 'success' for actually reach a checked COMMIT (did I await the commit and inspect its result), and does that ack mean stable storage — WAL/redo/journal flushed on commit — not just a memory buffer? With autocommit off and a forgotten COMMIT, the open transaction is silently rolled back when the connection returns to the pool. Durability settings differ by engine — did I verify the one matching this data's class (e.g. Postgres synchronous_commit, MySQL innodb_flush_log_at_trx_commit=1 + sync_binlog=1, SQLite synchronous=FULL; Redis is not durable by default)? For must-survive-node-loss data, did I require a quorum / synchronous-replication ack (e.g. w:majority, synchronous standby) so one node dying can't lose it, and state the failover RPO?",
|
|
4923
|
-
"Am I relying on an in-process mutex/singleton/in-memory dedup, or assuming one DB transaction can wrap work spanning multiple services, or reading a just-written value from a lagging read replica? With >=2 instances an in-process lock is useless, so move mutual exclusion to a shared point (DB UNIQUE constraint, row lock, advisory lock; pull queue work with FOR UPDATE SKIP LOCKED); atomicity can't cross a process/network boundary, so route cross-service work through an outbox + saga + compensating actions and collapse at-least-once delivery with an idempotency key (UNIQUE); and replication lag breaks read-your-writes, so route read-after-write / consistency-critical reads to the primary or gate on replica LSN/lag. If I use a distributed lock, did I issue a monotonically increasing fencing token (DB sequence / row version) and have the guarded resource reject lower tokens, so a worker that resumes after its lease expired can't overwrite newer work?"
|
|
4930
|
+
"Am I relying on an in-process mutex/singleton/in-memory dedup, or assuming one DB transaction can wrap work spanning multiple services, or reading a just-written value from a lagging read replica? With >=2 instances an in-process lock is useless, so move mutual exclusion to a shared point (DB UNIQUE constraint, row lock, advisory lock; pull queue work with FOR UPDATE SKIP LOCKED); atomicity can't cross a process/network boundary, so route cross-service work through an outbox + saga + compensating actions and collapse at-least-once delivery with an idempotency key (UNIQUE); and replication lag breaks read-your-writes, so route read-after-write / consistency-critical reads to the primary or gate on replica LSN/lag. If I use a distributed lock, did I issue a monotonically increasing fencing token (DB sequence / row version) and have the guarded resource reject lower tokens, so a worker that resumes after its lease expired can't overwrite newer work?",
|
|
4931
|
+
"Can a user callback wrapped by a transaction/lock/lease/single-flight return a promise? An async callback is synchronous only until its first suspension — if the returned promise isn't awaited, its continuations run after COMMIT/release, outside the protection (an outbox relay marking sent=1 before an async publish rejects; a fenced write landing after release). Did I await by design, or explicitly reject thenables? Does every 'track this operation' structure (single-flight map, lock table, in-progress registry) finish REGISTERING before user code can run or throw synchronously — a callback that throws pre-registration leaves the failure path staring at half-initialized state (the real error swallowed, a dead entry poisoning the key forever)? Is abort/release a real barrier that removes EVERY trace of the operation (held locks, queue entries, invalidation flags, generation counters) — a leftover queue entry hands the lock to a dead transaction, and a suspended acquisition loop without a post-await generation re-check keeps acquiring after the abort. And does cleanup use an identity guard (delete only if the map entry is still MINE) so it never removes a successor's entry?",
|
|
4932
|
+
"Does EVERY writer participate in the invariant that a version column / monotonic token / watermark protects — one bypassing path (an INSERT OR REPLACE resetting version to its default, a plain assignment rolling a token backward, a migration/seed/admin write) lets a stale snapshot's CAS pass (ABA). Does the token/key carry identity — a bare numeric LSN with no cluster/store branding compares 'equal' against an unrelated store's replica and exposes foreign data, and object keys compare by identity so two {id:1} objects are different keys. Are quantity/amount parameters validated at the boundary — a negative qty PASSES a stock >= qty guard while inverting the transfer's direction (reverse theft). Finally, do the tests for these defenses avoid flattering the implementation — assert on divergent data rather than a self-reported label (source:'replica'), actually run the no-op/naive mutant and watch the test FAIL it, and put timeouts on 'eventually completes/rejects' liveness checks so a disabled detector fails the suite instead of hanging it?"
|
|
4924
4933
|
],
|
|
4925
4934
|
affects: ['code', 'test'], affectsNote: '동시성 방어엔 인터리빙/부하/재시도/멀티인스턴스 실패를 재현하는 테스트가 따라와야 함(단일 인스턴스 초록불은 증거가 아님)',
|
|
4926
4935
|
affectsNoteEn: 'a concurrency defense needs an interleaving/load/retry/multi-instance reproduction test'
|
|
@@ -4936,7 +4945,7 @@ function _loadProjectLenses(root) {
|
|
|
4936
4945
|
return (j && typeof j === 'object' && j.domains && typeof j.domains === 'object') ? j.domains : {};
|
|
4937
4946
|
} catch { return {}; }
|
|
4938
4947
|
}
|
|
4939
|
-
// 내장 + 프로젝트 커스텀 병합. 기존 도메인 → 질문 추가(dedup, 최대 8); 신규 도메인 → 기본값으로 추가. _custom 플래그로 표시.
|
|
4948
|
+
// 내장 + 프로젝트 커스텀 병합. 기존 도메인 → 질문 추가(dedup, 최대 12 — 내장 최대치(database 10) + 커스텀 여유; 8이면 내장이 잘림); 신규 도메인 → 기본값으로 추가. _custom 플래그로 표시.
|
|
4940
4949
|
function _mergeLensCatalog(builtin, custom) {
|
|
4941
4950
|
const out = {};
|
|
4942
4951
|
for (const [k, v] of Object.entries(builtin || {})) out[k] = Object.assign({}, v, { questions: (v.questions || []).slice(), affects: (v.affects || []).slice() });
|
|
@@ -4946,10 +4955,10 @@ function _mergeLensCatalog(builtin, custom) {
|
|
|
4946
4955
|
if (out[k]) {
|
|
4947
4956
|
const seen = new Set(out[k].questions);
|
|
4948
4957
|
for (const q of cq) if (!seen.has(q)) { out[k].questions.push(q); seen.add(q); }
|
|
4949
|
-
out[k].questions = out[k].questions.slice(0,
|
|
4958
|
+
out[k].questions = out[k].questions.slice(0, 12);
|
|
4950
4959
|
out[k]._customAdded = cq.length > 0;
|
|
4951
4960
|
} else if (cq.length) {
|
|
4952
|
-
out[k] = { title: (c.title || k), persona: (c.persona || '검토자'), questions: cq.slice(0,
|
|
4961
|
+
out[k] = { title: (c.title || k), persona: (c.persona || '검토자'), questions: cq.slice(0, 12), affects: Array.isArray(c.affects) ? c.affects : [], affectsNote: c.affectsNote || '(프로젝트 정의)', _custom: true };
|
|
4953
4962
|
}
|
|
4954
4963
|
}
|
|
4955
4964
|
return out;
|