specs/053-archive.md
Rama main · réplica de lectura del repositorio original.
# Spec: Archive Actions
## Status
Partially implemented.
Conformance covers one small ZIP create/list/extract workflow, create dry-run
output suppression, relative paths resolved from the step working directory
(including extraction with no destination), and representative invalid
configurations. Executor tests
own format matrices, filtering, permissions, malformed archives, and path
safety edge cases.
This spec defines conformance behavior for the built-in `archive.create`,
`archive.extract`, and `archive.list` actions.
## Scope
This spec covers:
- `archive.create`'s required `with.source` and `with.destination`
(except under `with.dry_run`), and format selection: an explicit
`with.format` always wins; otherwise the format is inferred from
`with.destination`'s file extension
- `archive.extract`'s required `with.source`, and format detection by
content sniffing (with a filename hint) rather than by extension alone
- `archive.list`'s required `with.source`, reporting archive contents
without extracting anything
- `with.strip_components` (extract) and `with.preserve_paths` (extract,
default `true`)
- `with.include` and `with.exclude` (extract and list): glob filters over
archive entry paths
- `with.overwrite` (extract, default `false`): whether extracting into an
existing destination is allowed
- `with.password`: meaningful only for 7z and rar archives, and only for
`archive.extract`/`archive.list`
- `with.dry_run` (create and extract): reports counts without writing archive
or extracted payload files, but still reads sources and requires a determinable
format; extraction directory behavior is outside conformance scope
- that path-escaping entries inside an archive (zip-slip) are rejected
during extraction
- validation and runtime errors
This spec does not define:
- the specific set of archive formats and compression algorithms
supported (this is an implementation detail of the underlying archive
library), beyond the formats exercised by this spec's examples
- `with.compression_level`, `with.verbose`, `with.follow_symlinks`,
`with.verify_integrity`, or `with.continue_on_error`, beyond noting
that they exist
- behavior for 7z or rar archives specifically (this spec does not
construct or exercise password-protected 7z/rar fixtures)
## Goal
Workflow authors create, extract, and inspect common archive formats
(zip, tar, tar.gz, and others) as DAG steps, without shelling out to
`tar`, `zip`, or `unzip`.
## Behavior
A relative `with.source` or `with.destination` names a path under the step
working directory. `archive.extract` without `with.destination` extracts into
the step working directory.
### Format selection
For `archive.create`, an explicit `with.format` always wins, even if it
conflicts with `with.destination`'s extension (for example,
`destination: out.dat` with `format: zip` produces a genuine zip file).
Without `with.format`, the format is inferred from `with.destination`'s
extension (`.zip`, `.tar.gz`, `.tgz`, `.tar.bz2`, `.tar.xz`,
`.tar.zst`, `.gz`, `.bz2`, `.xz`, `.zst`, `.lz4`, and others).
For `archive.extract` and `archive.list`, format detection uses source
content and the filename. Invalid or unsupported archives fail. Exact
third-party parser diagnostics are outside this contract.
### Create
`archive.create` requires `with.source`. `with.destination` is required
unless `with.dry_run: true` -- but even under `with.dry_run`, a format
must still be determinable from either an explicit `with.format` or a
non-empty `with.destination`'s extension; if neither is available,
`archive.create` fails even in dry-run mode.
### Extract
`archive.extract` requires `with.source`. Without `with.overwrite`,
extracting into a destination that already contains the extracted path
fails; with `with.overwrite: true`, existing files are replaced.
`with.strip_components` (an integer, minimum `0`) strips that many
leading path components from each extracted entry's name.
`with.preserve_paths: false` flattens every extracted entry to just its
basename inside the destination root, discarding any directory
structure the archive recorded.
`with.include` and `with.exclude` (arrays of doublestar glob patterns)
filter which archive entries are written; entries that don't match
`with.include` (when set) or that match `with.exclude` are skipped and
counted in the result's `filesSkipped`, not extracted.
### List
`archive.list` reports an archive's contents (path, size, mode,
modification time, and whether each entry is a directory) without
extracting anything. `with.include` and `with.exclude` filter which
entries are reported, the same way they filter extraction.
### Password
`with.password` only affects 7z and rar archives; it is silently
ignored for zip and tar-based formats. It is only a valid configuration
for `archive.extract` and `archive.list` -- setting it for
`archive.create` is a runtime error.
### Dry run
`with.dry_run: true` suppresses archive creation and extracted payload
writes. Sources are still read to determine formats and counts. The result
reports would-be counts (`filesAdded`/`bytesArchived` for create,
`filesExtracted`/`bytesExtracted` for extract).
Conformance checks that a configured create dry run leaves its archive
destination absent. Extraction directory behavior and dry-run count
permutations are outside this partial conformance scope.
### Path safety
Extracting an archive containing an entry whose name would resolve
outside `with.destination` (a "zip-slip" entry, such as one named
`../escape.txt`) is rejected without writing that entry outside the
destination. This contract does not require transactional extraction or
rollback of entries already extracted.
## Errors
### Validation
Invalid configurations fail with a nonzero exit status:
- Any `archive.*` action without `with.source` fails `dagu validate`.
- Negative `with.strip_components` fails `dagu validate`.
- `archive.create` requires `with.destination` unless `with.dry_run: true`.
- `with.password` is valid only for extract/list operations.
This spec does not require invalid configurations to pass validation before
failing at runtime.
### Runtime
- `archive.extract`/`archive.list` on a source that does not exist: an
error containing `"source not found"`.
- `archive.extract`/`archive.list` on invalid archive content fails with a
nonzero exit status.
- `archive.create` with neither `with.format` nor a destination
extension to infer from (including under `with.dry_run`): an error
containing `"could not infer format"`.
- `archive.extract` into an existing destination without
`with.overwrite`: an error containing `"exists (overwrite disabled)"`.
- `archive.extract` of an archive containing a path-escaping entry: an
error containing `"escapes destination"`; no file is written outside
`with.destination`.
## Related Specs
- Run context: [Spec 017: Built-In Run Context](017-built-in-run-context.md)
## Examples
Create a zip archive from a directory, then extract it elsewhere with
its leading path component stripped:
```yaml
steps:
- id: pack
action: archive.create
with:
source: ./build/output
destination: ./dist/release.zip
- id: unpack
depends: pack
action: archive.extract
with:
source: ./dist/release.zip
destination: ./staging
strip_components: 1
```
List an archive's contents, restricted to a subset of files, without
extracting anything:
```yaml
steps:
- action: archive.list
with:
source: ./dist/release.zip
include:
- "**/*.json"
```