Skip to content

security(score): own private attachment publication and retention #1239

Open

Description

@seonghobae

Buyer-visible gap

Score attachment bytes are buyer data. BandScope needs a Score Storage owner for write-time confidentiality, bounded publication, interrupted-publication recovery, successful-return durability, object identity, retention/deletion and restart discovery independently of Project Persistence.

This is not #865's read-time allocation/content-validation problem and not #970's durable project/workspace/revision-CAS problem. Score Storage may consume those owners through narrow contracts, but score-byte lifecycle truth stays here.

Canonical owner / live implementation

Canonical bounded context: Score Storage / Score Attachment.
Implementation: Draft PR #1241, branch fix/score-attachment-publication-1239.
Exact current source/docs head: b29b7b522478780db44db1c754ab7f660ed2b17b.
Protected base: develop@314ddeae7b775a4957594b599358c8255617eb2e.
Read-time prerequisite: #865 exact 1f4877413e2eed30b224eaf1b095af3b0b905cb0.

Fresh compare is ordinary-forward (ahead=169, behind=0, merge base exactly protected develop). Keep #1241 Draft until #1176→#865 integrates and one unchanged exact head has terminal owner/UI/repository/central/security/review evidence. Predecessor GREEN does not transfer.

Acceptance

  • Unix staging is private at first visibility (0600) even under permissive inherited umask.
  • Windows consumes and tests the app-owned parent-DACL inheritance contract instead of claiming POSIX-equivalent permissions.
  • Selected PDF publication is descriptor-bounded to 25 MiB; growth, truncation, wrong magic and oversize fail closed.
  • Publication is no-clobber and final destination is attested to the synchronized stage object (Unix device/inode; Windows volume serial + 128-bit FILE_ID_INFO).
  • Current-contract writer death leaving stage-only synchronized bytes is recovered under an OS-released cross-process Score Storage lease.
  • Writer death after hard-link publication but before stage retirement preserves the destination and retires only an equal-content stage alias; content-different/indeterminate pairs preserve evidence and fail closed.
  • Restart discovery exposes exact owned score ids and path-free {score_id, SHA-256} receipts; id-only inventory is discovery-only.
  • Same-id remove→republish ABA cannot let a stale receipt delete replacement bytes.
  • Recovery-stage cleanup is bound to bounded content admitted earlier in the recovery transaction; a different replacement is preserved and fails closed.
  • Explicit native deletion is object-aware: Windows handle-bound disposition; Unix parent-dirfd/open/recheck/unlinkat, with the final basename race still explicit.
  • Successful-return publication holds the Score Storage lease through a macOS fsync_volume_np(FULLSYNC|WAIT) / other-Unix directory sync_all() metadata barrier. No Windows directory-entry power-loss claim is made.
  • Fault-recovery owner evidence covers interrupted partial copy, Unix permission-denied restart recovery and Windows-native ACL denial without inventing published object truth.
  • The actual public publication path is exercised under a Unix kernel-enforced file-size write failure without changing the 25 MiB production ceiling.
  • Actual macOS filesystem capacity exhaustion is exercised on a fixed-size HFS+ image until raw ENOSPC(28); publication leaves no destination or owned partial stage and fresh inventory is empty.
  • The real public publisher is terminated at after-stage-sync-before-link, after-link-before-stage-retirement and before-metadata-barrier; restart semantics are verified while live inventory is blocked by the child's .score-storage.lock.
  • Owner-only desktop-package executable score-storage-fault-harness invokes the same real public publisher at those boundaries and release-mode feature checks reject fault-injection leakage.
  • Ordinary buyer detach captures a path-free content receipt before metadata mutation and uses only receipt-bound deletion after accepted metadata detachment; same-id replacement bytes are preserved rather than deleted by stale intent.
  • Source repair binds ordinary in-component ScoreView read/attach/detach work to (projectId, song.id) and suppresses late old-project UI/metadata/delete continuation after a project switch (470aceb8... RED → 7dfa1c6c... repair).
  • Project-context reset uses the pre-paint useLayoutEffect boundary so already-rendered old-project score state is cleared before the newly active project can repaint (e95de494...).
  • Same-context detach clears the currently selected attachment after accepted metadata detachment even when that score was opened while receipt/persistence work was in flight (721bddc7... RED → 9f63bd53... repair).
  • Same-project/song async attach and detach construct metadata proposals from the latest rendered same-song snapshot after their native wait, preserving newer visible title/attachment state instead of replaying the initiating render closure (8dd038be... RED → 8529ec09... repair; TRACEABILITY b29b7b52...). This is not a durable revision/CAS claim; fix(project): enforce crash-safe revision-bound persistence #970 remains the durable concurrency owner.
  • Focused project-context regressions and TRACEABILITY source are owned by the Score Storage workflow path contract.
  • Exact-current-head b29b7b52... owner/UI generation is terminal-success. Current score-storage-native run 35543493216 is nonterminal at the latest read; predecessor GREEN remains lineage only.
  • Exact-current-head repository/central/security/review settlement is terminal-success. build-baseline, ci, Security Scan, Semgrep, SBOM and CodeQL PR generations are nonterminal and no qualifying independent non-author approval exists.
  • Actual shipped/bundled bandscope-desktop cancellation is verified through supported product packaging/runtime without weakening the real frontend/build contract.
  • Sudden power-loss fault paths are complete.
  • Windows directory-entry successful-return durability has a verified supported-runtime primitive and fault evidence.
  • Compatibility with an older concurrently running BandScope build that does not acquire .score-storage.lock is fully resolved.
  • Complete retention/lifecycle semantics are product-complete across detach, project deletion, project recovery/rollback and missing_referenced_score_ids broken-reference handling.
  • Protected/released Score Storage contract is consumed by fix(project): enforce crash-safe revision-bound persistence #970 without filesystem source copy, with buyer-visible Recover / Preserve / Discard orchestration revalidating durable active project identity, fresh Project Persistence classification and fresh Score Storage receipt immediately before mutation.
  • Signing/notarization, immutable release, SBOM/provenance/reproducibility and updater rollback acceptance are complete for the release head.

Project-context freshness lineage

Receipt freshness, project freshness, live viewer-selection freshness and same-song rendered-snapshot freshness are separate invariants. Receipt-bound deletion prevents an old score-object identity from deleting same-id replacement bytes, but does not by itself stop stale UI intent or stale render closures.

470aceb8c0e2d76f935cc0ed326204007b0c0674 adds stale-project RED coverage. 7dfa1c6c7c8100619dd3eafcd9f01b153fd3cb9b derives (projectId, song.id) context identity and revalidates it before later UI state, metadata mutation and byte deletion. e95de494fd3747a09fd23780188a474d51dd26ef moves visible reset to useLayoutEffect so stale score content does not survive the project/song repaint boundary.

721bddc70081329c860c267cb3b83ce73531a3c8 adds the same-context selection RED and 9f63bd53db01949670f35a7ed8cdf7cee46bc10e repairs it using live selection identity.

8dd038be0f115377cda7071e2d49a909effb3013 adds two same-song aggregate REDs: pending attach must preserve a newer same-song title and concurrent attachment; pending detach must remove only the target from the newer snapshot. 8529ec09254ae5c6c27d92454a1ae8a02629c27b tracks the latest rendered song and derives proposals from that snapshot after context revalidation. b29b7b522478780db44db1c754ab7f660ed2b17b records the invariant and claim boundary in TRACEABILITY. No hosted RED is claimed for the test-only head because repair followed before terminal hosted evidence.

If old-project native attach completes after navigation, ScoreView does not attach it to the newly rendered project and does not invent compensating deletion authority; the published object remains a recovery candidate. If the same song rerenders while native work is pending, ScoreView preserves the latest rendered snapshot. Durable active-project identity/revision CAS, concurrent external writers and restart recovery authorization remain #970 responsibilities.

Current publication / recovery contract

Publication uses a fixed-buffer descriptor copy into a private reserved stage, sync_all, no-clobber hard-link publication and OS-object identity attestation. The cross-process lease remains live through the supported-platform successful-return metadata barrier before attachment authority returns.

Recovery owns only exact .score-<valid-score-id>.stage names. Stage cleanup is bound to admitted bounded content identity; equal-content post-link state retires only the temporary alias, while different/indeterminate content preserves evidence and fails closed. Restart inventory exposes path-free content receipts and receipt-bound removal reacquires the lease and revalidates current object content before deletion.

Exact-head gate state

Exact b29b7b522478780db44db1c754ab7f660ed2b17b is Open / Draft / mergeable and ordinary-forward from protected develop. Its score-storage-native run 35543493216 and repository/security generation are currently nonterminal. Formal review inventory contains only COMMENTED predecessor reviews; there is no qualifying independent non-author current-head APPROVED.

Merge / release gate

Do not close #1239 merely because source repairs exist. Close only after normal protected integration or a verified successor inherits every valid delta, regression, contract and evidence. #1241 remains Draft while #1176→#865, exact-head owner/UI/repository/central gates, independent approval, #970 consumer integration, buyer recovery UX and release/signing evidence are incomplete.

Activity

  1. added
    bugSomething isn't working
    area: securitySecurity boundary, hardening, or vulnerability prevention
    type: securitySecurity vulnerability or security-specific remediation
    on Sep 19, 2026
  2. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    Canonical implementation is now Draft PR #1241 (fix/score-attachment-publication-1239). The branch is intentionally stacked from #865 exact c2c86b8b4f82cbdceabdc52516a33d8d2bd8614a; #865 remains the read-time owner and must integrate first.

    Current Score Storage delta owns descriptor-bounded write publication: fixed-size copy, early-EOF/growth rejection, %PDF- revalidation on copied bytes, Unix 0600 at first visibility, no-clobber hard-link publication, Unix inode-bound stage cleanup, Tauri call-site wiring, and an exact-head macOS/Windows native workflow. docs/traceability/score-attachment-publication.md records the boundary and rejected alternatives.

    Still open under this issue: terminal exact-head hosted results, actual Windows ACL/inheritance acceptance, identity-bound Windows cleanup after handle close, detach/retention semantics, and non-force reconciliation after #865 / relevant #970 workspace-authority integration. Do not close #1239 yet.

  3. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    진행 증거를 #1239 canonical owner에 갱신합니다.

    • Windows post-close stage cleanup authority RED: a7373ca153e8740923ad13098953a61a7c263cc0. Exact score-storage-native run 35452152802에서 macOS job 105921067494는 성공했고 Windows Server 2025 job 105921067591은 windows_cleanup_preserves_replaced_stage_path 하나만 실패했습니다. 기존 non-Unix cleanup이 원래 stage handle을 닫은 뒤 같은 pathname을 차지한 foreign regular file을 regular-file 여부만으로 cleanup authority로 받아들여 삭제할 수 있음을 hosted RED로 확인했습니다.
    • Source repair: 49976461c7158869d1f10a6a43b46e0ee522352f. Windows stage identity를 GetFileInformationByHandleEx(FileIdInfo)의 volume serial + 128-bit file id로 캡처하고, cleanup 시 pathname을 다시 연 파일이 같은 identity일 때만 unlink하도록 변경했습니다. 마지막 identity-check→pathname remove_file 구간까지 descriptor-relative라고 주장하지 않으며 residual TOCTOU로 남깁니다.
    • Foundation #865는 protected develop@314ddeae7b775a4957594b599358c8255617eb2e보다 behind 2인 diverged 상태였으므로 force/rebase 없이 ordinary two-parent merge 1f4877413e2eed30b224eaf1b095af3b0b905cb0로 수리했습니다. Fresh compare는 protected develop 대비 behind_by=0이고 unique score-read delta는 기존 6파일 그대로입니다.
    • #1241도 fix(score): bound native PDF reads before allocation #865 새 exact head를 ordinary/non-force merge 7d30d141c8eb9d1c0d86fe76dd6716fff49a0f21로 승계했습니다. Fresh compare는 fix(score): bound native PDF reads before allocation #865 대비 behind_by=0입니다.
    • Current exact 7d30d141… native GREEN: run 35452505092, macOS job 105921992778 SUCCESS, Windows job 105921992946 SUCCESS. Windows 로그에서 exact SHA checkout 후 4/4 owner unit tests(새 replacement regression 포함), 2/2 publication regressions, 1/1 Tauri wiring regression이 통과했습니다.

    아직 완료 처리하지 않습니다. Repository-wide CI/build/security/SBOM/SAST/CodeQL은 current head에서 미종료이고 qualifying independent approval도 없습니다. 실제 Windows ACL/inheritance acceptance는 #970 workspace-authority reconciliation 뒤 검증해야 하며, detach/retention/delete semantics와 최종 check→unlink TOCTOU의 위협모델 처리도 #1239에 남습니다.

  4. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    다음 Score Storage vertical도 source 기준으로 특정했습니다. Current #1241 descendant에서 remove_score_pdf는 resolve_existing_score_pdf로 id/path/symlink/containment를 확인한 뒤 별도 std::fs::remove_file(path)를 수행합니다. 따라서 retention/delete owner는 아직 publication과 같은 file-identity authority를 갖지 않습니다. 이 항목은 #1239에 남기되, 이번 head에서 단순 pathname 재확인만 추가해 ‘race closed’라고 주장하지 않습니다. Windows는 handle/file-disposition 계열, Unix는 dirfd/descriptor-relative unlink 계열을 포함해 실제 deletion authority를 RED로 먼저 고정한 뒤 구현해야 합니다. UI의 remove metadata 갱신과 Project Persistence lifecycle도 함께 검증해야 하므로 #970 contract를 복제하지 않고 owner reconciliation 뒤 진행합니다.

  5. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    Deletion/retention vertical advanced on canonical PR #1241.

    Hosted RED 06cc24a1b0bf89d1525cfb0b06921e9229e7b5dd added a product-wiring contract requiring remove_score_pdf to use a Score Storage deletion boundary rather than direct pathname std::fs::remove_file. Exact score-storage-native run 35453934315 failed that wiring step on both macOS (105925792583) and Windows Server 2025 (105925792679) after owned Score Storage unit tests had passed.

    GREEN 09610fb26127408f818a754a42b75a083e0e5045 now routes the Tauri command through remove_score_pdf_attachment.

    • Windows: open exact object with DELETE | FILE_READ_ATTRIBUTES, reject reparse objects, then SetFileInformationByHandle(FileDispositionInfo) on the same handle. Native regression replaces the pathname after handle acquisition and proves the foreign replacement survives while the originally opened object is deleted.
    • Unix: pin parent directory, openat(..., O_NOFOLLOW) the basename, capture device/inode, reopen relative to the same parent, then unlinkat. A replacement before the second identity check fails closed and is preserved. The final identity-check-to-unlinkat name race remains explicitly documented; no race-free portable Unix claim.

    Exact GREEN run 35454259017 is terminal success on both current-head lanes: macOS job 105926649166, Windows job 105926649120.

    Still open under #1239: actual Windows ACL/inheritance acceptance, threat-model disposition of the residual Unix and stage-cleanup name races, retention/project lifecycle semantics across #970, repository-wide gates, #865 protected integration, and independent approval.

  6. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    #1239 진행 증거를 current Score Storage owner 기준으로 갱신합니다.

    Windows file-level ACL/inheritance acceptance를 실제 native 계약으로 추가했습니다. 579a67d4767d098d0dd608b5678d59e66efe1701에서 publication regression이 GetNamedSecurityInfoW로 최종 <score_id>.pdf DACL을 읽고, SE_DACL_PROTECTED가 clear인지와 INHERITED_ACE가 하나 이상 존재하는지를 검증합니다. POSIX 0600과 동등하다고 가정하지 않고 BandScope가 선택한 Windows 정책인 parent-DACL inheritance 자체를 검증합니다.

    해당 source의 exact run 35455174325는 macOS job 105929062031, Windows Server 2025 job 105929062235 모두 SUCCESS였습니다. 이어 TRACEABILITY를 같은 계약으로 code-current하게 만든 current exact head 48e6195cfd3b4574fd30225185c51aa6f29546bc에서도 score-storage-native run 35455282784가 Windows 105929348352, macOS 105929348482 모두 terminal SUCCESS입니다.

    따라서 현재 app-owned parent directory 모델에서 score file 자체가 Windows parent DACL inheritance를 유지한다는 acceptance는 닫았습니다. 다만 #970이 최종 workspace ACL/authority를 바꾸거나 통합하면 predecessor evidence를 전용하지 않고 같은 exact-head acceptance를 다시 수행해야 합니다.

    아직 #1239는 닫지 않습니다. #865 protected integration, repository-wide gates, independent approval, #970 workspace/lifecycle reconciliation, Unix final-name race disposition, buyer-visible detach/project-delete/recovery semantics가 남아 있습니다. 또한 publication 성공 반환 직전 destination이 실제 staged object와 같은 file identity인지 검증하는 acceptance는 별도 current gap으로 남겨 두었습니다.

  7. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    #1241에 restart/readback acceptance를 실제 source로 추가했습니다. Exact current head는 0e4070ef6d92d55a91463e8bc4c1fa6b71f86003입니다.

    • 297327de172bbe30cdf226518a5135605db983ef: 성공적으로 publish한 실제 PDF fixture를 parent test process에서 만들고, fresh test executable child를 다시 기동해 production resolve_existing_score_pdf → read_validated_score_pdf 경로로 정확한 bytes를 읽습니다. child 성공 뒤 .score-*.stage alias가 0개인지도 확인합니다.
    • 0e4070ef6d92d55a91463e8bc4c1fa6b71f86003: 위 score_pdf_restart_readback target을 macOS/Windows score-storage-native owner workflow에 포함했습니다. fix(score): bound native PDF reads before allocation #865 exact 1f487741… 대비 ordinary descendant이며 fresh compare는 ahead 37 / behind 0입니다.
    • Exact owner run 35463057196 (macOS 105950252549, Windows Server 2025 105950252698)과 repository-wide checks는 현재 queued입니다. 따라서 아직 restart/readback checklist를 완료로 바꾸거나 predecessor GREEN을 전용하지 않습니다.

    Claim boundary는 좁게 유지합니다. 이 test는 successful publication → fresh process readback과 성공 경로의 stage-alias retirement만 증명합니다. process kill/cancellation/disk-full/power-loss 도중 남은 staging artifact의 discovery/cleanup은 여전히 #1239의 open acceptance입니다. #1241은 Draft 유지합니다.

  8. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    Post-link interruption gap is now narrowed to a concrete owner boundary rather than a generic recovery TODO.

    Fresh source trace on protected develop@314ddeae7b775a4957594b599358c8255617eb2e shows the frontend path is attachScorePdf(...) → construct ScoreAttachment → onSongUpdate(...), while App.handleSongUpdate only mutates jobResult in React state. No app-local crash-safe Project Persistence metadata commit is coupled to the successful <score_id>.pdf publication.

    Therefore #1241 must continue to treat stage + destination after interruption as ambiguous and preserve/fail closed. Score Storage must not infer that a destination without in-memory metadata is orphaned, and must not auto-adopt it as durable project truth. I recorded the canonical lifecycle repair requirement on #970: Project Persistence should expose a narrow durable attachment lifecycle/reconciliation contract (published bytes / metadata committed / detached or rolled back), then #1241 can consume that contract without taking ownership of project persistence.

    This means #1239's post-link interruption acceptance remains open even though stage-only abandoned-writer recovery is GREEN. Required evidence after #970 reconciliation: native process-kill windows around byte publication vs durable metadata commit, restart reconciliation with no silent delete/adopt, then fresh Windows ACL and Score Storage exact-head acceptance. Current #1241 source remains abed538d02e4813286ea62ac5c3aab914b89a774; no source-neutral wake commit or predecessor evidence transfer.

  9. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    Project Persistence prerequisite advanced on #970 (current descendant 5110271e0a350707a85d5efd45b1e1d03e7fdf53). Local-project handleSongUpdate no longer treats React memory as acceptance: it asks native save_project to persist a fixed app-owned project.bscope snapshot under the already-minted project root and only then replaces accepted renderer state. The WebView supplies no snapshot path.

    For Score Storage this changes the post-link recovery boundary but does not close it. <score_id>.pdf may still become durable before attachment metadata reaches project.bscope; a kill in that interval is an explicit recovery-candidate state, not a safe orphan deletion and not automatic adoption. #1239/#1241 should continue to preserve/fail closed on that ambiguity until #970/#962 owns startup discovery plus durable attachment lifecycle/reconciliation semantics. Stage-only abandoned-writer recovery remains separately GREEN and should not be reopened.

    Do not restack #1241 yet: #970 exact-head native/repository checks for this persistence descendant are still settling, and #970 remains Draft.

  10. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    Fresh consumer review on #970 found a distinct attachment-interaction gap that should stay with the Score UI/Storage lifecycle owner rather than be patched into Project Persistence. ScoreViewProps.onSongUpdate is currently typed (song) => void; handleAttach calls it without awaiting and immediately clears attaching state / opens the new PDF. #970's current handleSongUpdate is now async and does not accept React song state until app-owned project.bscope persistence succeeds, but ScoreView does not observe that acknowledgement. Result: the attach interaction itself can appear complete before durable attachment metadata commits, and a failed metadata commit can leave the published PDF as a recovery candidate while the score view has already advanced locally.

    Do not solve this by deleting the PDF on metadata failure: that would guess across the still-open PDF durable -> metadata not durable recovery window. After #970/#962 provides protected persistence/restart semantics, #1241 (or the canonical Score UI vertical) should make the callback acknowledgement explicit/awaitable and keep failure in a truthful pending/recovery state. Detach remains a separate, harder ordering problem because the current path deletes score bytes before durable metadata removal; that needs the attachment lifecycle contract rather than a callback-only patch.

  11. seonghobae commented on Sep 19, 2026

    @seonghobae
    CollaboratorAuthor

    Score UI/application ordering sub-gap repaired on #1241 current lineage.

    Source-level RED 5450dbbb7dfdb449c393e1b4d94bc60559db4989 adds three regressions: an asynchronously rejected attachment metadata update must not be presented/opened; a rejected detach metadata update must not delete stored bytes; accepted detach must commit metadata before destructive Score Storage removal. The general ci run for that test-only head was superseded/cancelled by descendants before it could provide a hosted RED, so no hosted RED is claimed.

    Causal repair 48d77841da464e8b21d2a998b58f13ee5d23311d changes ScoreView to await an async project-owner outcome. Attach keeps a published-but-unaccepted PDF as a recovery candidate and does not open it when the metadata owner returns false. Detach now asks the project owner to accept metadata removal first and only then invokes remove_score_pdf; rejection leaves referenced buyer bytes intact. Legacy synchronous void callbacks remain compatible for the pre-#970 stack and are not treated as evidence of durable persistence.

    TRACEABILITY is code-current at exact #1241 head bbfa0b5693590f12ef29d4a9545426f34ed558c1. Exact-head score-storage-native run 35473815994 is terminal GREEN on Windows Server 2025 job 105979458946 and macOS job 105979459071. That native lane does not execute the new React regression; repository ci for this exact head remains queued, so frontend GREEN is not claimed yet.

    This narrows detach lifecycle but does not complete it. Restart reconciliation for PDF published -> metadata not durable, post-link process kill, project deletion/recovery rollback, cancellation/disk-full/permission/power-loss, and #970 workspace reconciliation remain open.

  12. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    #1241 advanced the restart-reconciliation prerequisite from “Score Storage cannot enumerate durable objects” to an explicit owner contract.

    Current exact head: 4c72d9eaa48cf775db5003724a02cc88a76aaae3.

    • RED lineage: 7979ea23... adds fresh-process inventory/stage tests; 25fcd0a3... wires them into the macOS/Windows owner workflow. No hosted RED is claimed: the test-only native run is still queued and general descendant runs were superseded/cancelled. Source RED is deterministic because the public inventory symbol did not exist.
    • Repair: inventory_published_score_pdf_ids now acquires the existing Score Storage OS lease, runs abandoned-stage recovery, inventories only exact <uuid>.pdf objects through the existing native containment resolver, returns deterministic score ids only, and fails closed on stage+destination ambiguity. 1765fa07... corrected an initial wrong sibling-owner import found during self-review; f7b31910... exposes the contract at the crate root.
    • TRACEABILITY: docs/traceability/score-attachment-recovery-inventory.md; 4c72d9ea... makes that document and the new regression explicit owner-workflow inputs.

    This does not close PDF durable -> metadata not durable. It supplies Score Storage byte/object truth only. #970 still has to compare durable project attachment references against this inventory and own the buyer-visible recover/discard decision; #1241 must not infer project lifecycle intent or auto-delete/auto-attach an unreferenced object.

    Exact-head workflows are currently non-terminal (score-storage-native 35493457651; ci 35493457677; Security 35493457657; SBOM 35493457609; Semgrep 35493457659; build-baseline 35493457643; CodeQL 35493457564). Keep the issue open and do not transfer predecessor GREEN.

  13. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    Cross-owner handoff update: Project Persistence #970 now has a path-free reconciliation consumer contract at exact 75e5efe1be2c4f4b818d191b2ed8d79a04a38d2b. It accepts durable project attachment ids plus a caller-supplied validated Score Storage inventory and classifies matched, published-without-reference, and referenced-but-missing ids without filesystem I/O or lifecycle inference.

    This deliberately does not copy #1241 inventory_published_score_pdf_ids() or scan the scores directory from Project Persistence. #1241 remains the byte/object-existence owner. Once the Score Storage inventory contract is integrated through the canonical stack, Project Persistence can consume only its released/merged id-level result.

    Important lifecycle boundary: a published id absent from durable metadata is ambiguous between interrupted attach and completed detach with failed byte cleanup. Therefore neither owner may auto-attach or auto-delete from that fact alone; application recovery must obtain explicit buyer disposition. #1241 exact 4c72d9e... now has score-storage-native SUCCESS, while its repository/security gates remain unsettled.

  14. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    Exact-head correction only: the Project Persistence reconciliation consumer branch moved from 75e5efe... to 5f74aeb12a1b87f16ef9db4954d08467f73a29aa to record the test-harness RCA in TRACEABILITY. The cross-owner boundary is unchanged: Score Storage #1241 owns validated object inventory; #970 owns project-side set reconciliation and does not scan/copy Score Storage.

  15. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    Fresh cross-owner finding from Project Persistence recovery integration:

    #1241 exact 4c72d9eaa48cf775db5003724a02cc88a76aaae3 now exposes restart inventory as validated score ids only. That is correct for object discovery, but id-only freshness is not sufficient mutation authority if a score id can be removed and later republished with different bytes under the same id. In that ABA case, a recovery dialog opened against object A could re-read the same id after object B replaces it and falsely treat the old buyer decision as fresh.

    Current Score Storage contracts already allow explicit object removal and publication by caller-supplied valid score id; I do not see a current owner contract proving score ids are lifetime-nonreusable identities. Therefore Project Persistence #970 will not infer byte continuity or probe the filesystem to compensate.

    Canonical owner requirement for #1239/#1241: before restart inventory is used as freshness evidence for Recover/Discard, either (a) prove/enforce score-id non-reuse for the complete attachment lifetime with production tests, or (b) return a version/content/object-identity receipt from Score Storage and revalidate that receipt under the existing workspace lease immediately before mutation. The receipt must remain path-free and must not move PDF lifecycle ownership into Project Persistence. Add a realistic remove→republish-same-id regression so a stale recovery decision cannot authorize metadata or deletion for replacement bytes.

    This is a new owner gap, not evidence that current #1241 native inventory is incorrect for discovery. No #970 source copy or cross-context filesystem SQL/probing will be used as a workaround.

  16. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    Same-id ABA owner finding has been implemented on #1241 instead of being pushed into Project Persistence.

    Source RED 4d8d6c10761235c64ce5c5f70e4a0567ad5d4383 (refined by c1a4c78da0f4d76c67967dc2d05df5019599d511) now exercises the realistic lifecycle publish A -> inventory receipt A -> remove A -> republish different bytes B under the same score_id -> stale receipt A must preserve B -> fresh receipt B may remove B. These superseded test-only heads are recorded as source-level RED only; no hosted RED is claimed.

    Exact #1241 head is now b252cac4abf91d2722a2f9e87e8cbf949dd4d41d. The repair adds path-free PublishedScorePdfReceipt { score_id, content_sha256 }, receipt inventory under the existing Score Storage workspace lease, and remove_score_pdf_attachment_if_receipt_matches, which reacquires the same lease and holds it across fresh bounded-PDF digest calculation, equality comparison and the existing native identity-safe deletion. Missing current object or digest mismatch returns safe non-removal; suspicious/indeterminate state fails closed. No id-only fallback is permitted.

    The SHA-256 implementation is an adoption of the generic content_sha256 shared-kernel already present on #970, including FIPS 180-4 known-answer tests; it introduces no second algorithm/dependency. #1241/#970 later need ordinary/non-force consolidation of that identical shared-kernel delta rather than parallel copies.

    score-storage-native run 35507121922 has materialized for exact b252cac4... on macOS 15 and Windows Server 2025 and is currently in progress. General/security/required-workflow gates are also non-terminal, and there is no submitted independent review. No predecessor GREEN is transferred to this head. #1241 remains Draft and unmerged.

  17. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    Follow-up on the same-id ABA owner repair: exact b252cac4abf91d2722a2f9e87e8cbf949dd4d41d produced macOS/Windows score-storage-native failures before the intended ABA assertion because the new integration fixture had not created the app-owned scores_root; production admission correctly rejected that missing workspace. Unit tests and the shared SHA-256 vector already passed. This was test setup RCA, not a product-contract failure.

    ee7995e222756aa04f6f1cd83ed2dd5e2d928afe fixes only the fixture by creating scores_root; production admission was not weakened. 43fd4666eb92a9376d5253610753de7240596a54 records the RCA in TRACEABILITY.

    Exact 43fd4666... is now owner-native GREEN on both macOS 15 and Windows Server 2025 in run 35507283561 (jobs 106069129664, 106069129795), including the production publish/remove same-id ABA regression. #1241 remains Draft because general/security/required-workflow gates, #865 protected integration, independent review, #970 released-consumer reconciliation, buyer recovery UX and release evidence are still open.

  18. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    Post-link interruption recovery를 #1241 canonical Score Storage lane에서 계속 수리했습니다.

    • Source RED 1ce4bb86864ef845d39f7cb80614a2b686213499: score_pdf_interruption_recovery에 실제 persisted post-link shape를 만드는 child-process regression을 추가했습니다. child가 synchronized .score-<uuid>.stage를 만들고 <uuid>.pdf hard link까지 생성한 뒤 parent가 process kill/reap합니다. fresh process의 public receipt inventory가 destination bytes를 보존하면서 stage alias만 제거해야 합니다. 기존 recovery는 destination 존재 시 무조건 fail-closed였으므로 이 head는 deterministic source RED입니다. terminal hosted RED는 아직 주장하지 않습니다.
    • Causal repair 48a7ccfab996330fa415e68afaabc34782d62ad4: existing Score Storage lease 아래에서 destination을 canonical resolver로 검증하고 stage/destination을 existing bounded PDF reader + shared SHA-256으로 비교합니다. 동일 validated bytes일 때만 temporary stage alias를 제거하고 destination을 recovery candidate로 남깁니다. 다른 bytes/indeterminate state는 둘 다 보존하고 fail closed합니다. Project Persistence metadata나 buyer intent는 추론하지 않습니다.
    • TRACEABILITY b24770de264bcb812efc70b0809ad9a9a735ac5e: docs/traceability/score-attachment-post-link-recovery.md에 problem/alternatives/security/claim boundary/test points와 NIST FIPS 180-4, POSIX link/unlink 근거를 추가했습니다.
    • Exact current head 943256e4106bf2aea7ce13cc05cee892cd6f55f0: 새 TRACEABILITY를 score-storage-native owned path로 편입했습니다.

    남은 경계는 분리합니다. old unleased build 동시 실행 호환성, Unix final basename race, explicit cancellation/disk-full/permission/power-loss, project delete/rollback, #970 recovery UX는 이번 repair가 닫았다고 주장하지 않습니다. #865 foundation도 여전히 선행 integration 대상입니다.

  19. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    #1241 fresh Score Storage repair follow-up

    Exact current descendant after the Unix stage-cleanup authority repair is cd9b10570fe628720392068952991380b66266e3 on fix/score-attachment-publication-1239 (protected base remains develop@314ddeae7b775a4957594b599358c8255617eb2e).

    Finding: the lower Unix remove_owned_stage captured/checked device+inode but then deleted via fs::remove_file(path), leaving publication cleanup/normal stage retirement exposed to pathname/ancestor replacement after the identity check. This was narrower than the already stronger Unix published-score delete path.

    Repair lineage:

    • source-level RED 92b34e5c0288227652673d0614cc6fae8b3db5b3; no hosted RED claimed;
    • causal fix 46ff8f92c7f1983fba0805479e7cfe80dfeaebfb: pinned parent directory + openat(..., O_NOFOLLOW) + device/inode revalidation + descriptor-relative unlinkat for Unix owned-stage cleanup;
    • self-review found the initial RED filename was not in the owner workflow's explicit test invocation; canonical test was moved to score_pdf_unix_stage_cleanup_contract.rs, the unowned path was removed, and 6965c05f228f6a0d11ee077392e2222fcc5dcc41 explicitly executes it;
    • docs/traceability/score-attachment-unix-stage-cleanup.md records decision/risk/reference evidence; cd9b10570fe628720392068952991380b66266e3 adds that document to Score Storage workflow ownership.

    Claim boundary: this closes pathname-only stage deletion and ancestor-path substitution in the cleanup authority. It does not claim an atomic final-entry binding; the narrow identity recheck -> unlinkat basename replacement interval remains an explicit residual.

    Exact-current-head score-storage-native run 35513882911 is now terminal SUCCESS on Windows Server 2025 job 106086422623 and macOS 15 job 106086422754, including the owner unit suite and the explicit Unix-cleanup integration contract. Repository/security/SBOM/Semgrep/CodeQL settlement and independent approval remain incomplete, so keep #1239/#1241 open/Draft; no merge/release claim yet.

  20. seonghobae commented on Sep 20, 2026

    @seonghobae
    CollaboratorAuthor

    #1241 current 41c43d4a8049f3e679c6f5c7ab575d44ae0eb364 adds a new Score Storage recovery invariant after fresh review.

    Finding: recover_abandoned_score_stages admitted a reserved stage, performed later recovery checks, then called the generic remover, which captured deletion authority only at deletion time. A different stage substituted during that longer interval could therefore be recaptured as cleanup authority even though the workspace lease only coordinates current-contract writers.

    Source RED aa3b05ff110503eec4f98349f917593c6f28705a requires recovery to bind stage admission to later cleanup and forbids the previous generic remove_score_pdf_attachment(&stage) path. Repair a0e4f6285c2355c09800b09343f2b7bcab96d460 uses a native no-follow open, a bounded SHA-256 receipt for the admitted temporary-stage bytes, and fresh content revalidation immediately before lower identity-safe deletion. The unit regression replaces an admitted stage with different bytes and requires both objects to survive fail-closed cleanup. c2634de7... fixes the source-contract assertion; 41c43d4a... currentizes recovery TRACEABILITY.

    Claim boundary: this closes the long admission→cleanup recapture window for different-content replacement. It does not turn SHA-256 into provenance/authenticity, does not solve old-build coexistence generally, and does not claim elimination of the already documented final Unix identity-check→unlinkat basename micro-race.

    Exact-current-head score-storage-native run 35515508618 has materialized for macOS 15 and Windows Server 2025 but both jobs are currently queued. Repository/security gates are also non-terminal, so no exact-head GREEN, Ready transition, merge, tag or release is claimed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securitySecurity boundary, hardening, or vulnerability preventionbugSomething isn't workingpriority: highHigh-priority or P1 worktype: securitySecurity vulnerability or security-specific remediation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions