Skip to content

auto-improve: Hold the bodies the screens send against the fields the server reads - #1

Merged
hkjang merged 23 commits into
mainfrom
auto/2026-09-12-0631
Sep 11, 2026
Merged

auto-improve: Hold the bodies the screens send against the fields the server reads#1
hkjang merged 23 commits into
mainfrom
auto/2026-09-12-0631

Conversation

@hkjang

@hkjang hkjang commented Sep 11, 2026

Copy link
Copy Markdown
Owner

자율 개선 에이전트가 생성한 PR입니다. (run 2026-09-12-063115-SecCheck-improve, base c32eec7)

2026-09-12

  • 선택: 수정 과제 — 러너 검증이 다음에 멈출 자리(cd web && npm test 가 테스트 파일 0개로 exit 1)를 미리 고치기: 프런트엔드 post/put/patch 호출의 리터럴 본문 키를 payloads.go 와 대조하는 vitest 추가 + precheck.sh·ci.yml 프런트엔드 단계에 그 실행을 포함 (가치 3 / 위험 1 / 작업량 M)
  • 결과: 성공
  • 요약: 이전 열두 회차의 가이드 커밋(14412d65963517)이 아직 main 에 없어 auto/2026-09-12-0611 위로 fast-forward 한 뒤 작업했다. 수정 과제 확인: GitHub 의 release.yml 실행은 API 로 조회한 최근 5건(v1.0.140v1.0.144)이 모두 success 라 릴리즈 워크플로 자체는 실패한 적이 없고, 12회 연속 실패는 러너의 [ -d node_modules ] || npm ci 가 lock 없는 루트에서 돈 것(러너 쪽에서 수정 완료)이었다. 그런데 고쳐진 러너(bin/run.sh)는 이제 web/ 에서 npm cinpm test --silentnpm run build --silent 를 돌리는데, web/package.json 의 testvitest run 이고 테스트 파일이 하나도 없어 "No test files found, exiting with code 1" 로 exit 1 이 된다 — 로컬에서 재현해 확인. 다음 회차도 같은 자리에서 실패할 것이므로 보류 목록의 vitest 아이디어를 앞당겨 web/src/lib/payloads.test.ts 를 만들었다: web/src 의 post/put/patch(path, { … })api(path, { method, body: JSON.stringify({ … }) }) 중 본문을 호출 자리에 리터럴로 적은 28곳을 괄호·문자열 균형 파서로 읽어 (1) 메서드+경로(${…}{id} 정규화)가 payloads.go 의 요청 본문 표에 있는지, (2) 적은 키가 모두 서버가 읽는 필드인지(008c9ec 의 comment/reason 어긋남 류) 확인하고, 스캐너가 망가져 0건이 되는 것을 막는 하한(15건)을 둔다. 스프레드(...form)·변수 본문은 검사 대상에서 제외한다. 검증: 정상 트리 3/3 통과, 고의로 어긋낸 세 트리(comments 의 {body}{text} / totp/enable 경로를 totp/verify 로 / payloads.go 의 to_user_id 를 target_user_id 로)에서 각각 실패 확인, 러너와 동일한 세 명령(cd web && [ -d node_modules ] || npm ci, npm test --silent, npm run build --silent)을 bash -o pipefail 로 실행해 모두 exit 0, precheck.sh 전체 통과(gofmt·go vet·go test·프런트엔드 tsc+vitest+vite build·가이드 그림·가이드 PDF 신선도·gitleaks "no leaks found"). precheck.sh 프런트엔드 단계와 ci.yml 의 "Frontend build and dependency gate" 에 npm --prefix web test --silent 를 넣어 CI 가 이 대조를 돌리게 했다(워크플로를 느슨하게 한 것이 아니라 한 단계 더한 것). 가이드는 표준 구성·캡처 41장·PDF 2종이 이미 갖춰져 precheck 의 그림·PDF 검사를 통과하므로 이번 회차는 원고를 바꾸지 않았다. 참고: DecisionModal 이 action('reopen', data) 로 경로와 본문을 모두 변수로 넘기는 바로 그 자리(008c9ec 의 현장)는 정적으로 읽을 수 없어 아직 검사 밖이다.
  • 보류 아이디어: DecisionModal 의 종류별 본문을 정적 표({ reopen: ['reason'], withdraw: ['reason'], … })로 빼고 vitest 가 그 표까지 읽게 해 008c9ec 의 현장을 검사 범위에 넣기 (가치 2 / 위험 1 / S); vitest 를 get/del/upload 경로까지 넓혀 화면이 부르는 모든 메서드+경로가 server.go 의 s.handle 등록에 있는지 대조 — 라우트 이름이 바뀌면 화면이 404 를 받는 것을 잡는다 (가치 3 / 위험 1 / M); USER_GUIDE 3-8 의 알림 수신 설정 창을 캡처해 3-7 표 옆에 싣기 (가치 2 / 위험 1 / S, 서버 필요); Notifications.tsx 의 adminDestination 에 API_KEY_REVOKED·ACCOUNT_LOCKED·USER 대상 인계 알림의 이동 버튼 추가 — 캠페인 밖, docs_test 표 갱신 동반 (가치 2 / 위험 2 / S)

