Skip to content

Releases: digital-foundry/actualis

Actualis 0.6.0

Choose a tag to compare

@github-actions github-actions released this 02 Nov 15:50
4ae3596

Added

  • A PII gate for this repository (tools/pii-gate.py, make pii, a CI step): it fails when tracked files
    carry home paths with real user names, emails, phone numbers, tailnet hosts, public IPs, UUIDs that do not
    look hand-typed, or credentials actualis recognises. It decodes and canonicalises before matching, and
    reports path:line: detector, never the value. See CONTRIBUTING.md.

Changed

  • Design specs and implementation plans no longer live in this repository.

Actualis 0.5.1

Choose a tag to compare

@github-actions github-actions released this 01 Nov 14:43
c62cabb

Changed

  • The GitHub Action's description is now under GitHub Marketplace's 125-character limit, so a release can be
    listed there. The README shows a uses: digital-foundry/actualis@... example.

Actualis 0.5.0

Choose a tag to compare

@github-actions github-actions released this 31 Oct 13:46
dee4e24

Added

  • actualis-reputation, an optional companion command and the only part of actualis that uses the network.
    update downloads Google Safe Browsing threat lists; confirm checks local matches with 4-byte hash
    prefixes mixed with decoys drawn from the downloaded lists; virustotal looks hostnames up on VirusTotal
    (opt in; sends hostnames). Every run says what it will send, asks first (--yes in CI), shows ⊕🌐 while
    a request is in flight, and records what it did (--receipt, network-log.jsonl). Keys come from
    ACTUALIS_GSB_KEY / ACTUALIS_VT_KEY or --key-command, never an argument; the Safe Browsing key travels
    in a header, never a URL. Private and local hosts are never looked up or sent. See docs/reputation.md.
  • actualis --reputation: matches the network inventory against the local reputation cache, offline.
    New findings reputation-listed (high), reputation-candidate, reputation-suspicious,
    reputation-stale (medium). --reputation --fail-on with no usable lists exits 2.
  • --json: network.items[].reputation and network.reputation (additive; schema_version stays 1).
  • --self-check reports when actualis-reputation last used the network.
  • --explain reputation.
  • Tray: a network badge while actualis-reputation runs.
  • Tray: when the Safe Browsing lists are more than a day old, the icon shows the amber update badge and the menu offers "Update Safe Browsing Lists (N days old)…". It opens a terminal running actualis-reputation update, where you see what is sent and confirm. The tray itself never downloads anything or holds a key. Credential warnings still take priority over the badge (#69).

Changed

  • The README says "actualis never uses the network. The optional actualis-reputation command does, only
    when you run it, and tells you what it sends."

Actualis 0.4.0

Choose a tag to compare

@github-actions github-actions released this 14 Oct 00:38
e6d1742

Upgrading from 0.3.0: what can change a gate

The same transcripts can give a different exit code than they did on 0.3.0.
Each of these is listed in full below.

  • New agents are read by default. --agent defaults to all, which now
    includes Gemini CLI and Qwen Code. On a machine with their transcripts, their
    commands, credentials and downloads join the report and the gate.
  • --fail-on can newly exit 3 on transcripts 0.3.0 passed:
    • new high remote-exec shapes: a shell fed code through a heredoc, a
      here-string, cat <<EOF | sh, bash < <(…) or eval "$(curl …)";
      curl … | /usr/bin/env bash and other shells named by path; a brace-expanded
      program word (cu{r,}l … | sh); and commands that comments, quoting,
      heredocs, line continuations or $'…' used to hide from the audit;
    • newly counted credentials (high, or critical for a private key): classic
      sk- OpenAI keys, npm_, shpat_, lin_api_ and ya29. tokens, PEM and
      armored PGP private keys, quoted JSON keys, aws configure set, gh secret set --body, quoted passwords holding ; or spaces, credential options given
      with a space (--token VALUE), cookies, .netrc lines and Authorization:
      values with no scheme;
    • newly flagged shell commands: rm -r -f, rm --recursive --force, rm -rfv,
      sudo after then, do, (, $(, an assignment or time, /usr/bin/sudo,
      cat of ~/.ssh/id_ed25519 or id_ecdsa, curl --json @file, and a curl
      upload to a remote host that mentions "localhost" only in a header or query;
    • a failing gate whose output was piped into something that exits early
      (| head, | true, 2>&1 | head -1) used to exit 0 or 120. It now keeps 3.
  • --ioc and --network-strict see more. Downloads hidden by shell syntax,
    options, wrappers, proxies, brace expansion, percent-encoded or numeric hosts,
    or padding before an install are now inventoried and matched (network-ioc,
    high). A download that cannot be read or resolved (nested too deep, too long,
    a host that cannot be normalised, a redirected request, a brace expansion over
    16 words) is now network-ioc-unresolved instead of passing clean. Under
    --network-strict, redirected downloads, a registry given as a variable,
    gh/brew tap/npm i owner/repo hosts and trusted path prefixes followed by
    a shell variable are no longer trusted, so network-unasked findings can rise.
  • Combinations that used to be ignored now exit 2. One mode per run;
    --fail-on, --json, --network, --ioc, --ci-log, --with-prompts,
    --top, --no-redact, --reason, --quiet, --interval and the scope flags
    are refused where they would be dropped (full list under Changed). A CI line
    that passed one of them can now exit 2.
  • Bounds now exit 2: --days below 1 or above 36500, --top below 1,
    --interval outside 0.5 to 3600 seconds (or nan/inf), an unknown
    --explain topic, a malformed --suppress id or unknown --why id (was 1), a
    malformed or wrong-schema --diff baseline (was a traceback or 1), and an
    --mcp --root that does not exist.
  • A failed --self-check exits 4, not 3.
  • AF013 (rate table age) no longer fails --fail-on. A gate that went red
    only on the table's age now passes. AF013 is still reported.
  • Figures move. Resumed or forked sessions no longer count permission modes,
    denials and refusals twice; Codex turns are no longer counted twice in AF003
    and AISVS 9.2.1; Codex approval modes respect --project and --days; Codex
    under --days counts only the window's tokens; dated and alias model ids are
    priced as themselves; Google and Qwen models with no published price are no
    longer billed at the Claude ceiling. AF003, AF006, AISVS 9.2.x and the
    spend-based findings (AF001, AF007, AF009, AF010) can appear or vanish.
  • Counts change (secret_exposures, secret_projects,
    unreadable_commands, duplicate_tool_calls_skipped), and report_sha256 is
    not comparable with 0.3.0. Take a fresh --diff baseline.
  • --fail-on any can newly exit 3 on the new medium network-config flag:
    a command that writes download configuration (git insteadOf or proxy,
    npm/pnpm/yarn registry or proxy, pip index, or a write to .npmrc,
    .yarnrc, pip.conf, .gitconfig).
  • Cookie suppressions need re-adding. A cookie credential's id now names
    the cookie (cookie:<program>:<NAME>), not the header, so a suppression
    written for a cookie finding on 0.3.0 no longer matches.
  • GitHub Action: fail-on is case-insensitive, fail-on: None no longer
    skips the gate silently, and an unrecognised value fails the job.

Added

  • Gemini CLI support. Reads ~/.gemini/tmp (or
    $GEMINI_CLI_HOME/.gemini/tmp, plus ~/.cache/.gemini/tmp from macOS sandbox
    runs): the legacy .json sessions, the JSONL sessions with $set and
    $rewindTo, and subagent sessions.

    • Shell commands are audited and inventoried like every other agent's.
      web_fetch URLs (taken from its prompt) and google_web_search reach the
      network inventory; write_file and replace reach the audit-config
      tripwire.
    • Gemini CLI records no approvals, so every Gemini network item's approval is
      unknown. A call a person declined, or a policy blocked, counts as a
      refusal.
    • Tool calls a later rewind removed are still audited, marked rewound, and
      counted in rewound_tool_calls.
  • Qwen Code support. Reads ~/.qwen/projects (or $QWEN_HOME,
    $QWEN_RUNTIME_DIR), with the real cwd and branch per record and the approval
    mode per turn: yolo and auto count as unasked; auto-edit (which still
    asks before shell commands), default and plan as unknown.

  • --agent gemini and --agent qwen. Both agents also work with --root,
    --watch, --replay, --self-check and the MCP server. --explain gemini
    and --explain qwen describe them.

  • Gemini API prices from Google's price list, including the long-prompt rate
    above 200,000 tokens for Pro models and the announced price changes for
    gemini-3.6-flash, gemini-3.8-flash and gemini-4-argon, applied by message
    date.

  • An unpriced pricing tier. Models with no published price
    (gemini-3-pro-preview, gemini-3.5-flash, every Qwen model) show their tokens
    and no cost, instead of a guessed one.

  • make security builds a pinned Kali image and runs bandit, trufflehog,
    gitleaks and a transcript fuzzer (Claude Code, Codex and Copilot CLI formats)
    against a read-only checkout, with no network and a throwaway home. It works
    on native Linux Docker hosts, reports a scanner that failed to run as FAILED,
    and reports unverified secret candidates outside the fixture allowlist.
    Documented in CONTRIBUTING.md and SECURITY.md.

  • make security-remote runs the same scan on the build server over SSH
    (SECURITY_HOST, default mac-mini). It sends a git bundle of HEAD
    (committed work only) and copies security-out/ back.

  • New --json fields. All are additive, so schema_version stays 1:

    • scope (days, project, agent) and coach[].project;
    • unsupervised_pct, moded_commands and unmoded_commands;
    • bash.flagged.high and bash.flagged.medium (suppressed flags left out);
    • unparsable_lines, unsupported_records, rewound_tool_calls and
      unpriced_model_tokens;
    • vendors.capabilities[].gemini and .qwen, and network.items[].rewound;
    • two new network.ioc.matches[].reason values, host-unresolved and
      redirected;
    • in --replay --json, window.undated_sightings.
  • MCP additions. fleet_summary adds moded_commands, unmoded_commands,
    unsupervised_basis and unpriced_model_tokens; exposed_secrets rows carry
    suppressed and suppressed_reason; coach_findings includes project.

  • GitHub Action gate-reasons output, counting what the gate actually failed
    on, including flagged shell commands and IOC hits.

  • --diff warns when the two reports were made with different filters or
    roots.

  • network-config shell-audit finding (medium), so a download redirect
    set in one tool call is visible even when a later call's download cannot be
    tied to it.

Changed

  • One mode per run. Combining two of --diff, --why, --replay,
    --watch, --mcp, --explain, --agents, --suppress, --suppressions,
    --self-check, --service, --completions, --card, --share, --coach,
    --aisvs and --bash is a usage error that names both. Before, one was
    silently dropped, including a --suppress write.
  • Flags a mode would drop are refused (exit 2), naming both flags:
    • --fail-on with a mode that never evaluates the gate (--diff, --why,
      --replay, --watch, --mcp, --explain, --agents, --suppress,
      --suppressions, --self-check, --service, --completions);
    • --json with --share, --coach, --aisvs, --bash, --why,
      --explain, --suppress, --suppressions, --self-check, --service,
      --completions, --watch or --mcp;
    • --network and --ioc with modes that would drop them; --ioc with
      --why; --ioc, --network-strict or --network-trust with --coach,
      --aisvs or --share unless --fail-on is given;
    • --ci-log with --root, --agent, --project, --replay or a mode that
      does not scan;
    • --with-prompts with --agents, --replay, --watch or --mcp;
    • --top with --json, --html or any mode other than the text report,
      --bash and --network;
    • --reason without --suppress; --quiet or --interval without --watch
      (or with --service newsyslog);
    • --root, --agent, --days, --project or --no-redact with
      --explain, --agents, --completions, --suppress or --suppressions;
      --days or --project with --watch or --service; --project or
      --no-redact with `...
Read more

Actualis 0.3.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 13:54
7bd475d

Upgrading from 0.2.2: what can change a gate

The same transcripts can give a different exit code than they did on 0.2.2.
Each of these is listed in full below.

  • --fail-on high can newly exit 3 on transcripts 0.2.2 passed:
    • new high remote-exec shapes (bash <(curl …), source <(…),
      sh -c "$(curl …)", dequoted cu''rl … | s''h, curl … |& sh, and a
      shell at any later stage of the pipeline);
    • newly counted credentials (passwords passed as options, short
      PASSWORD variables, short URL and scp passwords, long password-less
      userinfo, unprefixed Authorization headers), which are high;
    • the new audit-config finding, which is high and cannot be suppressed.
  • --fail-on any can newly exit 3 on the new medium oversized-command
    flag.
  • Coach findings move with cost. The new pricing changes spend figures,
    and spend-based findings (AF001, AF007, AF009, AF010) can appear or vanish.
  • Only when you opt in: --network-strict adds network-unasked findings
    (any); --ioc adds network-ioc (high) and network-ioc-unresolved
    (any).
  • Counts rise (secret_exposures, secret_projects,
    unreadable_commands), and report_sha256 is not comparable with 0.2.2. Take
    a fresh --diff baseline.

Changed

  • Faster. The shell-audit rules and unreadable shapes check a hand-written
    literal prefilter before each regex (results are identical; a test compares
    both paths over every test string and 5,000 generated commands), and
    --network without --fail-on skips the audit and secret ranking it never
    shows. On a 1,169-file corpus --network went from 43 s to about 6 s and a
    full run from 44 s to about 21 s.

  • New high remote-exec detections. Besides curl … | sh, the shell
    audit now flags bash <(curl …), source <(…) and . <(…),
    sh -c "$(curl …)" (any shell, any -c cluster such as -lc), a dequoted
    cu''rl … | s''h or c\url … | busybox sh, curl … |& sh, and a shell at
    any later stage of the pipeline (curl … | tee f | sh). Each command is
    flagged once. These can newly fail --fail-on high (exit 3) on transcripts
    0.2.2 passed.

  • Location-based ids for person-chosen passwords. A password passed as an
    option (curl -u user:PASS, --user=, -U, wget --password=, docker login -p), and every credential first counted in this release (see Fixed),
    gets an id derived from where it appears (sha256("opt:" + program + ":" + option + ":" + user)[:8] and the like), not a hash of the value, so a
    published id cannot confirm a guessed password. Credentials counted in 0.2.2
    keep their value ids. See docs/secrets.md.

  • A suppression that covers more than one value is ignored. When a
    suppressed location id has seen more than one distinct value in the run, the
    secret is treated as unsuppressed, for display and for --fail-on, with the
    reason "suppression covers one value; N seen".

  • -u user:pw masking fails closed. The password after -u/--user is
    masked for every program except a short list where -u names a user, not a
    password (docker and podman exec/run/create, docker compose exec,
    sudo, su, ssh, chown, systemctl, git and similar), and the program
    that receives -u decides, past wrappers such as sudo, env and timeout.
    A numeric uid:gid is never a credential.

  • report_sha256 no longer moves with the clock. The digest excludes
    pricing.age_days, pricing.stale and the AF013 coach finding, which change
    as days pass over unchanged transcripts. report_sha256 values are not
    comparable with 0.2.2 reports. This is a known break of digest comparability,
    not of schema; schema_version stays 1. --diff says so when the digests
    differ but nothing it compares has changed.

  • Current-generation pricing. Opus 5.5 ($4/$20), Sonnet 5.5 ($2/$10),
    Haiku 5.5 ($0.10/$0.50, or $0.50/$2.50 for a message whose prompt exceeds
    100,000 tokens), Fable 5.1 and Mythos 5.1 ($10/$50) are vendor rates,
    verified 2026-10-08. The cache-read multiplier is now per model: 0.10 by
    default, 0.05 for Opus 5.5 and Sonnet 5.5, 0.025 for Fable 5.1 and Mythos 5.1.
    It applies to Claude Code, the subagent cost floor, Copilot and the cache
    savings figure. An unknown model in a known family is now priced from the
    newest known sibling, not the most expensive one. Fast mode, batch and data
    residency are not modelled.

Added

  • --network --html writes the network report as one offline page:
    inline SVG charts, sortable tables, every item with its trace, and the
    --network --json object embedded with its sha256. It never overwrites
    (actualis-network-2.html, …), is readable by you only, and a content
    security policy blocks every fetch. --out now applies to --card or
    --html, and --with-prompts to --json or --html.
  • --network prints only the NETWORK section (with the IOC block when
    --ioc is given), and --network --json emits only the network object. The
    full report gets a one-line network summary in FLEET when downloads exist.
    The view reads top down, every line at most 100 columns:
    • a chart strip: an approval bar (unasked solid, unknown medium, asked
      light), downloads per day as a sparkline with the unasked on the same
      scale under it (empty days blank), and the mix of kinds;
    • TOP HOSTS, TOP PACKAGES and, with two or more projects, BY PROJECT: up to
      ten rows each, a bar whose solid part is the unasked share, and
      unasked / all;
    • UNASKED, BY SESSION (UNKNOWN when nothing is unasked): the newest five
      sessions, each with its first five such downloads in the order they ran,
      the agent's why, and the actualis --network --session <id> command
      that lists them all.
      The charts need no colour, and an ASCII-only terminal gets ASCII (#, .,
      : and _.:-=+*). --explain network names the --json path behind each
      chart.
  • --host, --package, --session and --unasked narrow --network
    (text and --json), combinable with each other, --project and --days.
    --host is a suffix on a label boundary, --package the exact name (npm
    case-sensitive, pypi by PEP 503), --session a prefix. The filter runs
    before --network-strict and --ioc, so totals, findings and matches are of
    the filtered set. Filtered, the text view prints the chart strip for the
    filtered set, then a block per item under a line naming its session, newest
    first and capped at --top x 5: host and target, when, agent, project and
    session, approval with the mode it was judged from, the redacted command,
    why, the prompt, and a trace line (call id, file and line) that
    sed -n '<line>p' <file> turns into the exact record. Without --network
    each is a usage error.
  • network.by_day[], network.by_day_undated, network.by_project[] and
    network.packages[].unasked
    , the series the charts draw. They reconcile
    with network.totals (dated plus undated equals all items; project sums
    equal the item and unasked totals), and the text view says so in red when
    they do not.
  • network.items[] gains mode, why, prompt and transcript. mode
    is the permission-mode key approval was judged from. why is the agent's
    own text before the call (last sentence, 160 characters); prompt is the
    person's last real message before the turn (160 characters), emitted only
    with the new --with-prompts and null otherwise. Both are redacted and
    cleaned when read. transcript is {root, file, line}. What Claude Code,
    Codex and Copilot each provide is in actualis --explain network.
    schema_version stays 1; --share and --card carry none of it.
  • secrets[].distinct_values in --json and in the MCP exposed_secrets
    tool: how many different values were seen under the id in this run. The
    text report says "N distinct values" when it is more than 1.
  • More unreadable shapes are counted in unreadable_commands: ANSI-C
    quoting that spells a letter ($'\x63url'), a substitution piped to a shell,
    and an inline script that reaches the network (python -c with urllib,
    requests or socket, node -e with fetch, perl -MLWP). They are
    counted, not flagged.
  • bash.oversized_commands counts commands over 32 KB, and each raises a
    medium oversized-command flag (see Fixed).
  • --ioc FILE: did an agent install the bad version? Matches every
    download the transcripts show in the network inventory against a known-bad
    list, offline and only
    when named: the actualis-ioc line format (npm:x@=1.2.3||=1.2.4,
    pypi:name, host:evil.example) or OSV JSON/JSONL. Names compare as each
    ecosystem does (npm and Go exactly, pypi by PEP 503, images through the shared
    Docker Hub normaliser); versions by SemVer 2.0.0 or PEP 440, written out in
    the standard library. A match is a high finding (network-ioc); a listed name
    whose version cannot be decided is medium (network-ioc-unresolved), never
    clean. Refused calls are listed and never fail a gate, and the trust list
    never exempts a match. network.ioc in --json (always an object),
    network.items[].ioc, an IOC block in NETWORK, --explain ioc and
    docs/ioc.md. Any problem loading a list, or a list with
    nothing checkable, exits 2; every size, line, entry and nesting limit is hard.
    The Action gains an ioc input and an ioc-matches output.
  • Network inventory. A NETWORK section and a network key in --json list
    every download and fetch the transcripts show and actualis can read —
    packages installed, repos cloned, URLs fetched, including those behind
    control flow, wrappers (sudo, timeout, xargs, eval), bash -c and
    substitutions — and whether a person approved each (asked, unasked, or
    unknown in default mode, where an allowlist rule may have allowed i...
Read more

Actualis 0.2.2

Choose a tag to compare

@github-actions github-actions released this 08 Oct 20:51
1947706

Fixed

Found by an audit of every calculation for explainability, traceability and
determinism.

  • Results no longer depend on the order the filesystem lists transcripts.
    When the same message appears in two transcripts it is counted once, and the
    copy kept decides its day, branch and ticket. Files were read in directory
    order, which differs between machines, so a Mac and a Linux runner could
    report different per-day and per-branch figures and a different
    report_sha256 for the same files. Files are now read in sorted path order.
  • AF012 no longer fires a false critical on Codex- or Copilot-only fleets.
    It counted their sessions toward its 500-message threshold, but sessions are
    never re-emitted, so --fail-on critical failed on 500 Codex sessions. It
    now counts Claude Code messages only.
  • refusals.by_week labels weeks with their ISO year. 2025-12-29 was
    labelled 2025-W01 instead of 2026-W01, colliding with January.
  • The MCP server's days window matches --days. It counted back from
    the current time instead of from midnight UTC, so the same days covered an
    extra partial day and reported a different cost than the CLI.
  • Models priced from an aggregator or vendor docs are no longer reported as
    unpriced.
    They were added to unknown_models and
    cost_usd_from_unpriced_models, and the report said they were billed at
    Opus-tier rates. aggregator_priced_models is now filled for Claude Code,
    Codex and Copilot.

Actualis 0.2.1

Choose a tag to compare

@github-actions github-actions released this 08 Oct 01:56
01d8240

Fixed

  • The GitHub Action could not load in 0.2.0. GitHub evaluates ${{ ... }}
    expressions anywhere in a run: block, shell comments included, and the
    comment explaining 0.2.0's own fix contained an empty one. Every caller got
    "An expression was expected". The CLI was not affected. A new test fails the
    build on any empty or unclosed expression in action.yml or a workflow, so
    this is now caught before a release instead of by the post-release
    self-test.

Actualis 0.2.0

Choose a tag to compare

@github-actions github-actions released this 08 Oct 01:42
d126d94

Actualis now reads Copilot CLI. Post your card.

Added

  • GitHub Copilot CLI sessions are read, from
    $COPILOT_HOME/session-state/*/events.jsonl (default ~/.copilot): shell
    commands into the same audit, cost per model per session, supervision per
    command, refusals, subagents and premium requests. --agent copilot reads it
    alone. Copilot reports inputTokens including cache reads and writes for
    every provider, and Claude model ids with dots; both are handled, and
    --explain copilot says how. A person declining a prompt is recorded as
    denied-interactively-by-user, captured from a real denial, and is reported
    as a human refusal.
  • --card writes a 1200×630 SVG and PNG to post: supervision, cost or
    volume, in a hero or terminal style. Standard library only. The SVG and
    PNG are drawn from one list of pixel runs, so they cannot disagree. Nothing
    identifying can reach either, and the leak test now checks both, plus the
    printed caption.
  • --card refuses to combine with another mode -- --json, --fail-on,
    --diff, --why, --share, --watch, --mcp, --replay, --self-check,
    --explain, --agents, --suppress, --suppressions, --completions or
    --service -- so a card run can never silently skip a CI gate or quietly
    write nothing.
  • The MCP server (--mcp) now sees Copilot sessions too.
  • Copilot sessions with no shutdown record are reported, not dropped. The
    report says N Copilot sessions unpriced (no shutdown record) under the cost
    line, and --json carries copilot_unpriced_sessions. No cost is estimated
    for them.

Changed

  • The card counts supervision per shell command, using the permission mode
    in force when each command ran, rather than per turn. --share still counts
    per turn, but --share and the card now share the one definition of
    "unsupervised" the AISVS mapping already used, so codex:never counts there
    too.
  • MCP fleet_summary uses the same single "unsupervised" definition, so
    codex:never counts as unsupervised there as well.
  • The vendor matrix (--explain vendors, --json) has a copilot column.

Fixed

  • --share printed raw model ids. A fine-tune or private deployment id
    could reach a summary documented as containing nothing identifying. Model
    names now appear only when they are exact entries in the public price table;
    anything else is custom. The leak test seeds a private model id.
  • A GitHub expression inside the Action's run: block killed the audit
    step.
    GitHub expands ${{ ... }} before bash sees the script, so a help
    message quoting one left a dangling quote and a syntax error on the runner.

Actualis 0.1.14

Choose a tag to compare

@github-actions github-actions released this 05 Sep 22:33
465accb

Fixed

  • The GitHub Action reported nothing back exactly when it had something to
    say.
    A composite action that exits non-zero does not propagate its outputs,
    so exit-code and findings came back empty whenever fail-on fired —
    the one case a caller most needs them. The gate worked; the reporting did
    not.

    The audit step now always exits 0 and a separate Gate step does the
    failing, so the outputs come from a step that succeeded. The self-test now
    asserts both halves of the contract: that the job fails, and that the
    outputs survive.

    Found by the self-test on the first run against a published release, which is
    what it was written for. It had never run automatically, because of the
    second bug below.

  • The action self-test could never trigger. It listened for
    release: published, but a release created by a workflow using the default
    GITHUB_TOKEN does not trigger other workflows — GitHub blocks that to
    prevent recursion. It now runs on workflow_run when the release workflow
    completes, and skips when that release failed, since a failed release
    publishes nothing to test.

Actualis 0.1.13

Choose a tag to compare

@github-actions github-actions released this 05 Sep 22:16
44fc14c

Added

  • --ci-log FILE audits an agent that ran inside CI, and a GitHub Action
    wraps it.
    Until now the tool audited a developer's laptop. Agents
    increasingly run in CI, where the record lands on org infrastructure by
    construction — which is the population this was always aimed at and could not
    reach.

    On a runner there is no transcript directory to discover: the agent ran in
    this job. The Claude Code Action
    drives Claude Code through the SDK and writes every event to a single JSON
    array at $RUNNER_TEMP/claude-execution-output.json, exposing the path as
    its execution_file output. --ci-log reads that.

    Reading the documented output rather than guessing at ~/.claude on the
    runner is the deliberate part.
    Whether a transcript directory exists there
    at all is an SDK internal that could change without notice; execution_file
    is a published contract.

    The records inside are the ones the local parser already understands — the
    action's own README describes them as "top-level events with type;
    assistant text is nested under message.content". So the record-level
    ingest is now shared: _ingest_claude is the only place that knows what a
    record means, and a change to that meaning cannot apply to one
    serialisation and not the other. There is a test asserting the two agree on
    message count, dedup, cost, commands, permission modes and credentials.

    action.yml at the repository root wraps it: pass the upstream step's
    execution_file, get a gate, a job summary and an optional JSON report.
    Credentials are redacted, and the self-test fails the build if a raw
    credential ever reaches an artifact the action produces.

    What this does not cover: an execution log is one job's events. Cost, the
    shell audit and credential detection all work; there is no history before the
    run, so --diff against a saved baseline is how to ask what changed, and
    blast-radius replay is bounded by the single job.