diff --git a/backend/build.gradle b/backend/build.gradle index 70a77ad9..86fce060 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -65,7 +65,7 @@ dependencies { tasks.named('test') { useJUnitPlatform { - excludeTags 'permission-ha', 'permission-standby', 'permission-durability', 'benchmark', 'dashboard-cloud-load', 'minio-integration', 'claim-concurrency', 'local-e2e', 'storage-worker-e2e', 'real-pdf-version-e2e', 'vector-search-performance', 'vector-storage-performance', 'worker-indexing-throughput', 'worker-horizontal-scaling', 'worker-queue-backpressure', 'document-indexing-e2e-load', 'chunk-quality-performance', 'section-path-quality', 'opensql-ha-connection', 'opensql-ha-contract', 'opensql-outbox-isolated' + excludeTags 'permission-http-ha', 'permission-ha', 'permission-standby', 'permission-durability', 'benchmark', 'dashboard-cloud-load', 'minio-integration', 'claim-concurrency', 'local-e2e', 'storage-worker-e2e', 'real-pdf-version-e2e', 'vector-search-performance', 'vector-storage-performance', 'worker-indexing-throughput', 'worker-horizontal-scaling', 'worker-queue-backpressure', 'document-indexing-e2e-load', 'chunk-quality-performance', 'section-path-quality', 'opensql-ha-connection', 'opensql-ha-contract', 'opensql-outbox-isolated' } } @@ -661,3 +661,28 @@ tasks.register('permissionHaTest', Test) { } } } + + +// 실제 HTTP 인증·권한 응답을 격리 OpenSQL 승격 전후로 확인한다. +tasks.register('permissionHttpHaTest', Test) { + group = 'verification' + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath + useJUnitPlatform { includeTags 'permission-http-ha' } + maxParallelForks = 1 + outputs.upToDateWhen { false } + testLogging { events 'passed', 'failed'; showStandardStreams = true } + doFirst { + if (!(System.getenv('PERMISSION_HA_JDBC_URL') ?: '').matches('jdbc:postgresql://127\\.0\\.0\\.1:1844[23]/permission_test.*') || + !(System.getenv('PERMISSION_HA_MIGRATION_JDBC_URL') ?: '').matches('jdbc:postgresql://127\\.0\\.0\\.1:1843[123]/postgres')) { + throw new GradleException('전용 루프백 격리 HTTP 시험 DB 주소가 필요합니다.') + } + ['PERMISSION_HA_PASSWORD','PERMISSION_HTTP_JWT_SECRET','PERMISSION_HA_RUN_DIR'].each { name -> + if (!System.getenv(name)?.trim()) throw new GradleException('격리 HTTP 실행 설정이 필요합니다: ' + name) + } + def runDirectory = file(System.getenv('PERMISSION_HA_RUN_DIR')) + if (['http.jsonl','ready','fault.done'].any { new File(runDirectory, it).exists() }) { + throw new GradleException('새 HTTP 시험 회차 디렉터리가 필요합니다.') + } + } +} diff --git a/backend/src/test/java/com/opensource/docgrid/domain/permission/integration/PermissionHttpFailoverIntegrationTest.java b/backend/src/test/java/com/opensource/docgrid/domain/permission/integration/PermissionHttpFailoverIntegrationTest.java new file mode 100644 index 00000000..bb415c73 --- /dev/null +++ b/backend/src/test/java/com/opensource/docgrid/domain/permission/integration/PermissionHttpFailoverIntegrationTest.java @@ -0,0 +1,307 @@ +package com.opensource.docgrid.domain.permission.integration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.net.URI; +import java.net.http.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.security.MessageDigest; +import java.time.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import javax.sql.DataSource; + +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.*; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.opensource.docgrid.domain.auth.annotation.CurrentUser; +import com.opensource.docgrid.domain.auth.controller.AuthController; +import com.opensource.docgrid.domain.auth.jwt.*; +import com.opensource.docgrid.domain.auth.resolver.CurrentUserArgumentResolver; +import com.opensource.docgrid.domain.auth.service.command.AuthCommandService; +import com.opensource.docgrid.domain.auth.service.query.*; +import com.opensource.docgrid.domain.document.controller.DocumentQueryController; +import com.opensource.docgrid.domain.document.entity.*; +import com.opensource.docgrid.domain.document.enums.*; +import com.opensource.docgrid.domain.document.repository.*; +import com.opensource.docgrid.domain.document.service.DocumentFileService; +import com.opensource.docgrid.domain.document.storage.*; +import com.opensource.docgrid.domain.embedding.dto.EmbedResult; +import com.opensource.docgrid.domain.embedding.entity.*; +import com.opensource.docgrid.domain.embedding.enums.EmbeddingStatus; +import com.opensource.docgrid.domain.embedding.repository.*; +import com.opensource.docgrid.domain.embedding.service.query.QueryEmbeddingService; +import com.opensource.docgrid.domain.mcp.service.command.McpAccessTokenCommandService; +import com.opensource.docgrid.domain.permission.controller.PermissionController; +import com.opensource.docgrid.domain.permission.dto.request.GrantPermissionRequest; +import com.opensource.docgrid.domain.permission.enums.*; +import com.opensource.docgrid.domain.permission.service.command.DocumentPermissionCommandService; +import com.opensource.docgrid.domain.permission.service.query.PermissionTargetUserQueryService; +import com.opensource.docgrid.domain.rag.dto.*; +import com.opensource.docgrid.domain.rag.service.RagFacade; +import com.opensource.docgrid.domain.search.config.HybridSearchProperties; +import com.opensource.docgrid.domain.search.controller.SearchController; +import com.opensource.docgrid.domain.search.dto.VectorSearchCandidate; +import com.opensource.docgrid.domain.search.dto.response.CitationResponse; +import com.opensource.docgrid.domain.search.service.SearchFacade; +import com.opensource.docgrid.domain.search.service.command.*; +import com.opensource.docgrid.domain.search.service.query.*; +import com.opensource.docgrid.domain.user.entity.*; +import com.opensource.docgrid.domain.user.enums.UserStatus; +import com.opensource.docgrid.domain.user.repository.*; +import com.opensource.docgrid.global.config.*; +import com.opensource.docgrid.global.exception.GlobalExceptionHandler; +import com.opensource.docgrid.global.exception.RestAccessDeniedHandler; +import com.opensource.docgrid.global.exception.RestAuthenticationEntryPoint; + +/** + * 루프백 실제 HTTP·로그인·JWT·Valkey·원장을 격리 OpenSQL 승격 전후로 연결한다. + * 파일·검색 후보·답변만 합성이며 공개 서비스·실제 모델 품질·과거 대화는 검증하지 않는다. + */ +@Tag("integration") +@Tag("permission-http-ha") +@SpringBootTest(classes = PermissionHttpFailoverIntegrationTest.Config.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { + "search.hybrid.enabled=false", "server.address=127.0.0.1", "spring.main.banner-mode=off", "logging.level.root=OFF", + "spring.datasource.url=${PERMISSION_HA_JDBC_URL}", "spring.datasource.username=postgres", + "spring.datasource.password=${PERMISSION_HA_PASSWORD}", "spring.datasource.hikari.maximum-pool-size=5", + "spring.datasource.hikari.connection-timeout=5000", "spring.datasource.hikari.validation-timeout=2000", + "spring.flyway.url=${PERMISSION_HA_MIGRATION_JDBC_URL}", "spring.flyway.user=postgres", + "spring.flyway.password=${PERMISSION_HA_PASSWORD}", "spring.jpa.hibernate.ddl-auto=validate", + "spring.jpa.open-in-view=false", "permission.revocation.synchronous-enabled=true", + "spring.data.redis.host=127.0.0.1", "spring.data.redis.port=16389", + "jwt.secret=${PERMISSION_HTTP_JWT_SECRET}", "jwt.expiration=600", "management.server.port=0" +}) +@Timeout(600) +class PermissionHttpFailoverIntegrationTest { + private static final String REVOKED = "REVOKED_HTTP_SENTINEL_542"; + private static final String ALLOWED = "ALLOWED_HTTP_SENTINEL_542"; + @LocalServerPort int port; + @Autowired ObjectMapper json; + @Autowired PlatformTransactionManager manager; + @Autowired DataSource dataSource; + @Autowired PasswordEncoder encoder; + @Autowired UserRepository users; + @Autowired RoleRepository roles; + @Autowired UserRoleRepository userRoles; + @Autowired DocumentRepository documents; + @Autowired DocumentVersionRepository versions; + @Autowired DocumentChunkRepository chunks; + @Autowired FileObjectRepository files; + @Autowired EmbeddingRepository embeddings; + @Autowired EmbeddingModelRepository models; + @Autowired DocumentPermissionCommandService permissions; + @Autowired FixtureStorage storage; + @Autowired HybridSearchProperties searchProperties; + @MockitoBean QueryEmbeddingService queryEmbedding; + @MockitoBean VectorSearchQueryService candidates; + @MockitoBean HybridSearchQueryService hybrid; + @MockitoBean RagFacade rag; + @MockitoBean SearchAnswerQueryService savedAnswer; + @MockitoBean McpAccessTokenCommandService mcp; + @MockitoBean AuthQueryService me; + @MockitoBean PermissionTargetUserQueryService targetUsers; + private Path directory; + private final HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build(); + private final UUID requestKey = UUID.randomUUID(); + + /** 실제 인증·HTTP·권한·검색 저장만 기동하며 백그라운드 Worker는 실행하지 않는다. */ + @Configuration + @TestComponent + @EnableAutoConfiguration + @Import({PermissionDurabilityIntegrationTest.Config.class, SecurityConfig.class, CorsConfig.class, + WebMvcConfig.class, CurrentUserArgumentResolver.class, JwtProvider.class, RoleAuthorityService.class, + TokenBlacklistService.class, PrimaryRoleQueryService.class, RestAuthenticationEntryPoint.class, + RestAccessDeniedHandler.class, AuthCommandService.class, AuthController.class, + PermissionController.class, DocumentQueryController.class, DocumentFileService.class, + SearchController.class, SearchFacade.class, AccessibleDocumentQueryService.class, + SearchQueryCommandService.class, SearchConversationCommandService.class, + SearchConversationQueryService.class, SearchResultCommandService.class, GlobalExceptionHandler.class}) + static class Config { + @Bean FixtureStorage fixtureStorage() { return new FixtureStorage(); } + @Bean HybridSearchProperties hybridProperties() { + var value = new HybridSearchProperties(); value.setEnabled(false); return value; + } + } + + /** 합성 파일 바이트만 메모리에 보관하고 원본 읽기 횟수를 계측한다. 외부 저장소에는 접속하지 않는다. */ + static class FixtureStorage implements FileStorageService { + final Map bytes = new ConcurrentHashMap<>(); + final AtomicInteger reads = new AtomicInteger(); + @Override public byte[] read(StoredFile file) { + reads.incrementAndGet();return Objects.requireNonNull(bytes.get(file)).clone(); + } + @Override public StoredFile store(InputStream input, long size, String type, String key) { + throw new UnsupportedOperationException("Read-only synthetic fixture"); + } + @Override public void delete(StoredFile file) { throw new UnsupportedOperationException("Read-only fixture"); } + } + + @Test + @DisplayName("실제 HTTP 회수 확정 뒤 자동 승격해도 새 응답에 금지 문구가 없고 대조 문서는 정상이다") + void realHttpRevocationSurvivesPromotion() throws Exception { + directory = Path.of(System.getenv("PERMISSION_HA_RUN_DIR")); + Files.createDirectories(directory); + event("start", Map.of("purpose", "인증 HTTP 회수·승격·응답 차단", "location", "루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터", "criteria", "금지 내용 0·대조 정상·원장 보존", "post_recovery_repetitions", 3)); + String password = UUID.randomUUID().toString(); + Fixture fixture = new TransactionTemplate(manager).execute(status -> seed(password)); + event("search-strategy",Map.of("hybrid_enabled",searchProperties.isEnabled(),"controlled_vector_candidates",2)); + require(!searchProperties.isEnabled(),"fixture_hybrid_strategy_mismatch"); + configureControlledSearch(fixture); + // 1. 실제 로그인 응답의 토큰은 메모리에서만 보관하고 원장에는 상태·해시도 남기지 않는다. + String ownerToken = login(fixture.ownerEmail(), password, "owner"); + String targetToken = login(fixture.targetEmail(), password, "target"); + var anonymous = request("anonymous", "detail", "/api/documents/" + fixture.revoked().document(), null, null); + require(anonymous.statusCode()==401, "anonymous_must_be_401"); + checkDocuments("before", fixture, targetToken, false); + checkSearch("before", targetToken, true, fixture); + // 2. 정상 204 뒤 의도적으로 stale 캐시를 복원해 최종 권한 원장 검증을 자극한다. + var revoked = request("revoke", "permission", "/permissions/documents/"+fixture.revoked().document()+"/"+fixture.permission(), ownerToken, "DELETE"); + require(revoked.statusCode()==204,"revocation_not_confirmed"); + new TransactionTemplate(manager).execute(status -> { + new JdbcTemplate(dataSource).update("UPDATE user_document_access_cache SET invalidated_at=NULL WHERE source_type='DIRECT_DOCUMENT_PERMISSION' AND source_id=?",fixture.permission());return null; + }); + checkDocuments("after-confirmation", fixture, targetToken, true); + checkSearch("after-confirmation", targetToken, false, fixture); + event("revocation-confirmed",Map.of("request_key",requestKey.toString(),"stale_cache_injected",true)); + Files.writeString(directory.resolve("ready"),"confirmed_revocation_ready_for_primary_loss\n",StandardOpenOption.CREATE_NEW); + // 3. 외부 제어기가 실제 자동 승격을 관측할 때까지 다음 성공을 추정하지 않는다. + long deadline=System.nanoTime()+Duration.ofSeconds(120).toNanos(); + while(!Files.exists(directory.resolve("fault.done"))&&System.nanoTime() { + var jdbc=new JdbcTemplate(dataSource); + require(Boolean.FALSE.equals(jdbc.queryForObject("SELECT pg_is_in_recovery()",Boolean.class)),"final_connection_not_primary"); + require(jdbc.queryForObject("SELECT count(*) FROM permission_revocation_requests WHERE request_id=?",Long.class,requestKey)==1,"receipt_not_preserved"); + require(jdbc.queryForObject("SELECT count(*) FROM document_permissions WHERE id=?",Long.class,fixture.permission())==0,"permission_resurrected"); + return null; + }); + event("summary",Map.of("request_key",requestKey.toString(),"receipt_rows",1,"remaining_permissions",0,"forbidden_content",0,"post_recovery_repetitions",3,"storage_reads",storage.reads.get(),"passed",true)); + } + + private Fixture seed(String password) { + String token=UUID.randomUUID().toString(); + User owner=users.save(User.builder().email("owner-"+token+"@example.invalid").passwordHash(encoder.encode(password)).name("합성 소유자").status(UserStatus.ACTIVE).build()); + User target=users.save(User.builder().email("target-"+token+"@example.invalid").passwordHash(encoder.encode(password)).name("합성 대상자").status(UserStatus.ACTIVE).build()); + com.opensource.docgrid.domain.user.entity.Role role=roles.findByCode("USER").orElseGet(()->roles.save(com.opensource.docgrid.domain.user.entity.Role.builder().code("USER").name("합성 사용자 역할").build())); + for(User user:List.of(owner,target))userRoles.save(UserRole.builder().user(user).role(role).assignedBy(owner).assignedAt(LocalDateTime.now()).build()); + EmbeddingModel model=models.save(EmbeddingModel.builder().provider(com.opensource.docgrid.domain.embedding.enums.EmbeddingProvider.MOCK).modelName("permission-http-"+token).modelVersion("1").dimension(1024).distanceMetric(com.opensource.docgrid.domain.embedding.enums.DistanceMetric.COSINE).isActive(false).isSearchable(false).vectorStorageStrategy(com.opensource.docgrid.domain.embedding.enums.VectorStorageStrategy.SINGLE_DIMENSION).build()); + var revoked=seedDocument(owner,REVOKED,model);var allowed=seedDocument(owner,ALLOWED,model); + var request=new GrantPermissionRequest(PermissionTargetType.USER,target.getId(),null,null,PermissionType.READ,null); + Long permission=permissions.grantPermission(revoked.document(),owner.getId(),request).permissionId(); + permissions.grantPermission(allowed.document(),owner.getId(),request); + return new Fixture(owner.getEmail(),target.getEmail(),revoked,allowed,permission,model); + } + + private DocumentFixture seedDocument(User owner,String sentinel,EmbeddingModel model) { + byte[] bytes=(sentinel+"\n"+UUID.randomUUID()+"\n").getBytes(StandardCharsets.UTF_8);String key=UUID.randomUUID()+".txt"; + var file=files.save(FileObject.builder().bucketName("synthetic").objectKey(key).originalFilename("synthetic.txt").contentType("text/plain").fileSize((long)bytes.length).fileHash(hash(bytes)).storageProvider(StorageProvider.LOCAL).uploadedBy(owner).uploadedAt(LocalDateTime.now()).build()); + storage.bytes.put(new StoredFile(StorageProvider.LOCAL,"synthetic",key),bytes); + var document=documents.save(Document.builder().owner(owner).title(sentinel).documentType(DocumentType.TXT).sourceType(DocumentSourceType.UPLOAD).status(DocumentStatus.INDEXED).visibility(VisibilityType.PRIVATE).build()); + var version=versions.save(DocumentVersion.builder().document(document).fileObject(file).versionNo(1).titleSnapshot(sentinel).originalFilename("synthetic.txt").contentType("text/plain").status(DocumentVersionStatus.INDEXED).createdBy(owner).build()); + document.updateCurrentVersion(version); + var chunk=chunks.save(DocumentChunk.builder().documentVersion(version).chunkIndex(0).chunkText(sentinel).tokenCount(1).charStart(0).charEnd(sentinel.length()).build()); + float[] vector=new float[1024];vector[0]=1; + var embedding=embeddings.save(Embedding.builder().chunk(chunk).document(document).documentVersion(version).embeddingModel(model).vector(vector).dimension(1024).status(EmbeddingStatus.ACTIVE).build()); + return new DocumentFixture(document.getId(),chunk.getId(),embedding.getId(),sentinel); + } + + private void configureControlledSearch(Fixture f) { + float[] vector=new float[1024];vector[0]=1; + when(queryEmbedding.embed(anyString())).thenReturn(new EmbedResult(f.model(),vector)); + // 후보 대역은 stale 문서도 반환한다. 실제 SearchFacade의 최종 Primary 인가가 제거해야 한다. + when(candidates.search(any(),any(),anyList(),anyInt())).thenReturn(List.of(candidate(f.revoked()),candidate(f.allowed()))); + when(rag.enqueue(anyLong(),anyLong(),anyString(),anyList())).thenAnswer(invocation -> { + List verified=invocation.getArgument(3); + String answer=verified.stream().map(VectorSearchCandidate::chunkText).reduce("",(a,b)->a+" "+b); + List citations=new ArrayList<>();for(int i=0;inew String(b,StandardCharsets.UTF_8).contains(ALLOWED)).findFirst().orElseThrow())),"allowed_file_hash_mismatch"); + } + } + + private void checkSearch(String phase,String token,boolean includeRevoked,Fixture f) throws Exception { + var response=request(phase,"new-search","/search",token,"POST"); + require(response.statusCode()==200,"search_unavailable"); + String body=new String(response.body(),StandardCharsets.UTF_8); + require(body.contains(ALLOWED)&&body.contains(REVOKED)==includeRevoked,"search_content_mismatch"); + var data=json.readTree(body).path("data"); + require(data.path("results").size()==(includeRevoked?2:1),"search_result_count_mismatch"); + require(data.path("citations").size()==(includeRevoked?2:1),"search_citation_count_mismatch"); + if(!includeRevoked)for(var result:data.path("results"))require(result.path("documentId").asLong()!=f.revoked().document(),"revoked_document_returned"); + } + + private HttpResponse request(String phase,String target,String path,String token,String method) throws Exception { + var builder=HttpRequest.newBuilder(uri(path)).timeout(Duration.ofSeconds(15)).header("Accept","application/json"); + if(token!=null)builder.header("Authorization","Bearer "+token); + if("DELETE".equals(method))builder.header("Idempotency-Key",requestKey.toString()).DELETE(); + else if("POST".equals(method))builder.header("Content-Type","application/json").POST(HttpRequest.BodyPublishers.ofString("{\"queryText\":\"합성 권한 질문\",\"topK\":5}")); + else builder.GET(); + long start=System.nanoTime();var response=client.send(builder.build(),HttpResponse.BodyHandlers.ofByteArray()); + boolean forbidden=new String(response.body(),StandardCharsets.UTF_8).contains(REVOKED); + event("http",Map.of("phase",phase,"target",target,"status",response.statusCode(),"latency_ms",(System.nanoTime()-start)/1_000_000.0,"revoked_sentinel_present",forbidden,"response_sha256",hash(response.body()))); + return response; + } + private URI uri(String path) {return URI.create("http://127.0.0.1:"+port+path);} + private void event(String kind,Map detail) throws Exception { + Map row=new LinkedHashMap<>();row.put("at_kst",OffsetDateTime.now(ZoneOffset.ofHours(9)).toString());row.put("event",kind);row.putAll(detail); + String line=json.writeValueAsString(row);Files.writeString(directory.resolve("http.jsonl"),line+"\n",StandardOpenOption.CREATE,StandardOpenOption.APPEND);System.out.println("EVIDENCE "+line); + } + private static String hash(byte[] bytes) { + try{return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes));} + catch(Exception e){throw new IllegalStateException("SHA-256 unavailable");} + } + private static void require(boolean valid,String category) {if(!valid)throw new AssertionError(category);} + /** 합성 문서 ID와 검출 문구만 보관한다. */ + private record DocumentFixture(Long document,Long chunk,Long embedding,String sentinel) {} + /** 합성 로그인 ID를 메모리에서만 다루고 원시 HTTP 로그에 출력하지 않는다. */ + private record Fixture(String ownerEmail,String targetEmail,DocumentFixture revoked,DocumentFixture allowed,Long permission,EmbeddingModel model) {} +} diff --git a/docs/design/permission-http-failover.md b/docs/design/permission-http-failover.md new file mode 100644 index 00000000..f2ed5679 --- /dev/null +++ b/docs/design/permission-http-failover.md @@ -0,0 +1,20 @@ +# 실제 HTTP 권한 회수·승격 경계 검증 설계 + +## 범위와 전제 + +이슈 #542. 공개 설정·계정·데이터를 변경하지 않는다. 기존 라이선스 호스트의 별도 컨테이너·데이터·포트·DCS에서 strict 지정형 동기를 사용하고, 기존 읽기 전용 역할 어댑터 경유를 명시한다. + +로컬 루프백 HTTP 서버의 실제 SecurityConfig·로그인·JWT·Valkey·Controller·권한 원장과 검색 접수·결과 저장을 사용한다. 파일은 메모리의 합성 바이트, 임베딩·검색 후보·답변은 제어 가능한 시험 대역이다. 공개 브라우저·실제 모델 품질·GCS·과거 대화 재조회는 범위 밖이다. + +## 순서와 완료 기준 + +1. 소유자와 대상 사용자, 회수 대상·대조 문서, 원본 바이트·Chunk·Embedding·READ 원장을 격리 DB에 생성한다. 비밀번호·JWT는 메모리에서만 사용한다. +2. 실제 /auth/login으로 인증한다. 인증 없는 상세 요청은 401, 두 문서의 상세·본문·파일은 200, 검색은 두 합성 문구·근거를 포함해야 한다. +3. 실제 DELETE 회수 요청을 키 K로 보내 204를 확인한다. 오래된 USER 캐시를 시험 목적으로 다시 유효하게 만들고 새 요청의 차단을 확인한다. +4. 외부 제어기가 시험 Primary만 종료하고 후보 지정 없이 자동 승격을 관측한다. 이전 Primary는 중지한 채 새 Primary 경로를 확인하며 재가입은 별도 자원 복구로 구분한다. 회수 확정 시각 뒤 새 HTTP 요청을 사용한다. +5. 대상 상세·본문·파일은 403, 새 검색은 200이면서 대상 문구·문서 ID·근거가 없고 대조 문구·근거는 있어야 한다. 대조 문서 상세·본문·파일은 200이다. 반복 3회 측정한다. +6. 현재 Primary에서 K 원장 1개·권한 행 0개를 대조한다. 503 전체를 통과로 처리하지 않으며 장애 직후 과도 구간 오류는 별도 기록한다. + +## 증거와 비밀 보호 + +각 HTTP 응답에서 시각·단계·논리 문서 별칭·상태·지연·금지 문구 검출 여부·응답 SHA-256만 즉시 보존한다. 이메일·비밀번호·토큰·Authorization·실제 계정·인프라 주소·원시 로그인 응답을 출력하거나 파일에 쓰지 않는다. 실행 디렉터리는 재사용하지 않는다. 실패·재시험·공개 설정 hash·최종 정리 기록을 분리한다. diff --git a/docs/test-results/permission-http-failover-20261017/README.md b/docs/test-results/permission-http-failover-20261017/README.md new file mode 100644 index 00000000..7dd077c8 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/README.md @@ -0,0 +1,58 @@ +# 실제 HTTP 회수 확정·승격 후 권한 차단 + +## 결과 + +**격리 OpenSQL의 자동 승격 뒤 실제 인증 HTTP 차단 시험이 통과했습니다.** [최종 원본 응답](http-primary-run04/http.jsonl), [장애 제어](http-primary-run04/control.jsonl), [애플리케이션 실행](http-primary-run04/app.log), [집계](aggregate.json). + +| 검증 | 실제 결과 | +| --- | --- | +| 실제 로그인 | 소유자·대상 사용자 각 200, 토큰은 메모리에서만 사용 | +| 인증 없는 상세 | 401 | +| 회수 전 | 두 문서 상세·본문·파일 각 200, 새 검색 결과·합성 근거 각 2개 | +| 실제 회수 | 동일 키 회수 204, 그 뒤 시험 목적으로 stale 캐시 복원 | +| 자동 승격 뒤 대상 | 상세·본문·파일 3회씩, **403 9개** | +| 자동 승격 뒤 대조 | 상세·본문·파일 3회씩, **200 9개**, 파일 해시 일치 | +| 자동 승격 뒤 새 검색 | **200 3개**, 매번 대조 결과·합성 근거 1개, 대상 문구·문서 ID 제외 | +| 금지 응답 검출 | 승격 뒤 21개 새 HTTP 응답에서 **0건** | +| 최종 원장 | 현재 Primary에서 요청 키 1개·회수 대상 권한 행 0개 | + +종료 확인→자동 승격 관측은 **19.452초**입니다. 로컬 JVM→IAP 경로이고 전체 서비스 RTO로 표현하지 않습니다. 이전 시험 Primary는 중지한 상태에서 새 Primary 경로를 확인했습니다. + +## 실제 경로와 대역 + +```text +실제 /auth/login·BCrypt → JWT → 실제 SecurityConfig·Valkey·CurrentUser + → 실제 HTTP Controller → JPA·권한 원장 → 격리 OpenProxy·역할 어댑터 → OpenSQL + → 상세·본문·파일 바이트 / 새 검색 결과·통제된 합성 답변·근거 +``` + +SecurityContext 주입이나 standalone MVC 요청이 아닙니다. 실제 루프백 Tomcat·Java HTTP client·로그인·JWT 필터를 실행했습니다. 문서 원장·버전·Chunk·Embedding·검색 접수·검색 결과 저장은 실제 DB입니다. + +외부 파일 저장소는 메모리의 고유 합성 바이트, 질의 임베딩·검색 후보·RAG 응답은 명시적 시험 대역입니다. 후보 대역은 회수된 문서까지 반환하며 실제 SearchFacade의 Primary 최종 인가가 제거합니다. 실제 모델 품질·비동기 RAG Worker·GCS·공개 브라우저·과거 대화 재조회는 포함하지 않습니다. 파일 차단 때 저장소 read가 호출되지 않는지도 단언했습니다. + +## 실패·재시험·수집 한계 + +| 회차 | 관측과 판정 | +| --- | --- | +| [run01](http-primary-run01/app.log) | 로그인·파일 조회 뒤 검색 준비 단언 실패. local 프로필의 hybrid=true가 벡터 대역을 우회. 명시적으로 false 고정. **장애 주입 없음** | +| [run02](http-primary-run02/app.log) | 재시험 fixture의 같은 파일 해시가 유일 제약 위반. 실행별 고유 바이트로 변경. **장애 주입 없음** | +| [run03](http-primary-run03/control.jsonl) | 회수 204와 자동 승격은 관측했으나 이전 Primary 재가입이 90초 제한 안에 끝나지 않아 실패. [새 Primary 키 감사](http-primary-run03/post-failure-db-audit.log)는 원장 1·권한 0을 확인했으며 해당 회차를 성공으로 바꾸지 않음 | +| run04 | 새 Primary의 HTTP 차단을 먼저 확인하고 이전 노드 재가입과 분리. 위 결과 통과 | + +run03의 뒤늦은 읽기 진단에서 native Patroni가 pg_rewind 진행 중이라고 보고했습니다. 당시 재가입 지연의 정확한 원인은 확정하지 않았고 해결했다고 주장하지 않습니다. 이후 [격리 데이터·DCS 재초기화](reset-before-http-run04/reset.jsonl) 후 별도 회차로 시험했습니다. run04는 이전 노드 자동 재가입을 검증한 회차가 아닙니다. + +run01 제어 로그에 복수 기록자가 추가 메타데이터를 쓰면서 한 줄이 손상됐습니다. [원본 바이트](http-primary-run01/control-capture.raw.log)와 [수집 한계](http-primary-run01/capture-notes.json)를 보존했으며 판정·시간 집계에 사용하지 않았습니다. 초기 app.log의 실행 위치 header도 기존 runner의 새 task 분기 누락이었으며 실제 경로는 이 문서와 제어 원장에 명시했습니다. 이후 회차는 단일 기록자를 사용합니다. + +첫 컴파일의 타입 이름 충돌과 잘못된 enum 이름을 수정했습니다. 실패를 운영 장애로 표현하지 않습니다. + +## 실행·안전 경계 + +- 수동 작업: `./backend/gradlew -p backend permissionHttpHaTest`. 기본 CI에서는 별도 태그로 제외합니다. +- 전용 loopback JDBC·migration URL·새 실행 디렉터리만 허용합니다. HTTP도 loopback에만 바인딩합니다. +- 기존 SSH 키를 재사용했고 새 OS Login 키·IAM 권한을 추가하지 않았습니다. +- 격리 컨테이너·데이터·포트·DCS만 변경했습니다. [준비 기록](bootstrap-run01.jsonl)과 제어 단계에서 공개 설정 hash 불변을 확인했습니다. 원본 컨테이너 이미지 캡처는 pause=false입니다. +- 원본 OpenProxy의 동기 역할 오류는 그대로이며 어댑터 경유 결과입니다. 공개 클러스터의 동기/quorum 설정이나 기능 활성화를 변경하지 않았습니다. +- 실행 코드가 커밋 전이었으므로 제어 원장에 소스·Gradle SHA-256을 보존했습니다. 최종 Java 소스 SHA-256은 `405fe0ae8bc9a09f7bf5e5f3c0f19924496ca6a85c927093bf32ed6a1c1d4b4b`입니다. +- 공용 시험 자원은 후속 이슈 #543의 같은 격리 실험에 이어 사용합니다. 최종 자원 정리는 후속 결과에 연결하며 현재 제거 완료로 표현하지 않습니다. + +로그에는 로그인 원시 응답·이메일·비밀번호·JWT·Authorization·비공개 인프라 식별자를 기록하지 않았습니다. 응답 상태·지연·합성 문구 검출·해시만 보존했습니다. diff --git a/docs/test-results/permission-http-failover-20261017/aggregate.json b/docs/test-results/permission-http-failover-20261017/aggregate.json new file mode 100644 index 00000000..f0f5a183 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/aggregate.json @@ -0,0 +1,15 @@ +{ + "run_id": "http-primary-run04", + "http_responses_excluding_login": 37, + "login_successes": 2, + "post_promotion_target_403": 9, + "post_promotion_allowed_200": 9, + "post_promotion_search_200": 3, + "post_promotion_forbidden_sentinel": 0, + "receipt_rows": 1, + "remaining_permissions": 0, + "promotion_after_confirmed_fault_seconds": 19.451936, + "repetitions": 3, + "native_old_primary_rejoin_in_success_run": false, + "file_storage_reads": 6 +} diff --git a/docs/test-results/permission-http-failover-20261017/bootstrap-run01.jsonl b/docs/test-results/permission-http-failover-20261017/bootstrap-run01.jsonl new file mode 100644 index 00000000..6b37e83f --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/bootstrap-run01.jsonl @@ -0,0 +1,8 @@ +{"at_kst": "2026-10-17T13:55:34.337467+09:00", "run_id": "bootstrap-run01", "event": "start", "purpose": "기존 라이선스 호스트에 별도 동기 시험 컨테이너 3대 준비", "location": "기존 호스트 내 별도 데이터·포트·DCS", "procedure": "pause=false 이미지 캡처, 패키지 read-only, 별도 데이터", "criteria": "공개 설정 불변, 격리 scope·포트·자원 제한 확인"} +{"at_kst": "2026-10-17T13:55:55.255992+09:00", "run_id": "bootstrap-run01", "event": "remote", "node_alias": "node1", "script": "permission-remote-bootstrap-next.py", "result": {"node_alias": "node1", "isolated_container_started": true, "namespace_isolated": true, "postgres_port": 15439, "rest_port": 18009, "production_configuration_changed": false, "memory_limit_mb": 768}} +{"at_kst": "2026-10-17T13:55:59.849843+09:00", "run_id": "bootstrap-run01", "event": "remote", "node_alias": "node1", "script": "permission-remote-log-overlay.py", "result": {"isolated_log_overlay_created": true, "public_package_read_only": true, "native_container_log_capture": false, "production_changed": false}} +{"at_kst": "2026-10-17T13:56:24.682044+09:00", "run_id": "bootstrap-run01", "event": "remote", "node_alias": "node2", "script": "permission-remote-bootstrap-next.py", "result": {"node_alias": "node2", "isolated_container_started": true, "namespace_isolated": true, "postgres_port": 15439, "rest_port": 18009, "production_configuration_changed": false, "memory_limit_mb": 768}} +{"at_kst": "2026-10-17T13:56:29.560862+09:00", "run_id": "bootstrap-run01", "event": "remote", "node_alias": "node2", "script": "permission-remote-log-overlay.py", "result": {"isolated_log_overlay_created": true, "public_package_read_only": true, "native_container_log_capture": false, "production_changed": false}} +{"at_kst": "2026-10-17T13:57:31.470948+09:00", "run_id": "bootstrap-run01", "event": "remote", "node_alias": "node3", "script": "permission-remote-bootstrap-next.py", "result": {"node_alias": "node3", "isolated_container_started": true, "namespace_isolated": true, "postgres_port": 15439, "rest_port": 18009, "production_configuration_changed": false, "memory_limit_mb": 768}} +{"at_kst": "2026-10-17T13:57:36.022786+09:00", "run_id": "bootstrap-run01", "event": "remote", "node_alias": "node3", "script": "permission-remote-log-overlay.py", "result": {"isolated_log_overlay_created": true, "public_package_read_only": true, "native_container_log_capture": false, "production_changed": false}} +{"at_kst": "2026-10-17T13:57:36.022903+09:00", "run_id": "bootstrap-run01", "event": "complete"} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run01/app.log b/docs/test-results/permission-http-failover-20261017/http-primary-run01/app.log new file mode 100644 index 00000000..f97efc32 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run01/app.log @@ -0,0 +1,23 @@ +{"실행_ID": "http-primary-run01-app", "목적": "실제 HTTP 인증·회수·승격·금지 응답 차단", "시작_KST": "2026-10-17T14:01:17.924299+09:00", "위치": "로컬 격리 PostgreSQL 17.8 / JVM", "명령": "./backend/gradlew -p backend permissionHttpHaTest --console=plain", "성공_기준": "실제 커밋 경계·원장·인가 단언 전부 통과; 실패·미실행을 구분"} +> Task :compileJava UP-TO-DATE +> Task :processResources UP-TO-DATE +> Task :classes UP-TO-DATE +> Task :compileTestJava +> Task :processTestResources UP-TO-DATE +> Task :testClasses +> Task :permissionHttpHaTest +EVIDENCE {"at_kst":"2026-10-17T14:05:24.186053+09:00","event":"start","post_recovery_repetitions":3,"criteria":"금지 내용 0·대조 정상·원장 보존","purpose":"인증 HTTP 회수·승격·응답 차단","location":"루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터"} +EVIDENCE {"at_kst":"2026-10-17T14:05:32.646015+09:00","event":"login","status":200,"latency_ms":1104.419708,"actor":"owner"} +EVIDENCE {"at_kst":"2026-10-17T14:05:33.615538+09:00","event":"login","status":200,"latency_ms":964.842625,"actor":"target"} +EVIDENCE {"at_kst":"2026-10-17T14:05:33.636546+09:00","event":"http","latency_ms":19.508542,"target":"detail","response_sha256":"0dabfc1eaee4b46495d0ea72e0f1a5c34ea1bb16f456b061f8b2ae737664529d","phase":"anonymous","status":401,"revoked_sentinel_present":false} +EVIDENCE {"at_kst":"2026-10-17T14:05:35.824842+09:00","event":"http","latency_ms":2187.482875,"target":"revoked","response_sha256":"f5f84fd27a9f1915da22ae59969b5b15327e6bf5ddb30eab16fcad9162e970f6","phase":"before","status":200,"revoked_sentinel_present":true} +EVIDENCE {"at_kst":"2026-10-17T14:05:37.605629+09:00","event":"http","latency_ms":1779.563833,"target":"allowed","response_sha256":"1bf73784bbd38d8f477997186d93b39311143ecc46d6b096bd4c8de436b46ef8","phase":"before","status":200,"revoked_sentinel_present":false} +EVIDENCE {"at_kst":"2026-10-17T14:05:38.862989+09:00","event":"http","latency_ms":1256.119125,"target":"revoked/content","response_sha256":"47e17539fa6b02d8458990f8fa39199f947dcbffb0547038875522b894d31409","phase":"before","status":200,"revoked_sentinel_present":true} +EVIDENCE {"at_kst":"2026-10-17T14:05:40.122178+09:00","event":"http","latency_ms":1258.260375,"target":"allowed/content","response_sha256":"bfce8383fb92bc6e422219200f2fbd654baea2e4c07c51348e8e60acaca80c0d","phase":"before","status":200,"revoked_sentinel_present":false} +EVIDENCE {"at_kst":"2026-10-17T14:05:41.482917+09:00","event":"http","latency_ms":1359.76075,"target":"revoked/file","response_sha256":"69d177a49960b1d4cf3964b6ddf6e762b0721fd7550138240f625ad70d17a2e3","phase":"before","status":200,"revoked_sentinel_present":true} +EVIDENCE {"at_kst":"2026-10-17T14:05:42.728498+09:00","event":"http","latency_ms":1244.737292,"target":"allowed/file","response_sha256":"5f956c08013de453c0826605cf6b86a9dba1dd00d0d4b3c9c19565970f9149d4","phase":"before","status":200,"revoked_sentinel_present":false} +EVIDENCE {"at_kst":"2026-10-17T14:05:44.855105+09:00","event":"http","latency_ms":2124.112209,"target":"new-search","response_sha256":"641273f46cd8e93f6cfec8723e738e6af0e7d69acf87326cc796890888dba016","phase":"before","status":200,"revoked_sentinel_present":false} +PermissionHttpFailoverIntegrationTest > 실제 HTTP 회수 확정 뒤 자동 승격해도 새 응답에 금지 문구가 없고 대조 문서는 정상이다 FAILED +> Task :permissionHttpHaTest FAILED +BUILD FAILED in 4m 27s +{"종료_KST": "2026-10-17T14:05:45.364955+09:00", "exit_code": 1} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run01/capture-notes.json b/docs/test-results/permission-http-failover-20261017/http-primary-run01/capture-notes.json new file mode 100644 index 00000000..f5e9d693 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run01/capture-notes.json @@ -0,0 +1,9 @@ +{ + "recorded_at_kst": "2026-10-17T14:09:12.957881+09:00", + "raw_sha256": "d252ad87c0a3ccceaa51ab17bd27c99a749b73212ca3ebea046ff72845fdd152", + "raw_bytes_preserved": true, + "invalid_jsonl_lines": 1, + "reason": "기존 제어 기록자와 추가 메타데이터 기록자가 같은 파일 위치를 공유해 한 줄이 손상됨", + "interpretation": "실패 시각·app_not_ready는 실행 도구 출력과 app.log에서 확인. ready 이전에 끝났고 장애 주입은 없었음. 원본 재구성·성공 판정에 사용하지 않음", + "future_fix": "새 회차 메타데이터는 제어 기록자 하나만 작성" +} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run01/control-capture.raw.log b/docs/test-results/permission-http-failover-20261017/http-primary-run01/control-capture.raw.log new file mode 100644 index 00000000..70655f2e --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run01/control-capture.raw.log @@ -0,0 +1,4 @@ +{"run_id": "http-primary-run01", "at_kst": "2026-10-17T14:01:17.903219+09:00", "event": "start", "purpose": "실제 HTTP 회수 확정 뒤 격리 Primary 상실·승격 검증", "location": "로컬 루프백 HTTP → IAP → 격리 OpenSQL·Proxy·역할 어댑터", "revision": "7e45248b81b219b5db37cc615d88632314e88cf5", "uncommitted_test_harness": true, "criteria": "확정 원장 보존·금지 응답0·대조 정상·반복3"} +{"run_id": "http-primary-run01", "at_kst": "2026-10-17T14:05:45.566161+09:00", "event": "failed", "category": "app_not_ready"} +og start header", "actual_location": "로컬 실제 HTTP JVM → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터", "reason": "기존 공통 runner의 새 task 위치 분기 누락, 이 회차 원본 header는 보존"} +{"run_id": "http-primary-run01", "at_kst": "2026-10-17T14:02:32.247089+09:00", "event": "source-fingerprint", "source_unchanged_since_launch": true, "files": {"backend/src/test/java/com/opensource/docgrid/domain/permission/integration/PermissionHttpFailoverIntegrationTest.java": "b8374ba4d2db0bac811b374ab4332ba8fbf881eedaca1f92709ba1470707e18b", "backend/build.gradle": "b13cc0b02760d8b28568a6223b6ab9b91a151cdc96c5f2c1cc44c58b8d24f81b"}} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run01/http.jsonl b/docs/test-results/permission-http-failover-20261017/http-primary-run01/http.jsonl new file mode 100644 index 00000000..9cdf52c9 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run01/http.jsonl @@ -0,0 +1,11 @@ +{"at_kst":"2026-10-17T14:05:24.186053+09:00","event":"start","post_recovery_repetitions":3,"criteria":"금지 내용 0·대조 정상·원장 보존","purpose":"인증 HTTP 회수·승격·응답 차단","location":"루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터"} +{"at_kst":"2026-10-17T14:05:32.646015+09:00","event":"login","status":200,"latency_ms":1104.419708,"actor":"owner"} +{"at_kst":"2026-10-17T14:05:33.615538+09:00","event":"login","status":200,"latency_ms":964.842625,"actor":"target"} +{"at_kst":"2026-10-17T14:05:33.636546+09:00","event":"http","latency_ms":19.508542,"target":"detail","response_sha256":"0dabfc1eaee4b46495d0ea72e0f1a5c34ea1bb16f456b061f8b2ae737664529d","phase":"anonymous","status":401,"revoked_sentinel_present":false} +{"at_kst":"2026-10-17T14:05:35.824842+09:00","event":"http","latency_ms":2187.482875,"target":"revoked","response_sha256":"f5f84fd27a9f1915da22ae59969b5b15327e6bf5ddb30eab16fcad9162e970f6","phase":"before","status":200,"revoked_sentinel_present":true} +{"at_kst":"2026-10-17T14:05:37.605629+09:00","event":"http","latency_ms":1779.563833,"target":"allowed","response_sha256":"1bf73784bbd38d8f477997186d93b39311143ecc46d6b096bd4c8de436b46ef8","phase":"before","status":200,"revoked_sentinel_present":false} +{"at_kst":"2026-10-17T14:05:38.862989+09:00","event":"http","latency_ms":1256.119125,"target":"revoked/content","response_sha256":"47e17539fa6b02d8458990f8fa39199f947dcbffb0547038875522b894d31409","phase":"before","status":200,"revoked_sentinel_present":true} +{"at_kst":"2026-10-17T14:05:40.122178+09:00","event":"http","latency_ms":1258.260375,"target":"allowed/content","response_sha256":"bfce8383fb92bc6e422219200f2fbd654baea2e4c07c51348e8e60acaca80c0d","phase":"before","status":200,"revoked_sentinel_present":false} +{"at_kst":"2026-10-17T14:05:41.482917+09:00","event":"http","latency_ms":1359.76075,"target":"revoked/file","response_sha256":"69d177a49960b1d4cf3964b6ddf6e762b0721fd7550138240f625ad70d17a2e3","phase":"before","status":200,"revoked_sentinel_present":true} +{"at_kst":"2026-10-17T14:05:42.728498+09:00","event":"http","latency_ms":1244.737292,"target":"allowed/file","response_sha256":"5f956c08013de453c0826605cf6b86a9dba1dd00d0d4b3c9c19565970f9149d4","phase":"before","status":200,"revoked_sentinel_present":false} +{"at_kst":"2026-10-17T14:05:44.855105+09:00","event":"http","latency_ms":2124.112209,"target":"new-search","response_sha256":"641273f46cd8e93f6cfec8723e738e6af0e7d69acf87326cc796890888dba016","phase":"before","status":200,"revoked_sentinel_present":false} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run02/app.log b/docs/test-results/permission-http-failover-20261017/http-primary-run02/app.log new file mode 100644 index 00000000..a182b27a --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run02/app.log @@ -0,0 +1,13 @@ +{"실행_ID": "http-primary-run02-app", "목적": "실제 HTTP 인증·회수·승격·금지 응답 차단", "시작_KST": "2026-10-17T14:08:13.552033+09:00", "위치": "로컬 JVM → IAP → 격리 GCP OpenSQL·OpenProxy·역할 어댑터", "명령": "./backend/gradlew -p backend permissionHttpHaTest --console=plain", "성공_기준": "실제 커밋 경계·원장·인가 단언 전부 통과; 실패·미실행을 구분"} +> Task :compileJava UP-TO-DATE +> Task :processResources UP-TO-DATE +> Task :classes UP-TO-DATE +> Task :compileTestJava +> Task :processTestResources UP-TO-DATE +> Task :testClasses +> Task :permissionHttpHaTest +EVIDENCE {"at_kst":"2026-10-17T14:08:32.790395+09:00","event":"start","location":"루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터","purpose":"인증 HTTP 회수·승격·응답 차단","criteria":"금지 내용 0·대조 정상·원장 보존","post_recovery_repetitions":3} +PermissionHttpFailoverIntegrationTest > 실제 HTTP 회수 확정 뒤 자동 승격해도 새 응답에 금지 문구가 없고 대조 문서는 정상이다 FAILED +> Task :permissionHttpHaTest FAILED +BUILD FAILED in 21s +{"종료_KST": "2026-10-17T14:08:35.378294+09:00", "exit_code": 1} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run02/control.jsonl b/docs/test-results/permission-http-failover-20261017/http-primary-run02/control.jsonl new file mode 100644 index 00000000..286d1e0b --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run02/control.jsonl @@ -0,0 +1,3 @@ +{"run_id": "http-primary-run02", "at_kst": "2026-10-17T14:08:13.509618+09:00", "event": "start", "purpose": "실제 HTTP 회수 확정 뒤 격리 Primary 상실·승격 검증", "location": "로컬 루프백 HTTP → IAP → 격리 OpenSQL·Proxy·역할 어댑터", "revision": "7e45248b81b219b5db37cc615d88632314e88cf5", "uncommitted_test_harness": true, "criteria": "확정 원장 보존·금지 응답0·대조 정상·반복3"} +{"run_id": "http-primary-run02", "at_kst": "2026-10-17T14:08:13.510412+09:00", "event": "source-fingerprint", "files": {"backend/src/test/java/com/opensource/docgrid/domain/permission/integration/PermissionHttpFailoverIntegrationTest.java": "c002285e99a3d7fdd9538688f5b13fa02afe49db2b7f5c24fb4989c159d15a42", "backend/build.gradle": "b13cc0b02760d8b28568a6223b6ab9b91a151cdc96c5f2c1cc44c58b8d24f81b"}} +{"run_id": "http-primary-run02", "at_kst": "2026-10-17T14:08:35.531320+09:00", "event": "failed", "category": "app_not_ready"} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run02/http.jsonl b/docs/test-results/permission-http-failover-20261017/http-primary-run02/http.jsonl new file mode 100644 index 00000000..99fb990f --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run02/http.jsonl @@ -0,0 +1 @@ +{"at_kst":"2026-10-17T14:08:32.790395+09:00","event":"start","location":"루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터","purpose":"인증 HTTP 회수·승격·응답 차단","criteria":"금지 내용 0·대조 정상·원장 보존","post_recovery_repetitions":3} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run03/app.log b/docs/test-results/permission-http-failover-20261017/http-primary-run03/app.log new file mode 100644 index 00000000..9cbf4f1e --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run03/app.log @@ -0,0 +1,29 @@ +{"실행_ID": "http-primary-run03-app", "목적": "실제 HTTP 인증·회수·승격·금지 응답 차단", "시작_KST": "2026-10-17T14:09:13.025778+09:00", "위치": "로컬 JVM → IAP → 격리 GCP OpenSQL·OpenProxy·역할 어댑터", "명령": "./backend/gradlew -p backend permissionHttpHaTest --console=plain", "성공_기준": "실제 커밋 경계·원장·인가 단언 전부 통과; 실패·미실행을 구분"} +> Task :compileJava UP-TO-DATE +> Task :processResources UP-TO-DATE +> Task :classes UP-TO-DATE +> Task :compileTestJava +> Task :processTestResources UP-TO-DATE +> Task :testClasses +> Task :permissionHttpHaTest +EVIDENCE {"at_kst":"2026-10-17T14:09:31.936450+09:00","event":"start","purpose":"인증 HTTP 회수·승격·응답 차단","criteria":"금지 내용 0·대조 정상·원장 보존","post_recovery_repetitions":3,"location":"루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터"} +EVIDENCE {"at_kst":"2026-10-17T14:09:39.090625+09:00","event":"search-strategy","controlled_vector_candidates":2,"hybrid_enabled":false} +EVIDENCE {"at_kst":"2026-10-17T14:09:40.385777+09:00","event":"login","status":200,"actor":"owner","latency_ms":1266.397208} +EVIDENCE {"at_kst":"2026-10-17T14:09:41.292212+09:00","event":"login","status":200,"actor":"target","latency_ms":901.395583} +EVIDENCE {"at_kst":"2026-10-17T14:09:41.306596+09:00","event":"http","response_sha256":"dbcd71dc785bbbb13753adb742a94d7f57041b4234c28c38d5d353e61f8322a3","target":"detail","latency_ms":13.207916,"revoked_sentinel_present":false,"status":401,"phase":"anonymous"} +EVIDENCE {"at_kst":"2026-10-17T14:09:43.397016+09:00","event":"http","response_sha256":"0a37a647fac9c51517818fb0f94fb34525e78affa28c00e14863b5991deaec8a","target":"revoked","latency_ms":2089.521958,"revoked_sentinel_present":true,"status":200,"phase":"before"} +EVIDENCE {"at_kst":"2026-10-17T14:09:45.078209+09:00","event":"http","response_sha256":"c606aff57688863410f35079821c9f228b682bf90ae5ba512a9c032fbb1bc815","target":"allowed","latency_ms":1679.529584,"revoked_sentinel_present":false,"status":200,"phase":"before"} +EVIDENCE {"at_kst":"2026-10-17T14:09:46.412625+09:00","event":"http","response_sha256":"7f1408e8870ab2a0f6317946c7e1a5bb84915bbdf9c52f0b9277b73bd030f905","target":"revoked/content","latency_ms":1333.46225,"revoked_sentinel_present":true,"status":200,"phase":"before"} +EVIDENCE {"at_kst":"2026-10-17T14:09:47.699739+09:00","event":"http","response_sha256":"a5257f814b2e6bd899f1b4c1b3f97b295047e445c953ae77c980b216edb5869b","target":"allowed/content","latency_ms":1286.045333,"revoked_sentinel_present":false,"status":200,"phase":"before"} +EVIDENCE {"at_kst":"2026-10-17T14:09:49.036283+09:00","event":"http","response_sha256":"fe0296dcf5b34b63f8627cef057c5c8e6234ed0bef96cca5c05a0de0fd9651e0","target":"revoked/file","latency_ms":1335.717084,"revoked_sentinel_present":true,"status":200,"phase":"before"} +EVIDENCE {"at_kst":"2026-10-17T14:09:50.400838+09:00","event":"http","response_sha256":"b3b6abab37f5019641448c7f101100a24d4458920f6869bd3610c44824a581c1","target":"allowed/file","latency_ms":1363.668083,"revoked_sentinel_present":false,"status":200,"phase":"before"} +EVIDENCE {"at_kst":"2026-10-17T14:09:54.770310+09:00","event":"http","response_sha256":"db5de9276f95d6945a7610c605314b2c7c034aaef818a2f5be63b75971c3f98c","target":"new-search","latency_ms":4367.443292,"revoked_sentinel_present":true,"status":200,"phase":"before"} +EVIDENCE {"at_kst":"2026-10-17T14:09:57.956719+09:00","event":"http","response_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","target":"permission","latency_ms":3183.314083,"revoked_sentinel_present":false,"status":204,"phase":"revoke"} +EVIDENCE {"at_kst":"2026-10-17T14:10:00.388852+09:00","event":"http","response_sha256":"357be73eea1a69d5d39d6692a6bfc466be1d1ad705c69da044382a2ffc77bf9a","target":"revoked","latency_ms":1980.57225,"revoked_sentinel_present":false,"status":403,"phase":"after-confirmation"} +EVIDENCE {"at_kst":"2026-10-17T14:10:02.057049+09:00","event":"http","response_sha256":"da4a177bdbaa21c4d5b4bf302ee69c0e3ebcd2beaaed63b8bc89a38127525c46","target":"allowed","latency_ms":1667.0755,"revoked_sentinel_present":false,"status":200,"phase":"after-confirmation"} +EVIDENCE {"at_kst":"2026-10-17T14:10:03.948176+09:00","event":"http","response_sha256":"edc77ab14c16862a92c5fbf2abb7aa42b16636c7e3bbe57365db47cc6b193463","target":"revoked/content","latency_ms":1890.060208,"revoked_sentinel_present":false,"status":403,"phase":"after-confirmation"} +EVIDENCE {"at_kst":"2026-10-17T14:10:05.197694+09:00","event":"http","response_sha256":"0dfe88b9fdde4334dd3c06a21bb90fd852813c761ed71a37a9f06751e2295d2d","target":"allowed/content","latency_ms":1248.605417,"revoked_sentinel_present":false,"status":200,"phase":"after-confirmation"} +EVIDENCE {"at_kst":"2026-10-17T14:10:07.093151+09:00","event":"http","response_sha256":"e89955a7776bd6743fd1e024e96e342cd832a39855f1978b2712757e7ed972fb","target":"revoked/file","latency_ms":1894.579166,"revoked_sentinel_present":false,"status":403,"phase":"after-confirmation"} +EVIDENCE {"at_kst":"2026-10-17T14:10:08.340829+09:00","event":"http","response_sha256":"b3b6abab37f5019641448c7f101100a24d4458920f6869bd3610c44824a581c1","target":"allowed/file","latency_ms":1244.58025,"revoked_sentinel_present":false,"status":200,"phase":"after-confirmation"} +EVIDENCE {"at_kst":"2026-10-17T14:10:13.387035+09:00","event":"http","response_sha256":"33f2b739ab9a74970f7c235f339fa92d0e499ed0780daa6a975a42a62a1d2e05","target":"new-search","latency_ms":5045.165583,"revoked_sentinel_present":false,"status":200,"phase":"after-confirmation"} +EVIDENCE {"at_kst":"2026-10-17T14:10:13.388599+09:00","event":"revocation-confirmed","request_key":"6bba46a9-d7cf-401c-ae9f-9bc6f12671b0","stale_cache_injected":true} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run03/control.jsonl b/docs/test-results/permission-http-failover-20261017/http-primary-run03/control.jsonl new file mode 100644 index 00000000..7681562c --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run03/control.jsonl @@ -0,0 +1,9 @@ +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:09:13.005184+09:00", "event": "start", "purpose": "실제 HTTP 회수 확정 뒤 격리 Primary 상실·승격 검증", "location": "로컬 루프백 HTTP → IAP → 격리 OpenSQL·Proxy·역할 어댑터", "revision": "7e45248b81b219b5db37cc615d88632314e88cf5", "uncommitted_test_harness": true, "criteria": "확정 원장 보존·금지 응답0·대조 정상·반복3"} +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:09:13.005734+09:00", "event": "source-fingerprint", "files": {"backend/src/test/java/com/opensource/docgrid/domain/permission/integration/PermissionHttpFailoverIntegrationTest.java": "40d43140764b1f59ed80c39a6b5b42a0a0a0629adc1db222b22b8d267316760e", "backend/build.gradle": "b13cc0b02760d8b28568a6223b6ab9b91a151cdc96c5f2c1cc44c58b8d24f81b"}} +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:10:13.511583+09:00", "event": "fault-requested", "target": "node1", "scope": "isolated-only"} +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:10:14.569622+09:00", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "stop", "completed": true, "at_utc": "2026-10-17T05:10:14.452060+00:00", "production_config_unchanged": true}} +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:10:14.569823+09:00", "event": "fault-confirmed", "target": "node1"} +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:10:34.386177+09:00", "event": "automatic-promotion", "new_primary": "node2"} +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:10:35.412368+09:00", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "start", "completed": true, "at_utc": "2026-10-17T05:10:35.233583+00:00", "production_config_unchanged": true}} +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:10:36.338662+09:00", "event": "remote", "node_alias": "node1", "result": {"adapter_launch_exit": 0, "role_only_normalization": true, "read_only": true, "scope_guard": true, "port": 18010}} +{"run_id": "http-primary-run03", "at_kst": "2026-10-17T14:12:06.748209+09:00", "event": "failed", "category": "old_primary_rejoin_missing"} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run03/http.jsonl b/docs/test-results/permission-http-failover-20261017/http-primary-run03/http.jsonl new file mode 100644 index 00000000..7d8f13f2 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run03/http.jsonl @@ -0,0 +1,21 @@ +{"at_kst":"2026-10-17T14:09:31.936450+09:00","event":"start","purpose":"인증 HTTP 회수·승격·응답 차단","criteria":"금지 내용 0·대조 정상·원장 보존","post_recovery_repetitions":3,"location":"루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터"} +{"at_kst":"2026-10-17T14:09:39.090625+09:00","event":"search-strategy","controlled_vector_candidates":2,"hybrid_enabled":false} +{"at_kst":"2026-10-17T14:09:40.385777+09:00","event":"login","status":200,"actor":"owner","latency_ms":1266.397208} +{"at_kst":"2026-10-17T14:09:41.292212+09:00","event":"login","status":200,"actor":"target","latency_ms":901.395583} +{"at_kst":"2026-10-17T14:09:41.306596+09:00","event":"http","response_sha256":"dbcd71dc785bbbb13753adb742a94d7f57041b4234c28c38d5d353e61f8322a3","target":"detail","latency_ms":13.207916,"revoked_sentinel_present":false,"status":401,"phase":"anonymous"} +{"at_kst":"2026-10-17T14:09:43.397016+09:00","event":"http","response_sha256":"0a37a647fac9c51517818fb0f94fb34525e78affa28c00e14863b5991deaec8a","target":"revoked","latency_ms":2089.521958,"revoked_sentinel_present":true,"status":200,"phase":"before"} +{"at_kst":"2026-10-17T14:09:45.078209+09:00","event":"http","response_sha256":"c606aff57688863410f35079821c9f228b682bf90ae5ba512a9c032fbb1bc815","target":"allowed","latency_ms":1679.529584,"revoked_sentinel_present":false,"status":200,"phase":"before"} +{"at_kst":"2026-10-17T14:09:46.412625+09:00","event":"http","response_sha256":"7f1408e8870ab2a0f6317946c7e1a5bb84915bbdf9c52f0b9277b73bd030f905","target":"revoked/content","latency_ms":1333.46225,"revoked_sentinel_present":true,"status":200,"phase":"before"} +{"at_kst":"2026-10-17T14:09:47.699739+09:00","event":"http","response_sha256":"a5257f814b2e6bd899f1b4c1b3f97b295047e445c953ae77c980b216edb5869b","target":"allowed/content","latency_ms":1286.045333,"revoked_sentinel_present":false,"status":200,"phase":"before"} +{"at_kst":"2026-10-17T14:09:49.036283+09:00","event":"http","response_sha256":"fe0296dcf5b34b63f8627cef057c5c8e6234ed0bef96cca5c05a0de0fd9651e0","target":"revoked/file","latency_ms":1335.717084,"revoked_sentinel_present":true,"status":200,"phase":"before"} +{"at_kst":"2026-10-17T14:09:50.400838+09:00","event":"http","response_sha256":"b3b6abab37f5019641448c7f101100a24d4458920f6869bd3610c44824a581c1","target":"allowed/file","latency_ms":1363.668083,"revoked_sentinel_present":false,"status":200,"phase":"before"} +{"at_kst":"2026-10-17T14:09:54.770310+09:00","event":"http","response_sha256":"db5de9276f95d6945a7610c605314b2c7c034aaef818a2f5be63b75971c3f98c","target":"new-search","latency_ms":4367.443292,"revoked_sentinel_present":true,"status":200,"phase":"before"} +{"at_kst":"2026-10-17T14:09:57.956719+09:00","event":"http","response_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","target":"permission","latency_ms":3183.314083,"revoked_sentinel_present":false,"status":204,"phase":"revoke"} +{"at_kst":"2026-10-17T14:10:00.388852+09:00","event":"http","response_sha256":"357be73eea1a69d5d39d6692a6bfc466be1d1ad705c69da044382a2ffc77bf9a","target":"revoked","latency_ms":1980.57225,"revoked_sentinel_present":false,"status":403,"phase":"after-confirmation"} +{"at_kst":"2026-10-17T14:10:02.057049+09:00","event":"http","response_sha256":"da4a177bdbaa21c4d5b4bf302ee69c0e3ebcd2beaaed63b8bc89a38127525c46","target":"allowed","latency_ms":1667.0755,"revoked_sentinel_present":false,"status":200,"phase":"after-confirmation"} +{"at_kst":"2026-10-17T14:10:03.948176+09:00","event":"http","response_sha256":"edc77ab14c16862a92c5fbf2abb7aa42b16636c7e3bbe57365db47cc6b193463","target":"revoked/content","latency_ms":1890.060208,"revoked_sentinel_present":false,"status":403,"phase":"after-confirmation"} +{"at_kst":"2026-10-17T14:10:05.197694+09:00","event":"http","response_sha256":"0dfe88b9fdde4334dd3c06a21bb90fd852813c761ed71a37a9f06751e2295d2d","target":"allowed/content","latency_ms":1248.605417,"revoked_sentinel_present":false,"status":200,"phase":"after-confirmation"} +{"at_kst":"2026-10-17T14:10:07.093151+09:00","event":"http","response_sha256":"e89955a7776bd6743fd1e024e96e342cd832a39855f1978b2712757e7ed972fb","target":"revoked/file","latency_ms":1894.579166,"revoked_sentinel_present":false,"status":403,"phase":"after-confirmation"} +{"at_kst":"2026-10-17T14:10:08.340829+09:00","event":"http","response_sha256":"b3b6abab37f5019641448c7f101100a24d4458920f6869bd3610c44824a581c1","target":"allowed/file","latency_ms":1244.58025,"revoked_sentinel_present":false,"status":200,"phase":"after-confirmation"} +{"at_kst":"2026-10-17T14:10:13.387035+09:00","event":"http","response_sha256":"33f2b739ab9a74970f7c235f339fa92d0e499ed0780daa6a975a42a62a1d2e05","target":"new-search","latency_ms":5045.165583,"revoked_sentinel_present":false,"status":200,"phase":"after-confirmation"} +{"at_kst":"2026-10-17T14:10:13.388599+09:00","event":"revocation-confirmed","request_key":"6bba46a9-d7cf-401c-ae9f-9bc6f12671b0","stale_cache_injected":true} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run03/post-failure-db-audit.log b/docs/test-results/permission-http-failover-20261017/http-primary-run03/post-failure-db-audit.log new file mode 100644 index 00000000..fe1f5af5 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run03/post-failure-db-audit.log @@ -0,0 +1,6 @@ +목적: HTTP 실패 회차의 확정 회수 키를 새 Primary에서 대조 +위치: 격리 OpenProxy·현재 Primary +시작 KST: 2026-10-17T14:17:56.717493+09:00 +성공 기준: 원장 1개·권한 0개. HTTP 회차를 성공으로 변경하지 않음 +EVIDENCE receipt_rows=1 remaining_permissions=0 +exit_code=0 diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run04/app.log b/docs/test-results/permission-http-failover-20261017/http-primary-run04/app.log new file mode 100644 index 00000000..1c62a14b --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run04/app.log @@ -0,0 +1,54 @@ +{"실행_ID": "http-primary-run04-app", "목적": "실제 HTTP 인증·회수·승격·금지 응답 차단", "시작_KST": "2026-10-17T14:18:46.754249+09:00", "위치": "로컬 JVM → IAP → 격리 GCP OpenSQL·OpenProxy·역할 어댑터", "명령": "./backend/gradlew -p backend permissionHttpHaTest --console=plain", "성공_기준": "실제 커밋 경계·원장·인가 단언 전부 통과; 실패·미실행을 구분"} +> Task :compileJava UP-TO-DATE +> Task :processResources UP-TO-DATE +> Task :classes UP-TO-DATE +> Task :compileTestJava +> Task :processTestResources UP-TO-DATE +> Task :testClasses +> Task :permissionHttpHaTest +EVIDENCE {"at_kst":"2026-10-17T14:19:05.484494+09:00","event":"start","post_recovery_repetitions":3,"criteria":"금지 내용 0·대조 정상·원장 보존","purpose":"인증 HTTP 회수·승격·응답 차단","location":"루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터"} +EVIDENCE {"at_kst":"2026-10-17T14:19:13.085032+09:00","event":"search-strategy","controlled_vector_candidates":2,"hybrid_enabled":false} +EVIDENCE {"at_kst":"2026-10-17T14:19:14.146203+09:00","event":"login","status":200,"latency_ms":1050.608542,"actor":"owner"} +EVIDENCE {"at_kst":"2026-10-17T14:19:15.058880+09:00","event":"login","status":200,"latency_ms":909.506958,"actor":"target"} +EVIDENCE {"at_kst":"2026-10-17T14:19:15.072350+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":12.380042,"target":"detail","response_sha256":"9d5b6fd87fb30e075a36a9bb974b9468a7fe507e7beac9b71f910e0f4a7796e0","phase":"anonymous","status":401} +EVIDENCE {"at_kst":"2026-10-17T14:19:17.369570+09:00","event":"http","revoked_sentinel_present":true,"latency_ms":2296.559375,"target":"revoked","response_sha256":"089dbd9621d38afd8ef57f2f7cc03e04568c5ead3051507a2d15dcd5ce765a2f","phase":"before","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:19.043338+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1672.727166,"target":"allowed","response_sha256":"890c030b72ca5bddaa3a68acb1b300c7ada647117af57928e46fa01433ea52c1","phase":"before","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:20.312639+09:00","event":"http","revoked_sentinel_present":true,"latency_ms":1268.341959,"target":"revoked/content","response_sha256":"365db22882dd4d0eb715172f37a0b5f3b12849960d787bd84868e7d8d778bc1c","phase":"before","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:21.556786+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1243.049209,"target":"allowed/content","response_sha256":"7530b353df8da9771e993e20853d57ee71c3e0ed5753a2416ae9b7f07c8ec6de","phase":"before","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:22.840961+09:00","event":"http","revoked_sentinel_present":true,"latency_ms":1283.217917,"target":"revoked/file","response_sha256":"a436e2930a313fdcb21ff147f12596b6971b6b87e7e22247fd3eb1403df535f7","phase":"before","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:24.089519+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1247.673584,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"before","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:28.345615+09:00","event":"http","revoked_sentinel_present":true,"latency_ms":4254.148791,"target":"new-search","response_sha256":"6c8bd63eeff5c04872c130dd89f0ca7b5d1ec974ca981e8714d20d84209274a8","phase":"before","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:31.527492+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":3180.250416,"target":"permission","response_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","phase":"revoke","status":204} +EVIDENCE {"at_kst":"2026-10-17T14:19:33.861364+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1905.183292,"target":"revoked","response_sha256":"57abdfd4a78fae1306c20aa1739eaa9b5ba20bdb4569b603e781c4698f76d673","phase":"after-confirmation","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:19:35.505975+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1643.821375,"target":"allowed","response_sha256":"f876dca2ec5485f5cfdba21d2df7a79f5e689666f0b975fa2d74a839d1c0f85a","phase":"after-confirmation","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:37.396491+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1889.706291,"target":"revoked/content","response_sha256":"3c9751702650199295074b68475e03f6a2de12f389c9b55bde19480a14f1e193","phase":"after-confirmation","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:19:38.650514+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1253.22725,"target":"allowed/content","response_sha256":"28f9e4a16557e156b3cb9816e2b937f1f5a8fd40ff18ddef254a989390cf60c7","phase":"after-confirmation","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:40.544473+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1893.30325,"target":"revoked/file","response_sha256":"d4e354d70b5fa20b7af557eca8cc401d00bbee2085c7e3d16b6b6bc459933d85","phase":"after-confirmation","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:19:41.795580+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1250.124042,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"after-confirmation","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:47.057367+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":5260.516417,"target":"new-search","response_sha256":"1f1d8a6570f2cc960598d5060bde233eba3602f20b3d5fe51031a26b247327da","phase":"after-confirmation","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:19:47.058179+09:00","event":"revocation-confirmed","stale_cache_injected":true,"request_key":"bc1d4ef2-ef91-48d7-9807-1d1dcafcc1f4"} +EVIDENCE {"at_kst":"2026-10-17T14:20:10.027661+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":2350.425,"target":"revoked","response_sha256":"90571e0dbe682612532e7d09a07fb11333fd7fddde242e759013c811535ed3c3","phase":"after-promotion-1","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:11.683118+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1651.738666,"target":"allowed","response_sha256":"15dedc52539399f8dfae5831194a5d30534934e654e89d580f6221f2123dc75e","phase":"after-promotion-1","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:13.575277+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1891.11275,"target":"revoked/content","response_sha256":"c3b47eafd100725e068bb8ad1353d995c1073742c39e7e8fc5f62d161c9696e0","phase":"after-promotion-1","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:14.828390+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1252.125083,"target":"allowed/content","response_sha256":"5a3109a9e773792bcd960ac06898f6b44d9025bfd348ee0f71c37a176ed2ca84","phase":"after-promotion-1","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:16.720445+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1891.049375,"target":"revoked/file","response_sha256":"f80192d2506c6390c4010f9fc73d08628181b0551a1f1168127ad0a483c804b2","phase":"after-promotion-1","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:17.975250+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1253.727083,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"after-promotion-1","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:23.020479+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":5044.013042,"target":"new-search","response_sha256":"81825a8a008d5321c231edfecef54d06723b494921591130ac66ac6af50a7b3c","phase":"after-promotion-1","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:24.904867+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1883.334,"target":"revoked","response_sha256":"1ebe92b9212cca134eb6ee5e7156665ce3c5a15a3089e53335f999051920077d","phase":"after-promotion-2","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:26.565697+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1659.82375,"target":"allowed","response_sha256":"445c547e9ec7ad48d18d48481d05c66d090cdef1b40108cb96172298350ad50c","phase":"after-promotion-2","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:28.455309+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1888.610583,"target":"revoked/content","response_sha256":"07824894cf1ea8399cb4347530732ccbd544c8f5339b4d2a082530c04982280a","phase":"after-promotion-2","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:29.780674+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1324.69675,"target":"allowed/content","response_sha256":"8ad33c9260d094e36fbb0cb11836a6439b925d0e8d0b63ebbefccbc8720454ef","phase":"after-promotion-2","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:31.839065+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":2057.465708,"target":"revoked/file","response_sha256":"174a86b4cd558e7f14611106afa6eae8c96a80a4e000b8557dba3985904bc57d","phase":"after-promotion-2","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:33.093672+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1253.612792,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"after-promotion-2","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:38.111061+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":5016.366625,"target":"new-search","response_sha256":"e94b73c810a8db07ab3b49f5257132ebe2084ea1f4a5c6fdc48646d74f0a188a","phase":"after-promotion-2","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:39.988383+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1876.141542,"target":"revoked","response_sha256":"01264d3483bb5308ba1b66fde66fa29dee8284634270c29acbcfa2aa7e382114","phase":"after-promotion-3","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:41.888022+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1896.166167,"target":"allowed","response_sha256":"c38ef43c01d62694363ddd2fa46201c9606a2201c997ca3b24ad11f1e4cb8dfd","phase":"after-promotion-3","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:43.794158+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1905.061417,"target":"revoked/content","response_sha256":"9055d03c086f2902a96f6544d3b21aa2e0c9f609124312185fc23e4ec733e099","phase":"after-promotion-3","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:45.035715+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1240.612334,"target":"allowed/content","response_sha256":"0f73cdbbf46a94aaca54a10dcccad3822d57d67fd34714b4e891ca99272202c7","phase":"after-promotion-3","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:47.015594+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1978.814333,"target":"revoked/file","response_sha256":"f651032ab4db074486db94c41a042a9eec8167a246bcd4306d6621f850fbd86f","phase":"after-promotion-3","status":403} +EVIDENCE {"at_kst":"2026-10-17T14:20:48.299681+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1283.3505,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"after-promotion-3","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:53.541799+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":5240.993875,"target":"new-search","response_sha256":"f908318378d684a8e7e05cbfaae2ddd743382e3603a790af50f93fdfc70d76e0","phase":"after-promotion-3","status":200} +EVIDENCE {"at_kst":"2026-10-17T14:20:54.345723+09:00","event":"summary","post_recovery_repetitions":3,"forbidden_content":0,"receipt_rows":1,"request_key":"bc1d4ef2-ef91-48d7-9807-1d1dcafcc1f4","remaining_permissions":0,"passed":true,"storage_reads":6} +PermissionHttpFailoverIntegrationTest > 실제 HTTP 회수 확정 뒤 자동 승격해도 새 응답에 금지 문구가 없고 대조 문서는 정상이다 PASSED +BUILD SUCCESSFUL in 2m 8s +{"종료_KST": "2026-10-17T14:20:54.907192+09:00", "exit_code": 0} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run04/control.jsonl b/docs/test-results/permission-http-failover-20261017/http-primary-run04/control.jsonl new file mode 100644 index 00000000..02bcac7c --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run04/control.jsonl @@ -0,0 +1,9 @@ +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:18:46.733513+09:00", "event": "start", "purpose": "실제 HTTP 회수 확정 뒤 격리 Primary 상실·승격 검증", "location": "로컬 루프백 HTTP → IAP → 격리 OpenSQL·Proxy·역할 어댑터", "revision": "7e45248b81b219b5db37cc615d88632314e88cf5", "uncommitted_test_harness": true, "criteria": "확정 원장 보존·금지 응답0·대조 정상·반복3"} +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:18:46.734147+09:00", "event": "source-fingerprint", "files": {"backend/src/test/java/com/opensource/docgrid/domain/permission/integration/PermissionHttpFailoverIntegrationTest.java": "405fe0ae8bc9a09f7bf5e5f3c0f19924496ca6a85c927093bf32ed6a1c1d4b4b", "backend/build.gradle": "b13cc0b02760d8b28568a6223b6ab9b91a151cdc96c5f2c1cc44c58b8d24f81b"}} +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:19:47.258530+09:00", "event": "fault-requested", "target": "node1", "scope": "isolated-only"} +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:19:48.173350+09:00", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "stop", "completed": true, "at_utc": "2026-10-17T05:19:48.059030+00:00", "production_config_unchanged": true}} +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:19:48.173526+09:00", "event": "fault-confirmed", "target": "node1"} +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:20:07.625462+09:00", "event": "automatic-promotion", "new_primary": "node2"} +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:20:07.628254+09:00", "event": "post-promotion-http-released", "old_primary_stopped": true} +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:20:54.937855+09:00", "event": "app-finished", "exit": 0} +{"run_id": "http-primary-run04", "at_kst": "2026-10-17T14:20:54.938129+09:00", "event": "complete", "passed": true} diff --git a/docs/test-results/permission-http-failover-20261017/http-primary-run04/http.jsonl b/docs/test-results/permission-http-failover-20261017/http-primary-run04/http.jsonl new file mode 100644 index 00000000..7da32ab7 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/http-primary-run04/http.jsonl @@ -0,0 +1,43 @@ +{"at_kst":"2026-10-17T14:19:05.484494+09:00","event":"start","post_recovery_repetitions":3,"criteria":"금지 내용 0·대조 정상·원장 보존","purpose":"인증 HTTP 회수·승격·응답 차단","location":"루프백 HTTP → IAP → 격리 OpenSQL·OpenProxy·역할 어댑터"} +{"at_kst":"2026-10-17T14:19:13.085032+09:00","event":"search-strategy","controlled_vector_candidates":2,"hybrid_enabled":false} +{"at_kst":"2026-10-17T14:19:14.146203+09:00","event":"login","status":200,"latency_ms":1050.608542,"actor":"owner"} +{"at_kst":"2026-10-17T14:19:15.058880+09:00","event":"login","status":200,"latency_ms":909.506958,"actor":"target"} +{"at_kst":"2026-10-17T14:19:15.072350+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":12.380042,"target":"detail","response_sha256":"9d5b6fd87fb30e075a36a9bb974b9468a7fe507e7beac9b71f910e0f4a7796e0","phase":"anonymous","status":401} +{"at_kst":"2026-10-17T14:19:17.369570+09:00","event":"http","revoked_sentinel_present":true,"latency_ms":2296.559375,"target":"revoked","response_sha256":"089dbd9621d38afd8ef57f2f7cc03e04568c5ead3051507a2d15dcd5ce765a2f","phase":"before","status":200} +{"at_kst":"2026-10-17T14:19:19.043338+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1672.727166,"target":"allowed","response_sha256":"890c030b72ca5bddaa3a68acb1b300c7ada647117af57928e46fa01433ea52c1","phase":"before","status":200} +{"at_kst":"2026-10-17T14:19:20.312639+09:00","event":"http","revoked_sentinel_present":true,"latency_ms":1268.341959,"target":"revoked/content","response_sha256":"365db22882dd4d0eb715172f37a0b5f3b12849960d787bd84868e7d8d778bc1c","phase":"before","status":200} +{"at_kst":"2026-10-17T14:19:21.556786+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1243.049209,"target":"allowed/content","response_sha256":"7530b353df8da9771e993e20853d57ee71c3e0ed5753a2416ae9b7f07c8ec6de","phase":"before","status":200} +{"at_kst":"2026-10-17T14:19:22.840961+09:00","event":"http","revoked_sentinel_present":true,"latency_ms":1283.217917,"target":"revoked/file","response_sha256":"a436e2930a313fdcb21ff147f12596b6971b6b87e7e22247fd3eb1403df535f7","phase":"before","status":200} +{"at_kst":"2026-10-17T14:19:24.089519+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1247.673584,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"before","status":200} +{"at_kst":"2026-10-17T14:19:28.345615+09:00","event":"http","revoked_sentinel_present":true,"latency_ms":4254.148791,"target":"new-search","response_sha256":"6c8bd63eeff5c04872c130dd89f0ca7b5d1ec974ca981e8714d20d84209274a8","phase":"before","status":200} +{"at_kst":"2026-10-17T14:19:31.527492+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":3180.250416,"target":"permission","response_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","phase":"revoke","status":204} +{"at_kst":"2026-10-17T14:19:33.861364+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1905.183292,"target":"revoked","response_sha256":"57abdfd4a78fae1306c20aa1739eaa9b5ba20bdb4569b603e781c4698f76d673","phase":"after-confirmation","status":403} +{"at_kst":"2026-10-17T14:19:35.505975+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1643.821375,"target":"allowed","response_sha256":"f876dca2ec5485f5cfdba21d2df7a79f5e689666f0b975fa2d74a839d1c0f85a","phase":"after-confirmation","status":200} +{"at_kst":"2026-10-17T14:19:37.396491+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1889.706291,"target":"revoked/content","response_sha256":"3c9751702650199295074b68475e03f6a2de12f389c9b55bde19480a14f1e193","phase":"after-confirmation","status":403} +{"at_kst":"2026-10-17T14:19:38.650514+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1253.22725,"target":"allowed/content","response_sha256":"28f9e4a16557e156b3cb9816e2b937f1f5a8fd40ff18ddef254a989390cf60c7","phase":"after-confirmation","status":200} +{"at_kst":"2026-10-17T14:19:40.544473+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1893.30325,"target":"revoked/file","response_sha256":"d4e354d70b5fa20b7af557eca8cc401d00bbee2085c7e3d16b6b6bc459933d85","phase":"after-confirmation","status":403} +{"at_kst":"2026-10-17T14:19:41.795580+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1250.124042,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"after-confirmation","status":200} +{"at_kst":"2026-10-17T14:19:47.057367+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":5260.516417,"target":"new-search","response_sha256":"1f1d8a6570f2cc960598d5060bde233eba3602f20b3d5fe51031a26b247327da","phase":"after-confirmation","status":200} +{"at_kst":"2026-10-17T14:19:47.058179+09:00","event":"revocation-confirmed","stale_cache_injected":true,"request_key":"bc1d4ef2-ef91-48d7-9807-1d1dcafcc1f4"} +{"at_kst":"2026-10-17T14:20:10.027661+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":2350.425,"target":"revoked","response_sha256":"90571e0dbe682612532e7d09a07fb11333fd7fddde242e759013c811535ed3c3","phase":"after-promotion-1","status":403} +{"at_kst":"2026-10-17T14:20:11.683118+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1651.738666,"target":"allowed","response_sha256":"15dedc52539399f8dfae5831194a5d30534934e654e89d580f6221f2123dc75e","phase":"after-promotion-1","status":200} +{"at_kst":"2026-10-17T14:20:13.575277+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1891.11275,"target":"revoked/content","response_sha256":"c3b47eafd100725e068bb8ad1353d995c1073742c39e7e8fc5f62d161c9696e0","phase":"after-promotion-1","status":403} +{"at_kst":"2026-10-17T14:20:14.828390+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1252.125083,"target":"allowed/content","response_sha256":"5a3109a9e773792bcd960ac06898f6b44d9025bfd348ee0f71c37a176ed2ca84","phase":"after-promotion-1","status":200} +{"at_kst":"2026-10-17T14:20:16.720445+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1891.049375,"target":"revoked/file","response_sha256":"f80192d2506c6390c4010f9fc73d08628181b0551a1f1168127ad0a483c804b2","phase":"after-promotion-1","status":403} +{"at_kst":"2026-10-17T14:20:17.975250+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1253.727083,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"after-promotion-1","status":200} +{"at_kst":"2026-10-17T14:20:23.020479+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":5044.013042,"target":"new-search","response_sha256":"81825a8a008d5321c231edfecef54d06723b494921591130ac66ac6af50a7b3c","phase":"after-promotion-1","status":200} +{"at_kst":"2026-10-17T14:20:24.904867+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1883.334,"target":"revoked","response_sha256":"1ebe92b9212cca134eb6ee5e7156665ce3c5a15a3089e53335f999051920077d","phase":"after-promotion-2","status":403} +{"at_kst":"2026-10-17T14:20:26.565697+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1659.82375,"target":"allowed","response_sha256":"445c547e9ec7ad48d18d48481d05c66d090cdef1b40108cb96172298350ad50c","phase":"after-promotion-2","status":200} +{"at_kst":"2026-10-17T14:20:28.455309+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1888.610583,"target":"revoked/content","response_sha256":"07824894cf1ea8399cb4347530732ccbd544c8f5339b4d2a082530c04982280a","phase":"after-promotion-2","status":403} +{"at_kst":"2026-10-17T14:20:29.780674+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1324.69675,"target":"allowed/content","response_sha256":"8ad33c9260d094e36fbb0cb11836a6439b925d0e8d0b63ebbefccbc8720454ef","phase":"after-promotion-2","status":200} +{"at_kst":"2026-10-17T14:20:31.839065+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":2057.465708,"target":"revoked/file","response_sha256":"174a86b4cd558e7f14611106afa6eae8c96a80a4e000b8557dba3985904bc57d","phase":"after-promotion-2","status":403} +{"at_kst":"2026-10-17T14:20:33.093672+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1253.612792,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"after-promotion-2","status":200} +{"at_kst":"2026-10-17T14:20:38.111061+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":5016.366625,"target":"new-search","response_sha256":"e94b73c810a8db07ab3b49f5257132ebe2084ea1f4a5c6fdc48646d74f0a188a","phase":"after-promotion-2","status":200} +{"at_kst":"2026-10-17T14:20:39.988383+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1876.141542,"target":"revoked","response_sha256":"01264d3483bb5308ba1b66fde66fa29dee8284634270c29acbcfa2aa7e382114","phase":"after-promotion-3","status":403} +{"at_kst":"2026-10-17T14:20:41.888022+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1896.166167,"target":"allowed","response_sha256":"c38ef43c01d62694363ddd2fa46201c9606a2201c997ca3b24ad11f1e4cb8dfd","phase":"after-promotion-3","status":200} +{"at_kst":"2026-10-17T14:20:43.794158+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1905.061417,"target":"revoked/content","response_sha256":"9055d03c086f2902a96f6544d3b21aa2e0c9f609124312185fc23e4ec733e099","phase":"after-promotion-3","status":403} +{"at_kst":"2026-10-17T14:20:45.035715+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1240.612334,"target":"allowed/content","response_sha256":"0f73cdbbf46a94aaca54a10dcccad3822d57d67fd34714b4e891ca99272202c7","phase":"after-promotion-3","status":200} +{"at_kst":"2026-10-17T14:20:47.015594+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1978.814333,"target":"revoked/file","response_sha256":"f651032ab4db074486db94c41a042a9eec8167a246bcd4306d6621f850fbd86f","phase":"after-promotion-3","status":403} +{"at_kst":"2026-10-17T14:20:48.299681+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":1283.3505,"target":"allowed/file","response_sha256":"e3a8e15ab451a25b7c56630cf95587d463dec553e6db837df6f75f3c7ecd6aac","phase":"after-promotion-3","status":200} +{"at_kst":"2026-10-17T14:20:53.541799+09:00","event":"http","revoked_sentinel_present":false,"latency_ms":5240.993875,"target":"new-search","response_sha256":"f908318378d684a8e7e05cbfaae2ddd743382e3603a790af50f93fdfc70d76e0","phase":"after-promotion-3","status":200} +{"at_kst":"2026-10-17T14:20:54.345723+09:00","event":"summary","post_recovery_repetitions":3,"forbidden_content":0,"receipt_rows":1,"request_key":"bc1d4ef2-ef91-48d7-9807-1d1dcafcc1f4","remaining_permissions":0,"passed":true,"storage_reads":6} diff --git a/docs/test-results/permission-http-failover-20261017/jdbc-preflight-run01.log b/docs/test-results/permission-http-failover-20261017/jdbc-preflight-run01.log new file mode 100644 index 00000000..ed7cc9da --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/jdbc-preflight-run01.log @@ -0,0 +1,13 @@ +실행 ID: jdbc-preflight-run01 +목적: 실제 격리 OpenSQL·두 OpenProxy의 read-write Primary 도착·동기 적용 확인 +위치: 로컬 JDBC → IAP → 격리 GCP 컨테이너 +명령: PermissionHaPreflight (pgJDBC 42.7.11) +성공 기준: Primary 도착 3경로·streaming 2대·on·동기 후보 확인 +시작: 2026-10-17T14:00:50.006785+09:00 +EVIDENCE alias=direct-primary recovery=false version_num=170008 commit=on sync_names="test-standby" +EVIDENCE streaming_replicas=2 +EVIDENCE replica_alias=permission-node2 sync_state=sync +EVIDENCE replica_alias=permission-node3 sync_state=async +EVIDENCE alias=proxy-2 recovery=false version_num=170008 commit=on sync_names="test-standby" +EVIDENCE alias=proxy-3 recovery=false version_num=170008 commit=on sync_names="test-standby" +exit_code=0 diff --git a/docs/test-results/permission-http-failover-20261017/jdbc-preflight-run02.log b/docs/test-results/permission-http-failover-20261017/jdbc-preflight-run02.log new file mode 100644 index 00000000..b2d96fde --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/jdbc-preflight-run02.log @@ -0,0 +1,13 @@ +실행 ID: jdbc-preflight-run02 +목적: 실제 격리 OpenSQL·두 OpenProxy의 read-write Primary 도착·동기 적용 확인 +위치: 로컬 JDBC → IAP → 격리 GCP 컨테이너 +명령: PermissionHaPreflight (pgJDBC 42.7.11) +성공 기준: Primary 도착 3경로·streaming 2대·on·동기 후보 확인 +시작: 2026-10-17T14:18:41.361551+09:00 +EVIDENCE alias=direct-primary recovery=false version_num=170008 commit=on sync_names="test-standby" +EVIDENCE streaming_replicas=2 +EVIDENCE replica_alias=permission-node2 sync_state=sync +EVIDENCE replica_alias=permission-node3 sync_state=async +EVIDENCE alias=proxy-2 recovery=false version_num=170008 commit=on sync_names="test-standby" +EVIDENCE alias=proxy-3 recovery=false version_num=170008 commit=on sync_names="test-standby" +exit_code=0 diff --git a/docs/test-results/permission-http-failover-20261017/prepare-run01.jsonl b/docs/test-results/permission-http-failover-20261017/prepare-run01.jsonl new file mode 100644 index 00000000..92870536 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/prepare-run01.jsonl @@ -0,0 +1,14 @@ +{"at_kst": "2026-10-17T14:00:45.446902+09:00", "run_id": "prepare-run01", "event": "start", "purpose": "역할 어댑터·시험 Proxy·Valkey·JDBC 준비", "location": "격리 컨테이너와 루프백 Valkey", "criteria": "동기 Primary·streaming2·Proxy2경로·실제 Valkey 확인"} +{"at_kst": "2026-10-17T14:00:46.314293+09:00", "run_id": "prepare-run01", "event": "remote", "node_alias": "node1", "result": {"adapter_launch_exit": 0, "role_only_normalization": true, "read_only": true, "scope_guard": true, "port": 18010}} +{"at_kst": "2026-10-17T14:00:47.158416+09:00", "run_id": "prepare-run01", "event": "remote", "node_alias": "node2", "result": {"adapter_launch_exit": 0, "role_only_normalization": true, "read_only": true, "scope_guard": true, "port": 18010}} +{"at_kst": "2026-10-17T14:00:47.999350+09:00", "run_id": "prepare-run01", "event": "remote", "node_alias": "node2", "result": {"isolated_proxy_launch_exit": 0, "proxy_listen_loopback": true, "backend_count": 3, "patroni_port": 18010, "public_proxy_changed": false}} +{"at_kst": "2026-10-17T14:00:48.933123+09:00", "run_id": "prepare-run01", "event": "remote", "node_alias": "node3", "result": {"adapter_launch_exit": 0, "role_only_normalization": true, "read_only": true, "scope_guard": true, "port": 18010}} +{"at_kst": "2026-10-17T14:00:49.777043+09:00", "run_id": "prepare-run01", "event": "remote", "node_alias": "node3", "result": {"isolated_proxy_launch_exit": 0, "proxy_listen_loopback": true, "backend_count": 3, "patroni_port": 18010, "public_proxy_changed": false}} +{"at_kst": "2026-10-17T14:00:49.972443+09:00", "run_id": "prepare-run01", "event": "valkey-start", "exit": 0, "loopback_only": true} +{"at_kst": "2026-10-17T14:00:55.314068+09:00", "run_id": "prepare-run01", "event": "jdbc", "observation": "EVIDENCE alias=direct-primary recovery=false version_num=170008 commit=on sync_names=\"test-standby\""} +{"at_kst": "2026-10-17T14:00:55.314555+09:00", "run_id": "prepare-run01", "event": "jdbc", "observation": "EVIDENCE streaming_replicas=2"} +{"at_kst": "2026-10-17T14:00:55.314596+09:00", "run_id": "prepare-run01", "event": "jdbc", "observation": "EVIDENCE replica_alias=permission-node2 sync_state=sync"} +{"at_kst": "2026-10-17T14:00:55.314638+09:00", "run_id": "prepare-run01", "event": "jdbc", "observation": "EVIDENCE replica_alias=permission-node3 sync_state=async"} +{"at_kst": "2026-10-17T14:00:55.314661+09:00", "run_id": "prepare-run01", "event": "jdbc", "observation": "EVIDENCE alias=proxy-2 recovery=false version_num=170008 commit=on sync_names=\"test-standby\""} +{"at_kst": "2026-10-17T14:00:55.314688+09:00", "run_id": "prepare-run01", "event": "jdbc", "observation": "EVIDENCE alias=proxy-3 recovery=false version_num=170008 commit=on sync_names=\"test-standby\""} +{"at_kst": "2026-10-17T14:00:55.314711+09:00", "run_id": "prepare-run01", "event": "complete", "exit": 0} diff --git a/docs/test-results/permission-http-failover-20261017/reset-before-http-run04/reset.jsonl b/docs/test-results/permission-http-failover-20261017/reset-before-http-run04/reset.jsonl new file mode 100644 index 00000000..b3ab3e07 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/reset-before-http-run04/reset.jsonl @@ -0,0 +1,21 @@ +{"at_kst": "2026-10-17T14:17:58.822180+09:00", "run_id": "reset-before-http-run04-reset", "event": "start", "purpose": "실패 회차와 분리된 격리 데이터·DCS 초기화", "criteria": "공개 설정 불변, 초기 Primary1·standby2, 스키마만 복구"} +{"at_kst": "2026-10-17T14:17:59.773790+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "schema_snapshot_stored": true, "bytes": 109676, "sha256": "6e0b694a702059d2c3c6a74d57e7dd30a32066df26767d1e96afed179e692193"}} +{"at_kst": "2026-10-17T14:18:00.749605+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node2", "result": {"node_alias": "node2", "schema_snapshot_stored": true, "bytes": 109676, "sha256": "6e0b694a702059d2c3c6a74d57e7dd30a32066df26767d1e96afed179e692193"}} +{"at_kst": "2026-10-17T14:18:01.684647+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node3", "result": {"node_alias": "node3", "schema_snapshot_stored": true, "bytes": 109676, "sha256": "6e0b694a702059d2c3c6a74d57e7dd30a32066df26767d1e96afed179e692193"}} +{"at_kst": "2026-10-17T14:18:02.630596+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node3", "result": {"node_alias": "node3", "action": "stop", "completed": true, "at_utc": "2026-10-17T05:18:02.492079+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:03.577431+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node2", "result": {"node_alias": "node2", "action": "stop", "completed": true, "at_utc": "2026-10-17T05:18:03.464187+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:04.495691+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "stop", "completed": true, "at_utc": "2026-10-17T05:18:04.376991+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:05.300601+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "reset-dcs", "completed": true, "at_utc": "2026-10-17T05:18:05.184226+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:06.170176+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "reset-data", "completed": true, "at_utc": "2026-10-17T05:18:06.056797+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:07.059235+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node2", "result": {"node_alias": "node2", "action": "reset-data", "completed": true, "at_utc": "2026-10-17T05:18:06.943827+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:07.950850+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node3", "result": {"node_alias": "node3", "action": "reset-data", "completed": true, "at_utc": "2026-10-17T05:18:07.832987+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:08.922393+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "start", "completed": true, "at_utc": "2026-10-17T05:18:08.784307+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:14.001998+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node2", "result": {"node_alias": "node2", "action": "start", "completed": true, "at_utc": "2026-10-17T05:18:13.875958+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:22.555240+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node3", "result": {"node_alias": "node3", "action": "start", "completed": true, "at_utc": "2026-10-17T05:18:22.415768+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:27.789744+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "restore-schema", "completed": true, "at_utc": "2026-10-17T05:18:27.671946+00:00", "production_config_unchanged": true}} +{"at_kst": "2026-10-17T14:18:28.621088+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node1", "result": {"adapter_launch_exit": 0, "role_only_normalization": true, "read_only": true, "scope_guard": true, "port": 18010}} +{"at_kst": "2026-10-17T14:18:29.465119+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node2", "result": {"adapter_launch_exit": 0, "role_only_normalization": true, "read_only": true, "scope_guard": true, "port": 18010}} +{"at_kst": "2026-10-17T14:18:30.314269+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node3", "result": {"adapter_launch_exit": 0, "role_only_normalization": true, "read_only": true, "scope_guard": true, "port": 18010}} +{"at_kst": "2026-10-17T14:18:31.153572+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node2", "result": {"isolated_proxy_launch_exit": 0, "proxy_listen_loopback": true, "backend_count": 3, "patroni_port": 18010, "public_proxy_changed": false}} +{"at_kst": "2026-10-17T14:18:32.008976+09:00", "run_id": "reset-before-http-run04-reset", "event": "remote", "node_alias": "node3", "result": {"isolated_proxy_launch_exit": 0, "proxy_listen_loopback": true, "backend_count": 3, "patroni_port": 18010, "public_proxy_changed": false}} +{"at_kst": "2026-10-17T14:18:32.009187+09:00", "run_id": "reset-before-http-run04-reset", "event": "complete"} diff --git a/docs/test-results/permission-http-failover-20261017/ssh-preflight-run01.jsonl b/docs/test-results/permission-http-failover-20261017/ssh-preflight-run01.jsonl new file mode 100644 index 00000000..191fb828 --- /dev/null +++ b/docs/test-results/permission-http-failover-20261017/ssh-preflight-run01.jsonl @@ -0,0 +1,7 @@ +{"at_kst": "2026-10-17T13:54:47.598638+09:00", "run_id": "ssh-preflight-run01", "event": "start", "purpose": "기존 SSH 자격으로 공개 상태 읽기와 격리 시험 준비 가능 여부 확인", "location": "기존 라이선스 호스트 3대", "procedure": "기존 키·IAP로 public-final-health 읽기만 실행", "criteria": "실행 권한 확인, 설정 변경 없음, 비공개 식별자 기록 없음"} +{"at_kst": "2026-10-17T13:54:52.032491+09:00", "run_id": "ssh-preflight-run01", "event": "remote", "node_alias": "node1", "result": {"node_alias": "node1", "action": "public-final-health", "member_count": 3, "roles": {"leader": 1, "replica": 2}, "states": {"running": 1, "streaming": 2}, "at_utc": "2026-10-17T04:54:51.911446+00:00"}} +{"at_kst": "2026-10-17T13:54:52.032764+09:00", "run_id": "ssh-preflight-run01", "event": "ssh-result", "node_alias": "node1", "result": {"node_alias": "node1", "exit": 0, "stderr": "omitted", "debug": {"key_offered": false, "key_accepted": false, "signing_failed": false, "partial_success": false, "keyboard_interactive": false, "signing_done": false, "key_decryption": false, "account_locked": false, "authentication_methods_publickey": false}}} +{"at_kst": "2026-10-17T13:54:52.032837+09:00", "run_id": "ssh-preflight-run01", "event": "remote", "node_alias": "node2", "result": {"node_alias": "node2", "action": "public-final-health", "member_count": 3, "roles": {"leader": 1, "replica": 2}, "states": {"running": 1, "streaming": 2}, "at_utc": "2026-10-17T04:54:51.873003+00:00"}} +{"at_kst": "2026-10-17T13:54:52.032874+09:00", "run_id": "ssh-preflight-run01", "event": "ssh-result", "node_alias": "node2", "result": {"node_alias": "node2", "exit": 0, "stderr": "omitted", "debug": {"key_offered": false, "key_accepted": false, "signing_failed": false, "partial_success": false, "keyboard_interactive": false, "signing_done": false, "key_decryption": false, "account_locked": false, "authentication_methods_publickey": false}}} +{"at_kst": "2026-10-17T13:54:52.032910+09:00", "run_id": "ssh-preflight-run01", "event": "remote", "node_alias": "node3", "result": {"node_alias": "node3", "action": "public-final-health", "member_count": 3, "roles": {"leader": 1, "replica": 2}, "states": {"running": 1, "streaming": 2}, "at_utc": "2026-10-17T04:54:51.869403+00:00"}} +{"at_kst": "2026-10-17T13:54:52.032940+09:00", "run_id": "ssh-preflight-run01", "event": "ssh-result", "node_alias": "node3", "result": {"node_alias": "node3", "exit": 0, "stderr": "omitted", "debug": {"key_offered": false, "key_accepted": false, "signing_failed": false, "partial_success": false, "keyboard_interactive": false, "signing_done": false, "key_decryption": false, "account_locked": false, "authentication_methods_publickey": false}}}