에이전트가 만든 검토 문서나 작은 자료를 첨부하려면 매번 사람이 파일 선택창을 열어야 할까. 반대로 편의를 위해 아무 로컬 경로나 읽게 하면 권한이 너무 커진다. 북극성에는 현재 문맥과 파일 이름, 명시한 바이트만 받는 생성 첨부 경로를 따로 만들었다.

create와 choose의 입력을 섞지 않는다

외부 요청 스키마는 files의 list·choose·create를 나눈다. create에는 이름과 dataBase64가 필요하고 다른 동작에는 그 필드를 넣지 못한다. context도 profileId·workspaceId·tabId를 명시해야 한다. strictObject이므로 path 같은 임의 필드를 추가해 로컬 파일 읽기 요청으로 바꾸는 계약은 없다.

실제 handler는 현재 문맥을 확인하고 유휴 조건을 검사한 뒤 생성기를 부른다. 생성 중에도 문맥과 작업 상태를 다시 확인하는 콜백을 전달한다. 형식에 맞는 JSON을 보냈다는 사실과 실행 가능한 현재 문맥이라는 사실을 함께 보되 서로 대체하지 않았다. 스키마 통과만으로 모든 실행 권한을 얻는 구조는 아니다.

확장자와 인코딩을 생성 전에 검사한다

이름은 basename이어야 하고 길이, 금지 문자, 숨김 형태, 끝의 점·공백, Windows 예약명을 검사한다. 허용한 문서·이미지·자료 확장자만 받으며 실행 파일 이름을 허용하지 않는다. 이름 검사는 확장자의 허용 여부를 다루는 것이지 내용의 악성 여부나 실제 파일 형식을 판별하는 백신 검사는 아니다.

base64는 단순히 Buffer로 변환되는지만 보지 않는다. 허용 문자와 길이·패딩을 검사하고 디코딩한 뒤 다시 인코딩해 입력과 같아야 한다. 디코딩 결과는 비어 있지 않고 1MiB 이하여야 한다. 깨진 표현을 대충 받아 다른 바이트로 저장하지 않으려는 선택이다. 대용량 업로드 전송로를 이 기능 하나로 제공한다고 설명하지 않는다.

프로젝트 범위 아래 새 폴더에만 만든다

생성기는 기준 폴더와 staging·scope의 심볼릭 링크 여부를 검사한다. 프로필과 프로젝트를 해시한 범위 아래 새 UUID 폴더를 만들고 파일은 wx 플래그로 새로 쓴다. 같은 이름의 문서를 다시 만들더라도 기존 폴더의 파일을 찾아 덮는 방식이 아니다.

기준 폴더의 realpath도 비교한다. 다만 몇 번의 경로 검사로 공격적인 외부 변경이 가능한 모든 파일 시스템 경쟁을 완전히 막았다고 보장하지 않는다. 운영에서는 신뢰할 수 있는 저장 루트와 권한도 필요하다. 경계를 검사한 코드의 존재와 임의 환경에서의 완전한 격리는 서로 다른 주장이다.

파일 쓰기와 핸들 등록이 함께 끝나야 한다

바이트를 쓴 뒤에는 현재 파일 목록에 등록해 ID를 받는다. bootstrap은 새로 등록한 ids를 기억하고 등록 후 check가 실패하면 그 ID만 forget하도록 rollback을 전달한다. 파일은 생겼지만 다음 작업이 쓸 수 없는 핸들이 남는 불일치를 줄이기 위한 연결이다.

오류가 나면 생성기의 정리는 이번 UUID 폴더의 한 파일과 빈 폴더만 대상으로 삼는다. 재귀 삭제로 프로젝트 전체를 지우지 않는다. rollback이나 정리까지 실패하면 원래 오류와 함께 AggregateError에 남긴다. 앞선 임시파일 정리 글과 닮은 원칙이지만 여기서는 외부 생성 요청과 첨부 핸들 등록 사이의 경계를 다루는 것이다.

생성 영수증은 첨부 전송 완료가 아니다

외부 응답에는 새 자료의 ID·이름·크기·SHA256이 있고 selectionOnly가 붙는다. 이것은 생성·선택 단계의 결과다. 서비스에 업로드하거나 게시글에 반영한 결과까지 그 자리에서 만들어 주지 않는다. 파일을 생성하는 권한과 서버에 보내는 동작을 분리해 두어야 후속 검증도 읽을 수 있다.

원본 시험에서는 공급 바이트 그대로의 저장, 프로젝트 분리와 비덮어쓰기, 잘못된 이름·인코딩·링크 루트·문맥 없는 요청의 거절을 다룬다. 이번에는 그 시험을 읽었고 원래 생성 API를 호출하지 않았다. 공유 합성 모듈은 더 작은 메모리 바이트·핸들 비교이며 실제 파일 생성기는 아니다. 어느 단계까지 직접 실행했는지도 함께 공개한다.