Cavil-CLI

 view release on metacpan or  search on metacpan

docs/Architecture.md  view on Meta::CPAN

`CAVIL_MAX_UPLOAD_MB` for an instance configured to accept more) and refuses with guidance if it is over, so an
accidental large file fails fast rather than after a slow doomed upload. The server enforces the same limit, so
a client whose limit is set too high still gets a clear rejection rather than a raw error.

### Uploading and waiting

The archive is posted to Cavil, which starts the same unpack, index and analyze pipeline any other package
goes through. Submitting the same archive under the same name again is idempotent on the server, so a re-run of
an unchanged tree does not pile up duplicate reviews.

The review is not instant, so the client polls the report endpoint, which answers "not ready" until the
analysis finishes and then returns the report. Its "not ready" reply names the pipeline stage (queued,
unpacking, indexing, analyzing, finalizing), which the client shows on the progress line so the wait names what
is happening rather than sitting on a bare "Reviewing". The line is on standard error so it never pollutes the
report or a pipe, and only on a real terminal so CI logs stay clean. A timeout bounds the wait.

### The verdict

The report carries the maximum license risk Cavil found, on its one-to-nine scale, and the instance's own
acceptable-risk threshold. The client turns those into a headline, a short tally, the licenses found ordered by
risk, and a link to the full web report, in colour on a terminal and plain text otherwise. For CI there is a

docs/Architecture.md  view on Meta::CPAN

Cavil's own web upload form: it needs a read-write API key whose user has admin (`infra`) access. That is a
high bar on purpose for a first version - it keeps random submissions out of the production queue while the
workflow is proven. The planned sandbox namespace is what will open ad-hoc checks to ordinary users without
that cost; until it exists, `check` is for admins and for CI configured with an appropriately privileged key.
The client preflights with `whoami` so a key that cannot submit is reported clearly before a large tree is
packaged, rather than as an opaque rejection afterwards.

## The service contract

The CLI depends on a small, stable contract with the Cavil server, deliberately narrow so it can be reasoned
about and mocked. An identity endpoint backs `whoami`. An upload endpoint takes the archive and its checksum
and starts a review, returning the new package's id. A report endpoint returns the report for that id, or "not
ready" while it builds, with the risk and acceptable-risk fields the gate needs. A documents endpoint serves
the generated SPDX SBOM and NOTICE files, likewise "not ready" until generated. That is all; the CLI needs no
knowledge of Cavil's internals beyond these questions and their answers.

The upload also carries an `ephemeral` flag, asking for a one-off report with no lasting side effects, no open
review left in the legal backlog. That is what a developer or CI check always wants, so it is always sent. It
is the forward edge of the planned sandbox mode: today's servers do not act on it (which is why submission is
still gated to high access), but a server that gains the ad-hoc mode can honour it without any client change,
and the access gate can then relax for ephemeral requests.

## Testing

The CLI is tested against a mock of the Cavil server, not a real one. Each scenario stands up a small
in-process web service that answers those endpoints with canned data and records what the CLI asked it. The
requests go through the real HTTP machinery, so the tests exercise the true wire behaviour, but there is no
database, no network, and no port to bind. A field problem becomes a test with almost no translation: the
responses that triggered it become the mock's answers, and the requests that led to it become the assertions.

The parts that never talk to the server - the archive builder, the report renderer, the risk gate and the exit
codes - are plain functions of their inputs and tested directly, with small throwaway directories and git
repositories as fixtures.

## Design choices and their reasons

lib/Cavil/CLI/Client.pm  view on Meta::CPAN

  };
  my $res = $self->_request('POST', '/api/v1/packages/upload', {form => $form, ok_codes => [400, 403, 413]});
  die "This Cavil key may not submit packages (needs a read-write key with admin access)\n" if $res->code == 403;
  die "The archive is too large for this Cavil instance; trim it with a .cavilignore file or --exclude-path\n"
    if $res->code == 413;
  die "Upload rejected: @{[$res->json->{error} // 'bad request']}\n" if $res->code == 400;
  return $res->json;
}

# Fetch a report. Returns {ready => 1, data => ...} when it exists, or {ready => 0, stage => ...} while it is
# still being built (the endpoint answers 408 with the pipeline stage until the package is analyzed).
sub report ($self, $id, $format = 'json') {
  my $res = $self->_request('GET', "/api/v1/report/$id.$format", {ok_codes => [408]});
  return {ready => 1, data  => ($format eq 'json' ? $res->json : $res->text)} if $res->code == 200;
  return {ready => 0, stage => eval { $res->json->{stage} }};
}

# Fetch a generated document (spdx or notice), or undef while it is still being generated (408). Bytes are
# returned ready to write; the user agent transparently decompresses the server's gzip.
sub document ($self, $id, $key) {
  my $res = $self->_request('GET', "/api/v1/documents/$id/$key", {ok_codes => [408]});



( run in 2.080 seconds using v1.01-cache-2.11-cpan-5c0b1e786e0 )