# ForgePack SDK Overview

Status: Stage 9 draft

ForgePack exposes a small C++ API for in-memory archive workflows.

Public headers:

- `forgepack/ForgePack.h`
- `forgepack/Archive.h`
- `forgepack/Error.h`
- `forgepack/Format.h`

## High-Level Reader and Writer

`ArchiveWriter` and `ArchiveReader` provide a convenient SDK facade for common
in-memory workflows. They do not hide archive errors and they do not perform
filesystem I/O.

```cpp
forgepack::ArchiveBuildOptions options;
options.includePayloadIntegrity = true;

forgepack::ArchiveWriter writer(options);
writer.AddDirectory("docs");

const std::array<uint8_t, 2> payload{'o', 'k'};
writer.AddFile("docs/readme.txt", payload);

const forgepack::ArchiveBytesResult archive = writer.Finish();

forgepack::ArchiveReader reader(archive.bytes);
const forgepack::ListArchiveResult list = reader.List();
const forgepack::FindArchiveEntryResult found = reader.FindEntry("docs/readme.txt");
const forgepack::ExtractArchiveResult one = reader.ExtractEntry("docs/readme.txt");
const forgepack::ValidateArchiveResult verified = reader.Verify();
```

This facade is a cold-path convenience layer and owns copied input bytes. It is
intended for SDK integration code and tests, not for future zero-copy hot paths.
`AddFile()` and `AddDirectory()` validate archive paths before storing entries,
so invalid traversal or platform-specific absolute paths are rejected early.
Duplicate normalized archive paths return `ErrorCode::FileExists`.
Use `ReserveEntries()` when the entry count is known ahead of time.

## Identity and Capabilities

```cpp
#include "forgepack/ForgePack.h"

const forgepack::Version libraryVersion = forgepack::GetLibraryVersion();
const forgepack::Version formatVersion = forgepack::GetFormatVersion();
const auto codecs = forgepack::GetCodecCapabilities();
```

## Create an Archive

```cpp
#include "forgepack/Archive.h"

std::vector<forgepack::RawArchiveFile> files;

forgepack::RawArchiveFile file;
file.path = "docs/readme.txt";
file.bytes = std::vector<uint8_t>{'o', 'k'};
files.push_back(std::move(file));

forgepack::ArchiveBuildOptions options;
options.compressionMethod = forgepack::CompressionMethod::Zlib;
options.includePayloadIntegrity = true;

const forgepack::ArchiveBytesResult archive =
    forgepack::BuildArchive(files, options);
```

## List Entries

```cpp
const forgepack::ListArchiveResult list =
    forgepack::ListArchive(archive.bytes);
```

## Extract One Entry

```cpp
const forgepack::ExtractArchiveResult extracted =
    forgepack::ExtractArchiveEntry(archive.bytes, "docs/readme.txt");
```

## Verify Archive

```cpp
const forgepack::ValidateArchiveResult verified =
    forgepack::VerifyArchive(archive.bytes);
```

## Inspect Archive Info

```cpp
const forgepack::ArchiveInfoResult info =
    forgepack::GetArchiveInfo(archive.bytes);
```

## Compare Archives

```cpp
const forgepack::ArchiveCompareResult diff =
    forgepack::CompareArchives(leftArchive.bytes, rightArchive.bytes);
```

`CompareArchives()` and `CompareArchiveLists()` compare entry metadata without
extracting payload bytes. The result reports equality, entry counts, difference
class counters, and path lists for left-only, right-only, and changed entries.
`differenceCount` is the sum of left-only, right-only, and changed matched
paths.
`changedCount` is the stable counter for matched paths whose metadata differs.
`unchangedCount` and `unchangedPaths` expose matched paths whose metadata is
identical.
`severity` classifies the outcome as `None`, `StorageOnly`, or `Logical`.
Use `ToString(ArchiveCompareSeverity)` for stable report labels: `none`,
`storage-only`, and `logical`.
`ParseArchiveCompareSeverityName()` parses those labels back into SDK enum
values for tools that consume ForgePack JSON reports.
Comparison includes first compression method and mixed-method status, so raw and
compressed archives are reported as different even when path and payload sizes
match.
`logicalChangedCount`/`logicalChangedPaths` and
`storageChangedCount`/`storageChangedPaths` split matched changed entries by
difference category. A path can appear in both categories when a logical change
also changes stored size or chunk metadata.
`distinctPathCount`, `matchedEntryPermille`, `differencePermille`, and
`unchangedPermille` provide integer ratio metrics for release dashboards without
requiring floating-point formatting in callers.
Per-class ratio fields such as `leftOnlyPermille`, `rightOnlyPermille`,
`changedPermille`, `logicalChangedPermille`, and `storageChangedPermille` use
the same distinct-path denominator.
`CalculateRatioPermille()` is available for SDK callers that need the same
rounded integer ratio convention in external reports.
`ArchiveCompareDriftClass` derives a coarse `none`/`low`/`moderate`/`high`
scale from the difference ratio for dashboards and release summaries.
`ArchiveCompareDominantClass` reports the dominant compare difference category,
or `mixed` when several categories tie.
`ClassifyCompareDrift()` exposes the same threshold mapping for SDK-side
reporting and policy tests.
`ParseArchiveCompareDriftClassName()` parses the stable drift class labels used
by CLI and JSON contracts.
`ParseArchiveCompareDominantClassName()` parses dominant class labels from the
same JSON/reporting contract.
`IsCompareDriftAtLeast()` is the intended SDK helper for release policy gates
that should fail only above a chosen drift threshold.
`IsCompareDriftAtMost()` provides the inverse policy check for report stages
that should pass while drift stays within a configured tolerance.
`IsCompareSeverityAtMost()` is the SDK-side equivalent of the CLI severity
threshold gate and should be used when policies are based on semantic severity.
`IsCompareDifferencePermilleAtMost()` is the numeric policy helper for gates
that allow a bounded share of distinct archive paths to differ.
`IsCompareDifferenceCountAtMost()` is the count-based policy helper for gates
that allow up to a fixed number of differences.
`IsCompareMatchedPermilleAtLeast()` is the coverage policy helper for gates
that require a minimum share of matched archive paths.
Set `ArchiveCompareOptions::logicalOnly` to ignore storage representation
differences such as stored size, chunk count, and compression method.
For more precise policies, `ArchiveCompareOptions` can ignore stored size,
chunk count, compression method, matched-entry type, or uncompressed size
independently.
Set `ArchiveCompareOptions::collectPathLists` to `false` for count-only
workflows over large archives. Counters, severity, drift class, and ratio
metrics remain populated, while path vectors stay empty.
Set `ArchiveCompareOptions::pathListLimit` to cap emitted path entries globally
while keeping counters complete; `pathListsTruncated` reports when the cap was
hit.
Set `ArchiveCompareOptions::pathExact` to compare one archive path exactly.
Set `ArchiveCompareOptions::pathContains` to compare only paths containing a
selected substring.
Set `ArchiveCompareOptions::pathPrefix` to compare only entries under a selected
archive path prefix. Entry counts and ratios then describe the filtered view.
Set `ArchiveCompareOptions::pathSuffix` to compare only paths ending with a
selected suffix; prefix and suffix filters can be combined.
`MatchesArchiveComparePathFilter()` exposes the same prefix/suffix matching
rules for callers that pre-filter or explain compare scopes outside the library.
`ArchiveCompareEntryFilter` can limit comparison to all entries, regular files,
or directories. `MatchesArchiveCompareEntryFilter()` exposes the same filter
logic for SDK-side diagnostics.
`ParseArchiveCompareEntryFilterName()` parses the stable CLI/JSON names back
into SDK enum values.
Use `IsSuccessfulCompare()` and `HasArchiveDifferences()` for concise result
handling without duplicating `errorCode` checks in callers.
`HasStorageOnlyDifferences()` and `HasLogicalDifferences()` wrap the severity
classification for policy code.
`HasUnmatchedPathDifferences()` detects left-only/right-only path set changes.
`HasMatchedEntryDifferences()` detects metadata changes on paths present in
both archives.
When using the facade API, `ArchiveReader::CompareTo()` performs the same
comparison against another opened reader. The overload accepting
`ArchiveCompareOptions` is available for logical-only comparisons without
leaving the reader facade.

## Error Handling

All high-level operations return structured result objects with `errorCode`.

```cpp
if (!forgepack::IsSuccess(archive.errorCode))
{
    const std::string_view message = forgepack::ToString(archive.errorCode);
}
```

The SDK does not throw exceptions from these high-level APIs.

## Archive Information Metrics

`GetArchiveInfo()` exposes release and benchmark metrics through
`ArchiveInfoResult`:

- entry, directory, section, and chunk counts
- hot/cold/integrity/recovery/append metadata sizes
- archive size, stored payload size, uncompressed payload size, and container overhead
- raw, zlib, and unknown codec chunk counts

These values are cold-path diagnostics. They are useful for CLI output,
compatibility reports, and benchmark summaries, but should not be used as a hot
path extraction dependency.
## Auto Compression Policy

Set `ArchiveBuildOptions::chooseSmallestCompression` to let the writer choose the
smallest available encoded payload per regular file. The selected codec is stored
as a normal chunk descriptor, so readers do not need a special `auto` codec.

This is a cold-path writer policy. It may perform more codec work during archive
creation because each candidate codec can be evaluated before selection.
