diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59391b3b2..6bbdcb0a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,9 @@ name: CI on: push: branches: [main] - pull_request: - branches: [main] + # Every pull request target, including immutable stack branches, receives the + # same exact-head, synthetic-merge, and buyer-readiness acceptance evidence. + pull_request: {} permissions: contents: read @@ -17,14 +18,86 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Verify exact checked-out revision + env: + EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA" - name: Use preinstalled Temurin JDK 21 # Uses the runner image's bundled JDK instead of actions/setup-java to # keep every workflow dependency hash-pinned (Scorecard Pinned-Dependencies). run: | echo "JAVA_HOME=$JAVA_HOME_21_X64" >> "$GITHUB_ENV" echo "$JAVA_HOME_21_X64/bin" >> "$GITHUB_PATH" - - name: Run tests - run: mvn -B --no-transfer-progress test + - name: Run tests and coverage acceptance gates + shell: bash + run: | + if ! mvn -B --no-transfer-progress verify; then + if [[ -f target/site/jacoco/jacoco.csv ]]; then + echo "::group::JaCoCo CSV diagnostics" + cat target/site/jacoco/jacoco.csv + echo "::endgroup::" + fi + if [[ -f target/site/jacoco/jacoco.xml ]]; then + echo "::group::JaCoCo uncovered line diagnostics" + python3 - <<'PY' + import xml.etree.ElementTree as ET + from pathlib import Path + + report = Path("target/site/jacoco/jacoco.xml") + root = ET.parse(report).getroot() + gaps = [] + for package in root.findall("package"): + package_name = package.get("name", "") + for source_file in package.findall("sourcefile"): + source_name = source_file.get("name", "") + source_path = f"{package_name}/{source_name}" if package_name else source_name + for line in source_file.findall("line"): + missed_instructions = int(line.get("mi", "0")) + missed_branches = int(line.get("mb", "0")) + if missed_instructions or missed_branches: + gaps.append( + ( + source_path, + int(line.get("nr", "0")), + missed_instructions, + missed_branches, + ) + ) + + for source_path, line_number, missed_instructions, missed_branches in gaps: + print( + f"{source_path}:{line_number}: " + f"missed_instructions={missed_instructions} " + f"missed_branches={missed_branches}" + ) + PY + echo "::endgroup::" + fi + exit 1 + fi + python3 scripts/verify_maven_test_reports.py + + merge-compatibility: + name: Maven merge compatibility + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Verify merge revision + env: + EXPECTED_SHA: ${{ github.sha }} + run: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA" + - name: Use preinstalled Temurin JDK 21 + run: | + echo "JAVA_HOME=$JAVA_HOME_21_X64" >> "$GITHUB_ENV" + echo "$JAVA_HOME_21_X64/bin" >> "$GITHUB_PATH" + - name: Verify merged result + run: | + mvn -B --no-transfer-progress verify + python3 scripts/verify_maven_test_reports.py script-checks: name: Buyer-readiness script tests @@ -33,6 +106,11 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Verify exact checked-out revision + env: + EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA" - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.12' diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index c2178971c..ea6ae6eb7 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -45,6 +45,14 @@ jobs: - TenantClaimsFuzzTest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Verify exact checked-out revision + env: + EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA" - name: Set up JDK 21 uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 diff --git a/AGENTS.md b/AGENTS.md index ae0a7b55c..e314601df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,10 +7,19 @@ including mandatory quality and security merge gates. ## Mandatory merge gates -- `mvn -DskipTests compile` must pass with warning/deprecated budget = 0. -- `mvn test` must pass. -- JaCoCo coverage for production package must remain 100% line/branch. -- JavaDoc gate must pass (`mvn -q -DskipTests javadoc:javadoc`) with no warnings/errors. +- `mvn -B --no-transfer-progress verify` is the authoritative local and CI + acceptance command. Do not substitute `compile`, `test`, or a predecessor + head result for this exact-head lifecycle. +- Java 21 compilation must pass with warning and deprecation budget = 0. +- Every test must pass with zero failures, errors, and skips. +- JaCoCo coverage for the `com.clearfolio.viewer.*` production package must + remain 100% statement/line and branch coverage, expressed as zero missed + production lines and branches. +- The verify lifecycle must generate public Javadocs with Maven Javadoc Plugin + 3.12.0, `doclint=all`, `failOnError=true`, and `failOnWarnings=true`. Public + record components, constructors, methods, enum values, fields, parameters, + return values, and thrown failures must be understandable without reading the + implementation. - Markdown lint for changed docs must pass. - Security evidence must be attached on PR (SAST/code-scanning checks). - CodeQL Java/Kotlin analysis must remain enabled through repository default @@ -31,10 +40,15 @@ including mandatory quality and security merge gates. `python3 scripts/summarize_buyer_readiness.py --manifest docs/diligence/2026-07-03-buyer-data-room-manifest.json --output docs/diligence/2026-07-03-buyer-readiness-scorecard.md --summary docs/qa/evidence/2026-07-02-krw2b-sale-readiness/buyer-readiness-scorecard-summary.json --check`. - Figma Slides generation payload check must pass: `python3 scripts/check_figma_deck_payload.py --payload docs/design/2026-07-03-buyer-diligence-slides-generation-payload.json --summary docs/qa/evidence/2026-07-02-krw2b-sale-readiness/figma-deck-payload-check.json`. -- `mvn test` includes `DependencyPolicyTest`, which prevents reintroducing the - broad `tika-parsers-standard-package`, default Logback starter, or excluded - Jakarta annotation dependency unless a future PR updates the license policy, - SBOM evidence, attribution package, and buyer diligence docs together. +- `mvn verify` includes `DependencyPolicyTest`, which prevents reintroducing the + broad `tika-parsers-standard-package`, default Logback starter, excluded + Jakarta annotation dependency, an unreviewed Netty version, or a weakened + public-Javadoc gate unless a future PR updates the corresponding security, + license, SBOM, attribution, acceptance, and buyer-diligence evidence together. +- CI, Security Scan, SAST Semgrep, every fuzz target, required organization + reviews, and branch protection must all pass on the exact current PR head. + Queued, pending, cancelled, skipped-required, stale-head, or predecessor-head + evidence is not passing. ## Change management rule @@ -58,7 +72,7 @@ Codex, Cursor, opencode, …) working in this repo. then **remediate**: - This is a Maven / Spring Boot app — findings are almost always vulnerable Java dependencies. Fix by bumping the offending artifact (or its managed - version) in `pom.xml`; re-run `mvn -DskipTests compile` and `mvn test`. + version) in `pom.xml`; re-run `mvn -B --no-transfer-progress verify`. - There is currently no `Dockerfile` or k8s manifest here; if one is added, trivy will also flag image/IaC misconfigs — fix those at the source. - For a genuine false positive only, add a narrow, **documented** @@ -82,15 +96,32 @@ Codex, Cursor, opencode, …) working in this repo. - Reference implementation: xtrmLLMBatchPython's pgcrypto-encrypted Postgres credential registry (`get_credential(name)`). Reuse that pattern (a DB-backed KV is fine) unless a dedicated KV is adopted. -- **This repo applies** — it is a Spring Boot service with real runtime secrets - (artifact-token HMAC secret, tenant-claims HMAC secret). **Known deviation to - migrate:** those secrets are currently injected straight from env via Spring - placeholders in `application-buyer-demo.yml` - (`clearfolio.artifact-token.secret: ${CLEARFOLIO_ARTIFACT_TOKEN_SECRET:}`, - `clearfolio.tenant-claims.hmac-secret: ${CLEARFOLIO_TENANT_CLAIMS_HMAC_SECRET:}`, - consumed by `ArtifactLinkService` / `TenantAccessService`). Move these to a - KV-backed lookup so env is only the bootstrap transport into the KV. New - secrets/credentials must go through the KV from the start, not new env reads. +- **This repo applies** — it is a Spring Boot service with real runtime secrets. + Tenant-claim and artifact-token HMAC secrets are loaded respectively as + `clearfolio.tenant-claims.hmac-secret` and + `clearfolio.artifact-token.secret` from the Spring config-tree credential + mount selected by the non-secret `CLEARFOLIO_SECRET_CONFIG_DIR` bootstrap + setting. Do not restore direct runtime environment binding for either key. + New secrets and credentials must enter through the mounted credential source, + not new process-environment reads. + +### Durable cleanup boundary + +- This slice integrates a single-process, receipt-first cleanup worker. It + forces deletion intent before the first artifact read, binds an exact digest + before metadata tombstoning, retries incomplete cleanup after startup and on + a bounded fixed-delay schedule, and exposes only low-cardinality aggregate + evidence. The filesystem-backed receipt ledger is restart-replayable; the + in-memory adapter remains available for standalone tests and ephemeral use. +- Do not overstate that reference boundary. It does not provide a cross-resource + transactional outbox, a distributed generation fence, or remote-object-store + atomicity across multiple service instances. Production adapters must provide + equivalent durable uniqueness, object-version preconditions, idempotent + retries, and operator recovery evidence before distributed cutover. +- Issue #263 remains the umbrella for the distributed adapter and later truthful + API/UI lifecycle work. Do not describe durable cleanup as absent, and do not + describe the current single-process implementation as a distributed deletion + transaction. ### Code exploration @@ -108,11 +139,11 @@ Codex, Cursor, opencode, …) working in this repo. DOM-decomposes emails and files into a persisted knowledge graph. Each component is a standalone program that must ALSO work as a git submodule of the hub, grown separately and together. -- Sibling components: **waf-ids-ai-soc** (WAF / IDS / AI SOC / LB / APIM), +- Sibling components: **wardnet** (WAF / IDS / AI SOC / LB / APIM), **pg-erd-cloud** (ERD tool), **contextual-orchestrator** (LLM cost/perf/upstream-LB gateway, beyond LiteLLM), **codec-carver** (STT / omni-modal speech-video codec), **fast-mlsirm** (LLM-as-a-Judge calibration + - evaluation-item quality, using aFIPC FIPC + kaefa item-fit), **feelanet-adfs** + evaluation-item quality, using aFIPC FIPC + kaefa item-fit), **keyverse** (passwordless SSO — OIDC/SCIM/ADFS/LDAP/FIDO2/OAuth2.1, eliminate passwords), **newsdom-api** (PDF→DOM sidecar), and **semantic-data-portal** (upper ontology / catalog / governance plane with its own graph engine). diff --git a/CHANGELOG.md b/CHANGELOG.md index 373a661a2..ab3facb27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,68 @@ -## [Unreleased] -### Added -- **UI UX 개선**: 'Details' 버튼 클릭 시, 작업 상세 정보 로드 중에 사용자가 명시적인 로딩 상태를 확인할 수 있도록 'Loading...' 텍스트와 비활성화 상태를 표시하도록 추가했습니다. - -### Changed -- PDF.js WebJar를 `6.1.200`으로 올리고, Clearfolio가 동일 버전의 `pdf.mjs`와 `pdf.worker.mjs`를 직접 사용해 서명된 same-origin artifact의 첫 페이지를 렌더링하도록 통합했습니다. 패키징·셸 경로·서명된 `artifactToken` 흐름을 회귀 테스트로 고정했습니다. - # Changelog ## [Unreleased] -### 추가된 기능 (Added) +### Added + +- **UI UX 개선**: 'Details' 버튼 클릭 시, 작업 상세 정보 로드 중에 사용자가 명시적인 로딩 상태를 확인할 수 있도록 'Loading...' 텍스트와 비활성화 상태를 표시하도록 추가했습니다. +- `RECEIPT_V1` append-only artifact-deletion receipt foundation을 추가했습니다. 영구 `job_id`, tenant, artifact SHA-256, idempotency request, privacy-safe audit correlation, 단조 상태와 시각, controlled failure code를 하나의 불변 증거로 묶고 standalone in-memory 및 restart-replay file adapter를 같은 `ArtifactDeletionReceiptStore` 계약으로 제공합니다. +- `ArtifactDeletionCoordinator`, primary `DurableDocumentDeletionService`, fixed-memory `ArtifactLifecycleLockRegistry`, `LifecycleFencedArtifactStore`를 추가했습니다. Receipt-first metadata tombstone, exact-digest cleanup, repeated DELETE idempotency, startup/fixed-delay recovery, bounded batch, write-after-receipt rejection을 독립 Slice C 계약으로 제공합니다. - **관리자용 단건 작업 삭제 및 재시도 API 추가** - 특정 변환 작업을 삭제할 수 있는 `DELETE /api/v1/admin/convert/jobs/{jobId}` 엔드포인트를 추가했습니다. - 실패(dead-lettered) 상태인 작업을 관리자가 재시도 큐에 등록할 수 있는 `POST /api/v1/admin/convert/jobs/{jobId}/retry` 엔드포인트를 추가했습니다. - - **비동기 버튼 로딩 피드백 및 상태 복원 개선** - - KPI 스냅샷 증거를 다시 불러오는 `refreshKpiEvidence` 동작 중에 "Refresh evidence" 버튼을 비활성화하고 "Refreshing..." 이라는 피드백을 제공하여 사용자의 중복 클릭을 방지했습니다. + - KPI 스냅샷 증거를 다시 불러오는 `refreshKpiEvidence` 동작 중에 "Refresh evidence" 버튼을 비활성화하고 "Refreshing..."이라는 피드백을 제공하여 사용자의 중복 클릭을 방지했습니다. - 버튼 상태 변경 시 내부 DOM 구조를 보존하기 위해 `Array.from(button.childNodes)`로 원래 노드를 저장하고, 성공 및 실패 후 `finally` 블록에서 `replaceChildren(...)`으로 안전하게 복원하도록 구현했습니다. +### Changed + +- PDF.js WebJar를 `6.1.200`으로 올리고, Clearfolio가 동일 버전의 `pdf.mjs`와 `pdf.worker.mjs`를 직접 사용해 서명된 same-origin artifact의 첫 페이지를 렌더링하도록 통합했습니다. 패키징·셸 경로·서명된 `artifactToken` 흐름을 회귀 테스트로 고정했습니다. +- CI가 pull request의 정확한 head SHA를 명시적으로 체크아웃하고 검증하며, 합성 merge revision은 별도 호환성 작업에서 검증하도록 분리했습니다. +- Maven `verify` 단계에서 JaCoCo production line 및 branch missed count가 각각 0인지 강제하고, 실패 시 누락 위치 진단을 출력하도록 했습니다. +- Maven `verify` 이후 Surefire 보고서가 존재하고 실행 테스트 수가 1개 이상이며 skipped·failure·error 수가 모두 0인지 검증합니다. Failsafe 보고서가 생성된 경우 동일한 규칙을 적용하며, 보고서 누락·손상·음수 카운트·전체 skip·실패 결과는 exact-head CI와 merge-compatibility 모두에서 fail closed 처리합니다. +- Maven `verify` 단계에서 Java 21 public Javadocs를 `doclint=all`로 생성하고 warning 또는 error가 하나라도 발생하면 실패하도록 했습니다. 공개 record 구성요소, 생성자, enum 값, 필드와 매개변수 문서를 초보자도 코드 분석 없이 이해할 수 있는 수준으로 보완했습니다. +- Jazzer fuzzing도 pull request의 정확한 head SHA를 명시적으로 체크아웃하고 검증하도록 강화했습니다. +- CycloneDX Maven Plugin 2.9.1의 정확한 `outputFormat`/`outputName` 사용자 속성으로 생성한 61개 구성요소 SBOM과 제3자 고지문을 buyer evidence에 반영했습니다. 생성 source head, UTC 시각, artifact/archive/SBOM/attribution 해시, 17개 Netty 구성요소의 purl·bom-ref·dependency-edge 정합성, 로컬 생성 증거와 공유 가능한 데이터룸 증거의 경계를 ADR 및 실행 가능한 drift test로 고정했습니다. +- Spring scheduling을 활성화하고 artifact deletion recovery를 30초 fixed delay, 실행당 최대 100개 receipt로 제한했습니다. Cleanup 완료·실패 누적값과 pending receipt 수는 dependency-free aggregate evidence로 제공하며 tenant/job/checksum/예외/경로 차원을 저장하지 않습니다. +- Bounded artifact-deletion recovery는 실패한 pre-snapshot receipt의 attempt 수, controlled failure code, `lastAttemptAt`, 최신 durable transition time을 append-only ledger에 저장하고 그 시각으로 다음 eligible 순서를 결정합니다. 영구적으로 실패하는 가장 오래된 receipt는 bounded batch 뒤의 older eligible cleanup을 기아 상태로 만들지 않으며, process restart 후에도 ledger replay가 같은 순서를 복원하므로 process-local rotation cursor에 의존하지 않습니다. +- Durable artifact deletion은 프로세스 전체 모니터 대신 공유 per-job lifecycle lock만 사용해 서로 다른 문서의 삭제와 복구를 병렬화합니다. WebFlux 관리자 DELETE는 blocking artifact I/O를 Reactor bounded-elastic worker로 격리합니다. + +### Security + +- `GET /api/v1/convert/jobs/{jobId}/download`가 리소스 조회 전에 전용 `artifact:read` 권한을 검증하고, PDF 저장소 접근 전에 작업의 tenant 소유권을 확인하도록 강화했습니다. `job:read`만으로는 문서 바이트를 읽을 수 없으며, 인증 누락·권한 누락·교차 tenant UUID 접근은 각각 fail closed 처리되고 교차 tenant 요청은 리소스 존재를 숨기는 `404`를 반환합니다. +- Deletion receipt ledger는 각 transition snapshot을 16 KiB 이하 strict UTF-8 `RECEIPT_V1` record로 검증하고 append 후 `FileChannel.force(true)`를 완료하기 전 durable 상태를 반환하지 않습니다. Record terminator는 host-independent fixed LF로 고정하고 commit delimiter로 취급하므로 non-empty unterminated final tail은 crash 또는 torn append의 미확정 증거로 간주해 startup에서 fail closed 처리하고 replay하지 않습니다. Replay는 attempt evidence, 시각 순서, immutable request identity, pending-marker-to-exact-digest의 단방향 binding, legal successor를 검증합니다. Cleanup coordinator는 첫 artifact read 전에 `DELETION_REQUESTED` receipt를 `pending` marker로 영속화하고, successful read 후 exact digest 또는 confirmed-absence digest를 force한 뒤에만 metadata tombstone을 허용합니다. Initial read failure는 absence로 오인하지 않고 metadata를 유지한 채 restart-safe retry evidence와 low-cardinality failure metric을 남깁니다. +- Conversion과 deletion은 동일 per-job lifecycle lock을 공유합니다. Artifact boundary는 receipt가 생긴 job ID의 이후 `putPdf`를 거부하므로 deletion 완료 뒤 late worker가 confidential bytes를 재생성할 수 없습니다. Multi-instance adapter는 동일한 distributed generation fence 또는 object-version precondition을 구현해야 합니다. +- Maven XML 테스트 보고서 검증기는 각 `testsuite`의 `tests`, `skipped`, `failures`, `errors` 속성을 모두 필수 증거로 요구합니다. 누락된 결과 수를 암묵적으로 0으로 간주하지 않고 fail closed 처리하며, 각 속성 누락 회귀 테스트를 추가했습니다. +- Maven XML 테스트 보고서 검증기는 UTF-8만 허용하고 UTF-8 BOM은 수용하며, NUL 바이트·DTD·엔터티 선언을 파싱 전에 거부합니다. UTF-16 같은 대체 인코딩으로 위험 선언을 바이트 검사에서 숨기는 우회와 외부 엔터티 읽기·엔터티 확장형 서비스 거부를 회귀 테스트로 차단했습니다. +- Maven XML 테스트 보고서 검증기는 파일당 16 MiB 상한을 적용하고 한 번의 제한된 읽기로 실제 입력 크기를 검증합니다. 테스트 코드가 보고서 파일을 교체하거나 확장해도 크기 사전검사와 파싱 사이의 경쟁 조건을 이용할 수 없습니다. +- Spring Boot 3.5.16이 관리하던 Netty `4.1.135.Final` 전이 의존성 전체를 Spring Boot의 공식 `netty.version` 속성을 통해 `4.1.136.Final`로 정렬했습니다. 실제 POM을 읽는 회귀 테스트와 보안 ADR을 추가해 개별 Netty 모듈의 혼합 버전 및 향후 무의식적 downgrade를 차단했습니다. +- 정책 재정의 승인자의 원문 식별자를 감사 로그에서 제거하고, 전용 회전형 키와 도메인 분리를 사용하는 HMAC 기반 `approverFingerprint`로 대체했습니다. 정책 재정의 서명이 비활성화된 경우에만 전용 키 부재를 비상관 `unavailable` 표식으로 표현하며, 원문이나 비키 해시로 폴백하지 않습니다. +- 정책 재정의 서명 키를 활성화하면서 전용 감사 가명화 키를 누락하면 Spring 시작과 `DefaultDocumentValidationService`의 독립·모듈식 직접 생성을 모두 거부하도록 강화했습니다. 관리자 예외를 승인하면서 승인자별 상관 가능한 감사 증거를 남기지 못하는 구성을 모든 실행 모드에서 fail closed로 차단하고, 두 키의 최소 강도와 용도 분리를 유지합니다. +- 감사 가명화 키의 소유권, 회전, 보존, 사고 대응 및 GDPR상 가명정보의 개인정보 지위를 문서화하고, 원문 승인자 식별자와 승인 토큰이 로그에 남지 않는 회귀 테스트를 추가했습니다. +- 경로·쿼리 파라미터 타입 변환 실패 응답에서 사용자가 제출한 거부 값을 고정된 `[redacted]` 표식으로 대체해 오류 응답을 통한 개인정보·비밀값 반사를 차단했습니다. 값이 실제로 없었던 경우에만 `null` 진단을 유지합니다. +- 관리자 API에 서명된 tenant claim 검증, `admin:read`/`admin:write` 최소 권한, tenant 소유권 검사를 적용했습니다. 누락 및 cross-tenant 객체는 동일한 not-found 응답으로 은폐합니다. +- 관리자 목록 조회는 repository 경계에서 tenant predicate를 적용한 뒤 선택적 dead-letter 필터를 처리하도록 변경했습니다. +- 관리자 delete/retry가 검증된 `TenantContext`를 tenant-aware service mutation boundary에 전달하도록 변경해 controller 우회 호출에서도 소유권 검사가 적용되도록 했습니다. +- tenant-aware service 기본 구현은 global lookup 또는 legacy mutation을 호출하지 않고 `false`/`NOT_FOUND`로 실패 종료합니다. 모듈형 대체 adapter는 원자적 tenant-scoped mutation을 명시적으로 구현해야 관리자 작업을 성공시킬 수 있습니다. +- 관리자 delete/retry는 `deleteByTenantAndId`와 `retryDeadLetteredForTenant` 원자적 tenant-scoped persistence contract를 사용합니다. artifact cleanup은 소유권이 확인된 repository 삭제가 성공한 뒤에만 실행되며, worker enqueue는 소유권 확인과 dead-letter 상태 전이가 원자적으로 성공한 뒤에만 실행됩니다. +- tenant-scoped lookup/delete/retry는 누락된 job UUID를 예외로 노출하지 않고 empty/`false`/`NOT_FOUND`로 실패 종료하며 기존 저장 상태를 변경하지 않습니다. +- tenant-scoped content-hash lookup은 tenant 식별자가 `null` 또는 공백이면 demo tenant로 폴백하지 않고 빈 결과로 fail closed 처리합니다. 명시적인 legacy `findByContentHash` 호출만 `buyer-demo` 범위를 선택할 수 있으며, 회귀 테스트가 누락된 tenant로 demo 보조 인덱스를 조회할 수 없음을 고정합니다. +- tenant-and-content-hash 보조 인덱스가 현재 UUID 레코드의 tenant/hash와 일치하는지 재검증합니다. conversion-job UUID는 불변 lifecycle identity로 예약하며, 동일한 live 객체의 idempotent save만 허용하고 distinct live 객체 또는 삭제 후 tombstoned UUID의 재바인딩은 보조 인덱스 작업 전에 fail closed 처리합니다. +- tenant-scoped 삭제와 동일 UUID 저장이 동시에 발생해도 primary job map, 영구 UUID reservation, tenant-content secondary index를 하나의 임계 구역에서 갱신합니다. 삭제는 live 레코드와 인덱스를 제거하지만 UUID reservation은 해제하지 않으므로 대기 중인 교체 저장은 실패하며, durable adapter도 동일 트랜잭션의 uniqueness와 tombstone 또는 lifecycle-generation reservation을 구현해야 합니다. +- 관리자 허용·거부·미존재·재시도 불가·실패 결정을 actor·tenant·conversion-job별 도메인 분리 HMAC 지문으로 기록하고, raw subject·tenant·job UUID·claim signature·문서 메타데이터가 감사 로그와 retry provenance에 남지 않도록 했습니다. +- buyer-demo profile의 tenant-claims HMAC secret 환경변수 직접 바인딩을 제거하고 공통 Spring config-tree secret mount에서 읽도록 변경했습니다. + +### Fixed + +- 뷰어 UI의 재시도 버튼 로딩 상태가 내부 DOM을 손상시키지 않고 안전하게 복원되도록 수정했습니다. +- 관리자 삭제의 첫 artifact snapshot read가 실패하면 receipt조차 생성되지 않아 restart/scheduled recovery가 불가능하던 경로를 수정했습니다. 이제 read 전에 `pending` marker가 포함된 durable receipt를 force하고, exact digest가 성공적으로 bound될 때까지 metadata를 유지합니다. 회귀 테스트는 최초 read 실패 → process restart → exact digest binding → metadata tombstone → artifact cleanup 완료를 결정적으로 검증합니다. +- 관리자 삭제에서 metadata 제거 뒤 artifact-store 실패가 로그로만 소실되던 CWE-459 경로를 durable receipt와 bounded recovery로 수정했습니다. 같은 tenant의 반복 DELETE는 기존 receipt를 resume/no-op하며, 다른 tenant receipt는 숨깁니다. +- Legacy global DELETE가 metadata tombstone 뒤 실패한 receipt를 다시 호출했을 때 cleanup을 재개하지 않던 상태 전이 오류를 수정했습니다. ## [0.1.0] - 2026-06-25 ### 추가된 기능 (Added) + - **비동기 버튼 로딩 상태 UX 개선 (Async Button Loading States)** - 문서 제출(`submitDocument`), 데모 데이터 로드(`loadDemoData`), 실패 작업 재시도(`retryActiveJob`) 등 비동기 요청을 수행하는 버튼들에 대해 처리 중 명시적인 로딩 상태(Loading, Submitting, Retrying 등)를 추가했습니다. - 사용자의 중복 클릭을 방지하기 위해 작업 중에는 버튼이 비활성화되도록 수정했습니다. @@ -31,15 +72,17 @@ - 변환 성공한 작업에 대한 PDF 바이너리 다운로드 엔드포인트를 구현했습니다. - 파일 다운로드 시 원본 파일명 기반의 `.pdf` 확장자 처리와 파일 무결성을 위한 체크섬(`X-Checksum-Sha256`) 헤더를 응답에 포함하도록 지원합니다. -- **관리자용 전체 작업 조회 API 추가 (`GET /api/v1/admin/convert/jobs`)** - - 시스템 내 전체 변환 작업 내역을 조회할 수 있는 Admin 엔드포인트를 구현했습니다. +- **관리자용 tenant 범위 작업 조회 API 추가 (`GET /api/v1/admin/convert/jobs`)** + - 검증된 요청 tenant 범위 내 작업 내역을 조회할 수 있는 Admin 엔드포인트를 구현했습니다. - `deadLettered` 필터 조건을 쿼리 파라미터로 제공하여 실패한 작업들만 조회할 수 있습니다. - - 관련 `AdminJobListResponse` DTO 모델과 이를 처리하는 Repository 및 Service 계층의 `findAll`/`getAllJobs` 메서드를 추가했습니다. + - 관련 `AdminJobListResponse` DTO 모델과 이를 처리하는 Repository 및 Service 계층의 tenant-scoped 조회 메서드를 추가했습니다. ### 테스트 커버리지 (Tests) + - 신규 구현된 Repository, Service, Controller 계층에 대한 유닛 테스트(Unit Tests)를 작성하여 JaCoCo 기준 라인 및 브랜치 커버리지 100%를 달성했습니다. ### 보안 (Security) + - **의존성 취약점 일괄 정리 (trivy-fs / osv-scan 대응)**: Spring Boot 부모 POM을 `3.5.0`에서 `3.5.16`으로 올려 Spring Framework, Netty, Reactor Netty, logback 관련 다수의 HIGH/MEDIUM 권고를 해소했습니다. - Jackson 계열을 `jackson-bom` import로 `2.22.1`에 고정하여 jackson-databind case-insensitive deserialization bypass 권고(GHSA-5jmj-h7xm-6q6v / CVE-2026-54515)를 제거했습니다. - Apache Tika 표준 파서를 통해 유입되던 전이 의존성을 `dependencyManagement`로 고정했습니다: junrar `7.6.0`(경로 순회 RCE/파일 쓰기), commons-io `2.20.0`(XmlStreamReader DoS), commons-lang3 `3.18.0`, BouncyCastle `bcprov-jdk18on 1.84` 및 `bcpkix-jdk18on 1.84`(CRITICAL/Medium). 전체 347개 테스트 통과를 확인했습니다. @@ -47,7 +90,3 @@ - 과거 QA 증거 SBOM의 Jackson purl/ref도 `2.21.5`로 갱신해 Scorecard/OSV가 저장소 내 stale SBOM을 취약 의존성으로 재탐지하지 않게 했습니다. - 루트 `LICENSE`와 Maven license metadata를 추가해 Scorecard License alert가 표준 Apache-2.0 파일을 확인할 수 있게 했습니다. - logback-core 신규 권고(GHSA-jhq6-gfmj-v8fx) 대응을 위해 Logback 관리 버전을 `1.5.35`로 고정했습니다. -- 저장소 보안 정책, Maven/GitHub Actions Dependabot 설정, 기본 CodeQL/중앙 SAST 운영 지침, 다운로드 파일명 정규화 Jazzer fuzz target을 추가해 Scorecard 보안 거버넌스 신호를 보강했습니다. - -### Fixed -- 뷰어 UI의 재시도 버튼 로딩 상태가 내부 DOM을 손상시키지 않고 안전하게 복원되도록 수정 diff --git a/docs/deployment/2026-07-02-buyer-deployment-integration-playbook.md b/docs/deployment/2026-07-02-buyer-deployment-integration-playbook.md index 8a32dc847..827c11661 100644 --- a/docs/deployment/2026-07-02-buyer-deployment-integration-playbook.md +++ b/docs/deployment/2026-07-02-buyer-deployment-integration-playbook.md @@ -1,65 +1,82 @@ # Buyer Deployment and Integration Playbook -Date: 2026-07-02 +Date: 2026-07-02 +Last updated: 2026-08-07 -This playbook turns the current Clearfolio Viewer sale-readiness slice into a -repeatable buyer sandbox deployment. It is intentionally scoped to the current -runtime: Spring Boot, WebFlux JSON APIs, buyer-demo shell, gateway-signed tenant -headers, signed artifact links, local append-only evidence ledgers, and the -mandatory repository gates. It does not use Figma Code Connect and does not -claim production OIDC/JWT, durable database, durable object storage, or external -legal sign-off. +This playbook turns the current Clearfolio Viewer sale-readiness slice into a repeatable buyer sandbox. The runtime is Spring Boot and WebFlux with a browser shell, gateway-signed tenant headers, signed artifact links, filesystem-backed artifacts, append-only evidence ledgers, a receipt-first deletion worker, and mandatory exact-head repository gates. It does not claim production OIDC/JWT, a distributed database/object-store transaction, external legal sign-off, or a packaged Power Platform connector. -## Buyer-Ready Claim Boundary +## Buyer-ready claim boundary -The deployment can prove: +The current sandbox can prove: -- a buyer can run the upload, conversion, preview, KPI, and operator recovery - demo without adding a frontend framework; -- tenant-scoped JSON APIs can require gateway-signed Clearfolio headers when a - shared HMAC secret is configured; -- preview artifacts require signed artifact tokens, not bare document ids; -- issued links, revocations, artifact reads, and KPI exports can survive a - single-process restart through local append-only evidence ledgers; -- the same Maven, JavaDoc, coverage, Markdown, SAST, SBOM, and license-policy - gates remain attached to PR #74. +- document upload, asynchronous conversion, status polling, preview, KPI evidence, dead-letter retry, and tenant-scoped administration; +- gateway-signed tenant claims with action-specific least privilege; +- tenant-owned document bytes require dedicated `artifact:read`, while preview links require signed tenant-bound artifact tokens; +- signing keys come from a mounted Spring config tree rather than secret-valued runtime environment placeholders; +- filesystem artifacts, issued-link evidence, revocations, artifact reads, KPI exports, and deletion receipts survive a single-process restart; +- authorized deletion persists intent before the first artifact read, binds an exact generation before metadata tombstoning, and performs bounded scheduled recovery after cleanup failure; +- exact-head Maven, Javadoc, coverage, script, SAST, SBOM, attribution, and license-policy gates remain fail closed. -The deployment cannot yet prove: +The sandbox cannot yet prove: -- production OIDC/JWT issuer, audience, expiry, `kid`, and role validation; -- centralized durable job, artifact, revocation, audit, or analytics storage; -- final legal review of the attribution and redistribution package; -- a packaged Power Platform connector. +- production OIDC/JWT issuer, audience, expiry, `kid`, rotation, and role validation; +- centralized durable job, artifact, revocation, audit, and analytics storage across multiple instances; +- a cross-resource transactional outbox spanning a durable database and remote object store; +- a distributed generation fence or remote-object-store atomicity; +- final legal review of attribution and redistribution evidence; +- a buyer-specific packaged connector. -## Runtime Profile +## Runtime profile -Use the `buyer-demo` Spring profile for a buyer sandbox: +Use the `buyer-demo` Spring profile. Runtime key material is loaded from a Spring Boot config-tree mount. `CLEARFOLIO_SECRET_CONFIG_DIR` selects that mount and is not secret. Provision the mounted files `clearfolio.tenant-claims.hmac-secret` and `clearfolio.artifact-token.secret` independently through the deployment platform's secret manager. The tenant-claims key must contain at least 32 UTF-8 bytes for privileged administrative APIs. + +For a local sandbox: ```bash -mkdir -p .clearfolio/buyer-demo +umask 077 +mkdir -p .clearfolio/buyer-demo/secrets +mkdir -p .clearfolio/buyer-demo/data/artifacts +openssl rand -base64 48 \ + > .clearfolio/buyer-demo/secrets/clearfolio.tenant-claims.hmac-secret +openssl rand -base64 48 \ + > .clearfolio/buyer-demo/secrets/clearfolio.artifact-token.secret export SPRING_PROFILES_ACTIVE=buyer-demo -export CLEARFOLIO_TENANT_CLAIMS_HMAC_SECRET="replace-with-gateway-shared-secret" -export CLEARFOLIO_ARTIFACT_TOKEN_SECRET="replace-with-artifact-token-secret" +export CLEARFOLIO_SECRET_CONFIG_DIR="$PWD/.clearfolio/buyer-demo/secrets/" export CLEARFOLIO_ARTIFACT_LINK_LEDGER_PATH="$PWD/.clearfolio/buyer-demo/artifact-link-ledger.log" export CLEARFOLIO_ANALYTICS_SNAPSHOT_LEDGER_PATH="$PWD/.clearfolio/buyer-demo/kpi-snapshot-ledger.log" +export CLEARFOLIO_ARTIFACT_DELETION_LEDGER_PATH="$PWD/.clearfolio/buyer-demo/artifact-deletion-receipts.log" +export CLEARFOLIO_ARTIFACT_STORE_ROOT_DIR="$PWD/.clearfolio/buyer-demo/data/artifacts" export CLEARFOLIO_FRAME_ANCESTORS="self" mvn spring-boot:run ``` -The profile file is -`src/main/resources/application-buyer-demo.yml`. It uses environment variables -only; no secret value is committed. +Spring relaxed binding maps the non-secret path variables to the corresponding `clearfolio.*` properties. The repository defaults remain `data/artifacts` for PDF bytes and `data/artifact-deletion-receipts.log` for deletion receipt snapshots. The receipt file and artifact root must reside on durable owner-restricted storage in shared environments. Do not put either path on an ephemeral container layer when restart recovery is required. + +`src/main/resources/application-buyer-demo.yml` may use environment variables for non-secret operational settings. Neither HMAC key is bound from a secret-valued environment placeholder. Both are read from the common config-tree import in `application.yml`; the environment selects only the bootstrap directory. + +## Durable deletion and recovery operation + +Tenant-owned deletion is routed through `DurableDocumentDeletionService` and `ArtifactDeletionCoordinator`. + +1. The service acquires the shared per-job lifecycle lock. +2. It forces a durable receipt with a controlled `pending` checksum marker before the first artifact read. +3. If that read fails, metadata remains live and the receipt remains recoverable. The failure is represented by a controlled low-cardinality code, not exception text. +4. A successful read binds the same receipt identity one way to the exact SHA-256 digest, or to the explicit confirmed-absence digest when the artifact is truly absent. +5. Only then may tenant-scoped metadata be tombstoned. +6. Cleanup compares the bound generation, deletes idempotently, and records completion or retryable failure. +7. Startup recovery and the fixed-delay worker process a bounded batch of incomplete receipts. + +Defaults are a 30-second retry delay, a maximum of 100 receipts per pass, and a Spring scheduler pool of two threads. Per-job locks serialize the same generation while unrelated jobs can progress. Aggregate evidence is limited to completed, failed, pending, and execution-duration values; it carries no tenant, job, digest, path, filename, or exception-controlled dimension. -For a Power Platform embedding test, replace `CLEARFOLIO_FRAME_ANCESTORS` with -the exact buyer allowlist after the gateway hostname is known. Keep it narrow; -do not use a wildcard until a security owner explicitly accepts that risk. +`LifecycleFencedArtifactStore` prevents a conversion worker from publishing another artifact after receipt acceptance. An in-flight publication that wins the lock first becomes the captured generation and is removed; publication after receipt acceptance fails closed. -## Gateway Claim Contract +This is a restart-safe single-process reference adapter, not a distributed transaction. Multi-instance production deployment remains blocked until adapters provide a cross-resource transactional outbox or equivalent idempotent consumer, durable uniqueness and tombstones, a distributed generation fence, object-version preconditions, remote-object-store atomicity or safely compensating semantics, bounded dead-letter recovery, and operator evidence. -When `CLEARFOLIO_TENANT_CLAIMS_HMAC_SECRET` is set, every protected JSON API -call must include: +## Gateway claim contract + +When `clearfolio.tenant-claims.hmac-secret` is mounted, protected JSON APIs require: - `X-Clearfolio-Tenant-Id` - `X-Clearfolio-Subject-Id` @@ -67,7 +84,7 @@ call must include: - `X-Clearfolio-Claims-Issued-At` - `X-Clearfolio-Claims-Signature` -The HMAC payload is the exact newline-joined string: +The HMAC payload is the exact newline-joined form: ```text tenantId @@ -76,98 +93,83 @@ canonicalPermissions issuedAt ``` -The signature is Base64URL HMAC-SHA256 without padding. The default timestamp -skew window is 300 seconds and can be set with -`CLEARFOLIO_TENANT_CLAIMS_MAX_SKEW_SECONDS`. +The signature is Base64URL HMAC-SHA256 without padding. The default timestamp skew is 300 seconds and is configurable with `CLEARFOLIO_TENANT_CLAIMS_MAX_SKEW_SECONDS`. + +The verifier derives `canonicalPermissions` by splitting on commas, removing NUL, stripping whitespace, dropping empty values, de-duplicating in first-seen order, and joining with commas. Tenant and subject values are sanitized before verification as well. The gateway must send and sign canonical values, not a differently formatted raw header. -`canonicalPermissions` is **not** the raw `X-Clearfolio-Permissions` header -value — the verifier re-derives it before checking the signature, so a buyer -gateway must sign the same derived form or every request returns `401`. The -derivation (see `TenantContext.permissionsOf` / `canonicalPermissions`) is: +The authenticated gateway must strip untrusted inbound `X-Clearfolio-*` headers before mapping the principal, constructing claims, signing, and forwarding. Browsers and external API clients are not trusted issuers. -1. split the header on `,`; -2. sanitize each entry — remove NUL, `strip()` surrounding whitespace, drop empties; -3. de-duplicate **preserving first-seen order** (backed by a `LinkedHashSet`); -4. re-join with `,`. +Example user permission set: -`tenantId` and `subjectId` are sanitized the same way before signing. So a -gateway must send **and sign** already-canonical values: e.g. -`" viewer:read , job:read,viewer:read "` must be signed as -`viewer:read,job:read`. Sign what the verifier will re-derive, not the raw -string. +```text +job:create,job:read,job:retry,viewer:read,artifact:read,artifact-link:create,analytics:read +``` -Buyer-demo permission set: +Example tenant operator additions: ```text -job:create,job:read,job:retry,viewer:read,artifact-link:create,analytics:read +admin:read,admin:write ``` -Production role mapping should later replace this scaffold with validated -gateway or OIDC claims. Do not hand-roll JWT parsing in this service. - -For any environment that sets `SPRING_PROFILES_ACTIVE=production`, the service -fails startup unless `CLEARFOLIO_TENANT_CLAIMS_HMAC_SECRET` is present. The -buyer-demo profile can still run unsigned for local screenshots, but production -cannot accidentally inherit that unsigned mode. - -## Integration Flow - -1. Buyer browser, Power Platform, or internal workflow authenticates at the - buyer-controlled gateway. -2. Gateway maps the principal to Clearfolio tenant id, subject id, and - permissions. -3. Gateway signs the Clearfolio headers and forwards requests to - `POST /api/v1/convert/jobs`, status, viewer bootstrap, retry, artifact-link, - and analytics APIs. -4. Clearfolio verifies the signed headers, enforces permissions, and hides - cross-tenant resources. -5. The buyer-demo shell shows the upload, status, KPI evidence, and operator - recovery path at `GET /`. -6. Viewer bootstrap returns a signed `previewResourcePath`. -7. PDF.js reads `/artifacts/{docId}.pdf` only with a valid artifact token. -8. Optional local ledgers capture issued links, revocations, artifact reads, - and KPI snapshot exports for buyer evidence. - -## API Surface for a Connector +Production role mapping must later replace this scaffold with validated gateway or OIDC claims. Do not hand-roll JWT parsing in this service. + +For `SPRING_PROFILES_ACTIVE=production`, startup fails unless the config-tree mount supplies a sufficiently strong tenant-claims key. Setting only the bootstrap directory does not enable signed claims. Artifact-link issuance reads a distinct strong `clearfolio.artifact-token.secret`; do not restore the retired direct environment binding. + +## Integration flow + +1. A buyer browser, Power Platform client, or internal workflow authenticates at the buyer-controlled gateway. +2. The gateway strips untrusted Clearfolio headers, maps the principal to tenant, subject, and permissions, canonicalizes them, signs them, and forwards the request. +3. Clearfolio verifies the signature, freshness, action permission, and tenant ownership. +4. `POST /api/v1/convert/jobs` creates a durable asynchronous job identity and the worker produces a fenced PDF generation. +5. Status and viewer bootstrap remain tenant scoped; direct download additionally requires `artifact:read`. +6. PDF.js reads only a same-origin artifact authorized by a valid tenant-bound token. +7. Optional ledgers capture issued links, revocations, reads, KPI exports, and deletion receipts. +8. Tenant-owned deletion persists intent, binds the artifact generation, tombstones metadata, and completes or retries physical cleanup. +9. Restart with the same paths replays the append-only evidence and resumes bounded incomplete cleanup. + +## API surface for a connector | Connector step | Endpoint | Permission | Buyer evidence | | --- | --- | --- | --- | -| Submit document | `POST /api/v1/convert/jobs` | `job:create` | Returns `202`, `jobId`, `statusUrl`. | -| Poll lifecycle | `GET /api/v1/convert/jobs/{jobId}` | `job:read` | Shows status, attempts, retry time, dead-letter state. | -| Open viewer | `GET /viewer/{docId}` | HTML shell | Protected JSON APIs decide visible state. | -| Bootstrap preview | `GET /api/v1/viewer/{docId}` | `viewer:read` | Returns short-lived signed artifact URL. | +| Submit document | `POST /api/v1/convert/jobs` | `job:create` | Returns `202`, `jobId`, and `statusUrl`. | +| Poll lifecycle | `GET /api/v1/convert/jobs/{jobId}` | `job:read` | Shows status, attempts, retry time, and dead-letter state. | +| Download result | `GET /api/v1/convert/jobs/{jobId}/download` | `artifact:read` | Returns tenant-owned PDF bytes only. | +| Open viewer | `GET /viewer/{docId}` | HTML shell | Protected JSON APIs determine visible data. | +| Bootstrap preview | `GET /api/v1/viewer/{docId}` | `viewer:read` | Returns a short-lived signed artifact URL. | | Create artifact link | `POST /api/v1/viewer/{docId}/artifact-links` | `artifact-link:create` | Produces tenant-bound token metadata. | -| Retry dead letter | `POST /api/v1/convert/jobs/{jobId}/retry` | `job:retry` | Shows operator recovery evidence. | -| Read KPIs | `GET /api/v1/analytics/kpi-snapshot` | `analytics:read` | Shows runtime job count, success rate, P95 preview. | -| Read KPI exports | `GET /api/v1/analytics/kpi-snapshot-exports` | `analytics:read` | Shows exported buyer evidence without tenant id. | +| Retry dead letter | `POST /api/v1/convert/jobs/{jobId}/retry` | `job:retry` | Records operator recovery evidence. | +| List tenant jobs | `GET /api/v1/admin/convert/jobs` | `admin:read` | Returns only the authenticated tenant's scope. | +| Delete tenant job | `DELETE /api/v1/admin/convert/jobs/{jobId}` | `admin:write` | Enters the durable deletion lifecycle. | +| Retry tenant job | `POST /api/v1/admin/convert/jobs/{jobId}/retry` | `admin:write` | Uses an atomic tenant-scoped retry transition. | +| Read KPIs | `GET /api/v1/analytics/kpi-snapshot` | `analytics:read` | Shows bounded operational evidence. | -## Buyer Sandbox Smoke +## Buyer sandbox smoke test -After the app starts, run: +After startup: ```bash curl -sS http://localhost:8080/healthz curl -sS http://localhost:8080/ | head ``` -Then complete one browser flow: +Complete one realistic browser and operator flow: -1. Open `http://localhost:8080/`. -2. Upload a small supported document. -3. Confirm the status row reaches `SUCCEEDED`. -4. Open the viewer link. -5. Confirm the KPI strip and KPI snapshot evidence panel update. -6. Trigger or inspect operator recovery evidence through a failed or - dead-lettered job when available. -7. Restart the app with the same ledger paths and confirm exported evidence - records are replayed. +1. Upload a small supported office document. +2. Confirm the status reaches `SUCCEEDED`. +3. Open the signed viewer and direct-download path with the correct permissions. +4. Confirm the KPI evidence updates. +5. Exercise a dead-letter retry when available. +6. Delete an owned job and confirm a receipt snapshot is appended. +7. Inject one artifact-store failure in a controlled test environment and confirm metadata is not lost before exact digest binding and incomplete cleanup remains pending. +8. Restart with the same artifact and ledger paths, then confirm bounded recovery completes the cleanup. +9. Confirm cross-tenant UUID requests remain concealed and do not touch artifact storage. -For PR evidence refresh, use the existing evidence folder: +For exact-head engineering evidence: ```bash -mvn -DskipTests compile -mvn test -mvn -q -DskipTests javadoc:javadoc +mvn -B --no-transfer-progress verify +python3 scripts/verify_maven_test_reports.py +python3 -m unittest discover -s scripts python3 scripts/check_sbom_license_policy.py \ --sbom docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json \ --policy docs/security/2026-07-02-license-policy.json \ @@ -180,74 +182,47 @@ python3 scripts/check_buyer_dataroom_manifest.py \ --manifest docs/diligence/2026-07-03-buyer-data-room-manifest.json ``` -Buyer-release mode must keep `--require-no-review` enabled so any future -review-required component fails before buyer handoff. The attribution `--check` -must also pass so the buyer data-room notice matches the current SBOM. +Do not substitute `mvn test`, a standalone Javadoc invocation, a synthetic-only result, or predecessor-head evidence. Buyer-release mode keeps `--require-no-review` enabled. -## Diligence Handoff Checklist +## Diligence handoff checklist Before a buyer sandbox is shown, attach: -- PR #74 URL and latest head SHA; +- the authoritative PR URL and exact head SHA; - `docs/qa/evidence/2026-07-02-krw2b-sale-readiness/README.md`; - `docs/diligence/2026-07-03-buyer-data-room-manifest.json`; -- this playbook; -- `docs/deployment/clearfolio-buyer-connector.openapi.yaml`; +- this playbook and `docs/deployment/clearfolio-buyer-connector.openapi.yaml`; - `src/main/resources/application-buyer-demo.yml`; - `docs/security/2026-07-02-auth-tenant-model.md`; - `docs/security/2026-07-02-signed-artifact-link-design.md`; +- `docs/security/2026-08-05-administrative-authorization.md`; +- `docs/security/2026-08-06-durable-artifact-deletion-receipts.md`; - `docs/legal/2026-07-03-third-party-attribution.md`; - `docs/analytics/2026-07-02-durable-metrics-event-model.md`; -- FigJam board: - . - -The FigJam board includes `Clearfolio Buyer Integration Deployment Flow`, which -mirrors this playbook's gateway, runtime, evidence-ledger, and diligence -artifact path. Figma Code Connect is not used. - -## Connector Seed - -The repository includes `docs/deployment/clearfolio-buyer-connector.openapi.yaml` -as an OpenAPI 3.0 import seed for a buyer-owned gateway or Power Platform custom -connector. It covers: - -- document submission through multipart upload; -- conversion status polling; -- dead-letter retry; -- viewer bootstrap with signed artifact URL; -- signed artifact-link creation and revocation; -- artifact read audit lookup; -- KPI snapshot and KPI export lookup. - -The seed intentionally models the current signed Clearfolio tenant-header -scaffold. It is not a validated production OIDC/JWT connector profile, and it -has not been imported into a buyer Power Platform tenant. A buyer-specific -connector package should be created only after the gateway hostname, OIDC issuer, -role mapping, and `frame-ancestors` allowlist are known. - -## Library and Submodule Decision - -No separate library, submodule, or Maven multi-module split is justified for -this deployment slice. The profile and playbook reduce buyer integration cost -without introducing versioning, release, or source-of-truth overhead. Revisit a -split only after a packaged connector, SDK, or second service consumes the same -contracts independently. - -## Production Cutover Gates - -The buyer sandbox should not be promoted to production until these gates close: - -- buyer-release license-policy evidence remains green with - `--require-no-review`, attribution drift check remains green, and final legal - release review is obtained; -- `SPRING_PROFILES_ACTIVE=production` starts only with configured signed tenant - claims and later replaces the scaffold with validated OIDC/JWT claims; -- validated gateway or OIDC JWT issuer, audience, expiry, key rotation, and role - mapping; -- durable conversion job repository with persisted state transitions; -- durable object store metadata, token revocation, and artifact read audit - persistence; -- durable metrics event stream and daily KPI projections; -- buyer-specific `frame-ancestors` allowlist and security owner approval; -- connector seed imported and tested against the buyer's actual gateway and - Power Platform tenant. +- the FigJam `Clearfolio Buyer Integration Deployment Flow` board. + +The FigJam board mirrors the gateway, runtime, evidence-ledger, and diligence flow. Figma Code Connect is not used by this deployment slice. + +## Connector seed + +`docs/deployment/clearfolio-buyer-connector.openapi.yaml` is an OpenAPI 3.0 import seed for a buyer-owned gateway or Power Platform custom connector. It covers submission, status, retry, viewer bootstrap, signed-link creation and revocation, artifact-read audit lookup, and KPI evidence. It models the current signed-header adapter; it is not a validated production OIDC/JWT connector and has not been imported into a buyer tenant. + +A buyer-specific package should be created only after gateway hostname, OIDC issuer, role mapping, deletion-status API contract, and `frame-ancestors` allowlist are known. + +## Standalone and modular decision + +No Maven multi-module split is required for this sandbox. The runtime contracts are injectable: a standalone deployment uses the filesystem adapters, while naruon or another CWL host can replace repository, artifact, receipt, audit, and claim adapters. Replacement adapters must preserve tenant scoping, immutable generation identity, receipt-before-read ordering, one-way digest binding, generation fencing, idempotent recovery, and privacy-safe evidence. + +## Production cutover gates + +Do not promote the buyer sandbox until all of these gates close: + +- exact-head CI, Security Scan, SAST, fuzz, complete coverage, warning-free Javadocs, SBOM, provenance, attribution, and buyer-release license policy are successful; +- final independent write-authorized review and legal review are complete; +- production config-tree mounts contain distinct strong signing keys; +- validated OIDC/JWT issuer, audience, expiry, rotation, and role mapping replace the header scaffold; +- a durable conversion-job repository enforces persisted state transitions and permanent generation uniqueness; +- the object store, token revocation, read audit, and deletion receipt/outbox adapters are durable and multi-instance safe; +- distributed generation fencing, object-version preconditions, remote-object-store recovery, dead-letter operations, and restore/rollback drills are evidenced; +- metrics and OpenTelemetry evidence are privacy safe and operationally actionable; +- the buyer-specific `frame-ancestors` allowlist and connector package are tested against the actual gateway and tenant. diff --git a/docs/deployment/clearfolio-buyer-connector.openapi.yaml b/docs/deployment/clearfolio-buyer-connector.openapi.yaml index cb1e90f64..2e81683c2 100644 --- a/docs/deployment/clearfolio-buyer-connector.openapi.yaml +++ b/docs/deployment/clearfolio-buyer-connector.openapi.yaml @@ -34,8 +34,11 @@ paths: operationId: submitConversionJob summary: Submit a document for asynchronous preview conversion. description: > - Requires `job:create`. The buyer gateway must add signed Clearfolio - tenant headers when `CLEARFOLIO_TENANT_CLAIMS_HMAC_SECRET` is enabled. + Requires `job:create`. The authenticated buyer gateway must strip any + untrusted inbound `X-Clearfolio-*` claim headers, then add canonical + signed Clearfolio tenant headers. Runtime verification reads + `clearfolio.tenant-claims.hmac-secret` from the config-tree mount + selected by `CLEARFOLIO_SECRET_CONFIG_DIR`. parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/SubjectId" diff --git a/docs/diagrams/submit-flow.md b/docs/diagrams/submit-flow.md index 7b51e3cd4..bccde0f34 100644 --- a/docs/diagrams/submit-flow.md +++ b/docs/diagrams/submit-flow.md @@ -64,7 +64,7 @@ sequenceDiagram V->>P: getBlockedExtensions() alt Override headers valid V-->>V: validate override=true + token + approver - V-->>V: emit audit-safe log(extension, approver, tokenFingerprint) + V-->>V: emit audit-safe log(extension, approverFingerprint, tokenFingerprint) V-->>Svc: validation ok else Override missing/invalid V-->>Svc: UnsupportedDocumentFormatException or IllegalArgumentException @@ -94,6 +94,8 @@ sequenceDiagram end ``` +`approverFingerprint` is the versioned, domain-separated keyed audit pseudonym. The raw approver identifier is accepted only as validation input and is never emitted by the audit-safe log. + ## Exception paths covered - Missing or empty file diff --git a/docs/diagrams/submit-policy-adapter-flow.md b/docs/diagrams/submit-policy-adapter-flow.md index bc65dfe76..18cca5617 100644 --- a/docs/diagrams/submit-policy-adapter-flow.md +++ b/docs/diagrams/submit-policy-adapter-flow.md @@ -29,7 +29,7 @@ sequenceDiagram EH-->>C: 400 UNSUPPORTED_FORMAT else extension blocked and override=true alt token/approver valid - Val-->>Val: audit-safe log(extension, approverId, tokenFingerprint) + Val-->>Val: audit-safe log(extension, approverFingerprint, tokenFingerprint) Val-->>Svc: validation ok Svc->>Repo: findOrStoreByContentHash(job) Svc->>W: enqueue(jobId) when created @@ -60,6 +60,8 @@ sequenceDiagram end ``` +`approverFingerprint` is the versioned, domain-separated keyed audit pseudonym; the raw approver identifier is never written to the audit-safe log. + ## Deterministic adapter baseline - `pdf -> PDF_JS` diff --git a/docs/engineering/acceptance-criteria.md b/docs/engineering/acceptance-criteria.md index b0befceeb..dd50c5aba 100644 --- a/docs/engineering/acceptance-criteria.md +++ b/docs/engineering/acceptance-criteria.md @@ -1,8 +1,11 @@ # Engineering Acceptance Criteria -Last updated: 2026-02-21 +Last updated: 2026-08-06 -This document is the canonical acceptance policy for the current Clearfolio Viewer delivery baseline. +This document is the canonical acceptance policy for the current Clearfolio +Viewer delivery baseline. Historical evidence snapshots remain useful for +provenance, but the required source of truth is the exact pull-request head and +its protected GitHub Checks. ## Mandatory AC list (exact) @@ -14,50 +17,108 @@ This document is the canonical acceptance policy for the current Clearfolio View 6. deprecated 0 7. 1-day schedule+security verification +The labels above are stable governance identifiers. Their executable meanings +are defined by the fail-closed gates below; changing a label requires an ADR and +a coordinated update to `AGENTS.md`, `CLAUDE.md`, and both architecture maps. + ## Runtime stance -- Non-blocking web runtime is implemented with WebFlux (`spring-boot-starter-webflux`) in current code. -- Servlet/MVC runtime is not the selected implementation for this repository baseline. +- Non-blocking web runtime is implemented with WebFlux + (`spring-boot-starter-webflux`). +- Servlet/MVC runtime is not the selected implementation for this repository + baseline. +- Document conversion remains asynchronous; HTTP request handlers submit work + and expose status, retry, viewer, and artifact workflows rather than waiting + for conversion completion. ## Delivery context chain - `Clearfolio Viewer <-> internal WAS -> Azure On-premise Gateway -> Power Platform -> mobile/tablet` -- Current implementation in this repo covers the Clearfolio Viewer side of the contract and state gating. +- This repository owns the Clearfolio Viewer side of the contract and its state, + authorization, document, artifact, and operational gates. + +## Required local acceptance commands + +```bash +mvn -B --no-transfer-progress verify +python3 scripts/verify_maven_test_reports.py +``` + +The commands are intentionally shared with CI. A contributor must not substitute +`mvn test`, skip the documentation execution, disable JaCoCo, lower a threshold, +suppress warnings, omit test-report verification, or present evidence containing +skipped or zero executed tests. + +The report gate requires at least one Surefire `TEST-*.xml` report, a positive +total test count, zero skipped tests, zero failures, and zero errors. Every +`testsuite` element must explicitly provide non-negative integer `tests`, +`skipped`, `failures`, and `errors` attributes; an omitted outcome count is +incomplete evidence and fails closed rather than being inferred as zero. When +Failsafe `TEST-*.xml` reports are present, the same rules apply. Missing, +malformed, empty, negative-count, skipped, failing, or error-bearing report +evidence fails closed even when a preceding Maven process returned success. +Each XML report must be UTF-8, may include a UTF-8 byte-order mark, is limited to +16 MiB, and is rejected before parsing when it contains a NUL byte, DTD, or +entity declaration. This prevents alternate encodings from hiding +external-entity or expansion payloads from the pre-parse checks, even when test +code can write report files. ## Mandatory AC evidence mapping -| AC | Gate check | Repro command | Evidence pointers | -| --- | --- | --- | --- | -| coverage | JaCoCo line/branch miss = 0 | `mvn -q -Djacoco.includes=com.clearfolio.viewer.* org.jacoco:jacoco-maven-plugin:0.8.13:prepare-agent test org.jacoco:jacoco-maven-plugin:0.8.13:report` | `docs/qa/evidence/2026-02-21-ac-gates/jacoco.csv` | -| docstring | JavaDoc warnings/errors = none | `mvn -q -DskipTests javadoc:javadoc` | `docs/qa/evidence/2026-02-21-ac-gates/javadoc.log`, `docs/qa/evidence/2026-02-21-ac-gates/javadoc-status.txt` | -| non-blocking web | Request path does not run conversion inline | N/A (code-path verification) | `src/main/java/com/clearfolio/viewer/controller/ConversionController.java`, `src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java` | -| lightweight queue | Bounded queue + retry + dead-letter behavior | N/A (code-path verification) | `src/main/java/com/clearfolio/viewer/config/ConversionExecutorConfig.java`, `src/main/java/com/clearfolio/viewer/service/DefaultConversionWorker.java` | -| warning 0 | Compile path warning-free (`-Werror`) | `mvn -q -DskipTests compile` | `docs/qa/evidence/2026-02-21-ac-gates/compile.log` | -| deprecated 0 | Deprecated usage blocked by warning gate | `mvn -q -DskipTests compile` | `docs/qa/evidence/2026-02-21-ac-gates/compile.log` | -| 1-day schedule+security verification | Delivery plan and security checks completed | `semgrep --config auto --metrics=off --error --json --output docs/qa/evidence//semgrep.json src/main/java` and GitHub API checks in plan | `docs/plans/2026-02-20-24h-customer-delivery-plan.md`, `docs/qa/evidence/2026-02-21-ac-gates/semgrep.json`, `docs/qa/evidence/2026-02-21-ac-gates/gh-code-scanning-alerts-open.json` | +| AC | Fail-closed gate | Reproduction and evidence | +| --- | --- | --- | +| coverage | JaCoCo 0.8.15 applies bundle-level `LINE` and `BRANCH` `MISSEDCOUNT` limits with a maximum of `0` | `mvn -B --no-transfer-progress verify`; inspect `target/site/jacoco/jacoco.csv` and the exact-head CI job | +| docstring | Maven Javadoc Plugin 3.12.0 runs Java 21 doclint for public production APIs and fails on warnings or errors | `mvn -B --no-transfer-progress verify`; inspect `target/reports/apidocs` and the exact-head CI job | +| non-blocking web | Request paths do not execute document conversion inline | `ConversionController`, `DefaultDocumentConversionService`, and their concurrency/integration tests | +| lightweight queue | Capacity, rejection, retry, processing lease, and dead-letter behavior are executable contracts | `ConversionExecutorConfig`, `DefaultConversionWorker`, repository/state-store tests, and exact-head fuzzing | +| warning 0 | Java compilation uses `-Xlint:all -Werror`; Maven report acceptance rejects skipped and zero-test evidence | `mvn -B --no-transfer-progress verify`, `python3 scripts/verify_maven_test_reports.py`, and exact-head CI | +| deprecated 0 | Deprecated API warnings are build failures | `mvn -B --no-transfer-progress verify` | +| 1-day schedule+security verification | Required GitHub Checks must be successful for the exact current head; queued, pending, cancelled, stale-head, or skipped-required outcomes are not passing | Delivery-plan evidence plus GitHub CI, Security Scan, SAST Semgrep, fuzz, automated review, independent approval, and branch-protection evidence | + +## Evidence boundaries + +- Local output is diagnostic evidence only. Merge evidence must identify the + exact commit SHA and protected GitHub workflow runs for that SHA. +- A successful earlier head does not validate a later head. +- Generated reports containing local paths or internal runtime details remain + local unless an explicit privacy and disclosure review approves publication. +- Historical snapshots under `docs/qa/evidence/` must not be described as the + current gate after code, dependencies, tests, or workflows change. ## Optional tracks -- client DB pooler -- PostgreSQL 17 +- client DB pooler; +- PostgreSQL 17. -## DB and queue operating policy (future persistent DB phase) +## Database and queue operating policy for a future persistent phase -- Queue requests should not wait for completion in request path; use status polling/callback pattern. -- Keep DB transactions short; avoid external network calls inside transactions. -- Use timeout/retry and `SKIP LOCKED` for lock-contention-sensitive worker loops. -- Read routing uses provided read-only endpoint/DSN; lock-sensitive or strongly consistent flows stay on primary. -- Pooler detection is best-effort (`SHOW VERSION;` in `pgbouncer`/`pgcat` management DB), fallback state is `unknown`. +- Queue requests must not wait for completion in the request path; use status + polling, callbacks, or an equivalent durable asynchronous contract. +- Keep database transactions short and exclude external network calls from + transaction scope. +- Use bounded timeouts, bounded retries, and `SKIP LOCKED` for + lock-contention-sensitive worker loops. +- Read routing may use a provided read-only endpoint; lock-sensitive or strongly + consistent flows remain on the primary. +- Pooler detection is best-effort (`SHOW VERSION;` in a `pgbouncer` or `pgcat` + management database); the fallback state is `unknown`. +- New database objects must use at least two descriptive words and snake_case by + default. ## Architecture linkage -- Root architecture map: `ARCHITECTURE.md` (updated 2026-02-21). +- Root architecture map: `ARCHITECTURE.md`. - Detailed architecture: `docs/architecture.md`. -## File-level documentation evidence +## References + +Apache Software Foundation. (2026). *Apache Maven Javadoc Plugin 3.12.0: +`javadoc:javadoc`*. Retrieved August 5, 2026, from +https://maven.apache.org/plugins/maven-javadoc-plugin/javadoc-mojo.html + +Apache Software Foundation. (2026). *Surefire reports*. Maven Surefire Plugin. +Retrieved August 6, 2026, from +https://maven.apache.org/surefire/maven-surefire-plugin/examples/reporting.html -| File | Change(add/edit/delete/move) | Intent(의도) | Why(이유) | Risk/Notes | -|---|---|---|---|---| -| `docs/engineering/acceptance-criteria.md` | add | Canonicalize mandatory AC policy and evidence map | Prevent drift across PRD/TRD/plan docs | Keep run-id pointers current when evidence folder rotates | -| `docs/qa/acceptance_evidence_checklist.md` | edit (existing baseline) | Reusable detailed checklist | Preserve command-level reproducibility | Must stay aligned with AGENTS.md gates | -| `docs/qa/evidence/LATEST.md` | edit (existing baseline) | Latest evidence entrypoint | Fast operator lookup | Snapshot only, not historical trend | +JaCoCo. (2026). *JaCoCo Maven plug-in: `jacoco:check`*. Retrieved August 5, +2026, from https://www.jacoco.org/jacoco/trunk/doc/check-mojo.html diff --git a/docs/legal/2026-07-03-third-party-attribution.md b/docs/legal/2026-07-03-third-party-attribution.md index 4be3aeb5c..7d691a02f 100644 --- a/docs/legal/2026-07-03-third-party-attribution.md +++ b/docs/legal/2026-07-03-third-party-attribution.md @@ -20,26 +20,26 @@ CycloneDX SBOM. It is engineering evidence, not legal advice. | com.fasterxml.jackson.datatype:jackson-datatype-jsr310 | 2.22.1 | Apache-2.0 | `pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.22.1?type=jar` | | com.fasterxml.jackson.module:jackson-module-parameter-names | 2.22.1 | Apache-2.0 | `pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.22.1?type=jar` | | com.fasterxml:classmate | 1.7.3 | Apache-2.0 | `pkg:maven/com.fasterxml/classmate@1.7.3?type=jar` | -| commons-logging:commons-logging | 1.3.3 | Apache-2.0 | `pkg:maven/commons-logging/commons-logging@1.3.3?type=jar` | +| commons-logging:commons-logging | 1.4.0 | Apache-2.0 | `pkg:maven/commons-logging/commons-logging@1.4.0?type=jar` | | io.micrometer:micrometer-commons | 1.15.12 | Apache-2.0 | `pkg:maven/io.micrometer/micrometer-commons@1.15.12?type=jar` | | io.micrometer:micrometer-observation | 1.15.12 | Apache-2.0 | `pkg:maven/io.micrometer/micrometer-observation@1.15.12?type=jar` | -| io.netty:netty-buffer | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar` | -| io.netty:netty-codec | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar` | -| io.netty:netty-codec-dns | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec-dns@4.1.135.Final?type=jar` | -| io.netty:netty-codec-http | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec-http@4.1.135.Final?type=jar` | -| io.netty:netty-codec-http2 | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec-http2@4.1.135.Final?type=jar` | -| io.netty:netty-codec-socks | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec-socks@4.1.135.Final?type=jar` | -| io.netty:netty-common | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar` | -| io.netty:netty-handler | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-handler@4.1.135.Final?type=jar` | -| io.netty:netty-handler-proxy | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-handler-proxy@4.1.135.Final?type=jar` | -| io.netty:netty-resolver | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-resolver@4.1.135.Final?type=jar` | -| io.netty:netty-resolver-dns | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-resolver-dns@4.1.135.Final?type=jar` | -| io.netty:netty-resolver-dns-classes-macos | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.135.Final?type=jar` | -| io.netty:netty-resolver-dns-native-macos | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.135.Final?classifier=osx-x86_64&type=jar` | -| io.netty:netty-transport | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar` | -| io.netty:netty-transport-classes-epoll | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-transport-classes-epoll@4.1.135.Final?type=jar` | -| io.netty:netty-transport-native-epoll | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-transport-native-epoll@4.1.135.Final?classifier=linux-x86_64&type=jar` | -| io.netty:netty-transport-native-unix-common | 4.1.135.Final | Apache-2.0 | `pkg:maven/io.netty/netty-transport-native-unix-common@4.1.135.Final?type=jar` | +| io.netty:netty-buffer | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar` | +| io.netty:netty-codec | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar` | +| io.netty:netty-codec-dns | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec-dns@4.1.136.Final?type=jar` | +| io.netty:netty-codec-http | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec-http@4.1.136.Final?type=jar` | +| io.netty:netty-codec-http2 | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec-http2@4.1.136.Final?type=jar` | +| io.netty:netty-codec-socks | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-codec-socks@4.1.136.Final?type=jar` | +| io.netty:netty-common | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar` | +| io.netty:netty-handler | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-handler@4.1.136.Final?type=jar` | +| io.netty:netty-handler-proxy | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-handler-proxy@4.1.136.Final?type=jar` | +| io.netty:netty-resolver | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-resolver@4.1.136.Final?type=jar` | +| io.netty:netty-resolver-dns | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-resolver-dns@4.1.136.Final?type=jar` | +| io.netty:netty-resolver-dns-classes-macos | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.136.Final?type=jar` | +| io.netty:netty-resolver-dns-native-macos | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.136.Final?classifier=osx-x86_64&type=jar` | +| io.netty:netty-transport | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar` | +| io.netty:netty-transport-classes-epoll | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-transport-classes-epoll@4.1.136.Final?type=jar` | +| io.netty:netty-transport-native-epoll | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-transport-native-epoll@4.1.136.Final?classifier=linux-x86_64&type=jar` | +| io.netty:netty-transport-native-unix-common | 4.1.136.Final | Apache-2.0 | `pkg:maven/io.netty/netty-transport-native-unix-common@4.1.136.Final?type=jar` | | io.projectreactor.netty:reactor-netty-core | 1.2.18 | Apache-2.0 | `pkg:maven/io.projectreactor.netty/reactor-netty-core@1.2.18?type=jar` | | io.projectreactor.netty:reactor-netty-http | 1.2.18 | Apache-2.0 | `pkg:maven/io.projectreactor.netty/reactor-netty-http@1.2.18?type=jar` | | io.projectreactor:reactor-core | 3.7.19 | Apache-2.0 | `pkg:maven/io.projectreactor/reactor-core@3.7.19?type=jar` | @@ -48,9 +48,9 @@ CycloneDX SBOM. It is engineering evidence, not legal advice. | org.apache.logging.log4j:log4j-core | 2.25.4 | Apache-2.0 | `pkg:maven/org.apache.logging.log4j/log4j-core@2.25.4?type=jar` | | org.apache.logging.log4j:log4j-jul | 2.25.4 | Apache-2.0 | `pkg:maven/org.apache.logging.log4j/log4j-jul@2.25.4?type=jar` | | org.apache.logging.log4j:log4j-slf4j2-impl | 2.25.4 | Apache-2.0 | `pkg:maven/org.apache.logging.log4j/log4j-slf4j2-impl@2.25.4?type=jar` | -| org.apache.pdfbox:fontbox | 3.0.3 | Apache-2.0 | `pkg:maven/org.apache.pdfbox/fontbox@3.0.3?type=jar` | -| org.apache.pdfbox:pdfbox | 3.0.3 | Apache-2.0 | `pkg:maven/org.apache.pdfbox/pdfbox@3.0.3?type=jar` | -| org.apache.pdfbox:pdfbox-io | 3.0.3 | Apache-2.0 | `pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.3?type=jar` | +| org.apache.pdfbox:fontbox | 3.0.8 | Apache-2.0 | `pkg:maven/org.apache.pdfbox/fontbox@3.0.8?type=jar` | +| org.apache.pdfbox:pdfbox | 3.0.8 | Apache-2.0 | `pkg:maven/org.apache.pdfbox/pdfbox@3.0.8?type=jar` | +| org.apache.pdfbox:pdfbox-io | 3.0.8 | Apache-2.0 | `pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.8?type=jar` | | org.apache.tomcat.embed:tomcat-embed-el | 10.1.55 | Apache-2.0 | `pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.55?type=jar` | | org.hibernate.validator:hibernate-validator | 8.0.3.Final | Apache-2.0 | `pkg:maven/org.hibernate.validator/hibernate-validator@8.0.3.Final?type=jar` | | org.jboss.logging:jboss-logging | 3.6.3.Final | Apache-2.0 | `pkg:maven/org.jboss.logging/jboss-logging@3.6.3.Final?type=jar` | @@ -72,7 +72,7 @@ CycloneDX SBOM. It is engineering evidence, not legal advice. | org.springframework:spring-jcl | 6.2.19 | Apache-2.0 | `pkg:maven/org.springframework/spring-jcl@6.2.19?type=jar` | | org.springframework:spring-web | 6.2.19 | Apache-2.0 | `pkg:maven/org.springframework/spring-web@6.2.19?type=jar` | | org.springframework:spring-webflux | 6.2.19 | Apache-2.0 | `pkg:maven/org.springframework/spring-webflux@6.2.19?type=jar` | -| org.webjars.npm:pdfjs-dist | 6.0.227 | Apache-2.0 | `pkg:maven/org.webjars.npm/pdfjs-dist@6.0.227?type=jar` | +| org.webjars.npm:pdfjs-dist | 6.1.200 | Apache-2.0 | `pkg:maven/org.webjars.npm/pdfjs-dist@6.1.200?type=jar` | | org.yaml:snakeyaml | 2.4 | Apache-2.0 | `pkg:maven/org.yaml/snakeyaml@2.4?type=jar` | ## Release Note diff --git a/docs/operations/artifact-deletion-cleanup.md b/docs/operations/artifact-deletion-cleanup.md new file mode 100644 index 000000000..ed2f60a5b --- /dev/null +++ b/docs/operations/artifact-deletion-cleanup.md @@ -0,0 +1,154 @@ +# Artifact deletion cleanup operations + +## Purpose + +This runbook covers the durable cleanup worker introduced for issue #263. It is +for operators of the standalone Spring service and for teams implementing a +compatible database, queue, or object-store adapter. + +The authenticated administrator DELETE request may return while the receipt is +still waiting for an exact artifact snapshot, after metadata has been +tombstoned, or while physical byte deletion remains retryable. Until the API +status slice is integrated, the authoritative physical-cleanup state is the +`ArtifactDeletionReceiptStore`, not the HTTP response alone. + +A `DELETION_REQUESTED` receipt can contain the controlled non-digest checksum +marker `pending`. That marker means the request itself is durable but the first +artifact read has not succeeded yet. Metadata must still exist in this state. +The worker replaces the marker with an exact SHA-256 digest, or the explicit +confirmed-absence digest, before metadata tombstoning. An artifact-store read +exception never becomes the absence digest. + +Every failed pre-snapshot read is appended as another validated +`DELETION_REQUESTED` snapshot with a larger attempt count, a durable +`lastAttemptAt`, and the controlled `artifact_store_read_failed` code. Incomplete +work is ordered by its latest durable state or failed-attempt time. A repeatedly +unavailable artifact therefore moves behind older eligible receipts instead of +starving the bounded batch, and the same ordering is reconstructed from the +ledger after process restart without an in-memory cursor. + +## Safe operational signals + +`ArtifactDeletionMetrics` exposes only aggregate, dimension-free values: + +- `completedAttempts()` — cleanup attempts that reached a durable completed + receipt; +- `failedAttempts()` — pre-snapshot or cleanup failures retained for retry; +- `pendingReceipts()` — all nonterminal durable receipts; +- `recoveryBatchRuns()` — measured bounded recovery invocations; +- `recoveryBatchTotalDuration()` — cumulative elapsed recovery time; +- `recoveryBatchMaximumDuration()` — longest measured recovery invocation. + +Export these values through the deployment's existing OpenTelemetry, JMX, or +metrics adapter. Do not add tenant identifiers, job identifiers, checksums, +filenames, exception messages, tokens, subjects, document content, or storage +paths as labels or log fields. + +Average recovery duration is derived as +`recoveryBatchTotalDuration / recoveryBatchRuns` when the run count is nonzero. +The maximum is retained separately so a low average cannot conceal a stalled +storage call. + +## Alert conditions + +Alert when any of the following evidence persists across at least two scheduled +retry intervals: + +1. `pendingReceipts()` is greater than zero and does not decrease. +2. `failedAttempts()` increases while `completedAttempts()` does not. +3. A `DELETION_REQUESTED` receipt remains bound to the `pending` marker, which + indicates that the artifact generation still cannot be read safely. +4. `recoveryBatchMaximumDuration()` approaches the scheduler interval or the + artifact-store client timeout. +5. The receipt ledger cannot be loaded, contains an unterminated final record, + or rejects an illegal transition or checksum rebinding. +6. Disk capacity for the ledger or artifact root approaches the deployment's + reserved minimum. + +A transient failed-attempt increment followed by completed growth and a falling +pending count is expected recovery evidence, not data loss. + +## Investigation sequence + +1. Preserve the current append-only receipt ledger as restricted incident + evidence. Do not edit or truncate it. +2. Confirm the service exact build provenance, receipt format version, artifact + store configuration, and scheduler settings. +3. Determine whether pending receipts are: + - `DELETION_REQUESTED` with checksum marker `pending` — metadata must remain + intact while the worker retries the artifact snapshot; + - `DELETION_REQUESTED` with an exact digest — metadata tombstoning may resume; + - `METADATA_TOMBSTONED`; + - `ARTIFACT_CLEANUP_PENDING`; or + - `ARTIFACT_CLEANUP_FAILED`. +4. For pending pre-snapshot work, compare the durable `attemptCount`, + `lastAttemptAt`, and controlled failure code with later eligible receipts. + Do not infer scheduler fairness from process uptime or a memory-only cursor. +5. Validate storage reachability, credentials, filesystem permissions, quota, + object-version preconditions, and timeout behavior without printing a raw + document path or identifier. +6. For a pending pre-snapshot receipt, verify that no operator or adapter has + substituted the empty digest to force progress. The empty digest is valid + only after the store successfully reports absence. +7. Correct the infrastructure cause and allow startup or scheduled replay to + resume the receipt. Do not fabricate a checksum, tombstone, or completed + transition. +8. Confirm that pending count falls, completed count increases, and no new + controlled failure is recorded. +9. Retain only privacy-safe aggregate evidence in a buyer-shareable report. + Keep the raw ledger, infrastructure logs, and document identifiers in the + restricted operational boundary. + +## Restart and rollback + +Startup replay processes a bounded deterministic batch. A malformed existing +ledger fails closed; replacing it with an empty file is not a valid recovery. +Restore the exact last known durable ledger from protected operational backup or +repair the storage fault that prevented reading it. + +A restart may legitimately encounter `DELETION_REQUESTED` with `pending`. The +worker must retry the artifact read under the same lifecycle fence, durably bind +the exact digest, and only then tombstone metadata. This is the expected recovery +path for an outage during the very first artifact read. + +A durable failed snapshot retains the same request identity and pending marker, +but its new attempt timestamp becomes the receipt's ordering time. The next +bounded pass may therefore select another older eligible receipt. Restart replay +uses those persisted snapshots and must produce the same next-eligible order; a +rollback must not restore the retired process-local rotation behavior. + +Application rollback is safe only to a build that understands every receipt +format already present, including the controlled pending-marker checksum and +pre-snapshot failure-evidence contracts. A build that does not recognize +`RECEIPT_V1`, the current state machine, or the generation fence must not start +against the ledger. Preserve artifacts and receipts until the compatible build +has resumed all pending work. + +## Multi-instance requirements + +The reference lock registry fences one JVM. A multi-instance deployment must add +one of the following at the adapter boundary: + +- a transaction that commits conversion-job tombstone, deletion receipt, and + cleanup outbox task together; +- an immutable object generation or version precondition; +- compare-and-set receipt transitions plus a fenced single consumer; +- an equivalent distributed generation fence that cannot be extended by an + expired lock holder. + +A plain distributed mutex without a fencing token is not sufficient evidence +that a stale worker cannot publish bytes after deletion. The remote adapter must +also preserve the one-way transition from pending snapshot evidence to an exact +artifact generation before any metadata tombstone becomes visible. It must +persist failed pre-snapshot attempt evidence atomically with the ordering field +used to select the next bounded batch; a process-local cursor is not a compatible +MSA implementation. + +## Acceptance evidence + +Before release, verify the exact integrated head with full Maven verification, +zero skipped tests, zero missed production lines and branches, warning-free +public Javadocs, exact-head and synthetic-merge CI, security scanning, SAST, +fuzzing, Strix, CodeRabbit, OpenCode/Noema, and counted independent approval. +The receipt ledger and artifact storage contents are restricted evidence and +must not be copied into public CI artifacts or acquisition data rooms. diff --git a/docs/prd/clearfolio-viewer-unified-document-preview-prd.md b/docs/prd/clearfolio-viewer-unified-document-preview-prd.md index d9fe8f4d5..b4b8bc760 100644 --- a/docs/prd/clearfolio-viewer-unified-document-preview-prd.md +++ b/docs/prd/clearfolio-viewer-unified-document-preview-prd.md @@ -1,7 +1,7 @@ # PRD: Clearfolio Viewer Unified Document Preview (Internal) Date: 2026-02-23 -Last updated: 2026-02-23 +Last updated: 2026-08-05 Owner: Product Manager Sources: `docs/architecture.md`, `docs/trd-integrated-document-viewer-platform.md`, `docs/prd-integrated-document-viewer-platform.md`, `docs/engineering/acceptance-criteria.md`, `docs/workflow/one-day-delivery-plan.md`, `docs/diagrams/*`, `AGENTS.md` @@ -196,7 +196,7 @@ Minimum claims/scopes (MVP intent): - preview session creation - viewer access (success/fail) - blocked-format attempts - - exception lane approvals (including approver id, token fingerprint, and rationale id if available) + - exception lane approvals (including `approverFingerprint`, token fingerprint, and rationale id if available); the raw approver identifier is never logged - operator-triggered retries ### 10.4 Browser security headers / CSP @@ -306,4 +306,4 @@ Minimum one-day deliverables: - Risk: Office formats (`docx`/`pptx`/`xlsx`) preview quality depends on converter availability; failures could impact perceived “unified” promise if not clearly messaged. - Risk: Gateway-induced header/proxy limitations can constrain token propagation; mitigation is short-lived viewer session tokens and minimized header set. - Risk: Strict no-warnings/no-deprecations gates can slow dependency upgrades; mitigate with explicit upgrade windows and pre-merge checks. -- Risk: Exception lane governance (who can approve, how approvals are issued) can expand scope; mitigate by treating policy token issuance as external and logging only fingerprint + approver id. +- Risk: Exception lane governance (who can approve, how approvals are issued) can expand scope; mitigate by treating policy token issuance as external and logging only the token fingerprint and `approverFingerprint`; the raw approver identifier remains validation input only and is never logged. diff --git a/docs/qa/evidence/2026-07-02-krw2b-sale-readiness/README.md b/docs/qa/evidence/2026-07-02-krw2b-sale-readiness/README.md index d8f2b78b6..a071f400f 100644 --- a/docs/qa/evidence/2026-07-02-krw2b-sale-readiness/README.md +++ b/docs/qa/evidence/2026-07-02-krw2b-sale-readiness/README.md @@ -1,40 +1,49 @@ # KRW 2B Sale-Readiness Evidence -Date: 2026-07-02 -Verification source head SHA before this evidence refresh: -`7df3ac8b8253cd1a445ba7faddbf99bc9a5c5fcd` +Original evidence date: 2026-07-02 +Original verification source head: `7df3ac8b8253cd1a445ba7faddbf99bc9a5c5fcd` +Latest dependency-evidence refresh: 2026-08-05 +Netty SBOM generation source head: `3b6e43426790ab8590c9ef50656bfb5cbbb206ce` + +## Evidence Boundary + +This directory combines a historical sale-readiness snapshot with selected generated artifacts that remain under executable drift contracts. A `Pass` result describes the named artifact and its source revision; it is not automatically transferable to a later source head. + +The committed CycloneDX JSON and generated third-party attribution are shareable buyer data-room evidence. GitHub Actions logs and the one-day generation artifact are transient provenance. Any dependency change must regenerate the SBOM, attribution, hashes, and exact-head acceptance evidence before release. ## Gate Summary | Gate | Result | Evidence | | --- | --- | --- | -| Java runtime | Pass, Java 26.0.1 runtime with Java 21 release-target compile | `java-version.txt`, `compile.log` | -| Compile warnings/deprecations | Pass | `compile.log` | -| Tests + JaCoCo | Pass, 357 tests, `classes=49`, `line_missed=0`, `branch_missed=0` | `mvn-test.log`, `test-jacoco.log`, `jacoco.csv`, `jacoco-status.txt` | -| JavaDoc | Pass, `javadoc_warnings_or_errors=none` | `javadoc.log`, `javadoc-status.txt` | -| Markdown lint | Pass, 0 errors across changed docs | `markdownlint.log` | -| JS syntax | Pass | `node-check.log` | -| SAST | Pass, 0 findings | `semgrep.log`, `semgrep.json` | -| SBOM | Pass, CycloneDX 1.6, 61 components, 0 components without license metadata | `sbom-cyclonedx.log`, `sbom-cyclonedx.json`, `sbom-status.txt` | -| License review | Pass, buyer-release policy checker reports 61 allowed components, 0 review-required components, 0 unlisted violations, and passes `--require-no-review` | `docs/security/2026-07-02-license-allowlist-review.md`, `license-policy-summary.json`, `license-policy-test.log` | -| Third-party attribution | Pass, generated buyer data-room attribution contains all 61 current SBOM components and passes drift check | `docs/legal/2026-07-03-third-party-attribution.md`, `third-party-attribution-check.log` | -| Buyer data-room manifest | Pass, manifest references required buyer evidence artifacts, all local paths exist, and ready gates reference only ready artifacts | `docs/diligence/2026-07-03-buyer-data-room-manifest.json`, `buyer-dataroom-manifest-check.log` | -| Buyer readiness scorecard | Pass, generated scorecard reports 23 artifacts, 8 readiness gates, 38 percent conservative gate readiness, and ready-gate evidence integrity pass from the current data-room manifest | `docs/diligence/2026-07-03-buyer-readiness-scorecard.md`, `buyer-readiness-scorecard-summary.json` | -| Figma Slides generation payload | Pass, payload check reports 11 slides, 4 objectives, and 0 errors; actual Slides generation still requires Figma team or organization plan selection | `docs/design/2026-07-03-buyer-diligence-slides-generation-payload.json`, `figma-deck-payload-check.json` | -| Auth/tenant, signed artifacts, and KPI snapshots | Partial, runtime tenant enforcement, optional gateway HMAC tenant-claim validation, production-profile fail-closed startup without signed tenant secret, signed artifact tokens, token revocation, artifact read audit API, optional file-backed artifact-link ledger replay, optional file-backed KPI snapshot ledger replay, and tenant-scoped KPI snapshot export API implemented; OIDC/JWT and centralized durable revocation/audit/analytics persistence pending | `docs/security/2026-07-02-auth-tenant-model.md`, `docs/security/2026-07-02-signed-artifact-link-design.md`, auth/artifact/analytics tests | -| Buyer deployment integration | Pass for buyer sandbox scope; `buyer-demo` Spring profile, gateway-signed header contract, connector API table, OpenAPI connector seed, smoke path, and cutover gates are documented; buyer tenant import and production OIDC/JWT profile remain follow-up | `src/main/resources/application-buyer-demo.yml`, `docs/deployment/2026-07-02-buyer-deployment-integration-playbook.md`, `docs/deployment/clearfolio-buyer-connector.openapi.yaml` | -| Durable job repository design, state-store, lifecycle event, and recovery sweep slice | Partial, code boundary implemented; `ConversionJobStateStore` routes worker success/failure and operator retry transitions, `ConversionJobLifecycleEvent` records process-local append-only transition evidence, and `DefaultConversionWorker` now re-enqueues due submitted jobs plus stale processing leases from available repository state, while SQL persistence remains pending for true process-restart durability | `docs/persistence/2026-07-02-durable-conversion-job-repository-plan.md`, state-store, lifecycle event, and recovery sweep tests | -| Seeded buyer-demo screenshots | Pass for local screenshot scope; seeded desktop and mobile viewports render after `Load demo story`, with no mobile horizontal overflow and uploaded FigJam screenshot nodes `25:1423` and `25:1422` | `seeded-demo-story-verification.md`, `screenshots/seeded-demo-desktop-viewport.png`, `screenshots/seeded-demo-mobile-viewport.png` | -| Buyer diligence closure map | Pass for FigJam handoff scope; added `Clearfolio KRW 2B Buyer Diligence Closure Map`, `Clearfolio Buyer Readiness Scorecard Gate Map`, and `Clearfolio Buyer Diligence Slides Storyboard` on the existing evidence board, and captured Slides generation prerequisites plus deck outline | `docs/design/2026-07-03-buyer-diligence-slides-and-closure-map.md`, `docs/design/2026-07-02-buyer-demo-kpi-figjam-handoff.md` | -| Local smoke | Pass, signed tenant claims plus file-backed artifact/KPI ledgers, KPI snapshot export API, buyer-demo KPI evidence panel, and operator recovery evidence panel | `smoke-local.txt`, `smoke-app.log`, `smoke-ui-root.txt` | -| GitHub PR state | Seeded buyer-demo story branch is refreshed on current `main`; review and queued checks are not treated as blockers | PR body and GitHub UI | +| Java runtime | Pass for original snapshot, Java 26.0.1 runtime with Java 21 release-target compile | `java-version.txt`, `compile.log` | +| Compile warnings/deprecations | Pass for original snapshot | `compile.log` | +| Tests + JaCoCo | Pass for original snapshot, 357 tests, `classes=49`, `line_missed=0`, `branch_missed=0` | `mvn-test.log`, `test-jacoco.log`, `jacoco.csv`, `jacoco-status.txt` | +| JavaDoc | Pass for original snapshot, `javadoc_warnings_or_errors=none` | `javadoc.log`, `javadoc-status.txt` | +| Markdown lint | Pass for original snapshot, 0 errors across changed docs | `markdownlint.log` | +| JS syntax | Pass for original snapshot | `node-check.log` | +| SAST | Pass for original snapshot, 0 findings | `semgrep.log`, `semgrep.json` | +| SBOM | Refreshed 2026-08-05, CycloneDX 1.6, 61 components, 17 Netty components at `4.1.136.Final`, 0 components without license metadata | `sbom-cyclonedx.json`, Netty ADR, permanent drift test | +| License review | Pass for current 61-component generated SBOM; 0 review-required and 0 unlisted violations under buyer-release policy | `docs/security/2026-07-02-license-allowlist-review.md`, `license-policy-summary.json`, `license-policy-test.log` | +| Third-party attribution | Refreshed from the same generated SBOM and protected by byte-for-byte renderer drift validation | `docs/legal/2026-07-03-third-party-attribution.md`, `scripts/test_render_third_party_attribution.py` | +| Buyer data-room manifest | Pass for original snapshot; required local paths existed and ready gates cited only ready artifacts | `docs/diligence/2026-07-03-buyer-data-room-manifest.json`, `buyer-dataroom-manifest-check.log` | +| Buyer readiness scorecard | Pass for original snapshot; 23 artifacts, 8 readiness gates, 38 percent conservative gate readiness | `docs/diligence/2026-07-03-buyer-readiness-scorecard.md`, `buyer-readiness-scorecard-summary.json` | +| Figma Slides generation payload | Pass for payload scope; 11 slides, 4 objectives, 0 errors; actual Slides generation still requires an eligible Figma plan | `docs/design/2026-07-03-buyer-diligence-slides-generation-payload.json`, `figma-deck-payload-check.json` | +| Auth/tenant, signed artifacts, and KPI snapshots | Partial; runtime tenant enforcement, signed claims, signed artifact tokens, revocation, audit, and file-backed ledgers exist, while production OIDC/JWT and centralized durable persistence remain pending | Security model, artifact, analytics, and persistence tests | +| Buyer deployment integration | Pass for buyer sandbox scope; connector seed, gateway-signed claims, smoke path, and cutover gates documented | Buyer deployment playbook, connector OpenAPI, buyer-demo profile | +| Durable job repository and recovery slice | Partial; code boundary, state store, lifecycle events, and process-local recovery exist, while SQL process-restart durability remains pending | Persistence plan and repository/state-store tests | +| Seeded buyer-demo screenshots | Pass for local screenshot scope; desktop/mobile seeded story, no mobile overflow | Seeded demo verification and screenshots | +| Buyer diligence closure map | Pass for FigJam handoff scope | Design handoff documentation | +| Local smoke | Pass for original signed-tenant, artifact-ledger, KPI-ledger, viewer, revocation, and recovery scope | `smoke-local.txt`, `smoke-app.log`, `smoke-ui-root.txt` | +| GitHub PR state | Dynamic; queued or waiting review does not stop productive work but is never counted as merge acceptance | Current exact-head PR checks and reviews | ## SAST -Command: +Command used for the original evidence snapshot: ```bash -uvx semgrep --config p/java --metrics=off --error --json --output docs/qa/evidence/2026-07-02-krw2b-sale-readiness/semgrep.json src/main/java src/test/java +uvx semgrep --config p/java --metrics=off --error --json \ + --output docs/qa/evidence/2026-07-02-krw2b-sale-readiness/semgrep.json \ + src/main/java src/test/java ``` Result: @@ -45,53 +54,77 @@ Result: - Findings: 0. - Errors: 0. -Evidence: - -- `semgrep.json` +Evidence: `semgrep.json`. -## SBOM +## SBOM Generation -Command: +### Canonical command ```bash -mvn -DskipTests org.cyclonedx:cyclonedx-maven-plugin:2.9.1:makeAggregateBom -Dcyclonedx.skipAttach=true -Dcyclonedx.outputFormat=json -Dcyclonedx.outputName=clearfolio-viewer-sbom +mvn -B --no-transfer-progress -DskipTests \ + org.cyclonedx:cyclonedx-maven-plugin:2.9.1:makeAggregateBom \ + -Dcyclonedx.skipAttach=true \ + -DoutputFormat=json \ + -DoutputName=bom ``` -Result: +CycloneDX Maven Plugin 2.9.1 writes the canonical JSON output to `target/bom.json`. `outputFormat` and `outputName` are Maven user properties without a `cyclonedx.` prefix. The earlier evidence command incorrectly prefixed those two properties and is superseded by this contract. + +### Deterministic provenance + +Read-only workflow run `31004040777` generated the accepted dependency evidence at `2026-08-05T12:07:15Z`. + +| Field | Value | +| --- | --- | +| Source head | `3b6e43426790ab8590c9ef50656bfb5cbbb206ce` | +| Generator | `org.cyclonedx:cyclonedx-maven-plugin:2.9.1:makeAggregateBom` | +| Artifact ID | `8929593015` | +| Artifact archive SHA-256 | `07a0325e08157f00dda28c58ed4e41af51863cccb2ceea2c4e378ead77dc337f` | +| SBOM SHA-256 | `e138a9263edb40c613d5f159acba8fa89ee848a7cef4b6619e095c48451b095c` | +| Attribution SHA-256 | `e19a3767a545bd059e50003882d8ff2f8a3ff4d3b8fd28d3f305eead61261da9` | +| CycloneDX specification | `1.6` | +| Total components | `61` | +| Netty components | `17` | +| Netty version set | exactly `4.1.136.Final` | +| Components without license metadata | `0` | + +```mermaid +flowchart LR + H[Exact source head] --> R[Maven dependency resolution] + R --> G[CycloneDX 2.9.1] + G --> B[target/bom.json] + B --> V[Component and edge verifier] + V --> A[Attribution renderer] + B --> C[Committed SBOM] + A --> D[Committed attribution] + C --> T[Permanent drift test] + D --> T +``` + +The verifier requires every Netty component version, purl, bom-ref, and dependency edge to resolve to `4.1.136.Final`. It rejects the historical `4.1.135.Final` line, an empty component list, unmatched dependency references, or attribution that cannot be reproduced from the committed JSON. + +### Current generated result - CycloneDX BOM format: 1.6. - Components: 61. - Components without license metadata: 0. - Unique license metadata entries: 3. -- Engineering license review is now documented in - `docs/security/2026-07-02-license-allowlist-review.md`. -- The unused `tika-parsers-standard-package` dependency was removed, which - eliminated Tika transitive review-required components `jhighlight`, `junrar`, - and `juniversalchardet` from the current SBOM. -- Spring Boot's default Logback starter was replaced with - `spring-boot-starter-log4j2`, and `jakarta.annotation-api` is excluded from - the current starter paths. -- The standard-library license policy checker passes buyer-release mode: - 61 allowed components, 0 review-required components, and 0 unlisted - violations with `--require-no-review`. -- The standard-library attribution renderer generates - `docs/legal/2026-07-03-third-party-attribution.md` from the same SBOM and - the drift check confirms that the data-room attribution file is current. -- The buyer data-room manifest checker confirms the sale-readiness package links - to required local evidence and current Figma/GitHub handoff URLs, and prevents - ready gates from citing partial or external artifacts as complete evidence. -- The buyer readiness scorecard generator reports 23 current data-room - artifacts, 8 readiness gates, 38 percent conservative gate readiness, and - ready-gate evidence integrity pass while keeping partial gates as discount - risks. -- The Figma Slides payload checker confirms the buyer diligence deck payload has - 11 slides, 4 objectives, explicit no-Code-Connect wording, readiness - scorecard content, discount-risk content, and claim-boundary wording. +- The unused `tika-parsers-standard-package` dependency remains absent, eliminating Tika transitive review-required components `jhighlight`, `junrar`, and `juniversalchardet` from the buyer-release graph. +- Spring Boot's default Logback starter is replaced with `spring-boot-starter-log4j2`, and `jakarta.annotation-api` remains excluded from the current starter paths. +- The standard-library attribution renderer generates `docs/legal/2026-07-03-third-party-attribution.md` from the same SBOM. +- The buyer-release license policy records 61 allowed components, 0 review-required components, and 0 unlisted violations. -Evidence: +Primary generated evidence: -- `sbom-cyclonedx.log` - `sbom-cyclonedx.json` +- `docs/legal/2026-07-03-third-party-attribution.md` +- `docs/security/2026-08-05-netty-4.1.136-remediation.md` +- `scripts/test_render_third_party_attribution.py` +- `src/test/java/com/clearfolio/viewer/config/DependencyPolicyTest.java` + +Related historical and buyer-handoff evidence: + +- `sbom-cyclonedx.log` - `sbom-status.txt` - `license-policy.log` - `license-policy-summary.json` @@ -101,7 +134,6 @@ Evidence: - `buyer-readiness-scorecard-summary.json` - `figma-deck-payload-check.json` - `docs/design/2026-07-03-buyer-diligence-slides-generation-payload.json` -- `docs/legal/2026-07-03-third-party-attribution.md` - `docs/security/2026-07-02-license-allowlist-review.md` - `docs/security/2026-07-02-license-policy.json` - `docs/security/2026-07-02-auth-tenant-model.md` @@ -116,78 +148,36 @@ Evidence: - `docs/superpowers/plans/2026-07-02-conversion-job-lifecycle-events.md` - `docs/superpowers/plans/2026-07-03-conversion-recovery-sweep.md` - `buyer-deployment-slice-verification.md` -- FigJam diagrams: - [Clearfolio Gateway Signed Tenant Claims Flow](https://www.figma.com/board/114nJPcTcQzXvAEIS9T4gM) - and `Clearfolio KPI Snapshot Evidence Ledger Flow` plus - `Clearfolio KPI Snapshot Export Evidence API Flow` and - `Clearfolio Buyer Demo KPI Evidence Panel Flow` plus - `Clearfolio Operator Recovery Evidence Flow` and - `Clearfolio Conversion State Store Implementation Flow` plus - `Clearfolio Conversion Lifecycle Event Trail Flow` plus - `Clearfolio Buyer Readiness Scorecard Gate Map` plus - `Clearfolio Buyer Diligence Slides Storyboard` plus - `Clearfolio Ready Gate Evidence Integrity Check` plus - `Clearfolio Conversion Recovery Sweep Flow`. + +FigJam handoff includes the gateway signed-tenant flow, KPI snapshot ledger/export flows, buyer-demo KPI panel, operator recovery flow, conversion state-store and lifecycle-event flows, buyer readiness gate map, diligence slides storyboard, ready-gate evidence integrity check, and conversion recovery sweep flow. ## Local Smoke -Command path: +Original command path: -- Start app on a random local port with - `clearfolio.tenant-claims.hmac-secret` and - `clearfolio.artifact-link-ledger.path` plus - `clearfolio.analytics-snapshot-ledger.path` configured. -- Runtime Java: 21.0.11. -- Verify `GET /`, buyer-demo KPI evidence panel markup, - buyer-demo operator recovery evidence panel markup, `/assets/viewer/demo.js`, - demo JS KPI export endpoint reference, - missing-auth KPI denial, unsigned tenant-claim KPI denial, authenticated empty - KPI snapshot with signed tenant claims, authenticated empty KPI export lookup, - document upload with signed tenant headers, status polling to `SUCCEEDED`, - `/viewer/{docId}`, authenticated viewer bootstrap, signed artifact URL - creation, unsigned artifact denial, signed artifact range access, artifact - read audit lookup, artifact token revocation, revoked-token denial, - cross-tenant status denial, post-upload KPI snapshot, post-upload KPI export - lookup, and file-backed KPI snapshot ledger append evidence. +- Start the application on a random local port with `clearfolio.tenant-claims.hmac-secret`, `clearfolio.artifact-link-ledger.path`, and `clearfolio.analytics-snapshot-ledger.path` configured. +- Verify the root shell, buyer-demo KPI and recovery panels, demo assets, signed claims, upload and status polling, viewer/bootstrap, signed and ranged artifact access, read audit, revocation, cross-tenant concealment, KPI snapshots and exports, and file-backed ledger append evidence. -Result: +Original result: -- Root shell: 200. -- Root shell evidence panel: present. -- Root shell operator recovery panel: present. -- Demo JS: 200. -- Demo JS KPI export endpoint reference: present. -- Missing-auth KPI: 401. -- Unsigned tenant-claim KPI with secret configured: 401. -- Authenticated empty KPI: 200. -- Authenticated empty KPI exports: 200, 1 record, tenant id omitted. -- Final conversion status: `SUCCEEDED`. -- Status tenant: `buyer-demo`. -- Viewer HTML: 200. -- Viewer bootstrap: 200. -- Artifact link creation: 200. -- Unsigned artifact read: 401. -- Signed artifact range read: 206. -- Artifact read audit lookup: 200, 1 event, last status 206. -- Artifact token revocation: 200, `revoked=true`. -- Revoked artifact read: 403. +- Runtime Java: 21.0.11. +- Root shell: 200; evidence and recovery panels present. +- Missing or unsigned tenant claims: 401. +- Authenticated empty KPI and exports: 200. +- Final conversion status: `SUCCEEDED` for tenant `buyer-demo`. +- Viewer and bootstrap: 200. +- Signed artifact range read: 206; unsigned read: 401. +- Artifact read audit: 200; revocation succeeded; revoked read: 403. - Cross-tenant status lookup: 404. -- Post-upload KPI: `totalJobs=1`, `succeededJobs=1`, - `conversionSuccessRate=1.0`, numeric `p95TimeToPreviewMs`. -- Post-upload KPI exports: 200, 2 records, latest `totalJobs=1`, tenant id - omitted. -- Artifact ledger file: present, 2 `ISSUED` lines, 1 `REVOKED` line, - and 1 `READ` line. -- KPI snapshot ledger file: present, 2 `SNAPSHOT` lines. +- Post-upload KPI: one successful job and numeric preview latency. +- Artifact and KPI ledger append evidence present. Evidence: - `smoke-local.txt` - `smoke-ui-root.txt` +- `smoke-app.log` -## GitHub Checks +## GitHub Acceptance -This evidence refresh was produced locally before publishing the recovery-sweep -branch. The PR body should carry the local gate results from this file. Review -and queued GitHub checks are not treated as blockers for continuing the -sale-readiness work. +The historical snapshot is not a substitute for current pull-request evidence. A release or merge requires the exact current head to pass repository CI, Maven `verify`, zero missed production lines and branches, warning-free public Javadocs, Security Scan, SAST, every fuzz target, dependency/security review, current automated review, zero unresolved threads, and a counted independent approval. Queued, pending, cancelled, skipped-required, stale-head, or local-only results are not passing. diff --git a/docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json b/docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json index eb356a034..2f9e4c212 100644 --- a/docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json +++ b/docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json @@ -4,7 +4,7 @@ "serialNumber" : "urn:uuid:b6017fa5-aa1e-3a06-ab1c-8fd43d993316", "version" : 1, "metadata" : { - "timestamp" : "2026-07-10T19:18:50Z", + "timestamp" : "2026-08-05T12:07:15Z", "lifecycles" : [ { "phase" : "build" @@ -1035,45 +1035,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-codec-http@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-codec-http@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-codec-http", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "17647f3dcda67916dac602de8c8f2ca5" + "content" : "0ea9a6efa8033fdaec83eba2070dedad" }, { "alg" : "SHA-1", - "content" : "69d785784208bae296fa74d802772687c6947754" + "content" : "3f5101d264099848dc5fb708bad4a09c4601ac3f" }, { "alg" : "SHA-256", - "content" : "4018529d3d6aecf4044b98c75d9a90c91839ddf49c7aa484c5ac81c90a15da02" + "content" : "ffd1e1b19a533bc6e47ef2cbc1290374ff4a7cb53a280defa3df392538214948" }, { "alg" : "SHA-512", - "content" : "47d41c724aa9763ba2b36618d4905f02546e83ea0b498f0022a9a9483c158ce88794c657a679a6cf4495680f18d2a7d2f9c70038d5d0cb27d97f36cf4b36f423" + "content" : "ff2fa4c55a6954aa4dac52e66cad2a26e6fe551776bbf3a68da863610a0a8534496d2d66a80a941e8803994716ce3cc3635c21b666f6dd1ad91cae2a396ceb2d" }, { "alg" : "SHA-384", - "content" : "a986969aaa528a168981b4b70031a3ec9adc482a78224a96ee7110840acce8d4c8c5ce0b39d330821c993f1a6483a94b" + "content" : "5bbfae77116c14020723d681d5d28d0073b58225b6332887df13c915d3705d14dcc1b153152294421308e975a0058862" }, { "alg" : "SHA3-384", - "content" : "ec7423bd39906638bfd36a127f18f9fc9c8d51431aa7daa4981bc7361a1851e6afbd939f9d5c56e24ab94ada08c6ff52" + "content" : "2d41da28daea35313fc5c48c252e5aaaea2e53528ff989059e920861a02bce713b52b4ac9dec8e331f562cf714bd94d0" }, { "alg" : "SHA3-256", - "content" : "c108c82107511a4d96a6dd7613f2f7a7887e6c79c2f2c0256abfbd7fa93bba54" + "content" : "355212f7d6dcd40ac9d1cae43311c00ca9831b01ae06e264225985b2cf94e169" }, { "alg" : "SHA3-512", - "content" : "05d36acb34894cbfb3160bf2ec19950a0a1fdd15772c9e11a7006b8357b8a9c3b0111673b2fd1ac44f3d3a9cff7f37f56671baeec290428d0df008c086ebfb03" + "content" : "597974256e788cba3206ca7457b433dae2cf1eb34c9635e062be48df72fab1ff75ee40a915f96553b6d522529a1a1b6e08944ac52d4aae77036e6443fd78e37d" } ], "licenses" : [ @@ -1083,7 +1083,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-codec-http@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-codec-http@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1101,45 +1101,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-common", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "bfa2258e927ec224143136eda472297c" + "content" : "1fcc4e203d0b61b432bf10f4cfa94619" }, { "alg" : "SHA-1", - "content" : "3f1fd5004102cd1146a53d29127804569b63c90f" + "content" : "c11b3dad4ad80a34b3860dbdb7ce63166913eb12" }, { "alg" : "SHA-256", - "content" : "26775ca95820711403cf065fa2ec0134a0a04ff5417c688c0237aee68b55838d" + "content" : "e2f73be7ec359b46583ad875521137000eff520bafd320e730846ec9974f3be6" }, { "alg" : "SHA-512", - "content" : "a4317485b0b1434b552c98bcb69a1e1fffdffcdee22c84e61c107dfa9752faff7f5799617c81ba80861f929fe7dcf4436d7bf2af95c4eddd550ebfb75df12cc6" + "content" : "8401400d2c786a62b513d394fd372facab2581a7964982b1c8ec5af53a235a881cdb3d1447415b22f791b3282113728376b50ac7384c1b13ed90ce779548fdcf" }, { "alg" : "SHA-384", - "content" : "f6dbd09db24ddfbe0b0c7c4480eddbbfc29c9db89ecd38b56e4c9f0ca49907ea45a88479ec6d95ae83c98d490159ab58" + "content" : "94b02b8d7368c268828f85bf221907654dd6bfa334f6252fcaf26125a7b333d367b3a81d98c036d6d4a94716ded48ee2" }, { "alg" : "SHA3-384", - "content" : "151dc11ed193e806580144e00260c6e98f82e7716fe81d7181b923fb1f2656a07acf6e324c90c35cf066e90c023462ba" + "content" : "2ce8d61f5f58d1dc8cc8fe11c4d4f078ac92cc3d40f8a61d316a83501e4491faba68929f499a46741c48dbb9c580578b" }, { "alg" : "SHA3-256", - "content" : "5c0286ab9cd98756e203f38d8172577ed184c8236478532bbd78f85d4a583ea2" + "content" : "a9d06835f575037564b8ef926abfd5022bc09a5d57071082881691eec2fb889b" }, { "alg" : "SHA3-512", - "content" : "d2f40345230b7f1d5b04b78fd82de5cb94134ca12510d0e53a22245f9c24dcb420c30e8a2cffb26b474782d09cf986c57f9f99df856a481c2fc7f8d066839c10" + "content" : "4815b59200870be324cd4a506b7f278840ce9f4b152249851f690cd310fba26de890d8c1cf984978a4e21748d505d32cb784f3ebdbf0d5bd6c2baa455197dfb8" } ], "licenses" : [ @@ -1149,7 +1149,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1167,45 +1167,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-buffer", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "fcd9f423d78592d1ca785b68ee9b16ee" + "content" : "974bb8b00957141a3c8aaa56dc845ecc" }, { "alg" : "SHA-1", - "content" : "6e60e222c27d3534e63e519fb1a9d66c485f62bd" + "content" : "2675f7ca19b174466766127c59b52acdba01f6a2" }, { "alg" : "SHA-256", - "content" : "2a194f99fc93d07c4d442d04ac71bd2dc56d3188cd0e4270cdc2a953d1956bf9" + "content" : "c88f13fc41156fde5df918c7b240038dfbe97ac57576163f208630391789b5d5" }, { "alg" : "SHA-512", - "content" : "a839731d75317f515256c8340d73ea4344c9f488ce2cc31c99c2788d2a33a055482ebc438ebca63a27c0d49728fe778e7d0b06a031a0244728d0b0ccddfe6407" + "content" : "b335ae2f322969a4182fac5c8d056637fd4fadc3ef5a05a13f55fd2a25a22e13b4cade5d6af2aa8f77aed5e83269b3977966f7c57abda39b98420e72a749956a" }, { "alg" : "SHA-384", - "content" : "ed107c69a75793161a6e3ade436cb564224b7bd58ea551b42c5b7d10dd6384fa25538a0064dba4521beb3ca69857c455" + "content" : "324583f3af2c2c3933add108fbdc5922fefe5e77595c608a6f7088b7aff168a0c8139a90b1f94dc8cb27052e1fa922b5" }, { "alg" : "SHA3-384", - "content" : "6f1c0b2b2c428e9fdbda3aa37558fa91330be87a7fda93eb2ba66d3b224feef63120216a06e3de2388bdf8648c549f28" + "content" : "4b83e43ddef6aeeb0b55206b2abad41386812a60f33ff2303fc3e298dc0516b5798c12546f98fbea98881a6d140674eb" }, { "alg" : "SHA3-256", - "content" : "bb6f4ce57de81fb991b78f4e6861088e46a3e8de176a84757d48d14a2ac1ce74" + "content" : "155689ada0fc252379de770957847fa38bcc4b27c6c44fbe62aa1d54dd2b3636" }, { "alg" : "SHA3-512", - "content" : "13f9bafd0b5534316b77170719aab58a46daf9d7303b5541887b9171522501fff7b1fbbe6ab887a2a94b2d8455aa42fa059fce9dc21db98309d02e6539e32835" + "content" : "557b47c06465ce198de44efd3999af3f540b6682ac2888a42114721e7b657713d5f36936fd4c287b064e0d3ca536910ab8279aeca4842efb74e5783c25adc80f" } ], "licenses" : [ @@ -1215,7 +1215,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1233,45 +1233,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-transport", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "06b22eaac4fad8e22753969b96b2c36a" + "content" : "bb5b1ee9a2d1792931fcb008cf3557e3" }, { "alg" : "SHA-1", - "content" : "cbc238f1f9707f3652252fdc48568cce0e9a01d9" + "content" : "0c2e6961562760a9128766cb84a50d2ac51402ff" }, { "alg" : "SHA-256", - "content" : "6bde734d1ec073142eed31b1e68cd5d68fbf241e060b37f07a164e5ecb15631c" + "content" : "b92881ea925721ed42fee5122a42b8bce4b84da737dbc3ca2d84dfaca52c28b6" }, { "alg" : "SHA-512", - "content" : "ff06cf5971ed28f46f73ecc6f5cbcb2b7dc7f54b9c046678737616fde57ec8e7ca215c63cecd5d2f09c174c1483ec7538a63781009955539be782183fba6558f" + "content" : "a76ab760519837e9d0a28fa6699ad4e279be1bb5e63a5c4173e9e5566e0e1bcb143a18a66b54fb1261aa07de5beb383cd93d9f043ae1ee869a7bef32896ba650" }, { "alg" : "SHA-384", - "content" : "043d7346fd771eba584d272ab0160aa9c10c011c3f3e7006d02191af6fdd33f73eac4d6fc5e727cba4544dea670cf4a6" + "content" : "84d8a4776e8ec72e625381aca19ac105b4053ed7da431991ca8091bb2df7a25b2b9505f650219d1a4650bdec2224ab92" }, { "alg" : "SHA3-384", - "content" : "599fe6ed7bfc5f22614b8bb82d57467e18c45328d6714d5055d6b2d8cd5286fb014f90672f6b51c52759d1b64222717a" + "content" : "411ef1e0b07cdee90cfd5ad00563aed6df3f5c13b41e8156458fb668744711aae29c4817156ad6e2a2c7867f976a90ab" }, { "alg" : "SHA3-256", - "content" : "bb0022cdfa7c304bda6aeb620815ca795973cec8ee1f4d5a6e10225bac004f67" + "content" : "3024e3a5448062c69490c4f5496514cd902b3a3269ff024b4d344bdaf09849ad" }, { "alg" : "SHA3-512", - "content" : "92a7fee52ff206f36d150aeacab70169b3e89bb5c8c7878c2932cb8cf98738c7122584336e183cfeb132721f8f22a55b44bca1a724336cadece6ccc4d4621946" + "content" : "2aeddb9fa673e3698c1b2189bb33ee20ae8d369956b7f3fa9e4554ab8bb58507be0fd11a824c20305aa55ae2f0052f8506dd35371d1cb0c80cc136f9042eefcb" } ], "licenses" : [ @@ -1281,7 +1281,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1299,45 +1299,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-codec", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "d4c7467d989f3fce353fde66eb1b4bdf" + "content" : "b61698c2f484ca179da5699da54688f9" }, { "alg" : "SHA-1", - "content" : "cf36e54dc81aa160a93780478727bdb3c3fa4600" + "content" : "f91a3bb222a8da5e08a8a2feae60e5fcda74e4dc" }, { "alg" : "SHA-256", - "content" : "7252171264dbb5bb8ed38e77f89643b31e3cabc96144ec27b6882435d718a61e" + "content" : "2de4fc13005c7740b46427a47ee04265a19779c147d53e583f441889b1148159" }, { "alg" : "SHA-512", - "content" : "663db82a0b3a83a4417b647b0b08620c915c9f476033d241bd57790fb3e024972a2ac421a66bba49451439c101e04915828d109a76316b8533c87848b7e95453" + "content" : "b9fba7ceea4acc923745d097b30b9978b3ab058aad3e880ee3061bab22cf5460a170ff6f8b94932b491d22740b027b74fb65e4245acbdd754be1837479782dbb" }, { "alg" : "SHA-384", - "content" : "4d66221bfb9846a4abaf037c91acc3b50831210933f4d352559b075a118704eba58024e5bbaea33f1b96e38ff459030e" + "content" : "16893a5504150305e44df81704d862565d8089a149edaff9dff9ad8057f7b26a72aa1910dc943680c816bf88b1821e3b" }, { "alg" : "SHA3-384", - "content" : "db590cd2eaa49fe7f49473af1dc4d55bea45c68c56caa6794455182d5fd6a0792b9aa4a614912fd57859803d437138fa" + "content" : "3c8addeb374411ef72d87ed55d023521cd2997843ac1e76cc947d86bdae3357de431135d3d9b6a3b0076b45c3ded93b1" }, { "alg" : "SHA3-256", - "content" : "2e87051eec597bbdea9b1c6b621cc1a08c3737ad5d80abbb9dcfa7f5b6412932" + "content" : "60e83534593483ea4066da60d968c21891cc46ca51df3190e8d2bb64517d04da" }, { "alg" : "SHA3-512", - "content" : "6a7f5b8b01c8cd272ac0f4936867a896489854856f3b65ee7ba6dd3ecfda5bfad9e1e9150cd76d1421a6e6ef04f90847d1180c1a81b29496cf1424c0f2d39d75" + "content" : "7a9ba3da73796fdd34e4d6882809e8ada882e398a2625d83a67f1704028c49fdf05d70f4b757b1947a891f0005fa1133d99516e2751f706481c7c5025c6e74ff" } ], "licenses" : [ @@ -1347,7 +1347,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1365,45 +1365,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-handler@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-handler@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-handler", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "3ac32114d15dc2b284571f5d35c6b790" + "content" : "d7b41fac3cf644831b0a5bb70ec0ebcc" }, { "alg" : "SHA-1", - "content" : "567f8742a3d7a0a8be0401dada8fbdbaa7bf02b6" + "content" : "9114e02d69825eed3a901d762026261af15a56ea" }, { "alg" : "SHA-256", - "content" : "245e74e04b6f4e8ef98853152412e3bf1499ce6fcf15329b798c8ce36c3537e2" + "content" : "54bb1a59f46a3aefd117942e6acee09e555b756776bad731fc06159d89c2da28" }, { "alg" : "SHA-512", - "content" : "33542ba61fa2ae931d06d6e4c1df904331e0c02dee912a06793959982481df15cc284ebb105cd7de9f31b5beb140a9cd31a609f4fd71182c317a82ead35a4299" + "content" : "13a4d19f934e2327764b19ff12816baa6bd3cdb5991e06104520241276184e052f895a5e5ab1c0ac6492d002e6e412efa65c47c7f14c7fa8eecb5655b1371b27" }, { "alg" : "SHA-384", - "content" : "9d80cf4a3d8d402b453e7ce34a4d885a2924b6a56c9ffd9fb26100e123b5ca5397deb70c46085e8714c76db792d658ed" + "content" : "225271b726dbc059393cdc898688fb2733802451a2b0fd9e8e9743ec6578cf9d11e482e5c71e8f29d21e9248d8a54703" }, { "alg" : "SHA3-384", - "content" : "0bcbf90f65c5333a60b15e46ac69f0b669f5a8932c3042dc3bbd4dcd0f6186a21155911148ae2f19a58394d9dddb6cb7" + "content" : "5654da7bacdbeba834b698ebb680fbefdd36dcd9f1879df2a76020213ab9c80fcf17937b16dbc98786cad40baf3d0d97" }, { "alg" : "SHA3-256", - "content" : "2844a349d6c7446874174272167217bb70287ae50244fe9f6c9159e23d804dad" + "content" : "529cc2ac8c6bb306b64a02f9004e5b3533962a5d9ac3db5217f11de4752704b9" }, { "alg" : "SHA3-512", - "content" : "c668303216efc23b45f9efd6c02d8b7814941bcdceb63facb165334f1119a7cb76bfc042a6ca982a852f38a753da71515a02839ab325e230d1a362ce38e88760" + "content" : "42d68ceb93c6d3855f5c522716f48a8d090567a5d3cad589f2bb10820f196e30ea37341b43081711f8d6326838be206b691aef691bfe2ed88bf2109f89330dbd" } ], "licenses" : [ @@ -1413,7 +1413,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-handler@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-handler@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1431,45 +1431,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-codec-http2@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-codec-http2@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-codec-http2", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "6449e3a486f2ed3a3c3d2e55eee93cd9" + "content" : "a738556000f1858e63207524bbaa03a2" }, { "alg" : "SHA-1", - "content" : "56375e341f0ca44079b1bfb62030285b9de5d713" + "content" : "4ce8dfcec2376f945f7c29e5d423105161230117" }, { "alg" : "SHA-256", - "content" : "aa4e81ab5fa3b7b243eb3e814aa582ab26c073d31b0abffdbb58ee150fa49c16" + "content" : "14f67ae095c056b062aa0ee2c7b01f6b78004cf3620a6e9061bcb637f079387a" }, { "alg" : "SHA-512", - "content" : "aac3e7c20a1af95b8adf69b08cc09c0045f2ca4ec25516d57f8a4d5f05973369005b8b117e0c18e3741a77ffb0f50d9624bb64c81f9ddf33fe572e4607d79f29" + "content" : "920b1c00f297e56fc3b2dbb2d1a983d014f5727179d147c17bba56e4d9d85c1a2b1bc918bb4a63b59852e572a83c430646ad4a8d52644a083e66433ab4e8da6e" }, { "alg" : "SHA-384", - "content" : "a38dd44f5cb48587f0fb42c41a64d3d4d677ecfad681c4c4bee404d79294d9b5f099458648ab7047f3846eb9e15f85cf" + "content" : "805957ca9f9884b6c1bec3c92452233bd77a575f2b2851ed3a86c0dbd3ccc344e8092a3f66082341f1421ec7d9359474" }, { "alg" : "SHA3-384", - "content" : "3d3bf5ee22ddf455393ee7ab15d888640e86618ddce6f4649fbfd06323105113376c1f7c665c76e6d2d5d9f3e34a57cd" + "content" : "20d9c091e9401b70ff2564fb93fdca0488b00128751eeaac3e3d022d67ab9d0243bca9023860db9309b68876cc7dcbdd" }, { "alg" : "SHA3-256", - "content" : "612686c5b416a81f2bc9c8a4b553d59e6d218c83c4204b58df56f9109d2f82bb" + "content" : "229c79f4b60b7c5ed6b6200af10a15bd3918175a36162094fbf7a16eb4f7079e" }, { "alg" : "SHA3-512", - "content" : "85d4272bf01de6b1f64725dc168fb2ac00d83b2f2dd0c63f341967554f15b5dd0b902028448e7a24c9d5948f1d25b7163810c3a612b5056e212203635e5fc797" + "content" : "93e122e0c47079b60142cb8a4132c2ebfe354e1362803cf0e299070357609c54e653246e85956be346cfaff556cc241e0668bf428a91930ba3a15ee422895808" } ], "licenses" : [ @@ -1479,7 +1479,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-codec-http2@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-codec-http2@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1497,45 +1497,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-resolver-dns@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-resolver-dns@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-resolver-dns", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "2099aaf4771a939f4720897331acacaf" + "content" : "83bc3a4af7a32017330cbb698d9393e7" }, { "alg" : "SHA-1", - "content" : "0d092a2851ecef2a07722da7da80a843f7e936ae" + "content" : "de43e9f34094a67a3495659fc49ee1a303f4d78c" }, { "alg" : "SHA-256", - "content" : "ca25581e4cebd55797ef3b4d0953b75df32c1af77fe771b96bfaa9e701cdb7c3" + "content" : "9c05a9b18b5d54fcc1569c48b93958019cf3dde5ae7011f8a46451ee05e7daff" }, { "alg" : "SHA-512", - "content" : "6fc7130da77cfef4510fe9ae2bacd985980cb357d993242605e956c8c4f51bbbdfa9caf1950adaf524793ae0caa520277c0c036be9b5ae2a03447f836fe30256" + "content" : "4bc7209071556bdaebabeadcd17da258bf7859c7c4524351c1d292bb84211b14496158352c03421d043fcdd21be9cfac3bea2ce1a754dea0baa6a5557e00571a" }, { "alg" : "SHA-384", - "content" : "d858b05204f75696016ad813e8fdfd18a87d8f403bb32154fabcf1c5a08df2f97f8821142c07e08871575fb61093963a" + "content" : "5d3cedd0d3610b892ea03d7a2c8c7aae4fd6a6c7437c33234cc6d2a1575c70af75b335037dca562ac04bf039dd1807b2" }, { "alg" : "SHA3-384", - "content" : "7d098aad0661a71b9575c9cd6752ce08f4abb3a9fe786c949aa06cf3895f6afaad4debc800565fa710f6ad26bdd97330" + "content" : "3eca0a8d27cdcff557dcea06ab91213840d4cbf06e2f10c8f275dd35e83dbd0768c46d9ef0bfec6ead05bc2cd370c5f6" }, { "alg" : "SHA3-256", - "content" : "197fbd0361d6136445d478ebf488a050f3ce3ac6f81a95656d13a2c67490b5c4" + "content" : "7bfb9c4cb0d9ed788e6be029802fc5331a2f38e671bdb812fd2ca97111cf6705" }, { "alg" : "SHA3-512", - "content" : "d561f016fc0be5fddf0b45d3b84fdfd482f5fa3b8a9373778e2183a4ab3e7ca3a9e0ac66626e75763d374911358ee3e74d249037c6d2cbd1b9de416b466b5779" + "content" : "9e54e1c2759b87d07a5fbfdb5a698967d17a882e15a492833136a7a7f24a67a04c4029817f063b9fa5f4493c425202ac38d930b7b40a2aea25eeb742a243533a" } ], "licenses" : [ @@ -1545,7 +1545,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-resolver-dns@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-resolver-dns@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1563,45 +1563,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-resolver@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-resolver@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-resolver", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "129c1aa96462490d58a358aceaba0553" + "content" : "020fa71ad6ac3fd061d8a4427e118758" }, { "alg" : "SHA-1", - "content" : "d43b00856050cfd4445a063d5816a4782ae422f9" + "content" : "54e0ce6acf52451fa951a4632f40b516b00a87b6" }, { "alg" : "SHA-256", - "content" : "77dd03865965b6c12b9e521bddec82f035caeb33156e09c158289c5094318481" + "content" : "e64972fec474f5b9bd086b738154d190a923e82c4923ac550aff4f5ea9e98600" }, { "alg" : "SHA-512", - "content" : "f4f6d22bd7805e631780d660dfec268960780f9c73da4aa5c2835c89719cb738f457d6a833473c621ccf578cbeacfbf83764360bc50ae3d95b72fe7b5cf3a6c5" + "content" : "fb73dc56ac6ac0b4233c2f6eaa2918b58519ff1073ac70520a17f714d21cdef12b38c72f87895c680ab040d8b46cb8ce1db0ad1839b4f2712cfc422b36ad27c4" }, { "alg" : "SHA-384", - "content" : "efa50893602f0bbd54e7cf6e0066192882fb77b0f7271ccb8ba2f5efad3365fe0c5257dab21873300019113376ae9bcf" + "content" : "04bb48b4c9755f14d480fca64ac6b658acb6ccd112a34eb0e209eb9a50573232d98f2c9b1aa15aadff1661893f235320" }, { "alg" : "SHA3-384", - "content" : "edd91ab9326b41343c7030bef8d897e0ba861ee9e40898ed4b7a38512c31c76b34a8e085f6f96886686b4732e7d3d7a2" + "content" : "1a4006f6c3592c05d8160ed6b8670d2a51c7bc8c8948898a43e494fff092b3b7948bbaf58dfac2b91c824bf34a4e04ef" }, { "alg" : "SHA3-256", - "content" : "316ea500ee5b62175051f725693669a4909c046e4b009164a23584a0169d6b35" + "content" : "9a4bfc5f2afffe2725efecbeeb7602de72223fe00ec68bddb54c57d66055f9fc" }, { "alg" : "SHA3-512", - "content" : "3b44e554de6c1aee42d2087eb04e8b9096a5a1ed85ebdd5f8d8e2254dc4b1b3d0a3cf3daad4dfc3d9486de88c70b0b03d75f3e055e6880343d821f147f36e0bd" + "content" : "b9c0eaef1c4b98e7896d265eb6191c44c15488096972e1fb62e31c1c60c1ccb00c9d21d08d07ac64a12d4576076951924229989284d075f08f5bb133fc605f88" } ], "licenses" : [ @@ -1611,7 +1611,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-resolver@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-resolver@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1629,45 +1629,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-codec-dns@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-codec-dns@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-codec-dns", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "a424ef4d2ebfea699f9d1218c41f8662" + "content" : "6344e1103219485b1c46503c3f741859" }, { "alg" : "SHA-1", - "content" : "0814e353f6c5f9383f2b93d941961f89565f4161" + "content" : "143b952318f5624dff1707dfd1c4cf8565998385" }, { "alg" : "SHA-256", - "content" : "5e996d7ac7597f368ab114fbb91d16788918c7e5bf166345c51e56db54d50fd1" + "content" : "89bddc4ec42756c41a0195a50a1323a324836162b6712a843c3de5c29096c93b" }, { "alg" : "SHA-512", - "content" : "6776da183ffbdef92da487c1ff071cb77f1f020c2f9730183a1959226d3bb4f8a592fa8118ea78f3f8938d35129c1bcfd60176d0802803b8897ead66a6e92846" + "content" : "9aab4727b460c95e7b9a56e76cf0d0f18ac2f55f01c65cad7cc1559a6d7ede9236e73740350f890e6813394ab75491b60cc567ddb4a24e4c5874770a97f2d094" }, { "alg" : "SHA-384", - "content" : "8cf961cacb0ca2ad8edc654105d746078c76a8922780ca209babf65037cdbe1f9cdbbaa9cf6a4d602e88877032867c38" + "content" : "acfd49075478a33a0ed2393dfa64ee6400eabb904724f338697be544aa7cf99f1808a4a7cf41236987759ff37f7295c7" }, { "alg" : "SHA3-384", - "content" : "cb6d88cdc3ccf23c20d89322ffb35c6172746bb8b9a785599245fc72c645aee44cad19c69a5305e0559158a7fc62b562" + "content" : "eed5eb30fb135fb7d5e5cf37eb9f242b397990696c8a77709030a0277a9adbb5ee3b735a36cc3143dbda8ac3a9575c0f" }, { "alg" : "SHA3-256", - "content" : "d396287ecffdf697be77ea8bdb43da2f9ce91e13e5abc07ee931a889c5406e99" + "content" : "75161e25f223eb91cb7a574be46b058eb4f8060323144b94caa56f60bb134d07" }, { "alg" : "SHA3-512", - "content" : "d9cb1b743e95eed44fd9fe40db6638e6f0f870b536c240ac23364acc3be06b86e49421bc38d138c6bc30f272a90ccc9ba7cf9d0017acf03d3664cc69a40aa6e0" + "content" : "9fe9df9a48ab7e89d202a45b24eb96afb74dc0cffb87ad5ba5cf4ed416c3d6a3445eabf26cb869403bf84803687bd3eaddbd96bc58ae97cd650b11981890ce89" } ], "licenses" : [ @@ -1677,7 +1677,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-codec-dns@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-codec-dns@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1695,45 +1695,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.135.Final?classifier=osx-x86_64&type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.136.Final?classifier=osx-x86_64&type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-resolver-dns-native-macos", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "9aff4b62bef6c3e4cccb8fc5e72db35c" + "content" : "ef6154c32fff9fcf90ded6acabf6acd7" }, { "alg" : "SHA-1", - "content" : "160036ecf20002bf9b7b563ea2101f26c3ee64d8" + "content" : "fd7a2c2da74df9a3aaa6ae31902365ef594f4d48" }, { "alg" : "SHA-256", - "content" : "0c86fa27317c4172fff03a0c20286e2c62ef9d60ad78f389a83ede48a5bb54cd" + "content" : "0d4ee2dbe280d70099618c7efcd029b46b85359ba633a0eef05e5bab3667bdd5" }, { "alg" : "SHA-512", - "content" : "74ce3f47ea26a890d51e8fcbc2e25fba034b12a067b4df61734d5c7ac7d44971e8f55c3360e031f7e867e30b43ba4b8ca8d22e57ae78ed6a0cd769869feec128" + "content" : "05c0bbc8864f48b439e2af6403e9f51bded8239b759662f1cd696656fe6d89150cee3a0478645fd70fe9971d761f5870ff5e7eca863069628dd915d8dd432cc3" }, { "alg" : "SHA-384", - "content" : "849b343d9e7ef7ad09756ac9e347c0c08315ee60eab116f236eacec23af1ecf4d7a8329f2a0f8699faaf5e8888e135ac" + "content" : "ae9941f406a273bcadad741dc66a210b3aea3b9adf6bfeb07116dd2250356a8424a4a6773af6b996e862ba7cd3552a2a" }, { "alg" : "SHA3-384", - "content" : "9f64f63e61b7c8b7d57d196bb6067e8842643321e32afc7ad62362c8915fdc0f92f3efa639c80173715024cf5916c41d" + "content" : "4072ade6dec8794f3c9e999f3dac56671f8c419db690bd2ededc2307342c864a107af3732e1d81af5dfd29872d3e9f99" }, { "alg" : "SHA3-256", - "content" : "907a413fb830590379df951cfe91051df322ac640e5d8a4f4c4306d6217b0737" + "content" : "3d51e5edadf72fb575a32cd7b5d8f7627c22e83ce445f10712140271c2742d14" }, { "alg" : "SHA3-512", - "content" : "932585e4251601a5d6a6a482ae45a924fae7452e19609bc446d74c584977b6264e60bcc58761cf74b89912dad834520ec1dae16600978528a34a99488cec0b4f" + "content" : "9f37dfa1cc4f1d24b72dc8751d18065e94997c3c04fe84164b14fb40ae5224661fda5662164250e5342ae9e47b3206004efa5c491681e77818852524bdacf53c" } ], "licenses" : [ @@ -1743,7 +1743,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.135.Final?classifier=osx-x86_64&type=jar", + "purl" : "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.136.Final?classifier=osx-x86_64&type=jar", "externalReferences" : [ { "type" : "website", @@ -1761,45 +1761,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-resolver-dns-classes-macos", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "1cc229f2acf3ac7ea3331021f38a684f" + "content" : "77e4bc0cf256cbfcaf5a2bf73e378381" }, { "alg" : "SHA-1", - "content" : "508fc43de462e60c814e76d7e23dea59b0cc4fe8" + "content" : "474af89eaee42a9b9ceef222a7fcecc80cddbbed" }, { "alg" : "SHA-256", - "content" : "4aab49a507dbbe446ad2c6a7587fe69c511defa6c273ce1a559e3458a3378a5b" + "content" : "c8c3ad3b364d302cf2a3ed8702bfeae455536963382d0d9260b9fc47ea6f234a" }, { "alg" : "SHA-512", - "content" : "47d2792a6c257b22d4f61a3dbbc3150d8cada4f89343869c67af5d04282af9f53d3ae1f55b20b173d425725a8e41bb98747a37f8652428a39f0ed02cd801a326" + "content" : "ec1cd7fe56a1df1853934bc511e7a802f2e18a29edba881e54b443351473ea71780095c61d194ae87df89801f0a43e506fe05f8c4b7f2678f023d76535749865" }, { "alg" : "SHA-384", - "content" : "6cce60f35b59287ca061318411681bba557e046580065d326baaa9097593935b28904d10303cf6cad7b75e71648301cd" + "content" : "63e02bbf5166a9564af91c560235ac6bfe5098c8195edab93adc685e956b450a6807bb196b72c6a20e7bad87d1e87be6" }, { "alg" : "SHA3-384", - "content" : "ea645153e34d957274f4c98d1796976abe8bbf11655f77f6534143a24c003f1c26a6b70ac1449a5c64058c9be157e682" + "content" : "72daaa90d671802dba4462fc9cc23300969109e7818269af81ddddfe21daeeffa9b96ec38adc530a21a35f6ef1b0b61e" }, { "alg" : "SHA3-256", - "content" : "de57d13e362c759ec02c183ff526b130deff23b4c376084ed8dd36f760311159" + "content" : "d04a1022e2342063817caf392c4ecb585558e023a8a462eb8ba4f24ead3fdf66" }, { "alg" : "SHA3-512", - "content" : "b9dd08e2ea0d488f3943027d8a92837697f4687ab3e8fbe54af9ce64c04423740e8a392042e28e81902bc4c1efc5afe7731eaf4553b546ff1900868a70ed1c20" + "content" : "42fd7915ec51bfaf936465fe13eb003cb9bc405a914167f944662739295be38d5c03faf648de8fa36b4181325b31700616d924641a11c75ba7cade64326d2fd1" } ], "licenses" : [ @@ -1809,7 +1809,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1827,45 +1827,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-transport-native-epoll@4.1.135.Final?classifier=linux-x86_64&type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-transport-native-epoll@4.1.136.Final?classifier=linux-x86_64&type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-transport-native-epoll", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "97756f389188954811a6dacbfe0005ab" + "content" : "d01515edc2dc9280fcb605d0d1638c1a" }, { "alg" : "SHA-1", - "content" : "9321b17feb084585c9286208ad2c438cc9abed30" + "content" : "4d2ce2c0069e2b5bc5b7e546a5a7bf1e0742f75c" }, { "alg" : "SHA-256", - "content" : "18a40063da3364cffff81c6c2097fb6ebcb45c62264dabcce45aade4fdac3125" + "content" : "c3956f90241582bbbad5612c3159cffeb7c3533324759f3c3bd778ca9b176cae" }, { "alg" : "SHA-512", - "content" : "89e0517d5aded7a91dab4560ff65b7dd8ec30d8a0acadff8fd708dda843df7648cbed22225724376b699c221758a02c674b443a7d18dd707bae7c1985380cc0a" + "content" : "1b3077f7c921ddc37122ff95d69ceac288858ab59b8636548f8f7ecb1df872c33105c0c48fb574c1520bcd4d660d79710b2ed6efe7186f0b2fc65b55d8f0d940" }, { "alg" : "SHA-384", - "content" : "a87d40e90002dac61eff262131570623bac1d6a40ceb576601c8e68f5aa791539ac1c5983b6cfc783804fcbe7b2fa51f" + "content" : "f3b8f22160152e149da61dbb588c200f808a7f93136ae49af10bff0eb96adc7d1ab77fe2db0e212f03d6c1d02ff57937" }, { "alg" : "SHA3-384", - "content" : "015a7d8c67bd0354c13ceabc183ed717b4977d54e2b2c19602d75f439fef81e5e6618ea3222781d6bd7eecb865d5118b" + "content" : "2b3eb64886c36a4c4f5234233c6752fdf576538568e50f8257ff2c35493c6b34cc15f497111bd7a405b9932a0a5a41e4" }, { "alg" : "SHA3-256", - "content" : "48ba69067259e8ca33aecf4fa5849089ad078a309338e2f2a89a3fcb3aabc2a4" + "content" : "f6fd525ed45de1e68925283112b6902356943464a65df6dbec0110608129ad8d" }, { "alg" : "SHA3-512", - "content" : "102fdaf685a08dcf3e57101b3470b967a1aa84373b1a874c3e253a66b611daade82faf6954958f018d6e4ed82c612a250bec806d0c7a60966703054fa1ac328b" + "content" : "3ebcae1ce865793d18304e56b96dc14efaa724a087d0051b606ecc67c8425f24b4cf89a5d3f3509d876ff37a8f9bb0a2f3d43113a7ca658f0404525c390f3789" } ], "licenses" : [ @@ -1875,7 +1875,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-transport-native-epoll@4.1.135.Final?classifier=linux-x86_64&type=jar", + "purl" : "pkg:maven/io.netty/netty-transport-native-epoll@4.1.136.Final?classifier=linux-x86_64&type=jar", "externalReferences" : [ { "type" : "website", @@ -1893,45 +1893,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-transport-native-unix-common", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Static library which contains common unix utilities.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "1e2845ca46311605e0a5e1623bc22e47" + "content" : "4de177266bbf4335c7168ddeef5de796" }, { "alg" : "SHA-1", - "content" : "9ceeb22325232e9998e3f0ae396e5e4cb462672f" + "content" : "550e2091240ce07f9c1f114aecd89294df92fcb7" }, { "alg" : "SHA-256", - "content" : "a7895075f112611d1640a596c2678a28aab92d5681c1c14755b109b8998f995e" + "content" : "7e014c9b13defd9d254d4e5a5edd8ab6dde17533e1152f99699a6b28b2967a8a" }, { "alg" : "SHA-512", - "content" : "e6be72757a08d1389ec5f78c2cb4ab099ed1dc4f1d1003ca892641a07b2fbe248bf2fb5f0b23a67999b98f2feb0e6fa9d897dae4fb6413c912a8b8945986bc11" + "content" : "b96384fc0757b7b4da71a791addf9a6bd7b22794726de49148e7bb0c463b8481058eaeb02c99365fd990a9328c7d3801f4b607c7effda55b6993d1c87c9aef07" }, { "alg" : "SHA-384", - "content" : "fcb44d8789ce69fa7e3245ee13d01754a3a85c6fe9a6a9ecb0ef6c904ea8833525001cfb817db8afa6e412f670f93fcd" + "content" : "61e5ce1f86efef4422952ef6b34e157b3c0e022398af27fabf1f3e29cfc3ed9bf06412798d056befbbbbac7f4fa9814b" }, { "alg" : "SHA3-384", - "content" : "c8ae6de42edbfe2cff295a4b808beae783eacdfa9b431b090a59340548e03f138bb50dfca3e83ea6365b96fb4a86ee7d" + "content" : "ba535dfccf8eb23188059efa87d239b3ea1515d02ae2d66b6519e301dbb5a5d58775003490e7c899c02239523aaf0441" }, { "alg" : "SHA3-256", - "content" : "111d714be16afedf991598213fd8cab5f0b8ccaa6596ac6cb635b8bda62c61a0" + "content" : "b24eb906574fc11aaeb74026fc2e5554a24197a45c6010cf984123daa268bc3b" }, { "alg" : "SHA3-512", - "content" : "b6bc5cbb82b3cbcb250bbc1d1548084ca6d92bf3ced1bced111f4aa3bbfe6f77f87208d781165f49045262e4fc502d5a2349257046b08a6b8432ec2e6afc3819" + "content" : "82b09870019e5229a428519b5fdaca932abaa742512b01cbc7cae2c1891969a8e9f43face683b2f68f483587bad48168883a30baf91d52ef62c216d6f8f33e64" } ], "licenses" : [ @@ -1941,7 +1941,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -1959,45 +1959,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-transport-classes-epoll@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-transport-classes-epoll@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-transport-classes-epoll", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "5494b16595a173bc4353eb1fbc4b8033" + "content" : "544fc46a9f54b83567ffdff8700f0405" }, { "alg" : "SHA-1", - "content" : "4f2bd9bcd7c91768c1f9aca8e62870ece18527bd" + "content" : "bf7e09fc712f4c3fef6563fe121d4b424fede6f3" }, { "alg" : "SHA-256", - "content" : "9d9537ab9e15164c9f0dc0748884c148814a18d78ac6dfa65cf4b3d06068ce01" + "content" : "f6a0b631b98667f131daf4ca07a9cae1072d58e259dcbc0f8c6a053d843449c6" }, { "alg" : "SHA-512", - "content" : "1fb2bd89e154d771580ad2a14f954f3b0325bdf949c1cfe4102a62ff9eb53fd8b1c45373206262a64b644016302cf13f8ae8dfc8a757350402212f82b1dc4e8a" + "content" : "1198533994e14eb5f4afbbe0db83f6cafcfd2d1a58138a911386cfb8f35d554e733277bb1d079e0b63bf34c5f15eb2fa913e29ddb647f71a2b3d78fc9b4a9715" }, { "alg" : "SHA-384", - "content" : "d676506b94103de1b1adc82083eb84e4132c4419ee838a0cdbadef251a835d61bb427e3b4a258fd5d02493615ef67107" + "content" : "765cc327cadbe46d5230aa24d806d48a210f860e4d8575f700fe8b49788a0241efbf4f08bbe2aa9bd3a2e86485e47a7e" }, { "alg" : "SHA3-384", - "content" : "f71e38b5f318a186c0f405fa27a88f35a6029a64226730094db93f8714331d023775f84317d5bf4c6c9e40727f520e87" + "content" : "ae6158851bebb9fae34fbda6a2fd2466b167129c8588e260e17149a68bd29d4bd4b7391a2846c7e3ecb711fd88065d57" }, { "alg" : "SHA3-256", - "content" : "bc4e704f8a354fe07524d7eebe2852b8bcc7b61ef5dc285176f8c1d0867d9d08" + "content" : "fc5a54f07589f8e3a58e9bac8617b22f70a691f6741ee42ce4969e1a601669e6" }, { "alg" : "SHA3-512", - "content" : "1e2f5e1c527812bcda9830eac830242614842b2e0d1292f53bc0e7bddd9b6ab5816dfd0350c29cb2699b736f4e5319cce743bf3890ea117073cb350cda622e81" + "content" : "1f49a2f1c57a99b0edee3c40851b51cdf2d6187c6ac168ec0156f8a013e113b0d8145b15aae8ffc9009a5c4bea3a2ee000b10d8f8174357b5caecb2a2c698130" } ], "licenses" : [ @@ -2007,7 +2007,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-transport-classes-epoll@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-transport-classes-epoll@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -2091,45 +2091,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-handler-proxy@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-handler-proxy@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-handler-proxy", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "e981f38ac2e481c77e0685e5bcb0e284" + "content" : "b16d047bc4f63673b642984e973f08db" }, { "alg" : "SHA-1", - "content" : "df2a683563e8ca8442a49ddc9c2da9110dcc3cef" + "content" : "3172bd2f12e169a103140c8f82915376687aa22a" }, { "alg" : "SHA-256", - "content" : "75661010630a44468f0e85d7ed8be7779c0cb1369fe85d30799cedc52e9ed3b7" + "content" : "47400551f0444dea33629ee0c52d4e30856b2ddf00b12adf1881902957e74cf2" }, { "alg" : "SHA-512", - "content" : "367aff1b592e168775d8de14f5256a714a2bbbf85deb19a2b1c5d21f85d5bd067356dc028466080b6086301bf0a9df16be98286f0b4ddae6e1a679e3f68a58a5" + "content" : "8c8d99f25f8ae9a045a40e37b44b0b346126c4ddce8d149796c98d48c99a4b757e2f59df0f25c16e009f6bad63c0ce62b5117d3d74674df12135e0f94f01d8da" }, { "alg" : "SHA-384", - "content" : "fcd074ad72eea319a8c479f91a2ccf352199d32ea608dad8ec8f243e19e4cdbaeca3c3cdf9fdf568e73c77804b050f34" + "content" : "ac6b39a8f9794a778b0ee109237d9759d0f95a3763b9eeba0a541d179f42ea8647b65e4ef1ddb6791f47d1d761b6206d" }, { "alg" : "SHA3-384", - "content" : "bfd4c4c8f7a4f57239a824b442cf1ad5d11e70d7abf26794342b296bc8c1e59f510f6f7c082fca349e2cc1aa9c0d5fb8" + "content" : "e70f09b674ea3ec9092df0dfafdf3dd4eab14df925418e0dc23a2e3c22404af9b37d0bfd6b89cefd85cbab6ec86bb762" }, { "alg" : "SHA3-256", - "content" : "2bbd040bd29192dca98f50c9f28a711613d2bc72e1b38e20aaf91d7c8e6e0a7c" + "content" : "a60487c209137050a18453773bfa6a6ca51f7b08f1b95a9911b57148ba704a7b" }, { "alg" : "SHA3-512", - "content" : "b815dc49bfc7bccae35905aae97546c9156673f7c524ec1a0f505c8e7fc0e462710e611bc212c8b5ae803c2a77d458701ae02c54d127d18a0aeaa64ead762232" + "content" : "95cecf430d76f1e52219bd8a99f7f3d50160e6e2467056710476bb44eaed486c030e84b925b67da8820bbd91e23d20d0049deac7cca370a42df1a12d06d07533" } ], "licenses" : [ @@ -2139,7 +2139,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-handler-proxy@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-handler-proxy@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -2157,45 +2157,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/io.netty/netty-codec-socks@4.1.135.Final?type=jar", + "bom-ref" : "pkg:maven/io.netty/netty-codec-socks@4.1.136.Final?type=jar", "publisher" : "The Netty Project", "group" : "io.netty", "name" : "netty-codec-socks", - "version" : "4.1.135.Final", + "version" : "4.1.136.Final", "description" : "Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers and clients.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "f022acaa2b77f55e286168e22a868932" + "content" : "fc4f904e8b36adddf7e6da7b433fe1aa" }, { "alg" : "SHA-1", - "content" : "65675a46d40f11b27dce2593d05be327d083f5f2" + "content" : "9513dc426df3365f5422e5d83b0d40d2726f0460" }, { "alg" : "SHA-256", - "content" : "ec7a39e8d7d7e223014115a021273f011c3cb1e8fb187cbfb90a74e76d68c25c" + "content" : "341f47dbaac667a6e7e6cad4114218025f9755ced641bb0740fb69e34d29b421" }, { "alg" : "SHA-512", - "content" : "ab55a58bb253d2ad63adaef77e642f009902efa8749746798ad598e219ed487445ccc5f5c4dfaeb8a0616122b5b12257ef709c3ca834786152d7b887a674ec51" + "content" : "b004b9a30f536d53c7b15ef48757a0b26d9550459fede7b68d22a4821cf876a3268c059f49f89a71010cb160750f0ed2d519ed1715d262cb9ef56de314050b9a" }, { "alg" : "SHA-384", - "content" : "68d1053454ae05519e6092ec23c1b11986dac155e128dc6f3f249653bcf8c90e71e31c8db6c88b5691efe5cc26530dd0" + "content" : "42742f0902ef695eb82b7fd4118a11c37b5f01e971edfab27449d3e7fcd50c3fd98bb52508fad455dbb11c943108e910" }, { "alg" : "SHA3-384", - "content" : "bd2903d5efe990b81cfc9844cbfbfc2cf36d9f73a7d137bd89eda2590d507a622d0e89ff7d5f08d52be5d2944e71d360" + "content" : "0674aae29a22360a92e4278f81a187482a194b014fd170e99d1d70f4ef0b6ff4bc6bee661139b0d6f7e123f538ddd501" }, { "alg" : "SHA3-256", - "content" : "5d6ebec22d5ae3695aea50fff14777bc1b1452477b593ad3d44127149bf9b883" + "content" : "07c8f65fe764c1135132f00c34c15494ce9b6bcce39c8baef849efcbcc6fd113" }, { "alg" : "SHA3-512", - "content" : "06686081615d84611543363cdb0398e9411fd7d7c47765b9cf4ee6a2599095c8d55aed91c21759b50e9507eb535b825623a4b5be39caa60444bec00b5cb22805" + "content" : "64ee3f092ee297b9a7e1e719952d777a4d38de56f6467a1923453b2f551545d9e26b24582d7332d72d39be2d23d2ccf7d073b4acc68fdcd6994e20d660b8ad3b" } ], "licenses" : [ @@ -2205,7 +2205,7 @@ } } ], - "purl" : "pkg:maven/io.netty/netty-codec-socks@4.1.135.Final?type=jar", + "purl" : "pkg:maven/io.netty/netty-codec-socks@4.1.136.Final?type=jar", "externalReferences" : [ { "type" : "website", @@ -3493,45 +3493,45 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/org.apache.pdfbox/pdfbox@3.0.3?type=jar", + "bom-ref" : "pkg:maven/org.apache.pdfbox/pdfbox@3.0.8?type=jar", "publisher" : "The Apache Software Foundation", "group" : "org.apache.pdfbox", "name" : "pdfbox", - "version" : "3.0.3", + "version" : "3.0.8", "description" : "The Apache PDFBox library is an open source Java tool for working with PDF documents.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "15cd4480a886e22cd081537be5a11f14" + "content" : "92ed0441337f2ed1f43eeaa901bd7814" }, { "alg" : "SHA-1", - "content" : "a739bfc1b72d2f98d973cd1419f5ae2decd36068" + "content" : "c5d8c0b56aa156d64283f07729215a9938be2e69" }, { "alg" : "SHA-256", - "content" : "5be38d2ec81691b05d535eb720de4dc566c5d07e5a04731fa00668d153a8b4a6" + "content" : "97647cfbde61ebcfc06b4cf8c9b0ffcaaee073396eceb4a7f6836a9b9128903c" }, { "alg" : "SHA-512", - "content" : "84c51bce7ff6ebc9f7159a824a0de4ad575c6c546920cc776de494b1af7d28ba9f30c90a59ccd8ea25390c43567389e168535cc1da4b1d5aaad4cb8810b6c743" + "content" : "bd59e4918285dbed5d5a5e922368b139b2d3eed3e7b1102e726bbc69041328c82175826b92f341885f17f22c7af8438f4c5e676f25b90a8c062d42b1d7e5a57c" }, { "alg" : "SHA-384", - "content" : "6fd95bb4a5a20d9905ab1925e6721c4d57602f5a6aee4740f3fd152a50d042c63c945d6a48a67717a9529672f3945161" + "content" : "bf70e79d86149d82d3a475bf8be8bb19dedd88ebb975af4cb4e07b53c1ee70e6d53e18a83db2ce2b48918ea733014956" }, { "alg" : "SHA3-384", - "content" : "e6beb355db668b0cc477cb81163e4e70a9bb4cbce12021bc109c5f8f40270e06511d9e0989ac3744fdf163496631c5f5" + "content" : "420f994bff085278cc7f2061ec10373e5c0b39809cc6dff82986b0f4ed65bdca88cb3f65fb124066c6905c02f565430a" }, { "alg" : "SHA3-256", - "content" : "a47d4c4f31d25621ee4e77d534573d36a4502631a62ed2db9f1e6882695c927d" + "content" : "f54d3f1432e99a116d703f9d17dfca9adaf4cfe9e5dc2e57d1e6e22d7b9958b1" }, { "alg" : "SHA3-512", - "content" : "dd416a53eff57fbb8a5d41a6cb8d074a31352604ebd3749c2d0097a0d253d20dbb92682a291614c86ba7ed86901a4d40ce0ddf9850c8ac1cd32ee01037341ab3" + "content" : "e344572c8bf9a207113cb4a4f9ffc15e211d23413b791a4a8c009b4976eec9b4da39b3ef80d37b32f44a1e239c3b8c9148161f918d5452584472cf9d9a721364" } ], "licenses" : [ @@ -3542,7 +3542,7 @@ } } ], - "purl" : "pkg:maven/org.apache.pdfbox/pdfbox@3.0.3?type=jar", + "purl" : "pkg:maven/org.apache.pdfbox/pdfbox@3.0.8?type=jar", "externalReferences" : [ { "type" : "website", @@ -3562,51 +3562,51 @@ }, { "type" : "vcs", - "url" : "https://svn.apache.org/viewvc/pdfbox/tags/3.0.3/pdfbox" + "url" : "https://svn.apache.org/viewvc/pdfbox/tags/3.0.8/pdfbox" } ] }, { "type" : "library", - "bom-ref" : "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.3?type=jar", + "bom-ref" : "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.8?type=jar", "publisher" : "The Apache Software Foundation", "group" : "org.apache.pdfbox", "name" : "pdfbox-io", - "version" : "3.0.3", + "version" : "3.0.8", "description" : "The Apache PDFBox library is an open source Java tool for working with PDF documents. This artefact contains IO related classes.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "a348b5a7bdb7784fcf3d7ad7cc31d2b4" + "content" : "f45a2386cf4178b5955dda2807139745" }, { "alg" : "SHA-1", - "content" : "c40dcf555b72b1d8c0cd19391d63e5b58382b9cb" + "content" : "0953f459ebc08048b75afc3e823aea2e0466a01b" }, { "alg" : "SHA-256", - "content" : "123ea3187b497c54e661d50c3c867479bf77668ff450e50710c658f2bb4687ba" + "content" : "36a0e04001010b4c764857817412b96339930b19755e728959805cc0352061b2" }, { "alg" : "SHA-512", - "content" : "35a778196ccfb75959812cb17de5225413a99deb6b00e35533a32df9d5ba93fe6a9648765de8c03f242af5ab017580c1b5df4849370019867cd030fd2ea6b9fd" + "content" : "659c72ecfa9743df09321ef594cb0e948cabff17d7aed01583f78ec344e2126e6c75506abc43bc36369c0692bf09ea9e81b956ea9a3e28af29dd5c86bae9ff7b" }, { "alg" : "SHA-384", - "content" : "ca41c4bba6371ba6edf7d00f9bcacb83466a857b391b3bcf08b1e59e370241dac7607ebb164b97dc5c0e697f2f876f23" + "content" : "6df807534a26d19789036801d0d36e7efc10e53e854160732cf5827219c58f1a9fd8c1f84e9d8a8d08afec05545b81bc" }, { "alg" : "SHA3-384", - "content" : "41eca88d9e1daf1ea73761d6c3e5d47a699debcc546fc2216dc72231e449a1312220d17f46b0f7b8db745c82411eea84" + "content" : "83692991b95310b6d0a20dd5f591153740286615856ff72939df44628f0221d9f9f02275a8707bf59af7d12ed941bb05" }, { "alg" : "SHA3-256", - "content" : "118a036da75c311d6812d984f8f5788fcd2f3c4e6260dd744920cae920ece2ee" + "content" : "8a5620ef92655e317e1c8a9301d2a57ccd672354e6dd37fe24d5d9224867699e" }, { "alg" : "SHA3-512", - "content" : "710b5b1c4ec3a6f19201f0c9695b6c5601cfa9cf06776e431c642860e316e936971db1a58f35a8b6c8325041258e8c6f9ac8f081f9d820a181e1742692bccc4c" + "content" : "3b56fe5e9b77d3206ba4fb342c4a7b9329f357baf625bc5686cc89cc3df6057bcad526c90707ce2168733b3ca6113733abb9a96f1ccffbf04bfb1865b8ce4cdf" } ], "licenses" : [ @@ -3617,7 +3617,7 @@ } } ], - "purl" : "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.3?type=jar", + "purl" : "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.8?type=jar", "externalReferences" : [ { "type" : "website", @@ -3637,51 +3637,51 @@ }, { "type" : "vcs", - "url" : "https://svn.apache.org/viewvc/pdfbox/tags/3.0.3/io" + "url" : "https://svn.apache.org/viewvc/pdfbox/tags/3.0.8/io" } ] }, { "type" : "library", - "bom-ref" : "pkg:maven/org.apache.pdfbox/fontbox@3.0.3?type=jar", + "bom-ref" : "pkg:maven/org.apache.pdfbox/fontbox@3.0.8?type=jar", "publisher" : "The Apache Software Foundation", "group" : "org.apache.pdfbox", "name" : "fontbox", - "version" : "3.0.3", + "version" : "3.0.8", "description" : "The Apache FontBox library is an open source Java tool to obtain low level information from font files. FontBox is a subproject of Apache PDFBox.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "05adabd366e6f8a22ac04c293237dd89" + "content" : "379e6faf6ea614ad3318eda879e4f423" }, { "alg" : "SHA-1", - "content" : "9eebd1ee868a79fcf7390283b2baf4179dadb8ed" + "content" : "e9f4225dc564b212ecdc31d1574f2d0c1dc3ad30" }, { "alg" : "SHA-256", - "content" : "65690c3f39b04a14d12c17f4998c15186ce877d3e2ec222c708577e3cc028030" + "content" : "a1915c24e3edbe0ecec93896dfbf6d41427810b663ade97bd4e8bae86ec3fdab" }, { "alg" : "SHA-512", - "content" : "4e88662f50d0ecafe2bfccdbe3164a1ec01ab8ed451e8727f7c344794e53ee735fa1a4b584b8483795c905d13949eede22de417135e72652cd341d7317ef57a5" + "content" : "fdc1a5bcb016280c561e20684db77cba126660872f6ad03440596b8817ed97452e80237e0c3e41f8914711dac96abb8da8d1f37014eef7a7433fff85aff953b7" }, { "alg" : "SHA-384", - "content" : "41dfedf5735484e433cc8aae7e81fa388b5091ba6057940242960dfc78c873a48ddfe6c9245cfa01bd3e28ead30f7c32" + "content" : "a003c4f5a10df5885ae07dd485a4e2932bb9dfc1d858f44d582b6254e72171954e341f15b07f96f9fc745e47b2dfc6a5" }, { "alg" : "SHA3-384", - "content" : "79c39834996bc7fb66138f7e82dc1a64add1442a914f52710a5be006398bdcae50a43b2d98abd35d469bdb8a45a79d14" + "content" : "019569e5e5043e27220becc49b14d781c9e96ad3a20156b1e24249e8c36b4a2d4a6373b64b8640dea9b2c05dca9bda8f" }, { "alg" : "SHA3-256", - "content" : "98db31e368cb097e3e5928f1db6220f16ddd144b276471b2a8aa560cf8fdc89a" + "content" : "1d7995e3c97eab0405539ce436aac2b4f832a3ca7cb8628346e05955fb85c42c" }, { "alg" : "SHA3-512", - "content" : "8cc1fcf1d602d81b4680e5d34ae8bea76ffbf4e678be30fbe1fffa0d773b15d40e20ad9bb9f61e5af10dde46fb7e8df15e85ce83a40d37ddee55ee0d13c4cbc2" + "content" : "b00c2d71f06b9a754c5dbd667efad8edf2ac10fca14f2d222dcd09d8d1f3379a6675f7eb2a003ca7ea234ca8f67dcc7dd3d6872ce0b1401d4a13f4dd3bbd7dc0" } ], "licenses" : [ @@ -3692,7 +3692,7 @@ } } ], - "purl" : "pkg:maven/org.apache.pdfbox/fontbox@3.0.3?type=jar", + "purl" : "pkg:maven/org.apache.pdfbox/fontbox@3.0.8?type=jar", "externalReferences" : [ { "type" : "website", @@ -3712,51 +3712,51 @@ }, { "type" : "vcs", - "url" : "https://svn.apache.org/viewvc/pdfbox/tags/3.0.3/fontbox" + "url" : "https://svn.apache.org/viewvc/pdfbox/tags/3.0.8/fontbox" } ] }, { "type" : "library", - "bom-ref" : "pkg:maven/commons-logging/commons-logging@1.3.3?type=jar", + "bom-ref" : "pkg:maven/commons-logging/commons-logging@1.4.0?type=jar", "publisher" : "The Apache Software Foundation", "group" : "commons-logging", "name" : "commons-logging", - "version" : "1.3.3", + "version" : "1.4.0", "description" : "Apache Commons Logging is a thin adapter allowing configurable bridging to other, well-known logging systems.", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "62de1aea096b3ac52e46b908dac4ac97" + "content" : "954e27d33e55e587a4694d5952a0c5c6" }, { "alg" : "SHA-1", - "content" : "580ad1a4f34876c4f964c083361de31b3d60be68" + "content" : "e8f6313365dfa0580e49c58837afc8caa9b4ce05" }, { "alg" : "SHA-256", - "content" : "5828f96c09d886f9b1a0993c7804b27cf4fcec8534517164f5137ac8b67ea9b9" + "content" : "d175dbd751dd782a63bde28c7a039520e971f25e84b79c19b8435edc3603e0dc" }, { "alg" : "SHA-512", - "content" : "86adf089e9d5723bce8d4e0dcd0a7590c3aa27eed3982c8fde49f865dccb262043df8b47b2e721258fa8a880d0e32defa3406e9dd48ccc54c5d34793c919f36b" + "content" : "5c333d925f81fdd09a80176ff15cd38381eb12e9ec2dac75f113d66006002175b33cdd9a0668100c5c6a49ae81f9f576909b89219a345a0075ed4683a997570a" }, { "alg" : "SHA-384", - "content" : "b25673d250c7043c55ba65ba8160e4b8814ab99691f3a3c51e6069bbec330503a74e42a37bc00969beff19fcce4c6a35" + "content" : "31ed518d623d408894a188878693fd953a3aefebebdddc4a304769bf2862ae70623340cdea387b911a62ef7cb9043a37" }, { "alg" : "SHA3-384", - "content" : "f4c459c29b7f4cd9665f4110acb41370449db898512f95a424f2a06d2119dc22bf052a21d7896a3aa967603a010e0b59" + "content" : "1c91fab5c64bfa679da2186cdef39a0360c9d18122bf37b4dd932eee4502527e8458b5c849dbcc11e46ef5fb87d1a777" }, { "alg" : "SHA3-256", - "content" : "27483413bbea96155f333884b9c1ba0d776e4ce338674fa4ba7cc33af1073196" + "content" : "2189847ad7dd3d50bd17aab9dab8b31c8e7e25e72ca0d25ad83a4215bbfb5e2d" }, { "alg" : "SHA3-512", - "content" : "6af0c80bb419e4604f525e4fb5bd4e2cd373bc07daa106bef3d87af11c93759b2216e1ff6b1061017082145fc877c65b9cb079ef4a6acd4d16806de2fb47019b" + "content" : "f57be786553cc10966ce4af25ce401125ae76f6ee569812eafd67a6eaff06618ae835d637e5e198bd121463afa4993db61975f4ed0380fe1dcbfe3108b3ada51" } ], "licenses" : [ @@ -3767,7 +3767,7 @@ } } ], - "purl" : "pkg:maven/commons-logging/commons-logging@1.3.3?type=jar", + "purl" : "pkg:maven/commons-logging/commons-logging@1.4.0?type=jar", "externalReferences" : [ { "type" : "website", @@ -3775,7 +3775,7 @@ }, { "type" : "build-system", - "url" : "https://github.com/apache/commons-parent/actions" + "url" : "https://github.com/apache/commons-logging/actions" }, { "type" : "distribution-intake", @@ -3797,44 +3797,44 @@ }, { "type" : "library", - "bom-ref" : "pkg:maven/org.webjars.npm/pdfjs-dist@6.0.227?type=jar", + "bom-ref" : "pkg:maven/org.webjars.npm/pdfjs-dist@6.1.200?type=jar", "group" : "org.webjars.npm", "name" : "pdfjs-dist", - "version" : "6.0.227", + "version" : "6.1.200", "description" : "WebJar for pdfjs-dist", "scope" : "required", "hashes" : [ { "alg" : "MD5", - "content" : "72411f297134490511fcaafd566bf4e4" + "content" : "f1f7565a3639df404408f1770ea112c7" }, { "alg" : "SHA-1", - "content" : "308f852590def6b814c707312ccbb814f4d42fb5" + "content" : "92fb124655143e47cb0b3dcb902add53aa9cede3" }, { "alg" : "SHA-256", - "content" : "3943ada724d106abbc8f1f231087f2c09b7bed0b34e0a94a0f575f7dea6d3b99" + "content" : "22639fd7614aed05df9a06861e07540e11a2790bb820e008c17269425b68890e" }, { "alg" : "SHA-512", - "content" : "eb7fdf934e9d9b1dda84fb68519ab065dfc4e50958efe1f565dbaf8c03d1aaf06bf9629a57d02392da978cace39f6e125b597cbc9523fc667982faf43fe87c77" + "content" : "cf0b7394a2fd8db0cff1624f7141a810a1e7c34e51226c03c0d4582711e37edb82a48119c388788dcb26f5c5171528566431460ae30840e1e0b4e6c42494e202" }, { "alg" : "SHA-384", - "content" : "a178dc7a1bf0b98f569456a6c8b03ad3b781eb5b0a9786910e383bdcb75b5de86f6eb93b4313f204b1547f0e29d99fd6" + "content" : "c31dd289b74a7154cb86c5bf8a7ca204dfe0659f28cbcb981c2639a867af62427f77a346a24f86667bbef42d6ed05e89" }, { "alg" : "SHA3-384", - "content" : "59c16ea9820319e1b7af774d861788c8ba0f653a18cccab5e3e908abf3759017402844ecfa8ff29d6df50ebe480614ec" + "content" : "82260e56e7177a87633ec72b486d4f151d5af3aa3a646e8a59b5cfd3235f4f302145436ad54daeeb75e5e2594e42cc87" }, { "alg" : "SHA3-256", - "content" : "b54d499a0186e9ecdcec53a2ffda8c895419e1c43915578aa3991d52bc06b42d" + "content" : "e44c08ce546326f4ab445d94348cdb281ac94eafd866b2d3b6645b4f625aa650" }, { "alg" : "SHA3-512", - "content" : "50c2486ac3b73637b8d55a624fc6703bec5a7fa7646f310ff3109490a5b446288701c58263219a6f3476821e2e089bcbce7f767fe6746e5a67dd4d013177eb28" + "content" : "71eabf677a2fc65cdff96eb79c3e72773a5506f1c3315274d7955f0621c96cfe53d400ff312fdb59105d2753e744e76b0dd166b99e3342ea2c517f6a2c700450" } ], "licenses" : [ @@ -3845,7 +3845,7 @@ } } ], - "purl" : "pkg:maven/org.webjars.npm/pdfjs-dist@6.0.227?type=jar", + "purl" : "pkg:maven/org.webjars.npm/pdfjs-dist@6.1.200?type=jar", "externalReferences" : [ { "type" : "website", @@ -4211,8 +4211,8 @@ "pkg:maven/org.springframework.boot/spring-boot-starter-webflux@3.5.16?type=jar", "pkg:maven/org.springframework.boot/spring-boot-starter-validation@3.5.16?type=jar", "pkg:maven/org.springframework.boot/spring-boot-starter-log4j2@3.5.16?type=jar", - "pkg:maven/org.apache.pdfbox/pdfbox@3.0.3?type=jar", - "pkg:maven/org.webjars.npm/pdfjs-dist@6.0.227?type=jar", + "pkg:maven/org.apache.pdfbox/pdfbox@3.0.8?type=jar", + "pkg:maven/org.webjars.npm/pdfjs-dist@6.1.200?type=jar", "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.22.1?type=jar" ] }, @@ -4366,170 +4366,170 @@ { "ref" : "pkg:maven/io.projectreactor.netty/reactor-netty-http@1.2.18?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-codec-http@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec-http2@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-resolver-dns@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.135.Final?classifier=osx-x86_64&type=jar", - "pkg:maven/io.netty/netty-transport-native-epoll@4.1.135.Final?classifier=linux-x86_64&type=jar", + "pkg:maven/io.netty/netty-codec-http@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec-http2@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-resolver-dns@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.136.Final?classifier=osx-x86_64&type=jar", + "pkg:maven/io.netty/netty-transport-native-epoll@4.1.136.Final?classifier=linux-x86_64&type=jar", "pkg:maven/io.projectreactor.netty/reactor-netty-core@1.2.18?type=jar", "pkg:maven/io.projectreactor/reactor-core@3.7.19?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-codec-http@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-codec-http@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-handler@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-handler@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", "dependsOn" : [ ] }, { - "ref" : "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-resolver@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-resolver@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-resolver@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-resolver@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-handler@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-handler@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-resolver@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-resolver@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-codec-http2@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-codec-http2@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-handler@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec-http@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-handler@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec-http@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-resolver-dns@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-resolver-dns@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-resolver@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec-dns@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-handler@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-resolver@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec-dns@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-handler@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-codec-dns@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-codec-dns@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.135.Final?classifier=osx-x86_64&type=jar", + "ref" : "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.136.Final?classifier=osx-x86_64&type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-resolver-dns-classes-macos@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-resolver-dns@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-resolver-dns@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-transport-native-epoll@4.1.135.Final?classifier=linux-x86_64&type=jar", + "ref" : "pkg:maven/io.netty/netty-transport-native-epoll@4.1.136.Final?classifier=linux-x86_64&type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport-classes-epoll@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport-classes-epoll@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-transport-classes-epoll@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-transport-classes-epoll@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport-native-unix-common@4.1.136.Final?type=jar" ] }, { "ref" : "pkg:maven/io.projectreactor.netty/reactor-netty-core@1.2.18?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-handler@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-handler-proxy@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-resolver-dns@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.135.Final?classifier=osx-x86_64&type=jar", - "pkg:maven/io.netty/netty-transport-native-epoll@4.1.135.Final?classifier=linux-x86_64&type=jar", + "pkg:maven/io.netty/netty-handler@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-handler-proxy@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-resolver-dns@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-resolver-dns-native-macos@4.1.136.Final?classifier=osx-x86_64&type=jar", + "pkg:maven/io.netty/netty-transport-native-epoll@4.1.136.Final?classifier=linux-x86_64&type=jar", "pkg:maven/io.projectreactor/reactor-core@3.7.19?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-handler-proxy@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-handler-proxy@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec-socks@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec-http@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec-socks@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec-http@4.1.136.Final?type=jar" ] }, { - "ref" : "pkg:maven/io.netty/netty-codec-socks@4.1.135.Final?type=jar", + "ref" : "pkg:maven/io.netty/netty-codec-socks@4.1.136.Final?type=jar", "dependsOn" : [ - "pkg:maven/io.netty/netty-common@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-buffer@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-transport@4.1.135.Final?type=jar", - "pkg:maven/io.netty/netty-codec@4.1.135.Final?type=jar" + "pkg:maven/io.netty/netty-common@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-buffer@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-transport@4.1.136.Final?type=jar", + "pkg:maven/io.netty/netty-codec@4.1.136.Final?type=jar" ] }, { @@ -4620,32 +4620,32 @@ ] }, { - "ref" : "pkg:maven/org.apache.pdfbox/pdfbox@3.0.3?type=jar", + "ref" : "pkg:maven/org.apache.pdfbox/pdfbox@3.0.8?type=jar", "dependsOn" : [ - "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.3?type=jar", - "pkg:maven/org.apache.pdfbox/fontbox@3.0.3?type=jar", - "pkg:maven/commons-logging/commons-logging@1.3.3?type=jar" + "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.8?type=jar", + "pkg:maven/org.apache.pdfbox/fontbox@3.0.8?type=jar", + "pkg:maven/commons-logging/commons-logging@1.4.0?type=jar" ] }, { - "ref" : "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.3?type=jar", + "ref" : "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.8?type=jar", "dependsOn" : [ - "pkg:maven/commons-logging/commons-logging@1.3.3?type=jar" + "pkg:maven/commons-logging/commons-logging@1.4.0?type=jar" ] }, { - "ref" : "pkg:maven/commons-logging/commons-logging@1.3.3?type=jar", + "ref" : "pkg:maven/commons-logging/commons-logging@1.4.0?type=jar", "dependsOn" : [ ] }, { - "ref" : "pkg:maven/org.apache.pdfbox/fontbox@3.0.3?type=jar", + "ref" : "pkg:maven/org.apache.pdfbox/fontbox@3.0.8?type=jar", "dependsOn" : [ - "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.3?type=jar", - "pkg:maven/commons-logging/commons-logging@1.3.3?type=jar" + "pkg:maven/org.apache.pdfbox/pdfbox-io@3.0.8?type=jar", + "pkg:maven/commons-logging/commons-logging@1.4.0?type=jar" ] }, { - "ref" : "pkg:maven/org.webjars.npm/pdfjs-dist@6.0.227?type=jar", + "ref" : "pkg:maven/org.webjars.npm/pdfjs-dist@6.1.200?type=jar", "dependsOn" : [ ] } ] diff --git a/docs/security/2026-07-02-auth-tenant-model.md b/docs/security/2026-07-02-auth-tenant-model.md index d81a2a128..6f001be75 100644 --- a/docs/security/2026-07-02-auth-tenant-model.md +++ b/docs/security/2026-07-02-auth-tenant-model.md @@ -44,7 +44,7 @@ Current buyer-demo runtime headers: - `X-Clearfolio-Tenant-Id: buyer-demo` - `X-Clearfolio-Subject-Id: buyer-demo-operator` -- `X-Clearfolio-Permissions: job:create,job:read,job:retry,viewer:read,artifact-link:create,analytics:read` +- `X-Clearfolio-Permissions: job:create,job:read,job:retry,viewer:read,artifact:read,artifact-link:create,analytics:read` These headers are a runtime enforcement scaffold. In unsigned demo mode they are not a cryptographic identity proof. When @@ -112,9 +112,9 @@ matching permission without matching `tenantId` is insufficient. | Resource | Tenant binding | Access rule | | --- | --- | --- | -| Conversion job | `job.tenantId` | Caller `tenantId` must match before status, viewer bootstrap, retry, or analytics drill-down. | +| Conversion job | `job.tenantId` | Caller `tenantId` must match before status, direct download, viewer bootstrap, retry, or analytics drill-down. | | Source document metadata | `document.tenantId` | Exposed only through job/viewer APIs after permission check. | -| Preview artifact | `artifact.tenantId` and `artifactChecksum` | Read only through short-lived signed artifact token. | +| Preview artifact | `artifact.tenantId` and `artifactChecksum` | Read through short-lived signed artifact token; the direct job-download route additionally requires dedicated `artifact:read` and matching job tenant before artifact-store access. | | Artifact link | `artifactLink.tenantId` and `tokenId` | Revocable by operator or tenant admin in the same tenant. | | Metrics event | `event.tenantId` | Aggregate views must filter tenant unless explicitly buyer-demo scoped. | | Audit event | `audit.tenantId` | Read by operator, tenant admin, or buyer reviewer for scoped evidence. | @@ -129,6 +129,7 @@ unauthorized action, depending on route semantics. | --- | --- | --- | | `POST /api/v1/convert/jobs` | `job:create` | Assign job to caller `tenantId`. | | `GET /api/v1/convert/jobs/{jobId}` | `job:read` | `job.tenantId == token.tenantId`. | +| `GET /api/v1/convert/jobs/{jobId}/download` | `artifact:read` | Verify the dedicated artifact permission before job lookup; require `job.tenantId == token.tenantId` before artifact-store access; conceal mismatch as `404`. | | `POST /api/v1/convert/jobs/{jobId}/retry` | `job:retry` | Same tenant plus operator role. | | `GET /api/v1/viewer/{docId}` | `viewer:read` | `job.tenantId == token.tenantId`; artifact tokens are enforced in the signed-link slice. | | `GET /viewer/{docId}` | none for HTML shell | Shell does not inspect job existence; protected JSON APIs decide state. | @@ -138,13 +139,16 @@ unauthorized action, depending on route semantics. Current implementation status: -- Implemented: `job:create`, `job:read`, `job:retry`, `viewer:read`, and - `analytics:read` permission checks on JSON APIs. +- Implemented: `job:create`, `job:read`, `job:retry`, `viewer:read`, + `artifact:read`, and `analytics:read` permission checks on JSON APIs. +- Implemented: direct conversion-job downloads validate dedicated + `artifact:read` before resource lookup, enforce same-tenant ownership before + artifact-store access, and return `404` for cross-tenant UUID access. - Implemented: `ConversionJob.tenantId` and `ConversionJob.subjectId`. - Implemented: tenant-aware content-hash dedupe so two tenants do not collapse onto one canonical job for the same upload bytes. -- Implemented: cross-tenant status, retry, and viewer-bootstrap lookup returns - `404` without revealing the other tenant's job. +- Implemented: cross-tenant status, direct download, retry, and viewer-bootstrap + lookup returns `404` without revealing the other tenant's job. - Implemented: KPI snapshots filter to the request tenant. - Implemented: optional HMAC validation for gateway-signed tenant headers when `clearfolio.tenant-claims.hmac-secret` is configured. @@ -200,6 +204,9 @@ Store token fingerprints, not raw tokens. tenant boundary. - Every write or sensitive read has a server-side permission check. - Artifact reads use signed artifact tokens, not bare `docId` capability URLs. +- Direct conversion-job downloads require authenticated `artifact:read` + permission and same-tenant ownership before artifact bytes are touched; + `job:read` alone never authorizes document content. - Operator retry requires an operator permission and is auditable. - KPI snapshots can be shown for one tenant without leaking another tenant's volume, latency, or failure rate. @@ -212,8 +219,8 @@ Store token fingerprints, not raw tokens. buyer-demo runtime. 2. Done: add `tenantId`, `subjectId`, and permission checks to conversion job metadata and JSON API paths. -3. Done: enforce `job:create`, `job:read`, `job:retry`, `viewer:read`, and - `analytics:read` on existing JSON routes. +3. Done: enforce `job:create`, `job:read`, `job:retry`, `viewer:read`, + `artifact:read`, and `analytics:read` on existing JSON routes. 4. Done: add tenant-scoped KPI projection from current in-memory jobs. 5. Done: add optional gateway-signed tenant headers with HMAC and timestamp skew controls. @@ -221,8 +228,10 @@ Store token fingerprints, not raw tokens. secret is absent. 7. Next: replace demo headers with validated gateway/OIDC JWT claims. 8. Done: add signed artifact link creation and token verification. -9. Next: add durable revocation, persisted audit events, and CI/contract tests - for production token rejection paths. +9. Done: enforce tenant-scoped and artifact-scoped authorization on direct + conversion-job downloads. +10. Next: add durable revocation, persisted audit events, and CI/contract tests + for production token rejection paths. No library split is justified until a second Clearfolio service or external SDK needs to reuse this authorization contract. diff --git a/docs/security/2026-08-04-audit-pseudonymization.md b/docs/security/2026-08-04-audit-pseudonymization.md new file mode 100644 index 000000000..497679fa2 --- /dev/null +++ b/docs/security/2026-08-04-audit-pseudonymization.md @@ -0,0 +1,117 @@ +# Audit identifier pseudonymization + +## Decision + +Clearfolio must not write raw approver identifiers or approval tokens to application logs. Policy-override audit events use a domain-separated keyed HMAC for the approver identifier and a non-reversible token fingerprint for the already high-entropy approval signature. Authentication-token handling is outside this policy-override logging contract and remains governed by the repository-wide logging and authorization controls. + +The approver field is named `approverFingerprint`, not `approverId`, so downstream log consumers cannot mistake pseudonymous data for the source identifier. Pseudonymized values remain personal data when they can be related back to a person using separately held information; they are not treated as anonymized data. + +## Cryptographic contract + +### Policy override key + +A configured `conversion.policy-override-secret` authorizes blocked-document policy exceptions and must contain at least 32 UTF-8 bytes. Blank or absent configuration keeps policy override disabled. A nonblank value below the minimum fails application startup and direct construction of the public validation service before any conversion endpoint or standalone module can accept traffic. Configuring a valid policy-override key without a dedicated audit pseudonym key fails through the same shared validation contract, because accepting an administrative exception without approver-correlatable audit evidence would make the security decision operationally unauditable. The gates measure encoded bytes rather than Java character count and never log supplied key material. + +Deployments must generate this key from a cryptographically secure random source and must not use a password, person or tenant identifier, repository token, or other human-memorable value. The minimum-length gate prevents a weak configured secret from reducing the effective security of the HMAC approval token even when the HMAC algorithm itself is correctly implemented (National Institute of Standards and Technology, 2008; Turan & Brandão, 2024). + +### Approver identifier + +The approver fingerprint is calculated as follows: + +```text +HMAC-SHA-256( + dedicated_audit_key, + UTF-8("clearfolio:audit-approver:v1\n" + exact_approver_identifier) +) +``` + +The first 128 bits are encoded as lowercase hexadecimal and prefixed by the non-sensitive key version: + +```text +:<32 lowercase hexadecimal characters> +``` + +The implementation preserves the exact Java string bytes supplied after the policy override has passed its existing identity validation. It does not lowercase, Unicode-normalize, or trim inside the pseudonymizer because those transformations would silently alter identity semantics. Null input produces `absent:`. An empty Java string is not absent: it is processed as a zero-length identifier through the same domain-separated HMAC and produces a normal versioned fingerprint. A missing dedicated key produces `unavailable:` only while policy-override signing is disabled and never falls back to plaintext, the policy-signing secret, or an unkeyed identifier hash. Once a policy-signing key is configured, a missing or blank audit key prevents both application startup and direct construction of an override-capable validation service. + +A configured audit pseudonym secret must contain at least 32 UTF-8 bytes and must be generated from a cryptographically secure random source. The byte-length gate prevents accidentally deploying a short human-memorable secret whose effective strength would bound the HMAC protection. Blank or absent configuration retains the explicit non-correlatable `unavailable` behavior only for deployments where policy override remains disabled; a nonblank weak key, or an absent key paired with an enabled policy-signing key, fails the shared configuration validation before traffic is accepted. FIPS 198-1 remains the current final NIST HMAC standard while NIST SP 800-224 remains an initial public draft; NIST expects the final SP to be published concurrently with withdrawal of FIPS 198-1 (National Institute of Standards and Technology, 2008, 2025; Turan & Brandão, 2024). + +Only an absent key-version property defaults to `v1`. Explicit blank, oversized, or unsafe key-version values fail application startup so one version label can never identify multiple key generations accidentally. The accepted format is one to 32 Java UTF-16 code units matching the implementation-equivalent expression `^[\p{L}\p{Nd}._-]{1,32}$`: each character must satisfy Java `Character.isLetterOrDigit` or be `.`, `_`, or `-`. The value is retained as a Java Unicode string and written by the configured log encoding; deployments use UTF-8 log output. Control characters, separators, whitespace, slashes, and other punctuation are rejected. + +### Approval token + +The approval token is a policy-override HMAC signature and is therefore already a high-entropy authentication value. The audit-only token fingerprint is calculated independently as follows: + +```text +SHA-256(UTF-8(exact_approval_token)) +``` + +The first eight digest bytes are encoded as 16 lowercase hexadecimal characters and written as `tokenFingerprint`. The fingerprint is unkeyed and has no domain prefix because it is used only as a short diagnostic correlation value for an already high-entropy signature; it must never be accepted as an authentication credential or used to validate a policy override. Null, empty, and blank approval tokens are rejected by request validation before fingerprinting, so the audit fingerprint function has no absent or empty sentinel contract. + +## Runtime secret loading + +Runtime key material is supplied through Spring Boot's config-tree property source rather than direct secret-bearing environment variables. The default mount is `/run/secrets/clearfolio/`; `CLEARFOLIO_SECRET_CONFIG_DIR` may select another bootstrap directory but must not contain a secret value. + +The secret store or orchestrator mounts files with these exact names: + +```text +conversion.policy-override-secret +conversion.audit-pseudonym-secret +conversion.audit-pseudonym-key-version +``` + +Spring reads each file's contents as the corresponding property. The deployment must restrict file ownership and mode, prevent inclusion in container images and support bundles, and avoid logging the imported values. If the optional config tree is absent, the application retains safe disabled defaults because policy override remains disabled. If a deployment supplies `conversion.policy-override-secret`, it must supply a distinct strong `conversion.audit-pseudonym-secret` in the same rollout; otherwise Spring startup fails before traffic is accepted. Standalone and MSA consumers that instantiate `DefaultDocumentValidationService` directly receive the identical fail-closed validation and therefore cannot bypass the key-strength, mandatory-audit-key, or key-separation rules by omitting the Spring container. + +## Key ownership and rotation + +- `conversion.policy-override-secret` is an authorization key owned by the security function. It must contain at least 32 UTF-8 bytes, be generated from a cryptographically secure random source, and be rotated through the deployment secret manager. +- `conversion.audit-pseudonym-secret` is owned by the security or privacy operations function and must be stored in the deployment secret manager. It is mandatory whenever `conversion.policy-override-secret` is configured. +- The configured audit value must contain at least 32 UTF-8 bytes and should be a uniformly random 256-bit-or-stronger value rather than a password or identifier. +- The shared configuration guard used by Spring startup and direct validation-service construction rejects an enabled policy-signing key without a configured audit key and rejects identical nonblank values for `conversion.audit-pseudonym-secret` and `conversion.policy-override-secret`. Deployment policy must additionally keep the audit key operationally separate from tenant-claims signing keys, encryption keys, and API credentials; those keys are owned by their respective subsystems and are not all available to this component's guard. +- `conversion.audit-pseudonym-key-version` is a non-secret identifier such as `2026-08` but is mounted with the same versioned configuration bundle to keep key and label rotation atomic. +- Rotation changes both the secret and version. During an investigation that spans a rotation boundary, operators must treat fingerprints from different versions as intentionally unlinkable unless an approved, separately controlled re-identification process exists. +- Retired keys must not remain in application configuration. Any escrow or incident-response copy must be access-controlled, time-bounded, and audited. + +## Retention and access + +Audit log retention must be limited to the shortest period required by the documented security, contractual, and regulatory purpose. Read access is restricted by least privilege. Export, search, re-identification, and deletion workflows must be auditable. Logs and pseudonym keys must never be stored in the same access domain. + +## Incident response + +If the audit pseudonym key is suspected to be exposed: + +1. Rotate the key and version immediately. +2. Preserve affected log ranges under incident hold without broadening access. +3. Determine whether dictionary attacks against likely identifiers were feasible. +4. Treat exposed pseudonymized records as potentially exposed personal data. +5. Follow the applicable breach-assessment and notification process. +6. Verify that no raw identifiers, approval tokens, or key material were written to logs. + +## Verification requirements + +Automated tests must prove: + +- determinism within one key version and domain; +- separation across keys, versions, and domains; +- Spring-startup and direct-construction rejection of configured policy-override and audit keys shorter than 32 UTF-8 bytes; +- Spring-startup and direct-construction rejection when policy-override signing is enabled without a configured audit pseudonym key; +- acceptance of multibyte policy keys based on encoded byte length rather than character count; +- rejection of invalid explicit key versions; +- Spring-startup and direct-construction rejection when policy and audit purposes reuse the same nonblank key; +- distinct absent, empty, and unavailable approver behavior while policy signing is disabled; +- rejection of null, empty, or blank approval tokens before token fingerprinting; +- safe handling of Unicode and control characters; +- no raw approver identifier or approval token in captured policy-override audit output; +- stable failure behavior if the HMAC provider is unavailable; +- 100% JaCoCo line and branch coverage for the `com.clearfolio.viewer.*` production package. + +## References + +European Parliament and Council of the European Union. (2016). *Regulation (EU) 2016/679 of the European Parliament and of the Council of 27 April 2016 on the protection of natural persons with regard to the processing of personal data and on the free movement of such data (General Data Protection Regulation)*. *Official Journal of the European Union, L 119*, 1–88. + +National Institute of Standards and Technology. (2008). *The keyed-hash message authentication code (HMAC)* (FIPS PUB 198-1). U.S. Department of Commerce. https://doi.org/10.6028/NIST.FIPS.198-1 + +National Institute of Standards and Technology. (2025, June 23). *Proposed withdrawal of FIPS 198-1, HMAC*. Computer Security Resource Center. https://csrc.nist.gov/News/2025/proposed-withdrawal-of-fips-198-1-hmac + +OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 4, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html + +Turan, M. S., & Brandão, L. T. A. N. (2024). *Keyed-hash message authentication code (HMAC): Specification of HMAC and recommendations for message authentication* (NIST SP 800-224 Initial Public Draft). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-224.ipd diff --git a/docs/security/2026-08-05-administrative-authorization.md b/docs/security/2026-08-05-administrative-authorization.md new file mode 100644 index 000000000..d829fecc3 --- /dev/null +++ b/docs/security/2026-08-05-administrative-authorization.md @@ -0,0 +1,129 @@ +# Tenant-scoped administrative authorization + +## Decision + +Clearfolio's administrative conversion-job endpoints are tenant-scoped control-plane APIs, not global superuser APIs. Every request evaluates signed subject claims, an explicit administrative permission, the requested operation, and the target job's tenant ownership. Listing requires `admin:read`; deletion and dead-letter retry require `admin:write`. Possessing an opaque UUID is never authorization. Missing and cross-tenant jobs intentionally produce the same not-found outcome so an identifier cannot enumerate another tenant's documents or operational state. + +The implementation follows deny by default and least privilege. Administrative endpoints never fall back to unsigned demo headers. If the tenant-claims HMAC verifier is absent or its key contains fewer than 32 UTF-8 bytes, the endpoints return `503 Service Unavailable` before repository access. A missing or weak trust anchor is therefore an observable deployment failure rather than an authorization bypass. + +## Trust boundary + +The `X-Clearfolio-*` headers are an internal adapter contract between Clearfolio and an authenticated gateway or host such as naruon. They are not public client credentials. The upstream gateway must authenticate the caller, remove untrusted inbound copies, construct canonical tenant, subject, permission, and issue-time claims, sign them with the tenant-claims HMAC key, and forward only the verified replacement set. + +Clearfolio verifies signature and freshness before permission evaluation. The service remains independently deployable because the verifier is injectable, but production must not expose this internal header adapter directly to arbitrary clients. + +Tenant-claims and artifact-token HMAC secrets are read from the shared Spring config-tree mount as `clearfolio.tenant-claims.hmac-secret` and `clearfolio.artifact-token.secret`. The buyer-demo profile does not map either secret-bearing environment variable into runtime configuration. Environment variables may select non-secret operational values or bootstrap a mounted credential source; runtime authentication and artifact-link signing read mounted properties. Each signing key must be provisioned independently through the deployment platform's secret manager. + +## Authorization sequence + +Every administrative endpoint applies the same fail-closed sequence: + +1. Confirm that a strong signed-claim verifier is configured; otherwise return `503` before service access. +2. Parse tenant, subject, permissions, issue time, and signature. +3. Verify canonical claims, signature, and freshness. +4. Require the action-specific permission. +5. Pass the verified `TenantContext` into the object-specific service operation. +6. Select and mutate through one tenant-scoped persistence boundary. +7. Conceal absent and cross-tenant objects with the same not-found result. +8. Emit privacy-safe authorization and lifecycle evidence. + +List responses apply the tenant predicate before job objects cross the repository boundary and then apply the optional dead-letter filter. Delete and retry do not perform controller-level read-then-write authorization. Their service contracts pass the authenticated tenant to atomic repository or state-store operations, so a non-HTTP caller cannot bypass the controller and reach an unscoped administrative mutation. + +## Immutable job and retry handoff boundary + +A missing job identifier is treated as an absent object at every tenant-scoped lookup, delete, and retry boundary. Repository adapters return an empty lookup, `false`, or `NOT_FOUND` without invoking a global lookup, changing stored state, deleting an artifact, or enqueueing work. + +Compatibility-only unscoped delete and two-argument retry methods remain available for non-administrative adapters. Their tenant-aware service, repository, and state-store defaults fail closed without reading a job or invoking legacy mutation. A production adapter must explicitly implement tenant selection and mutation in one persistence boundary. The in-memory reference adapter supplies those scoped atomic overrides. + +The content-hash secondary index is tenant-bound, and each conversion-job UUID is an immutable lifecycle identity. The in-memory adapter permanently reserves a UUID when first accepted. Saving the exact same live object may be idempotent; a distinct live object or any later object using a tombstoned UUID is rejected before secondary-index work. Deletion removes the live record and tenant-plus-content-hash index while retaining the reservation. Indexed lookup also verifies that the current UUID record matches the requested tenant and hash. + +Primary UUID state, permanent reservation, and the tenant-content index are updated under one shared critical section. A concurrent same-UUID save waits for the active mutation and then fails closed; deletion never releases the identifier for reuse. Durable adapters must provide the equivalent invariant with a database uniqueness constraint and durable tombstone or lifecycle-generation reservation in the same transaction. + +This invariant closes the retry-to-worker handoff race in the reference adapter even though the worker queue carries a UUID. After an authorized tenant retry transitions the original object, another tenant cannot replace that UUID before enqueue or worker lookup. Distributed adapters must additionally bind queued work to tenant and immutable generation, then revalidate both at execution time. + +## Durable artifact-deletion boundary + +Authorized deletion is routed through `DurableDocumentDeletionService` and `ArtifactDeletionCoordinator`. The reference runtime now implements a durable deletion receipt and bounded cleanup lifecycle rather than best-effort-only removal. + +The lifecycle is receipt first: + +1. Under the per-job lifecycle lock, persist `DELETION_REQUESTED` with a controlled non-digest `pending` checksum marker before the first artifact-store read. +2. If the first read fails, preserve metadata, retain the durable receipt, record only a controlled low-cardinality failure, and allow startup or scheduled recovery to resume. +3. After a successful read, bind the same receipt identity exactly once to the artifact SHA-256 or the explicit confirmed-absence digest. Replay rejects reverse or conflicting binding. +4. Only after exact checksum binding may the tenant-scoped metadata tombstone be applied. +5. Mark cleanup pending, compare the exact generation, delete the artifact idempotently, and record completion or a controlled retryable failure. +6. Replay incomplete receipts at application startup and on a bounded fixed-delay schedule. + +Within one running process, bounded recovery rotates its starting position through the deterministic pending-receipt order. A permanently failing oldest receipt therefore remains durable and retryable without starving newer work behind a smaller batch limit. The fairness cursor is process-local coordination state, not receipt evidence: after restart the coordinator safely begins again from deterministic request order. A high-churn multi-instance adapter that requires fairness across process loss must persist an equivalent lease, cursor, or next-attempt ordering without weakening receipt identity, generation fencing, or idempotency. + +`LifecycleFencedArtifactStore` shares the per-job lifecycle lock and rejects publication after a deletion receipt exists. An in-flight publication that completes before receipt acceptance becomes the captured generation and is deleted; a later publication fails closed. Aggregate metrics expose only completed, failed, pending, and bounded execution evidence. They do not use tenant, job, digest, path, filename, exception message, or other high-cardinality dimensions. + +The append-only file adapter forces each accepted receipt snapshot before reporting it durable. It supports restart replay and preserves incomplete cleanup evidence. The in-memory adapter remains available for standalone tests and ephemeral use. + +This reference boundary is intentionally narrower than a distributed transaction. It has no cross-resource transactional outbox spanning an external database and object store, no distributed generation fence shared by multiple service instances, and no remote-object-store atomicity guarantee. A multi-instance production adapter must add durable uniqueness, object-version preconditions or compare-and-set semantics, an idempotent outbox/consumer boundary, bounded retry and dead-letter operations, and operator recovery evidence. Issue #263 remains the umbrella for those distributed adapters and later truthful API/UI deletion-state semantics. + +## Audit evidence + +Administrative evidence contains only: + +- a controlled action code; +- a controlled outcome code; +- HTTP status; +- tenant, actor, and conversion-job HMAC fingerprints in separate domains; +- a numeric result count for list operations; +- low-cardinality deletion lifecycle counts and controlled failure codes. + +It excludes raw tenant identifiers, raw subject identifiers, raw job UUIDs, claim signatures, permission headers, filenames, job messages, document text, storage paths, exception-controlled text, and artifact bytes. Job correlation uses a dedicated keyed HMAC domain so resource references cannot be joined directly with API paths, databases, support exports, or external telemetry. Retry provenance stores the actor-domain fingerprint rather than the source subject. Pseudonymized values remain personal data and inherit the retention, access, rotation, and incident-response requirements in `2026-08-04-audit-pseudonymization.md`. + +Operators must distinguish the persisted receipt ledger from ordinary application logs. The ledger and aggregate metrics provide durable single-process cleanup evidence; logs are diagnostic only and never substitute for receipt state. + +## Standalone and modular deployment + +`ArtifactDeletionReceiptStore`, `ArtifactStore`, `ConversionJobRepository`, and the tenant-aware service contracts are replaceable boundaries. The reference filesystem ledger and artifact store permit standalone operation. naruon or another CWL host may replace them with shared durable adapters without changing the authorization, immutable identity, receipt state machine, or privacy-safe evidence contracts. + +A modular deployment must preserve: + +- signed tenant context before service access; +- tenant-scoped atomic mutation; +- permanent UUID or immutable generation identity; +- receipt-before-read ordering; +- one-way exact-digest binding before metadata tombstone; +- conversion/deletion generation fencing; +- idempotent retry and recovery; +- low-cardinality, privacy-safe evidence; +- fail-closed behavior when a required adapter cannot prove those invariants. + +## Verification requirements + +Automated tests must exercise the real signed-claim verifier and prove: + +- absent and weak verifier keys make privileged endpoints unavailable before service access; +- missing, malformed, expired, and incorrectly signed claims fail before service access; +- missing `admin:read` or `admin:write` permissions fail before service access; +- list results contain only tenant-owned jobs for every dead-letter filter state; +- missing and cross-tenant delete/retry targets produce indistinguishable not-found responses; +- missing job identifiers fail closed without global lookup or mutation; +- delete and retry cross tenant-aware persistence boundaries without a separate unscoped lookup; +- compatibility adapters cannot reach global lookup, delete, or retry through tenant-aware defaults; +- exact same-object save is idempotent while distinct live or tombstoned UUID reuse is rejected; +- deletion removes the live job and secondary index but preserves UUID reservation; +- a colliding find-or-store candidate fails before index ownership changes; +- concurrent delete/save and retry/replacement interleavings preserve tenant and generation ownership; +- accepted retry provenance is a domain-separated keyed fingerprint; +- the durable deletion receipt is accepted before the first artifact read; +- first-read failure preserves metadata and restart-safe pending evidence rather than claiming confirmed absence; +- exact digest binding is one-way, durable, replay-validated, and required before metadata tombstone; +- successful cleanup, read failure, digest mismatch, delete failure, retry, restart, and bounded-batch recovery are deterministic; +- a permanently failing oldest receipt cannot starve newer cleanup across repeated bounded passes in one process; +- write-after-receipt and in-flight publication races cannot recreate deleted confidential bytes; +- cleanup evidence contains no raw UUID, tenant, path, digest dimension, exception text, or attached throwable; +- both signing keys are sourced from the shared config-tree mount; +- JaCoCo reports zero missed production lines and branches and public Javadocs pass with no warnings. + +## References + +Hu, V. C., Ferraiolo, D., Kuhn, D. R., Schnitzer, A., Sandlin, K., Miller, R., & Scarfone, K. (2014). *Guide to attribute based access control (ABAC) definition and considerations* (NIST Special Publication 800-162, updated August 2, 2019). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-162 + +OWASP Foundation. (2023). *API1:2023 broken object level authorization*. OWASP API Security Top 10. https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/ + +OWASP Foundation. (n.d.). *Authorization cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html diff --git a/docs/security/2026-08-05-netty-4.1.136-remediation.md b/docs/security/2026-08-05-netty-4.1.136-remediation.md new file mode 100644 index 000000000..e9a8dd998 --- /dev/null +++ b/docs/security/2026-08-05-netty-4.1.136-remediation.md @@ -0,0 +1,131 @@ +# ADR: Align the Reactive HTTP Stack on Netty 4.1.136.Final + +- **Status:** Accepted +- **Decision date:** 2026-08-05 +- **Decision owners:** Clearfolio maintainers and security reviewers +- **Applies to:** `clearfolio-viewer` reactive HTTP runtime and every transitive `io.netty` module managed through Spring Boot + +## Context + +Clearfolio uses Spring Boot WebFlux, which resolves Reactor Netty and the Netty transport, codec, resolver, and handler modules transitively. Spring Boot 3.5.16 manages the Netty 4.1 line at `4.1.135.Final`. Exact-head Strix run `30997430437`, job `92277868841`, reported a HIGH dependency finding against that line and identified `4.1.136.Final` as the fixed 4.1 release. + +The Netty project released `4.1.136.Final` at 20:18 UTC on July 8, 2026. Its primary release record includes HTTP/1.1 and HTTP/2 boundary validation, MQTT decoder and UTF-8 validation, compression safety, parser-boundary, flow-control, and traffic-shaping fixes relative to `4.1.135.Final`. + +A partial dependency override would be unsafe because Netty is a coordinated family of modules. Mixing codec, transport, resolver, and handler patch levels can create an unreviewed runtime graph even when Maven resolves successfully. + +## Decision + +Set the Spring Boot-supported Maven property below in the root `pom.xml`: + +```xml +4.1.136.Final +``` + +This property changes the managed version for the complete Netty module family while retaining Spring Boot's dependency-management structure. Do not pin individual Netty artifacts independently unless an accepted follow-up ADR proves that a mixed module graph is required and compatible. + +Two executable contracts guard the decision: + +- `DependencyPolicyTest.pomPinsPatchedNettyLineForReactiveHttpServing` reads the real root POM through an XML parser configured to reject document types, external entities, parameter entities, XInclude, and entity expansion. +- `scripts/test_render_third_party_attribution.py` independently reads the single nonblank `netty.version` property, then proves that every committed Netty component version, purl, bom-ref, dependency edge, and attribution row resolves to that version. + +## Deterministic buyer-evidence flow + +The committed SBOM is generated evidence, not a hand-edited dependency inventory. The accepted generation path is: + +```mermaid +flowchart LR + H[Exact source head
3b6e434] --> M[Maven resolves
Spring Boot + netty.version] + M --> C[CycloneDX Maven Plugin 2.9.1
makeAggregateBom] + C --> B[target/bom.json
CycloneDX 1.6] + B --> V[Graph verifier
61 components / 17 Netty] + V --> A[Deterministic attribution renderer] + B --> I[Immutable Actions artifact
ID 8929593015] + A --> I + I --> D[Committed buyer data-room evidence] + D --> T[Permanent drift and dependency tests] +``` + +The exact generation command is: + +```bash +mvn -B --no-transfer-progress -DskipTests \ + org.cyclonedx:cyclonedx-maven-plugin:2.9.1:makeAggregateBom \ + -Dcyclonedx.skipAttach=true \ + -DoutputFormat=json \ + -DoutputName=bom +``` + +The plugin writes the canonical JSON document to `target/bom.json`. The `outputFormat` and `outputName` parameters are Maven user properties without a `cyclonedx.` prefix; only `cyclonedx.skipAttach` uses that prefix in this invocation. + +## Evidence record + +Read-only workflow run `31004040777` generated the accepted evidence from source head `3b6e43426790ab8590c9ef50656bfb5cbbb206ce` at `2026-08-05T12:07:15Z`. + +- Artifact ID: `8929593015` +- Artifact archive SHA-256: `07a0325e08157f00dda28c58ed4e41af51863cccb2ceea2c4e378ead77dc337f` +- CycloneDX version: `1.6` +- Total components: `61` +- Netty components: `17` +- Netty version set: exactly `4.1.136.Final` +- SBOM SHA-256: `e138a9263edb40c613d5f159acba8fa89ee848a7cef4b6619e095c48451b095c` +- Attribution SHA-256: `e19a3767a545bd059e50003882d8ff2f8a3ff4d3b8fd28d3f305eead61261da9` + +The graph verifier proved that every Netty dependency reference has a corresponding current Netty component ref and that `4.1.135.Final` is absent from both generated files. The attribution renderer was rerun from the generated JSON and matched the committed Markdown byte contract. + +The committed SBOM and attribution are shareable buyer evidence. Workflow logs and the one-day artifact are transient generation provenance and must not be presented as durable data-room evidence after expiry. Reproduction therefore depends on the documented command, exact source revision, immutable plugin version, committed hashes, permanent drift tests, and fresh exact-head CI. + +## Security and compatibility boundaries + +- The override changes only the Netty patch line. It does not change Spring Boot, Spring Framework, Reactor, or the public Clearfolio API. +- Maven must resolve all applicable `io.netty` modules to `4.1.136.Final`; stale modules at `4.1.135.Final` or an older version are a release blocker. +- Existing zero-missed-line and zero-missed-branch JaCoCo gates, compiler warnings-as-errors, fuzzing, SAST, dependency review, OSV, Trivy, Scorecard, Strix, and independent review remain mandatory. +- A successful unit-test run does not replace dependency-tree and security-scanner evidence. +- The override must not be copied into downstream modules as separate ad hoc pins. Standalone builds inherit the root property; modular consumers should use a versioned BOM or equivalent explicit contract. +- The evidence record describes the dependency graph of its exact generation head. Any dependency change requires regeneration and a new evidence hash record. + +## Verification + +For the exact pull-request head: + +1. Run `mvn -B --no-transfer-progress verify`. +2. Run `mvn -B --no-transfer-progress dependency:tree -Dincludes=io.netty` and confirm one coherent `4.1.136.Final` line for every applicable Netty module. +3. Run `python3 scripts/test_render_third_party_attribution.py` and require the generated graph and attribution drift contract to pass. +4. Require successful CI, Security Scan, SAST Semgrep, every fuzz target, CodeRabbit, Strix, OpenCode, and Noema evidence for the same head. +5. Reject queued, cancelled, skipped-required, stale-head, local-only, or manually inferred results. +6. Preserve an independent approving review that GitHub counts under protected-branch rules. + +## Removal and upgrade rule + +Keep this override until one of the following occurs: + +- the Spring Boot parent used by Clearfolio manages Netty `4.1.136.Final` or a later reviewed compatible release; or +- Clearfolio moves to a different supported reactive HTTP stack through an accepted architecture decision. + +Removing or increasing the override requires the same exact-head dependency-tree, compatibility, security, coverage, SBOM regeneration, and review evidence. A newer version number alone is not proof of compatibility. + +## Consequences + +### Positive + +- The complete Netty family moves to one reviewed fixed 4.1 patch line. +- The remediation uses Spring Boot's documented version-property mechanism rather than fragile per-artifact pins. +- Real-project and generated-evidence contracts prevent silent dependency or data-room drift. +- Exact generation provenance, hashes, and local-versus-shareable evidence boundaries remain auditable for acquisition diligence. + +### Trade-offs + +- Clearfolio temporarily diverges from the Netty patch version selected by Spring Boot 3.5.16. +- The project must retain explicit exact-head compatibility and scanner evidence until the parent line catches up. +- A future parent upgrade must reconcile this property deliberately and regenerate the buyer evidence. + +## References + +CycloneDX Project. (n.d.). *CycloneDX Maven plugin* [Source code]. GitHub. Retrieved August 5, 2026, from https://github.com/CycloneDX/cyclonedx-maven-plugin + +Netty Project. (2026, July 8). *Netty 4.1.136.Final* [Software release]. GitHub. https://github.com/netty/netty/releases/tag/netty-4.1.136.Final + +OWASP Foundation. (2024, April 9). *CycloneDX 1.6* [Software bill of materials specification]. https://github.com/CycloneDX/specification/releases/tag/1.6 + +Spring. (n.d.). *Managed dependency coordinates: Spring Boot 3.5.16*. Retrieved August 5, 2026, from https://docs.spring.io/spring-boot/3.5/appendix/dependency-versions/coordinates.html + +Spring. (n.d.). *Version properties: Spring Boot 3.5.16*. Retrieved August 5, 2026, from https://docs.spring.io/spring-boot/3.5/appendix/dependency-versions/properties.html diff --git a/docs/security/2026-08-06-durable-artifact-deletion-receipts.md b/docs/security/2026-08-06-durable-artifact-deletion-receipts.md new file mode 100644 index 000000000..ba6541eba --- /dev/null +++ b/docs/security/2026-08-06-durable-artifact-deletion-receipts.md @@ -0,0 +1,250 @@ +# Durable artifact-deletion lifecycle + +- **Status:** Integrated reference cleanup worker; truthful HTTP/UI status remains Slice D +- **Decision date:** 2026-08-06 +- **Owner issue:** #263 +- **Format version:** `RECEIPT_V1` + +## Decision + +Clearfolio records every accepted artifact-deletion lifecycle as immutable, +versioned receipt snapshots **before the first artifact-store read and before +removing conversion-job metadata**. The Spring runtime uses an append-only file +ledger at `data/artifact-deletion-receipts.log`, while standalone and test +consumers may use the in-memory adapter behind the same +`ArtifactDeletionReceiptStore` contract. + +`DurableDocumentDeletionService` is the primary `DocumentConversionService` +decorator. It delegates submission, lookup, retry, and listing to the existing +`DefaultDocumentConversionService`, and routes only tenant-scoped and legacy +deletion to `ArtifactDeletionCoordinator`. This preserves conversion behavior +and keeps the deletion worker independently replaceable. + +The coordinator first persists deletion intent with a controlled +`pending` artifact-checksum marker. It then reads the artifact under the same +per-job lifecycle fence. A successful read durably replaces the marker with the +exact SHA-256 digest, or with the explicit absent-artifact digest when no bytes +exist. Only after that exact binding may metadata be tombstoned and physical +cleanup begin. If the initial artifact read fails, metadata remains intact, the +pending receipt remains restart-recoverable, and a low-cardinality failed +attempt signal is emitted. An unreadable artifact is never represented by the +absent-artifact digest. + +The reference implementation closes the process-local and restart-loss gap that +caused CWE-459 orphaned document bytes. A future database deployment must still +replace the file ledger and metadata repository with one transactional outbox +transaction. The current receipt-first ordering is crash recoverable but does +not claim cross-resource ACID atomicity. + +## Authorization and immutable identity + +Tenant deletion begins with `findByTenantAndId`. Missing and cross-tenant +identifiers return `false` before artifact bytes are read. If metadata is already +absent, the coordinator returns success only when a receipt for the same tenant +and permanently reserved job identifier already exists; another tenant's +receipt remains concealed. + +One receipt identity contains: + +- `request_id`: deletion idempotency identifier; +- `tenant_id`: tenant that owned the conversion job; +- `job_id`: permanently reserved conversion-job identifier; +- `artifact_checksum`: the controlled `pending` marker until the first + successful artifact snapshot, then the immutable lowercase SHA-256 digest for + the observed generation; +- `audit_correlation_id`: random privacy-safe lifecycle correlation; +- `requested_at`: durable request time. + +The `pending` marker is deliberately **not** a digest and is valid only while the +receipt remains `DELETION_REQUESTED`. `markMetadataTombstoned` rejects a pending +marker. The marker therefore cannot be interpreted as proof that bytes are +absent and cannot weaken the exact-generation deletion fence. + +Raw subjects, filenames, tokens, storage paths, exception messages, and document +content never enter the receipt or aggregate operational evidence. Repeated +same-tenant DELETE requests resume or observe the existing receipt and preserve +the same intended effect. Conflicting tenant or artifact identity fails closed. + +## Monotonic lifecycle + +```mermaid +stateDiagram-v2 + [*] --> DELETION_REQUESTED: persist pending marker + DELETION_REQUESTED --> DELETION_REQUESTED: bind exact digest + DELETION_REQUESTED --> METADATA_TOMBSTONED: digest must be bound + METADATA_TOMBSTONED --> ARTIFACT_CLEANUP_PENDING + ARTIFACT_CLEANUP_PENDING --> ARTIFACT_CLEANUP_COMPLETED + ARTIFACT_CLEANUP_PENDING --> ARTIFACT_CLEANUP_FAILED + ARTIFACT_CLEANUP_FAILED --> ARTIFACT_CLEANUP_PENDING + ARTIFACT_CLEANUP_COMPLETED --> [*] +``` + +Every snapshot records `state_changed_at`; time cannot move backward and +`ARTIFACT_CLEANUP_COMPLETED` is terminal. Cleanup failures increment +`attempt_count`, record `last_attempt_at`, and accept only controlled lowercase +codes matching `[a-z0-9_]{1,64}`. The pre-snapshot read failure remains a +`DELETION_REQUESTED` receipt with the pending marker and is additionally counted +by the dimension-free failed-attempt metric; it does not fabricate a cleanup +failure transition before an exact generation exists. Current cleanup codes are: + +- `artifact_store_read_failed`; +- `artifact_store_delete_failed`; +- `artifact_checksum_mismatch`. + +The ledger rejects inconsistent attempt evidence, illegal successors, +non-monotonic timestamps, unsafe failure details, malformed identity, illegal +checksum rebinding, and attempt erasure across retry or completion. + +## Receipt-first deletion flow + +1. Resolve tenant ownership at the repository boundary. +2. Confirm that SHA-256 is available before accepting work that cannot later be + generation-bound. +3. Force a `DELETION_REQUESTED` receipt with the controlled `pending` checksum + marker to durable storage. +4. Read the current artifact under the per-job lifecycle fence. If the read + fails or returns an invalid result, retain the pending receipt, keep metadata + intact, emit aggregate failure evidence, and retry after startup or schedule. +5. On a successful read, bind a present artifact to SHA-256. Bind true absence + to the SHA-256 digest of the empty byte sequence. Force the same receipt + identity with that exact digest before any metadata mutation. +6. Tombstone metadata without releasing the UUID reservation. +7. Force `METADATA_TOMBSTONED` and `ARTIFACT_CLEANUP_PENDING`. +8. Re-read the artifact before deletion. A non-empty expected digest must match. +9. Delete bytes and sidecar metadata through `ArtifactStore.deletePdf`. +10. Force `ARTIFACT_CLEANUP_COMPLETED`, or force one controlled cleanup-failed + state for later retry. + +The empty digest is an explicit **confirmed-absence** sentinel. It is written +only after `ArtifactStore.getPdf` has successfully returned an empty +`Optional`. An exception or invalid read result keeps the non-digest `pending` +marker instead. A non-empty mismatched digest is retained and flagged instead +of deleting bytes that cannot be proven to match the receipt. + +## Conversion/deletion generation fence + +`ArtifactLifecycleLockRegistry` provides a fixed-memory per-job lock stripe in +the standalone process. `ArtifactDeletionCoordinator` and +`LifecycleFencedArtifactStore` use the same Spring-managed registry. Artifact +reads, writes, and deletes are therefore serialized with receipt creation, +checksum binding, and metadata tombstoning for the same job identifier. + +`LifecycleFencedArtifactStore.putPdf` rejects every write after a durable receipt +exists, including a pre-snapshot pending receipt and failed cleanup. This +prevents an already-running conversion from recreating bytes after cleanup +completes or changing the generation between a failed initial read and its later +recovery. If publication wins the lock first, deletion snapshots and removes +that published generation. If deletion wins first, the later write fails closed. +Multi-instance and remote object-store adapters must provide an equivalent +distributed generation fence or object-version precondition. + +## Persistence and crash recovery + +Each complete `RECEIPT_V1` snapshot is encoded as bounded strict UTF-8, +Base64URL-encodes variable text, uses tab-separated fields and one fixed ASCII +LF commit delimiter, and is appended through `FileChannel`. The ledger calls +`force(true)` before exposing the transition as durable. + +Startup replay rejects malformed Base64URL, invalid state, timestamp or count, +immutable-request conflicts, illegal pending-marker placement, unsupported +checksum rebinding, oversized records, illegal transitions, and every non-empty +unterminated final tail. It never silently truncates forensic evidence. A +missing ledger means an empty store; a malformed existing ledger prevents +startup. + +`ArtifactDeletionCoordinator` replays every nonterminal state: + +- `DELETION_REQUESTED` with `pending`: retry the exact artifact snapshot while + metadata remains intact; if successful, durably bind the digest and continue; +- `DELETION_REQUESTED` with an exact digest: confirm or apply the tenant metadata + tombstone; +- `METADATA_TOMBSTONED`: queue cleanup; +- `ARTIFACT_CLEANUP_PENDING`: retry the interrupted attempt; +- `ARTIFACT_CLEANUP_FAILED`: return to pending and retry; +- `ARTIFACT_CLEANUP_COMPLETED`: perform no work. + +Recovery is serialized in the reference process and bounded by +`clearfolio.artifact-deletion-cleanup.max-receipts-per-run`, default `100`. +Scheduled retry uses +`clearfolio.artifact-deletion-cleanup.retry-delay-ms`, default `30000`. +Confidential-byte cleanup has no terminal retry count; operators must alert on +persistent pending or failed counts rather than discard the receipt. + +## Aggregate operational evidence and privacy + +`ArtifactDeletionMetrics` exposes dependency-free aggregate values: + +- `completedAttempts()`; +- `failedAttempts()`; +- `pendingReceipts()`. + +The component stores no tag map and accepts no tenant, job, checksum, exception, +filename, token, or path dimension. A host application may export these fixed +aggregate values through its existing OpenTelemetry, JMX, analytics, or metrics +adapter without adding a metrics framework or management HTTP surface to this +standalone module. + +## Modular and MSA contract + +`ArtifactDeletionReceiptStore`, `ConversionJobRepository`, `ArtifactStore`, the +primary deletion decorator, and the aggregate evidence component remain +replaceable boundaries. PostgreSQL, event-store, object-store, queue, or remote +service adapters must preserve: + +- tenant authorization before lookup or mutation; +- one permanently reserved `job_id` lifecycle; +- a durable pre-snapshot deletion intent before the first artifact read; +- exact immutable request identity and idempotency; +- one-way pending-marker-to-exact-digest binding before metadata tombstoning; +- artifact digest or generation binding; +- conversion/deletion generation fencing; +- monotonic state and time; +- controlled privacy-safe failure evidence; +- restart-safe pending-work replay; +- bounded consumer backpressure; +- terminal completion retention; +- fail-closed conflict behavior. + +Database objects must contain at least two descriptive words and use +`snake_case`, including `artifact_deletion_receipt`, `artifact_cleanup_task`, +`conversion_job_tombstone`, and `deletion_audit_event`. + +## Remaining acquisition-readiness work + +The next database slice should commit job tombstone, deletion receipt, and +cleanup outbox task in one transaction. A remote object-store adapter should bind +deletion to an immutable object version or generation. Slice D must revoke +issued artifact links and expose accepted, pending, failed, and completed status +through truthful APIs and an accessible viewer instead of representing metadata +tombstoning as full physical completion. + +Release evidence still requires the integrated exact head to pass full Maven +verification, zero skipped tests, 100% production line and branch coverage, +warning-free public Javadocs, CI, Security Scan, SAST, fuzzing, Strix, +CodeRabbit, OpenCode/Noema, and independent protected-branch approval. + +## Verification requirements + +The deterministic suite covers tenant concealment, successful completion, +durable pre-snapshot read failure across restart, metadata preservation before +checksum binding, confirmed absence versus unreadable-artifact distinction, +durable delete failure, controlled cleanup failure codes, restart replay, +already-tombstoned recovery, pending-at-crash recovery, digest mismatch, +repeated DELETE idempotency, write-after-receipt rejection, in-flight publication +serialization, bounded batches, aggregate evidence, invalid configuration, +conflicting identities, legal and illegal ledger transitions, strict UTF-8, +oversized input, unterminated-tail rejection, and completed-receipt retention. + +## References + +Fielding, R., Nottingham, M., & Reschke, J. (2022). *HTTP semantics* (RFC 9110; +STD 97). Internet Engineering Task Force. https://www.rfc-editor.org/rfc/rfc9110 + +National Institute of Standards and Technology. (2024). *Protecting controlled +unclassified information in nonfederal systems and organizations* (NIST Special +Publication 800-171, Revision 3). U.S. Department of Commerce. +https://doi.org/10.6028/NIST.SP.800-171r3 + +OWASP Foundation. (2023). *API1:2023 broken object level authorization*. In +*OWASP API Security Top 10—2023*. +https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/ diff --git a/docs/superpowers/plans/2026-08-06-durable-artifact-cleanup.md b/docs/superpowers/plans/2026-08-06-durable-artifact-cleanup.md new file mode 100644 index 000000000..a810eae2f --- /dev/null +++ b/docs/superpowers/plans/2026-08-06-durable-artifact-cleanup.md @@ -0,0 +1,45 @@ +# Durable artifact cleanup Slice C implementation plan + +Date: 2026-08-06 +Owner issue: #263 +Parent slice: #277 receipt ledger + +## Goal + +Integrate the durable receipt foundation with artifact cleanup without changing +conversion semantics, adding a dependency, or claiming the later truthful HTTP +and accessible UI slice. + +## Ordered implementation + +1. Add tests for repeat DELETE idempotency, cross-tenant receipt concealment, + in-flight publication ordering, write-after-receipt rejection, restart replay, + bounded recovery, aggregate counts, and service delegation. +2. Add a fixed-memory per-job lifecycle lock registry. +3. Add an artifact-store decorator that shares the lock and rejects writes after + durable deletion intent. +4. Add a receipt-first cleanup coordinator with exact digest validation, + controlled failure codes, startup replay, and scheduled bounded retry. +5. Add a primary document-service decorator that intercepts deletion only. +6. Enable Spring scheduling and configure retry delay and batch size. +7. Update the lifecycle ADR and changelog without changing dependencies, release, + deployment, or workflow policy. +8. Run exact-head Maven verify, zero-skip report validation, JaCoCo zero-missed + line/branch gates, warning-free public Javadocs, CI, security, SAST, fuzzing, + and independent review before integration. + +## Scope exclusions + +- no new Maven dependency or management HTTP endpoint; +- no controller response change; +- no deletion status API or viewer UI; +- no signed-link revocation; +- no database/outbox adapter; +- no release, version, deployment, or workflow change. + +## Rollback + +Remove the primary deletion decorator, lifecycle-fenced artifact-store wrapper, +coordinator, scheduling annotation, and cleanup configuration. The receipt-ledger +parent remains intact and the previous service and artifact delegates resume +without data-format rollback. diff --git a/docs/superpowers/specs/2026-08-06-durable-artifact-cleanup-design.md b/docs/superpowers/specs/2026-08-06-durable-artifact-cleanup-design.md new file mode 100644 index 000000000..5ae4fabc6 --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-durable-artifact-cleanup-design.md @@ -0,0 +1,119 @@ +# Durable artifact cleanup integration design + +- **Status:** Accepted for clean Slice C implementation +- **Decision date:** 2026-08-06 +- **Parent security finding:** PR #268, incomplete artifact cleanup (CWE-459) +- **Foundation:** `ArtifactDeletionReceiptStore` and `RECEIPT_V1` ledger from PR #277 + +## Objective + +An authorized job deletion must never lose the information needed to remove +confidential artifact bytes. The service must durably record deletion intent +before metadata mutation, recover incomplete work after restart, retry storage +failures with bounded serial work, reject writes after deletion intent, expose +privacy-safe aggregate evidence, and never delete bytes belonging to another +immutable lifecycle. + +## Chosen structure + +The slice adds four focused adapters: + +1. `ArtifactDeletionCoordinator` owns receipt-first tombstone and cleanup state. +2. `DurableDocumentDeletionService` is the primary service decorator and + intercepts deletion only. +3. `ArtifactLifecycleLockRegistry` supplies bounded per-job serialization. +4. `LifecycleFencedArtifactStore` serializes artifact I/O with deletion and + rejects publication after a receipt exists. + +The existing conversion service, repository, artifact-store delegates, and +receipt ledger remain independently usable. No dependency or management HTTP +surface is added. + +Rejected alternatives: + +- **Log-only retry hint:** not durable or idempotent. +- **Controller-local queue:** lost on restart and not reusable by MSA adapters. +- **Direct modification of conversion service:** couples cleanup to unrelated + submission/retry behavior and weakens modular replacement. +- **Actuator/Micrometer starter:** expands dependencies, management surface, SBOM, + attribution, and license evidence solely to count three aggregate values. +- **New database transaction in this slice:** the repository has no database + adapter yet; the receipt-first reference path remains replaceable by a later + transactional outbox. + +## Lifecycle flow + +1. Resolve the job through a tenant-scoped query. +2. Under the per-job lifecycle lock, read the current artifact and bind it to + lowercase SHA-256 or the documented absence sentinel. +3. Durably create or reuse `DELETION_REQUESTED`. +4. Tombstone metadata without releasing the job identifier. +5. Record `METADATA_TOMBSTONED` and `ARTIFACT_CLEANUP_PENDING`. +6. Re-read under the same lifecycle fence. A non-empty digest must match. +7. Delete exact bytes or record a controlled read/delete/mismatch failure. +8. Startup and fixed-delay recovery replay a bounded deterministic batch. +9. A later same-tenant DELETE resumes or observes the receipt; another tenant + receives the same concealed not-found result. + +## Write fence + +All configured artifact operations pass through `LifecycleFencedArtifactStore`. +If an artifact publication holds the job lock first, deletion waits, snapshots +that generation, and removes it. If deletion creates a receipt first, every +later `putPdf` fails closed. This prevents a conversion that began before +metadata tombstoning from recreating bytes after cleanup completion. + +The lock is process-local and fixed-memory. Multi-instance adapters must use an +object generation precondition, durable lease, distributed lock, or +transactional outbox consumer fence. + +## Backpressure and recovery + +`clearfolio.artifact-deletion-cleanup.max-receipts-per-run` defaults to `100`. +`clearfolio.artifact-deletion-cleanup.retry-delay-ms` defaults to `30000`. +One receipt failure is isolated from later receipts in the selected batch. +Cleanup has no terminal retry count because silently retaining confidential +bytes is not an acceptable terminal state. + +## Aggregate evidence + +`ArtifactDeletionMetrics` uses JDK `LongAdder` counters and the receipt store's +current pending count. It exposes only completed, failed, and pending aggregate +values. It stores no labels or identifiers and introduces no dependency. Host +applications may bridge these values to their existing observability plane. + +## Verification contract + +Tests must prove: + +- tenant concealment and missing-context failure; +- receipt before metadata mutation; +- exact digest and absence-sentinel behavior; +- read/delete/mismatch failure persistence and retry; +- restart and scheduled recovery; +- bounded batch isolation; +- repeated same-tenant DELETE idempotency; +- cross-tenant receipt concealment; +- write-after-receipt rejection; +- in-flight publication/deletion serialization; +- delegate-mode persistence for filesystem and volatility for in-memory storage; +- decorator delegation for every non-deletion method; +- aggregate evidence without high-cardinality input; +- 100% production line and branch coverage and warning-free public Javadocs. + +Exact-head CI, Security Scan, SAST, fuzzing, CodeRabbit, OpenCode/Noema, Strix, +zero unresolved threads, and independent protected-branch approval remain merge +gates. + +## References + +Fielding, R., Nottingham, M., & Reschke, J. (2022). *HTTP semantics* (RFC 9110; +STD 97). Internet Engineering Task Force. + +National Institute of Standards and Technology. (2024). *Protecting controlled +unclassified information in nonfederal systems and organizations* (NIST Special +Publication 800-171, Revision 3). U.S. Department of Commerce. +https://doi.org/10.6028/NIST.SP.800-171r3 + +OWASP Foundation. (2023). *API1:2023 broken object level authorization*. In +*OWASP API Security Top 10—2023*. diff --git a/pom.xml b/pom.xml index 51e22d13e..362f40552 100644 --- a/pom.xml +++ b/pom.xml @@ -29,8 +29,15 @@ ${java.version} UTF-8 + 0.8.15 + 3.12.0 3.0.8 6.1.200 + + 4.1.136.Final