🤖 auto-improve 2026-09-12 · https://hkjang.github.io/aidev/projects/SecCheck/

The user and admin guides described screens nobody had pictured since the
first documentation commit, and the capture script that could have fixed
that carried a password literal and a hard-coded target. The guides are
rewritten to the shared guide standard around thirty-four captures taken
from a seeded local install: first five minutes, screen by screen, the
common jobs, and the messages people actually hit. Environment variables
and settings are tabulated from the code rather than from memory.

The capture script now refuses to run without its own URL and credential
variables, stops unless the target is loopback, and puts back the two
settings it changes for the run. The old guides point at the new ones so
there is one canonical copy; README and the docs hub follow.
The 결재 요청 회수 and 보완 재개 dialogs posted their text as "comment",
the field the approval and rejection dialogs use, while both handlers
decode a "reason" and refuse unknown fields. Every press ended in
INVALID_JSON, so neither button ever worked from the screen; only the API
did. The dialog now sends the field each action reads, and stops calling
itself 심의 반려 with a 반려 button when it is withdrawing or reopening.
The approval step was the one part of the user guide told in words alone:
the capture run never turned approval on, so there was no 승인 대기 review,
no 결재 전 확인 card, no signature dialog and no rejection to photograph.
The capture script now turns approval_enabled on for the run alongside
allow_self_review (both read first and put back), names the capture
account as approver so submissions pass the gate, and seeds a review
parked for signature and one the approver sent back. The approved review
now travels through the signature too, so the list shows every state.

Section 4-4 walks the approver through the four new screens, and the
messages a reviewer meets when withdrawing or reopening are in the table.
The rest of the captures were retaken in the same run; both PDFs are
regenerated with the shared tool.
features.md was re-pointed at the new captures two rounds ago, but the
PDF next to it (and the complete manual that embeds it) kept showing the
screens that were deleted in the same change. Nothing tied the two
together: the PDFs came from scripts/generate_pdf.js, a converter this
repository carried on its own, while the user and admin guides were
already built with the shared md2pdf tool.

scripts/build_docs_pdf.sh now drives md2pdf for all six PDFs, by name or
all at once, and the complete manual is the three technical documents
concatenated inside docs/ so their image paths still resolve. The
repository-only converter is gone along with the marked devDependency
that existed only for it, and the docs/pdf/ copies it kept -- the same
files twice, referenced by nothing. The complete manual's description in
both READMEs now says what it actually contains.
Two rounds of guide work renamed every capture. The guides followed, but
nothing said whether they all had: a reference to a picture that is not
on disk only surfaces when the PDF is built, and a capture no document
uses just sits there after a rename. precheck.sh now compares the file
names the docs reference with what docs/screenshots holds, in both
directions, and fails on either.
The PDFs are committed next to the Markdown they come from, and nothing
rebuilds them on its own: features.md was re-pointed at the new captures
while its PDF kept showing the old ones until someone opened it. md2pdf
lives outside this repository, so precheck.sh cannot rebuild anything --
instead it asks git whether a document, or a picture it embeds, changed
after its PDF was last baked, in the working tree or in history, and says
which build_docs_pdf.sh name to run.

The document-to-PDF mapping now lives in one describe() function in
build_docs_pdf.sh, which precheck reads through a --list mode that needs
no converter, so the two scripts cannot disagree about what feeds what.
The only release asset is the image archive, and the admin guide's
install section told the operator to "use the repository's compose.yaml"
-- which a closed network has no way to reach. The section now carries
the file in full, with a note on the one line worth changing, and the
upgrade step no longer names a next version that a blanket bump would
rewrite into a no-op sed.

A copy of a file drifts, so a test holds the guide's yaml block to the
real compose.yaml byte for byte, and the guide joins the files whose
release version must agree with VERSION. The PDF is re-baked.
Every capture in the guides comes out of a 1440x900 headless window, and
a picture pasted in from somewhere else is the one that looks wrong on
the page. precheck now reads the width from each PNG's IHDR chunk with
od -- nothing to install -- and names any file that is not 1440 wide.
Height is left alone, since a full-page capture is legitimately taller.
The admin guide's environment-variable table claims to be everything the
runtime reads, and its service-settings tables give the value a fresh
installation starts with. Both were written by hand and neither was checked:
the deleted-evidence retention was filed under the general tab while the
screen keeps it under file security, service_name was presented as a value
an operator can change although the screen shows it fixed, and the OIDC tab
had no key table at all -- an operator scripting PUT /admin/settings/oidc
had to read the source to learn the names.

Two tests now hold the guide to the code. The environment-variable table
must list exactly the names that os.Getenv reads in internal/ and cmd/
(the test-only database helper aside). Each settings table must list exactly
the keys the migration seeds and the settings screen put on that tab, with
the default the seeds wrote first -- or, for keys the seeds never wrote, the
fallback the screen shows. The OIDC tab gets its table, the misfiled row
moves, and the PDF is re-baked.
The user guide's 막혔을 때 table and the admin guide's 장애 대응 table exist
so that someone can search the guide for the text in front of them -- an
error on the screen, a line in the log. Both tables were written by hand
and nothing checked them against the code. The admin guide quoted
"audit event could not be recorded" as a 서버 로그 row under component
audit, but that string only ever reaches the container's standard output;
the row the screen actually gets is "감사 이벤트를 기록하지 못했습니다."
The list of log components in 5-3 named seven where the code writes
twelve, so an operator filtering by component could not know about
evidence, export, review, admin or bootstrap.

Two tests now hold the tables to the code. Every phrase the user guide
quotes in backticks must appear letter for letter in a server literal or
in the web sources, with `N` before a counter and `<…>` standing for what
the message fills in at runtime. Every "로그에 `…`" in the admin guide must
be a string the Go code prints, and every "서버 로그 `component` 의 `…`"
must match a Store.Log call with that exact component and message; the
component list in 5-3 must equal the set the code uses. The two rows are
corrected, the format-string example becomes a placeholder, and both PDFs
are re-baked.
docs/README.md and docs/operations.md tell people to run
scripts/precheck.sh and scripts/build_docs_pdf.sh as written, and the
usage comments at the top of each script say the same. All three shell
scripts carry a shebang but were committed as 100644, so following the
docs on a fresh clone ends in "Permission denied" and one learns to put
bash in front. The mode is now 100755 in the index, which holds on
checkouts where core.filemode is off as well.
…shows

The user guide names buttons, menus, filters, statuses and badges in
backticks so a reader can find them on the screen, and the standard asks
for those names to match the UI to the letter. Nothing held them to it:
a button renamed on the screen left the guide pointing at something that
was not there. A test now takes every backticked phrase in the walkthrough,
screen and task sections and the glossary and requires it to exist in the
web sources or, for text the server composes, in the Go sources, sharing
the placeholder-aware matcher with the 막혔을 때 table test.

Running it found three quotes the screen never shows: `+ 신규 심의 요청`
(the plus is an icon, the text is `신규 심의 요청`), `2 / 210` and
`SC-2026-000001` (runtime values written as if they were labels). The
guide now names the button as the screen does and gives the two values
as plain examples; the admin guide's `+ 로컬 사용자` is fixed the same way.
Both PDFs are re-baked with the shared converter.
The admin guide's 5-1 table is what an operator wires a load balancer and
a monitor to, and its 5-4 table is what they read when a job is stuck.
Neither was held to the code. A path listed with the wrong method sends
whoever follows it into a 405; a job type the code stopped enqueuing, or
started enqueuing without a row, leaves the operator nothing to act on.

A test now checks each 5-1 row against the s.handle registration in
server.go -- method, path, and whether it is public or needs the named
role -- and compares the 5-4 job types both ways with the INSERT INTO jobs
statements in the Go code. Both tables already matched.
… user guide

The guide described Ctrl+K search, the participants card and what a
re-review copy leaves behind in words only. All three are now shot from a
running install and placed in 3-1, 3-4 and 4-3, and the search paragraph
says what the palette lists before and after typing.

capture_all.js takes SECCHECK_CAPTURE_ONLY so one screen can be added
without re-shooting the forty already in the guides; the run still seeds
and walks every screen, it just does not write the others. The copy scene
waits on the toast itself, since it lives four seconds and a wait on a
hidden <option> had let it lapse.
Every sidebar label must appear in a section heading or a picture caption
of the user or admin guide, so a screen cannot be added without one of
the guides naming it. The simulator's caption carried the page title
(Rule Engine 시뮬레이터) rather than the menu name; the caption now uses
the menu name and the text gives both.
The user guide quoted the button as 심의 요청자 인계, which is the title
of the dialog it opens; the button itself reads 요청자 인계. The guide
now names the button and says what the dialog is called.
…le on the bell

The 장애 대응 table quoted 작업 재시도 소진 and 감사로그 무결성 실패, which
are labels from the notification preference screen; the bell shows
작업이 재시도를 모두 소진했습니다 and 감사로그 체인 검증 실패, so the
administrator who searched the guide for what they had just read found
nothing. The stalled-queue and unwritable-volume alerts had no row at
all. Every alert the code sends to administrators now has a row keyed
by its title, with where the notification's own button leads and what
to do, and a test holds the guide to the send sites both ways.
The alert told the reader to search the server log for the notify
component; the mail worker has only ever logged as notification.
The quotation check only asks that each name in 3-2 exist somewhere in
the screens, so a chip added to the list left the guide short without
a test noticing. The chips and the sort table of Reviews.tsx are now
held to the two lines both ways.
web/package.json has run vitest since the start, but no test file was
ever written, and `vitest run` with nothing to run exits 1. Nothing in
the pipeline called it, so nobody saw; the improvement runner now does,
and every round would have stopped at that step.

The one test reads each post/put/patch call whose body is written out
at the call site and checks its keys against internal/web/payloads.go
-- the mistake of 008c9ec, where the withdraw and reopen dialogs sent
`comment` to endpoints that read `reason`, in a form a test can catch.
The run is added to precheck.sh and to the CI frontend gate.
@hkjang

hkjang commented Sep 11, 2026

Copy link
Copy Markdown
Owner Author

🔒 보호 파일을 건드려 자동 머지하지 않습니다. 사람이 검토해 주세요.

  • .github/workflows/ci.yml

(run 2026-09-12-063115-SecCheck-improve)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T21:46:15.864712Z 9c32069 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@hkjang
hkjang merged commit a0dbd0f into main Sep 11, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant