Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
## ADDED Requirements
### Requirement: 7z adapter exposes 7z entries as DriveStorage
The package SHALL export `SevenZipArchiveStorage` implementing the structural `DriveStorage` view. Entry paths inside the archive MUST appear as drive paths with directory separators normalised to `/`.
#### Scenario: Drive lists 7z entries
- **WHEN** a 7z archive containing two entries is wrapped and `drive.list()` is called
- **THEN** both entry paths are returned
### Requirement: LZMA, LZMA2, and BCJ filter support
The adapter SHALL support LZMA and LZMA2 compression codecs and the BCJ, BCJ2, Delta, and ARM/X86/SPARC preprocessor filters as composed in 7z `Folder` records. Unsupported codec IDs MUST raise `SEVENZ_UNSUPPORTED_CODEC`. Unsupported filter chains MUST raise `SEVENZ_BCJ_FILTER_UNSUPPORTED`.
#### Scenario: LZMA2 + BCJ executable decodes
- **WHEN** a 7z archive whose folder chain is LZMA2 → BCJ over an executable entry is read
- **THEN** the decoded bytes match the original executable
#### Scenario: Unknown codec errors clearly
- **WHEN** an archive references a codec ID not implemented by the adapter
- **THEN** the read throws `SEVENZ_UNSUPPORTED_CODEC` with the codec ID in details
### Requirement: Multi-volume support
The adapter SHALL support multi-volume 7z archives using `.7z.001`, `.7z.002`, …, `.7z.NNN` naming. Sibling discovery MUST use the `resolveSibling` callback from `drive-storage-archive-core`. The adapter MUST concatenate volumes in ascending index order before parsing the trailing header.
#### Scenario: Three-volume 7z assembles
- **WHEN** the primary `archive.7z.001` is opened with a `resolveSibling` returning `archive.7z.002` and `archive.7z.003`
- **THEN** all entries are listed and read
#### Scenario: Out-of-order volume rejected
- **WHEN** `resolveSibling` returns volume 1 at index 2 (i.e. mis-ordered)
- **THEN** the adapter throws `ARCHIVE_SPLIT_MISSING_VOLUME` with both expected and received indices
### Requirement: Header encryption and per-entry encryption
The adapter SHALL support reading 7z archives with encrypted headers (`7zAES` over the `EncodedHeader` block) and per-entry-encrypted content. The password hook from `drive-storage-archive-core` MUST be invoked lazily on first decryption need.
#### Scenario: Encrypted-header archive opens
- **WHEN** a 7z archive with encrypted headers is opened with a correct-password hook
- **THEN** entry listing and reading succeed
### Requirement: Read-write with copy-on-write or rewrite
The adapter SHALL support `read-only`, `copy-on-write`, and `rewrite` modes. Writes MUST produce a standards-compliant 7z file readable by reference 7-Zip implementations. Mutations re-encode the affected folder(s) and rewrite the trailing header.
#### Scenario: Round-trip write then read
- **WHEN** an adapter in `copy-on-write` mode writes `inside/n.txt`, flushes, then a new adapter is opened over the flushed bytes
- **THEN** `inside/n.txt` is listed and reads with the original content
### Requirement: Error code registry
The package SHALL register stable codes including `SEVENZ_HEADER_INVALID`, `SEVENZ_UNSUPPORTED_CODEC`, `SEVENZ_BCJ_FILTER_UNSUPPORTED`, `SEVENZ_CRC_MISMATCH`, `SEVENZ_CORRUPT`.
#### Scenario: Bad signature errors
- **WHEN** the host bytes do not begin with `7z\xBC\xAF\x27\x1C`
- **THEN** open throws `SEVENZ_HEADER_INVALID`

View File

@@ -0,0 +1,95 @@
## ADDED Requirements
### Requirement: Format detection from magic bytes
The package SHALL expose a `sniffFormat(bytes: Uint8Array): ArchiveFormat | null` function that identifies archive format by inspecting magic bytes in the first 512 bytes, treating any file-name extension as advisory only. Detection MUST cover ZIP, RAR4, RAR5, 7z, GZIP, and POSIX/USTAR tar. When magic bytes match a known format, a structural validation step MUST also succeed before the format is returned.
#### Scenario: Sniff matches when extension lies
- **WHEN** `sniffFormat` is called with bytes whose first four bytes are `PK\x03\x04` but whose source filename was `bundle.bin`
- **THEN** the function returns `"zip"` and the source filename is not consulted
#### Scenario: Mismatch between extension and content emits warning
- **WHEN** `openArchiveStorage` is called with a source named `archive.zip` whose magic bytes match `7z\xBC\xAF\x27\x1C`
- **THEN** an `ARCHIVE_FORMAT_MISMATCH` warning is logged with both detected and declared formats, and the detected format is used
#### Scenario: Unknown bytes return null
- **WHEN** `sniffFormat` is called with bytes matching no known magic-byte signature and no structural fallback succeeds
- **THEN** the function returns `null`
### Requirement: openArchiveStorage factory
The package SHALL expose `openArchiveStorage(source, options)` that returns a `DriveStorage` view backed by the appropriate per-format adapter. Options MUST include `format`, `mode`, `password`, and `resolveSibling`. The factory MUST dynamic-import the per-format adapter so unused format codecs are not bundled into consumers that do not need them.
#### Scenario: Auto-detect and construct adapter
- **WHEN** `openArchiveStorage(bytes, { format: "auto", mode: "read-only" })` is called with valid zip bytes
- **THEN** the function returns a `DriveStorage` instance from `@suaveplan/drive-storage-zip` constructed in read-only mode
#### Scenario: Explicit format overrides sniff
- **WHEN** `openArchiveStorage(bytes, { format: "tar" })` is called
- **THEN** the factory loads the tar adapter without invoking `sniffFormat`
### Requirement: Split-volume sibling resolution
The package SHALL define a `resolveSibling(basename: string, index: number): Promise<Uint8Array | ReadableStream<Uint8Array>>` callback contract that per-format adapters use to fetch additional volumes. The host application owns sibling discovery; the adapter never assumes a filesystem layout.
#### Scenario: Multi-volume archive triggers sibling fetch
- **WHEN** a zip adapter opens a primary volume whose EOCD references volumes 0..3
- **THEN** the adapter invokes `resolveSibling(basename, 0)`, `resolveSibling(basename, 1)`, `resolveSibling(basename, 2)` and uses the returned bytes in volume order
#### Scenario: Missing sibling raises typed error
- **WHEN** `resolveSibling` rejects or returns `null` for a required volume
- **THEN** the adapter throws `ARCHIVE_SPLIT_MISSING_VOLUME` whose details include the missing volume index and expected basename
### Requirement: Mutation mode contract
The package SHALL define three modes — `read-only`, `copy-on-write`, `rewrite` — and a `flush(): Promise<void>` method on the returned `DriveStorage`. In `read-only` mode any write/delete operation MUST throw `ARCHIVE_MODE_READ_ONLY`. In `copy-on-write` mode mutations MUST land in an in-memory overlay and be materialised to host storage only on `flush()`. In `rewrite` mode each mutation MUST re-encode the archive into host storage before returning.
#### Scenario: Write in read-only mode throws
- **WHEN** any mutation is attempted on an adapter constructed with `mode: "read-only"`
- **THEN** the call throws `ARCHIVE_MODE_READ_ONLY` and the host archive bytes are unchanged
#### Scenario: Copy-on-write defers materialisation
- **WHEN** an adapter in `copy-on-write` mode receives a write, the host archive bytes have not changed
- **AND WHEN** `flush()` is awaited
- **THEN** the host archive bytes contain the new entry and `flush()` is idempotent for a no-op state
### Requirement: Password hook contract
The package SHALL accept `password: () => Promise<string>` and invoke it lazily — only when an encrypted entry is encountered. Passwords MUST NOT be stored on the adapter instance beyond the lifetime of the active decrypt operation. An incorrect password MUST raise `ARCHIVE_PASSWORD_INVALID`; a missing hook on encrypted content MUST raise `ARCHIVE_PASSWORD_REQUIRED`.
#### Scenario: Password requested only on encrypted read
- **WHEN** an archive contains both unencrypted and encrypted entries and the consumer reads only unencrypted entries
- **THEN** the `password` hook is never invoked
#### Scenario: Wrong password reported
- **WHEN** the `password` hook returns a value that fails authenticated decryption on an encrypted entry
- **THEN** the read throws `ARCHIVE_PASSWORD_INVALID` and the password value is not retained
### Requirement: Conformance harness
The package SHALL expose a `runArchiveConformance(adapterFactory, opts)` test harness via the `./testing` subpath export that exercises a fixed set of behaviours every per-format adapter MUST satisfy: open, list, read, stat, password-failure path, mode enforcement, split-volume reassembly (where applicable), and round-trip of one binary entry through write/flush/read (where the format permits writes).
#### Scenario: Per-format package consumes harness
- **WHEN** a per-format package's test suite calls `runArchiveConformance(() => new ZipArchiveStorage(...))`
- **THEN** the harness executes the full behaviour set and any failure surfaces as a typed assertion identifying the failed behaviour
### Requirement: Error code registry
The package SHALL register the following stable error codes via `defineCodes` from `@suaveplan/error`: `ARCHIVE_FORMAT_UNKNOWN`, `ARCHIVE_FORMAT_MISMATCH`, `ARCHIVE_SPLIT_MISSING_VOLUME`, `ARCHIVE_PASSWORD_REQUIRED`, `ARCHIVE_PASSWORD_INVALID`, `ARCHIVE_MODE_READ_ONLY`, `ARCHIVE_FLUSH_REQUIRED`, `ARCHIVE_CORRUPT`, `ARCHIVE_UNSUPPORTED_FEATURE`, `ARCHIVE_CODEC_MISSING`. Each MUST have a stable string identifier never reused after removal.
#### Scenario: Code identifiers stable across releases
- **WHEN** the package is published at any version `0.x.y`
- **THEN** every code in the registry resolves to the same string identifier as in version `0.1.0`

View File

@@ -0,0 +1,60 @@
## ADDED Requirements
### Requirement: GZ adapter exposes the wrapped stream as one drive entry
The package SHALL export `GzipArchiveStorage` implementing the structural `DriveStorage` view. Because gzip wraps a single logical stream, the adapter MUST present exactly one entry whose path defaults to the original filename from the gzip `FNAME` field, or to a configurable fallback path when `FNAME` is absent.
#### Scenario: FNAME header used as entry path
- **WHEN** a gzip stream whose `FNAME` field equals `report.csv` is wrapped in `GzipArchiveStorage`
- **THEN** `drive.list()` returns exactly one entry at `report.csv`
#### Scenario: Fallback path used when FNAME absent
- **WHEN** a gzip stream without an `FNAME` field is opened with `fallbackPath: "data.bin"`
- **THEN** the single entry appears at `data.bin`
### Requirement: Multi-member gzip streams
The adapter SHALL transparently concatenate sequential gzip members (per RFC 1952 §2.2) when present, returning their decompressed bytes as a single logical entry stream.
#### Scenario: Two concatenated members read as one stream
- **WHEN** a gzip file containing two valid members each holding half of a logical file is read
- **THEN** the returned stream contains the concatenation of both members' decompressed bytes
### Requirement: Read-write with full re-encode
The adapter SHALL support `read-only`, `copy-on-write`, and `rewrite` modes. Because gzip is a single stream, any write MUST fully re-encode the host bytes. The adapter MUST document this cost.
#### Scenario: Write replaces entire host bytes
- **WHEN** an adapter in `rewrite` mode receives a write to the single entry
- **THEN** the host bytes are replaced by a freshly encoded gzip stream containing the new content
### Requirement: CRC32 and ISIZE validation
The adapter SHALL validate the trailing CRC32 and ISIZE fields on read. A mismatch MUST raise `GZ_CRC_MISMATCH` or `GZ_ISIZE_MISMATCH` respectively.
#### Scenario: Truncated stream errors
- **WHEN** the host bytes end before a valid CRC32+ISIZE trailer
- **THEN** the read throws `GZ_TRUNCATED`
### Requirement: Codec export for tgz composition
The package SHALL export a `gzipCodec: ArchiveCodec` object that `@suaveplan/drive-storage-tgz` composes over a tar adapter. The codec MUST expose `compress(stream)` and `decompress(stream)` returning `ReadableStream<Uint8Array>`.
#### Scenario: tgz consumes gzipCodec
- **WHEN** `@suaveplan/drive-storage-tgz` constructs an adapter with `codec: gzipCodec`
- **THEN** decompression of a `.tar.gz` host file produces the same entry listing as decompressing the gz then opening the tar
### Requirement: Error code registry
The package SHALL register stable codes including `GZ_HEADER_INVALID`, `GZ_CRC_MISMATCH`, `GZ_ISIZE_MISMATCH`, `GZ_TRUNCATED`, `GZ_UNSUPPORTED_FLAG`.
#### Scenario: Bad gzip magic errors
- **WHEN** the first two bytes are not `\x1F\x8B`
- **THEN** the open throws `GZ_HEADER_INVALID`

View File

@@ -0,0 +1,75 @@
## ADDED Requirements
### Requirement: RAR adapter exposes rar entries as DriveStorage (read-only)
The package SHALL export `RarArchiveStorage` implementing the structural `DriveStorage` view in read-only mode only. Construction with any mode other than `read-only` MUST throw `ARCHIVE_MODE_READ_ONLY` immediately.
#### Scenario: Construction rejects writable mode
- **WHEN** `new RarArchiveStorage(bytes, { mode: "rewrite" })` is called
- **THEN** the constructor throws `ARCHIVE_MODE_READ_ONLY` and does not allocate any decoder state
#### Scenario: Drive lists rar entries
- **WHEN** a RAR5 archive containing two entries is wrapped and `drive.list()` is called
- **THEN** both entry paths are returned
### Requirement: RAR4 and RAR5 format support
The adapter SHALL parse both RAR4 (signature `Rar!\x1A\x07\x00`) and RAR5 (signature `Rar!\x1A\x07\x01\x00`) container formats. Format detection MUST happen via magic bytes, and unsupported RAR3-or-older signatures MUST raise `RAR_VERSION_UNSUPPORTED`.
#### Scenario: RAR4 archive opens
- **WHEN** a RAR4 archive is opened
- **THEN** entries are listed correctly
#### Scenario: RAR5 archive opens
- **WHEN** a RAR5 archive is opened
- **THEN** entries are listed correctly
### Requirement: Multi-volume support
The adapter SHALL support RAR5 multi-volume archives using `.partN.rar` naming (e.g. `name.part1.rar`, `name.part2.rar`) and legacy RAR4 multi-volume using `.r00`, `.r01`, …, `.r99` extensions. Sibling discovery MUST use the `resolveSibling` callback from `drive-storage-archive-core`. Each subsequent volume's signature and volume number MUST validate before its bytes are consumed.
#### Scenario: RAR5 partN volumes assemble
- **WHEN** the primary `archive.part1.rar` is opened with a `resolveSibling` that returns `archive.part2.rar` and `archive.part3.rar`
- **THEN** entries spanning all three volumes are listed and read
#### Scenario: RAR4 r00..r02 volumes assemble
- **WHEN** the primary `archive.rar` is opened with a `resolveSibling` returning `archive.r00`, `archive.r01`
- **THEN** entries spanning all volumes are read
#### Scenario: Volume sequence gap errors
- **WHEN** `resolveSibling` returns volume 2 with a header indicating volume number 3
- **THEN** the adapter throws `ARCHIVE_SPLIT_MISSING_VOLUME`
### Requirement: Encrypted header and encrypted entry support
The adapter SHALL support reading entries protected by per-entry passwords and full encrypted-header archives. The password hook from `drive-storage-archive-core` MUST be invoked lazily on first encrypted-content access.
#### Scenario: Encrypted-header archive
- **WHEN** an archive with encrypted headers is opened and the password hook returns the correct passphrase
- **THEN** entry listing succeeds
### Requirement: Browser builds raise typed error
The package SHALL be server-tier. The `browser` field in `package.json` MUST resolve to a stub module whose entry points throw `RAR_UNAVAILABLE_IN_BROWSER` on any call.
#### Scenario: Browser bundle throws on use
- **WHEN** a browser bundle that imports `RarArchiveStorage` calls the constructor
- **THEN** it throws `RAR_UNAVAILABLE_IN_BROWSER`
### Requirement: Error code registry
The package SHALL register stable codes including `RAR_VERSION_UNSUPPORTED`, `RAR_FILTER_UNSUPPORTED`, `RAR_UNAVAILABLE_IN_BROWSER`, `RAR_CORRUPT`, `RAR_CRC_MISMATCH`.
#### Scenario: CRC mismatch surfaces
- **WHEN** an entry's stored CRC does not match the decoded bytes
- **THEN** the read throws `RAR_CRC_MISMATCH` with the entry path in details

View File

@@ -0,0 +1,56 @@
## ADDED Requirements
### Requirement: TAR adapter exposes tar entries as DriveStorage
The package SHALL export `TarArchiveStorage` implementing the structural `DriveStorage` view. Entry paths inside the tar MUST appear as drive paths with directory separators normalised to `/`.
#### Scenario: Drive lists tar entries
- **WHEN** a tar archive containing `etc/conf` and `etc/hosts` is wrapped in `TarArchiveStorage` and `drive.list("etc/")` is called
- **THEN** the result contains both `etc/conf` and `etc/hosts`
### Requirement: POSIX, USTAR, and PAX header support
The adapter SHALL parse legacy v7, USTAR, and POSIX.1-2001 PAX extended headers. Long filenames exceeding 100 bytes MUST be reconstructed from PAX `path` records or GNU `L` long-link entries. PAX size records MUST override the 12-octet octal size field.
#### Scenario: PAX long filename round-trips
- **WHEN** a tar entry whose path is 300 bytes long is read
- **THEN** the entry path is returned unchanged from `list()` and `stat()`
#### Scenario: PAX size record honoured
- **WHEN** an entry's PAX `size` record specifies a value larger than 8 GiB
- **THEN** the adapter reads the full byte count without truncation
### Requirement: Sparse file extension
The adapter SHALL parse GNU sparse 1.0 extension records and present sparse regions as zero bytes in the reconstructed entry stream. Sparse 0.0/0.1 variants MAY be supported but MUST raise `TAR_SPARSE_VARIANT_UNSUPPORTED` if unsupported rather than silently mis-reading.
#### Scenario: Sparse 1.0 entry reads as zero-filled
- **WHEN** a sparse 1.0 entry with one data region of 1 KiB and one hole of 1 GiB is read
- **THEN** the returned stream contains the data region followed by 1 GiB of zero bytes
### Requirement: Read-write with append and rewrite
The adapter SHALL support `read-only`, `copy-on-write`, and `rewrite` modes. Tar's sequential format means deletes require full rewrite; the adapter MUST document this cost and expose `pendingMutationCount`. Pure-append writes MAY append blocks before the two-block zero terminator and rewrite only the terminator.
#### Scenario: Append avoids full rewrite
- **WHEN** an adapter in `copy-on-write` mode writes one new entry and calls `flush()`
- **THEN** the host bytes are appended in place plus a new zero terminator, not fully re-encoded
#### Scenario: Delete triggers full rewrite
- **WHEN** an adapter in `copy-on-write` mode deletes one existing entry and calls `flush()`
- **THEN** `flush()` writes a complete new archive omitting the deleted entry
### Requirement: Error code registry
The package SHALL register stable codes including `TAR_HEADER_CHECKSUM_INVALID`, `TAR_PAX_HEADER_INVALID`, `TAR_SPARSE_VARIANT_UNSUPPORTED`, `TAR_BLOCK_ALIGNMENT_INVALID`.
#### Scenario: Bad header checksum errors
- **WHEN** a tar header's checksum field does not match the sum of its octets
- **THEN** the read throws `TAR_HEADER_CHECKSUM_INVALID` with the byte offset in details

View File

@@ -0,0 +1,42 @@
## ADDED Requirements
### Requirement: TGZ adapter composes tar over a codec
The package SHALL export `TgzArchiveStorage` implementing the structural `DriveStorage` view by composing a tar adapter over a pluggable codec. The default constructor MUST accept `codec: ArchiveCodec` and SHALL ship a convenience `openTarGz(source, opts)` that defaults to `gzipCodec` from `@suaveplan/drive-storage-gz`.
#### Scenario: Default tar.gz opens with gzip codec
- **WHEN** `openTarGz(bytes, { mode: "read-only" })` is called on valid `.tar.gz` bytes
- **THEN** entries from the inner tar are listed and read correctly
### Requirement: Codec slot for tar.bz2, tar.xz, tar.zst
The package SHALL accept any object satisfying the `ArchiveCodec` interface (`compress`, `decompress`). Codecs for bzip2, xz, and zstd are NOT shipped in v0.1.0 but the slot MUST be functional so consumers can plug them in. Construction with no codec MUST raise `ARCHIVE_CODEC_MISSING`.
#### Scenario: Custom xz codec works
- **WHEN** `new TgzArchiveStorage(bytes, { codec: customXzCodec })` is constructed and the bytes are a valid `.tar.xz`
- **THEN** entries are listed and read correctly using the custom codec's `decompress`
#### Scenario: Missing codec rejects clearly
- **WHEN** `new TgzArchiveStorage(bytes, {})` is constructed without a codec
- **THEN** construction throws `ARCHIVE_CODEC_MISSING`
### Requirement: Read-mostly with full re-encode on write
The adapter SHALL support `read-only`, `copy-on-write`, and `rewrite` modes. Writes MUST re-encode the full tar and then the full codec output. The cost MUST be documented and `pendingMutationCount` exposed.
#### Scenario: Round-trip write
- **WHEN** an adapter in `copy-on-write` mode adds one entry and calls `flush()`
- **THEN** the host bytes contain a valid codec-wrapped tar whose entry list contains the new entry
### Requirement: Error code registry
The package SHALL register stable codes including `TGZ_CODEC_FAILED`, `TGZ_INNER_TAR_INVALID`. Format-specific errors from the underlying tar and codec packages MUST be propagated unchanged.
#### Scenario: Inner tar corruption surfaces
- **WHEN** the codec decompresses successfully but the resulting tar bytes fail header checksum validation
- **THEN** the error chain contains both `TGZ_INNER_TAR_INVALID` and the underlying `TAR_HEADER_CHECKSUM_INVALID`

View File

@@ -0,0 +1,60 @@
## ADDED Requirements
### Requirement: ZIP adapter exposes archive entries as DriveStorage
The package SHALL export `ZipArchiveStorage` implementing the structural `DriveStorage` view so that `new Drive(new ZipArchiveStorage(...))` works without drive-side changes. Entry paths inside the archive MUST appear as drive paths preserving the archive's internal directory separators (normalised to `/`).
#### Scenario: Drive lists archive entries
- **WHEN** a zip archive containing `docs/a.txt` and `docs/b.txt` is wrapped in `ZipArchiveStorage` and handed to `new Drive(...)` and `drive.list("docs/")` is called
- **THEN** the result contains both `docs/a.txt` and `docs/b.txt`
### Requirement: ZIP64 support
The adapter SHALL transparently parse ZIP64 central directory extensions when the EOCD `total entries`, `central directory size`, or `central directory offset` fields equal their 32-bit sentinel value `0xFFFFFFFF`. ZIP64 archives MUST be read without consumer opt-in.
#### Scenario: ZIP64 archive opens
- **WHEN** an archive larger than 4 GiB with valid ZIP64 EOCD locator is opened
- **THEN** all entries are listed and read without error
### Requirement: Split-zip volume reassembly
The adapter SHALL support split-zip archives whose volumes follow the canonical naming `name.z01`, `name.z02`, …, `name.zNN`, `name.zip` (final volume holds EOCD). Volume discovery MUST use the `resolveSibling` callback from `drive-storage-archive-core`. Volume signatures MUST be validated; mismatched volumes raise `ARCHIVE_SPLIT_MISSING_VOLUME`.
#### Scenario: Three-volume zip opens via sibling resolver
- **WHEN** the primary `archive.zip` is opened with a `resolveSibling` callback that returns `archive.z01` and `archive.z02` for indices 0 and 1
- **THEN** the adapter lists entries spanning all three volumes and reads each correctly
#### Scenario: Missing middle volume errors clearly
- **WHEN** `resolveSibling` returns `null` for volume index 1
- **THEN** the adapter throws `ARCHIVE_SPLIT_MISSING_VOLUME` with `{ basename: "archive", missingIndex: 1 }` in the error details
### Requirement: Read-write with copy-on-write or rewrite mode
The adapter SHALL implement `read-only`, `copy-on-write`, and `rewrite` modes. In write modes the adapter MUST rebuild a valid EOCD on `flush()` (copy-on-write) or after each mutation (rewrite). The host archive bytes MUST remain readable by any standards-compliant zip tool after flush.
#### Scenario: Round-trip write then read
- **WHEN** an adapter in `copy-on-write` mode writes `inside/notes.txt`, calls `flush()`, then a new adapter is opened over the flushed bytes
- **THEN** `inside/notes.txt` is listed and read with the original byte content
### Requirement: AES-encrypted entries
The adapter SHALL support reading WinZip AE-2 AES-256 encrypted entries using the password hook from `drive-storage-archive-core`. Writing AES-encrypted entries is out of scope for v0.1.0 and SHALL raise `ARCHIVE_UNSUPPORTED_FEATURE` until v0.2.0.
#### Scenario: Encrypted entry decrypts with correct password
- **WHEN** an AES-256 encrypted entry is read with a `password` hook returning the correct passphrase
- **THEN** the decrypted bytes are returned and the password value is not retained on the adapter
### Requirement: Error code registry
The package SHALL register stable codes including `ZIP_EOCD_NOT_FOUND`, `ZIP_ZIP64_REQUIRED`, `ZIP_CENTRAL_DIR_CORRUPT`, `ZIP_CRC_MISMATCH`, `ZIP_UNSUPPORTED_COMPRESSION`, `ZIP_AES_WRITE_UNSUPPORTED`.
#### Scenario: CRC mismatch raises typed error
- **WHEN** an entry's decompressed CRC does not match the central directory CRC
- **THEN** the read throws `ZIP_CRC_MISMATCH` with the entry path in details