# changelog

author: dagnode <tools@dagnode.com> - 0.6.4-1
changelog:
  - Secret lockdown now precedes every access-widening step: a re-claim that only adds the
    group ACL, .git normalization, or the SELinux label scans first, and --sandbox-create
    clones privately (umask 077), locks tip-commit secrets, and only then opens and
    registers the clone -- declining leaves it private and unregistered, resumable by
    re-running on the clone path.
  - Reworked the --project-claim / --sandbox-create dialogs into self-contained blocks:
    each decision shows its own headline, details, and result, warnings frame at 50
    columns vs 80 for section headlines, and the sudo-password notices are gone.
  - Fixed the normalize-.git prompt re-asking on every re-claim of an already-normalized
    tree, and the drift listing's empty mode column (both an IFS parsing bug).
  - Fixed first claims flagging the entire tree as "interior permission drift"; the
    report now appears only on re-claims, where it is actionable.
  - Added an ai-tools(1) man page (man ai-tools), kept in sync with ai-tools --help by
    the test suite.
  - New operator guide docs/project-lifecycle.md (claim vs sandbox decision, what each
    prompt grants, recovery paths); README slimmed to a front page that links out.


author: dagnode <tools@dagnode.com> - 0.7.0-1
changelog:
  - Reorganized the package tree under two umbrellas: the Claude Code provider is now
    ai-tools-agents-claude-code-restricted (under the ai-tools-agents umbrella) and the
    nvm-managed Node toolchain is now ai-tools-integration-nodejs (under ai-tools-integration).
    The ai-tools metapackage weakly Recommends both umbrellas, so an install can drop either.
    No functional change to the sandbox, confinement, CLI, or bootstrap.
  - The renamed packages carry Provides/Obsoletes for their old names, so dnf performs the rename
    as an ordinary upgrade and no package has to be removed by hand.


author: dagnode <tools@dagnode.com> - 0.8.0-1
changelog:
  - The stack is multi-agent: nothing in ai-tools-base names an agent. Which agents exist, what
    each provisions, where it keeps its config directory, which binary the SELinux domain
    transition keys on, which side converges file ownership, and where it reads skills and
    subagents all come from per-package manifests under /usr/local/lib/ai-tools/agents.d. A second
    agent package ships a wrapper, a manifest, and a session-env fragment -- no sudoers rule, no
    policy change, no edit to the base.
  - Confinement is one agent-agnostic shim, /opt/ai-tools/bin/ai-tools-run. It accepts an
    executable only at a semver version directory in the sandbox toolchain whose launcher an
    enabled manifest claims, then wraps the session in the same confined transient unit as before.
  - operator.conf AI_TOOLS_AGENTS and AI_TOOLS_INTEGRATIONS choose what a host runs: a key present
    is an exact allowlist, absent means every installed provider its own package marked
    default_enable=yes. Every enabled provider contributes session environment and PATH through a
    root-owned session-env.d/<name>.env.sh fragment, integrations first and the agent last.
  - The gating is fail-closed and tamper-refusing throughout: each input is honored only while it
    is root-owned and writable by neither group nor other, and every failure resolves to LESS
    access -- the default-enabled baseline, never "enable all" -- and is reported.
  - Skills and subagent definitions live in one place each, /opt/ai-tools/skills and
    /opt/ai-tools/subagents, and every agent's directories hold a symlink per asset. An asset is
    authored and updated once however many agents read it, while an agent-specific one is a real
    file the linker never displaces. Adding one: /usr/share/ai-tools/skills/README.md.
  - Each integration keeps its sandbox-side state under /opt/ai-tools/integrations/<name>, covered
    by a single base-owned SELinux rule, so an integration package carries no file-context rules
    of its own and adds no directory to the sandbox home.
  - ai-tools-integration-dotnet integrates a host-managed .NET toolchain (no runtime packaged, no
    dotnet RPM dependency, inert without one): DOTNET_ROOT, a sandbox-writable NuGet cache, and the
    admin-provisioned shared tools on PATH. Provision with sudo ai-tools-dotnet setup /
    install-tools. Pulled as a dnf weak dependency, so it installs by default and removes cleanly.
    Known limitation on an SELinux-enforcing host: the SDK cannot restore or build, because NuGet
    mmaps a shared-memory file under /tmp and the sandbox domain holds no map permission on its own
    tmp files. Running a prebuilt assembly works, as does a DAC-only host; the policy grant is
    planned for 0.8.1.
  - New command: ai-tools --providers reports the installed agents and integrations, which of them
    a session gets, and why -- including any input refused as untrusted.
  - Upgrading from 0.7.0 needs no action beyond dnf, but five things moved. dnf handles the first;
    the rest are stale copies worth deleting once:
      /etc/sudoers.d/ai-tools-claude   is now /etc/sudoers.d/ai-tools (removed on upgrade)
      ai-tools-claude-symlink          is now ai-tools-launcher-symlink
      ai-tools-relabel-entrypoint      is now ai-tools-relabel-agent
      /opt/ai-tools/.nuget, .dotnet    dotnet state now lives under integrations/dotnet
      .claude/{skills,agents}          shipped copies now live in the shared roots; an unchanged
                                       per-agent copy is converted to a symlink on the next
                                       install or bootstrap, one you edited is kept and reported
    A hand-written agent manifest must spell the npm key npm_package, not npm_pkg.


author: dagnode <tools@dagnode.com> - 0.8.1-1
changelog:
  - Fixed the SELinux-enforcing limitation carried in 0.8.0: the sandbox domain held no map
    permission on its own /tmp files, so dotnet restore/build -- and git or SQLite run in a /tmp
    working tree -- failed with EACCES on the mmap. A new optional SELinux policy group, tmpmap,
    grants exactly that one permission (ai_tools_tmp_t:file map); it is mmap-at-all and NOT
    executable mapping (/tmp stays noexec), so it cannot run code from /tmp. With it enabled the SDK
    restores and builds normally. On an enforcing host: sudo ai-tools-admin selinux enable-group
    tmpmap.
  - The STABLE optional SELinux policy groups now ship prebuilt (currently tmpmap) and are managed
    on any installed host with a new ai-tools-admin selinux subcommand -- list-groups, enable-group
    <name>, disable-group <name> -- which loads the shipped .pp via semodule, needing no source
    checkout or selinux-policy-devel. The group set is single-sourced, so this helper and the
    source-tree install-selinux.sh never disagree on which groups exist.
  - The experimental groups (systemd, pkgmgmt, netadmin, podman) are unaudited drafts and are NOT
    shipped prebuilt; they must be compiled and verified from a source checkout first
    (install-selinux.sh enable-group + the avc bring-up loop). ai-tools-admin enable-group of an
    experimental group refuses and points at that workflow rather than loading an unaudited module.
  - ai-tools --providers now reports the SELinux confinement layer where SELinux is active -- the
    core module and any loaded optional group -- and, when the dotnet integration is enabled under
    enforcing but tmpmap is not loaded, names the enable command instead of letting the build fail
    with an opaque EACCES.
  - Upgrading from 0.8.0 needs no action beyond dnf. A DAC-only host is unchanged; on an enforcing
    host, dotnet builds now require enabling the tmpmap group once, as above.


author: dagnode <tools@dagnode.com> - 0.9.0-1
changelog:
  - NEW: Save tokens by default -- command filters narrow what a tool prints, over the root-owned
    rule sets in filters.d and operator.conf AI_TOOLS_FILTERS (an empty value disables).
    Access-neutral: the permission rules are re-evaluated on the rewritten command.
  - NEW: Apply the command filters on both Bash hook events
  - NEW: Quiet the dotnet SDK through its own command-filter rule set
  - NEW: Keep host-tuned permission rules across an upgrade -- settings.json ships
    %config(noreplace), so rpm keeps the file you edited and parks this version's copy as
    settings.json.rpmnew (earlier releases overwrote it and kept no copy)
  - NEW: Reconcile the .rpmnew copies an upgrade leaves with sudo ai-tools-admin postupgrade. It
    merges shipped hook declarations into settings.json after listing exactly what it will add and
    confirming, writing a dated .bak first. operator.conf and the sudoers grant are reported, never
    written -- a file of commented option blocks has no merge worth learning to predict. The install
    output points here whenever a .rpmnew is waiting
  - NEW: Merge shipped hook declarations into a kept settings.json, so a new version's hook no
    longer installs with nothing to invoke it (inline on a from-source install, through postupgrade
    on RPM)
  - NEW: Report the options a kept KEY=value config has not seen (inline on a from-source install,
    through postupgrade against operator.conf.rpmnew on RPM)
  - NEW: Add the shared config backup and baseline-copy layer to the from-source installer --
    dated .bak and .shipped sidecars, never overwritten
  - NEW: Read allowed-projects with the shared config grammar (conf.lib.sh): end-of-line comments
    and quoted paths, one parser for the wrapper, the CLI, and the handback helper
  - NEW: Add operator.conf(5)
  - NEW: Optional SELinux policy group apphost lets the sandbox build and run .NET executable and
    host projects -- console apps, ASP.NET Core and worker services, xunit.v3 tests, single-file
    publishes. A class library, or in-process MSTest (Microsoft.Testing.Platform), does not need it.
    It complements tmpmap (restore and build); enable both for a full build-and-run workflow. Off by
    default:  sudo selinux/install-selinux.sh enable-group apphost
  - NEW: Optional SELinux policy group netcore covers the rest of a .NET workflow under enforcing --
    dotnet test (its diagnostic socket and, for an out-of-process xUnit/VSTest host, the loopback TCP
    connection to it), multi-node MSBuild (lets you drop the -m:1 workaround), and running a binary
    you built from the project tree. Off by default:  sudo selinux/install-selinux.sh enable-group
    netcore
  - FIX: Parse the labelling report so the unconfined-entrypoint guard can fire -- an entrypoint
    that failed to take ai_tools_exec_t ran sessions UNCONFINED while the install reported success.
    Check an enforcing host after upgrading:  ps -eo label,cmd | grep '[c]laude'  (expect
    ai_tools_t)
  - FIX: Require jq, which every Claude Code hook depends on -- without it the ownership handback
    and the .git reclaim silently stopped
  - FIX: Drive the sandbox user manager over the machine transport -- the toolchain auto-update
    timer never started, failing with "Connection refused"
  - FIX: Repair project labels on re-install instead of reconverting (relabel.lib.sh), so a
    re-install no longer rewrites every file of every registered project
  - FIX: Group the install output by the work it reports
  - FIX: Stop the "failed to set default file creation context" SELinux warnings that cluttered
    command output under enforcing -- most visibly through dotnet build and NuGet restore
  - FIX: A second build of the same solution no longer fails on the previous project's locked
    output (dotnet/msbuild#6461)
  - FIX: Re-apply SELinux labels once per install, and repair them even when the SELinux step is
    declined on a host that already has the module loaded (e.g. after a Node upgrade)
  - FIX: Clearer optional-group install prompt -- each group is tagged stable or experimental, the
    explanation precedes the skip question, an already-loaded group is reported (and can be
    recompiled from source rather than offered for re-enable), and the summary lists every loaded
    group
  - Upgrading from 0.8.1 needs no action beyond dnf. Command filtering arrives ON for a host whose
    operator.conf predates AI_TOOLS_FILTERS; set AI_TOOLS_FILTERS="" to opt out.
  - For .NET workloads on an enforcing host, enable the optional groups they need: tmpmap
    (restore/build), apphost (build an executable or host project), and netcore (test and run); all
    stay off by default.


author: dagnode <tools@dagnode.com> - 0.9.1-1
changelog:
  - FIX: Strip the stray group-execute bit Claude Code's file writes leave on data files. The
    ownership handback and unclaim now clamp the group class off the owner-execute bit, so a data
    file hands back group rw (not rwx) while a real script keeps group r-x -- and the spurious bit no
    longer becomes a real group-execute when a project tree is archived (tar/zip) and extracted
    without ACLs.
  - FIX: Refuse an operator command (--project-*/--sandbox-*/--lockdown/--reclaim/--relabel) up
    front when the invoking user is not in OPERATORS in operator.conf, pointing at sudo
    ai-tools-admin operator add <user>, instead of running through registry writes and confirm
    prompts only to fail in a root helper and roll back. --help/--version/--list/--providers stay
    open to any user.


author: dagnode <tools@dagnode.com> - 0.10.0-1
changelog:
  - CHANGE: The root helper programs moved from /usr/local/sbin/ai-tools to
    /usr/local/libexec/ai-tools, so one install layout works on both EL and Fedora (Fedora merges
    /usr/local/sbin into /usr/local/bin, which collided the helper directory with the ai-tools CLI;
    /usr/local/libexec is untouched by that merge). The sudoers grant is re-pointed automatically on
    upgrade with no manual step and the old directory is removed; your operator configuration is
    untouched. If an upgrade prints a notice, run sudo ai-tools --relabel.
  - NEW: The RPMs now install on Fedora (42+). The EL9/EL10 packages previously refused the
    transaction there because of the bin/sbin merge the layout change above resolves; a native Fedora
    build also compiles the SELinux policy against Fedora's own reference policy. EL9/EL10 remain the
    gated, released targets; Fedora is smoke-tested per commit.
  - LICENSE: The project license identifier is now AGPL-3.0-only. Releases through 0.9.x were
    published as AGPL-3.0-or-later, and everyone who received them keeps the "or later" option on
    those versions; this narrowing applies from 0.10.0 forward. No change to what the license permits
    you to do with the code.
  - NEW: The SELinux confinement policy now ships as its own subpackage, ai-tools-selinux, licensed
    GPL-2.0-or-later. A compiled policy module embeds macro expansions from the GPL SELinux reference
    policy, so it is conveyed under the GPL as its own package rather than inside the AGPL base. A
    default install still pulls it (base recommends it); installing without it leaves the sandbox in
    the documented DAC-only mode.
  - NEW: Set a custom system prompt for sandboxed sessions by placing it at
    /etc/ai-tools/prompts/claude-system-prompt.md (root-owned). When the file is configured the
    launch fails closed rather than silently dropping it.
  - NEW: Route sandboxed sessions at a custom, Anthropic-compatible API endpoint via
    /etc/ai-tools/endpoints/custom-claude-endpoint.conf (root-owned: a base URL plus the name of the
    auth-token variable). It fails closed when configured but unusable, so a session never falls back
    to the default endpoint unnoticed.
  - NEW: Add CLAUDE_CODE_MAX_OUTPUT_TOKENS to the default session configuration.
  - FIX: The SELinux module now reaches the running kernel policy on install and leaves it on erase.
    Both scriptlets passed semodule --noreload, which commits to the module store without loading, so
    a fresh install could leave the agent entrypoint unlabelled (the launch preflight then refuses
    with mislabel until a reload) and dnf remove could leave the ai_tools domain live after the files
    were gone.
  - FIX: The source tarball and SRPM no longer pick up policy modules built locally from source; a
    filesystem wildcard was sweeping an experimental group compiled on the build host into the
    published source artifact.
  - FIX: The source package now carries the SELinux policy sources alongside the compiled modules, so
    a GPL binary is conveyed with its corresponding source (GPLv2 s.3) on every channel, including the
    offline release archive.
  - Upgrading from 0.9.x needs no action beyond dnf: the helper-path migration and the sudoers
    re-point are automatic. If a host prints a relabel notice, run sudo ai-tools --relabel.


author: dagnode <tools@dagnode.com> - 0.10.1-1
changelog:
  - FIX: The package now enables the ownership-handback socket (ai-tools-handback.socket) on install,
    via a shipped systemd preset. Without it a package install left the socket at the distribution
    default (disabled): every ownership hand-back then failed silently, so files the agent wrote
    stayed owned by the sandbox account and git reported "dubious ownership" on the project. The
    preset applies on initial install only, so a later systemctl disable survives upgrades -- and, by
    the same rule, upgrading a host that never had the socket enabled does not turn it on. If a host
    is already affected, run once: sudo systemctl enable --now ai-tools-handback.socket, then
    ai-tools --reclaim <project>.
  - FIX: A down handback socket is now reported instead of failing silently. The launch warns (naming
    the fix) and proceeds -- the socket restores ownership but is not a confinement boundary -- while
    the session sweeps and ai-tools --reclaim count only confirmed hand-backs and, when the socket is
    down, report the stranded work rather than a reassuring count of calls that changed nothing.
  - DOCS: The claim, unclaim, and reclaim entries in ai-tools --help and the man page now spell out
    what each does and how they differ: claim grants the agent access to a project, unclaim releases
    it (revoking that access and returning the tree to your group), and reclaim takes ownership back
    while the project stays claimed.


author: dagnode <tools@dagnode.com> - 0.11.0-1
changelog:
  - NEW: ai-tools --status is a single health report for the pieces a session depends on: the
    ownership-handback socket, the post-upgrade relabel watcher, and the sandbox account's toolchain
    updater and its timer. Each reads OK, STALE, DOWN, FAILED or not-installed, with what breaks
    while it is down and the exact command that fixes it. It also names the active Node version, and
    exits non-zero when anything is broken, so it can be run from cron or a monitor without parsing
    its output. A unit this host cannot query is not counted as a fault.
  - NEW: The report covers the toolchain updater even though it runs in the sandbox account's own
    systemd user instance, which your session cannot query. Until now a repeatedly failing update was
    invisible and the toolchain simply stopped advancing; each run's time and exit code are now
    recorded where the report can read them, and a successful run older than 48 hours reads STALE
    rather than OK -- an update that stops being triggered leaves Node and the agent packages quietly
    falling behind while every recorded run stays green. Anything reported broken prints the commands
    to read its journal and restart it, which differ from the usual ones because the unit belongs to
    that account's user instance.
  - NEW: Launching a session runs the same health check first and warns when a service it depends on
    is down, instead of letting the failure surface later as a confusing symptom.
  - NEW: ai-tools --sandbox-create is scriptable: --from, --branch, --dir and -y give every input a
    flag and a default, with the prompts kept as the interactive fallback, and the confirm previews
    the exact git commands. The branch may be any valid git ref and now defaults to
    sandbox/<name-of-source> (previously ai-tools/sandbox-<owner>/<leaf>); the name is convention
    only -- nothing parses it -- so no behaviour depends on the change.
  - NEW: ai-tools --project-unclaim --force releases a copy of a claimed project -- one moved or
    copied elsewhere that still carries the ai-tools group and ACLs but that no allowlist names. It
    changes only paths that still carry ai-tools access; --dry-run, --full and --group support
    scripted use.
  - NEW: ai-tools --lockdown also seals the paths you sealed by mode rather than by name, so a file
    or directory made owner-only after the claim is cleaned up without waiting for the next one.
    --dry-run previews that pass as well, naming each path and what would come off it.
  - NEW: ai-tools --project-claim reports, in its Review block, a sealed directory whose setgid bit
    belongs to a third group -- the one piece of residue a claim leaves, since it cannot tell a
    deliberate choice from a leftover -- with the chmod g-s that clears it.
  - NEW: ai-tools --list flags more kinds of inconsistent allowlist entry under Suggested cleanup,
    each with a copy-paste fix: a glob written in an allow line (globs work only in '!' exclusion
    lines, so a glob allow entry silently matches nothing), a stale '!' exclusion whose path no
    longer exists, and a git safe.directory entry that no allowlist line lists. The report stays
    read-only.
  - NEW: AI_TOOLS_REQUIRE_SELINUX=yes in operator.conf lets an operator require SELinux confinement:
    a session then refuses to launch on a host where the ai-tools policy is not enforcing, instead of
    falling back to DAC-only. Off by default, so intentional DAC-only hosts are unaffected.
  - CHANGE: ai-tools --project-unclaim now leaves a hardlinked file alone, in both modes. Changing it
    would change every other name for the same inode, including names outside the project -- which is
    what a locally cloned repo has, since git hardlinks .git/objects to the repo it was cloned from.
    Those files keep the group they have, so the agent is not off them: the count is reported at the
    end of the run, with the find command that lists them, and you decide.
  - FIX: Claiming a project no longer opens files and directories you had made owner-only (0600,
    0700) to the agent. The setgid pass regrouped every directory you owned -- at claim and again on
    every session start -- while the ACL pass skipped them; both now honour the seal, and the claim
    reports how many paths it skipped. Re-claim an existing project to repair directories already
    regrouped.
  - FIX: Sealing a path now removes the sandbox residue behind the mode. A path created inside a
    claimed tree inherits the group, the setgid bit and the project ACL at create time, and a later
    chmod only masks them -- so widening the mode once re-activated the lot. The claim walk, the
    lockdown and the ownership handback all strip it now, removing only what the sandbox put there
    and leaving your mode bits, ownership and other ACL entries alone.
  - FIX: A locked secret is owned <you>:<you>, not <you>:ai-tools. The proactive lockdown and the
    on-write quarantine gave the same secret two different owners, and the sandbox group would have
    re-exposed it the moment its mode was widened.
  - FIX: A session could silently run unconfined on a host that did have the SELinux policy loaded.
    The pre-launch confinement probe read module presence from the root-only policy store, which the
    sandbox account cannot read, so it always saw "absent" and on one path launched DAC-only. It now
    derives module presence from the world-readable file contexts, so a loaded policy is detected and
    an installed-but-unverifiable confinement fails the launch closed rather than open.
  - FIX: A sandbox clone gets the project SELinux label it needs. Clones under
    /var/opt/ai-tools/sandbox-projects were left unlabelled because EL and Fedora alias /var/opt to
    /opt before matching file contexts, so the clone-area rule was never reached -- and a launch
    refused with "SELinux label still missing" right after the claim had reported success. Labelling
    now also verifies the type it applied, so a mislabel surfaces at claim or relabel time instead of
    at the next launch.
  - FIX: The project and sandbox verbs check their target before changing anything. --project-unclaim
    refuses a directory that is neither a claimed project nor an ancestor of claimed ones, refuses a
    protected system directory up front, and hands ownership back before dropping the allowlist entry
    -- previously it could drop the entry first and leave the tree in the agent's group, releasing
    nothing. --sandbox-remove and the per-project verbs likewise refuse a target that is not a
    recognized sandbox clone or claimed project, so a mistyped path cannot delete or re-permission
    the wrong tree.
  - FIX: ai-tools --list is reliable on a hand-edited allowlist. An entry written with an end-of-line
    comment or quotes, or reached through a symlink, is now recognized everywhere the CLI reads the
    list -- such an entry was invisible to several checks, so claiming it appended a duplicate line,
    unclaiming it reported "not listed" and left the agent's access behind, and launching after an
    in-place claim could fail with "the claim did not complete." A stale or protected entry no longer
    aborts the listing either, which used to hide every entry after it along with the Suggested
    cleanup and Maintenance sections.
  - FIX: The post-upgrade relabel watcher (ai-tools-relabel.path) is enabled on install, so the agent
    entrypoint is re-labelled automatically after a Node toolchain upgrade. Without it a launch after
    an upgrade could fail closed on a mislabelled binary until you ran ai-tools --relabel by hand.
  - FIX: Upgrading the ai-tools-selinux policy refreshes the running handback socket's SELinux label,
    so ownership hand-back keeps working immediately after the upgrade instead of silently doing
    nothing until the next reboot.
  - FIX: The toolchain updater could abort silently, leaving nvm-update.service failed with an empty
    journal and Node and the agent packages frozen at their installed versions: its logging ran as a
    bare pipeline that killed the run it was reporting, before the line explaining why was written.
    Logging can no longer fail the run, and the message reaches the unit's journal first.
  - FIX: Reports read as intended: sudo ai-tools-admin selinux list-groups prints a clean sectioned
    report of the optional policy groups and their load state; a short list of flagged paths is
    printed once, whole, rather than sampled, counted and offered for re-listing (four paths took
    seven lines and a question); and several --status and --providers hints now name commands that
    work as printed.
  - DOCS: The README gives explicit install and upgrade commands, a working first-launch walkthrough,
    and a pre-1.0 stability notice, and the project-lifecycle documentation spells out what a claim
    and an unclaim each do to a project's permissions.
  - Upgrading from 0.10.x needs no action beyond dnf. If you had sealed directories (0700) inside a
    project claimed by an earlier release, re-claim it once -- ai-tools --project-claim <project> --
    to return them to your own group.


author: dagnode <tools@dagnode.com> - 0.11.1-1
changelog:
  - FIX: A toolchain update that could not reach the npm registry failed with an empty journal and
    left ai-tools --status reporting FAILED until the next day's window. It now says what it could
    not reach, reads SKIPPED instead (nothing changed; STALE after 48 hours if it persists), and
    retries every 30 minutes for up to 6 hours. The daily window moved to 07:55 local time, and it
    and the tracked Node LTS series are overridable with 'systemctl --user -M ai-tools@.host edit'.


author: dagnode <tools@dagnode.com> - 0.12.0-1
changelog:
  - NEW: 'sudo ai-tools --stop' ends every agent session running on this host, and everything it
    spawned. Until now every control changed only what the NEXT launch gets -- unclaiming a project,
    disabling a provider, revoking an operator -- and "stop what it is doing, now" meant hunting for
    units in an account you cannot reach. Sessions are found by their cgroup, so a child that
    double-forked or called setsid is still ended; each gets ten seconds to exit before it is killed.
    Preview with --dry-run, skip the confirmation with -y, skip the grace period with --force. It
    reports what it ended, and because a killed session cannot run its own hand-back it names the
    --reclaim for each project it interrupted. Use /exit inside a session you are simply finished
    with. Details in /usr/share/doc/ai-tools/session-stop.md.
  - NEW: The agent binary is checked against the checksum its vendor SIGNED, and the verified value
    is pinned where the sandbox account cannot write it -- so a binary altered after installation
    refuses to launch. npm's integrity hash covers what was delivered, not what is on disk weeks
    later, and 'npm install -g' does not reinstall an unchanged version, so such a change would
    otherwise persist across every session and operator. The signing key ships in the package rather
    than being fetched, and the pin is written automatically by the watcher that already relabels
    entrypoints: nothing to maintain per release. Requires gnupg2 (gpgv).
  - NEW: 'ai-tools --status' shows that verification per agent: VERIFIED with the pinned version and
    how long ago, or unverified. It is the only window an operator has onto it, the entrypoint itself
    living in a toolchain they cannot read. Unverified counts against the exit status only where
    verification is required, so an air-gapped host does not alarm.
  - NEW: AI_TOOLS_REQUIRE_ENTRYPOINT_VERIFY=yes in operator.conf refuses to launch an entrypoint
    carrying no verified checksum, and keeps the updater from activating a release it could not
    verify -- for hosts that should never run an unattested agent. A checksum MISMATCH always refuses
    regardless of this key. Off by default. See docs/entrypoint-verification.md.
  - NEW: 'ai-tools --relabel' now reconciles the whole entrypoint -- verify, pin, then label -- so it
    is also how to pin one on a DAC-only host, or on a host the watcher was offline for. An
    unreachable vendor is not an error; only a mismatch fails the command.
  - NEW: 'sudo ai-tools --audit [--since <when>]' reads back what has refused, been rejected, been
    stranded or been flagged. These detections were already being recorded and nothing read them,
    each landing in a root-only file or a journald tag someone had to think to query. It exits
    non-zero when anything is reported, so it runs from cron or a login banner without parsing its
    output. Launch refusals are reported separately, since those lines come from the sandbox account
    itself and are for reconciling against the root-only trail rather than relying on alone. Window
    defaults to 7 days; a --since date(1) cannot parse is refused rather than read as "everything".
  - NEW: Every tool call a session makes is recorded where the agent can append but neither edit nor
    delete -- 'journalctl -t ai-tools-hook _UID="$(id -u ai-tools)"', or '-o json' for structured
    fields. The session's own transcript is agent-owned, so after an incident there was nothing
    independent to reconcile it against; now there is a record of what ran and what was written. Each
    record is bounded on purpose: for a shell command, its first two words and word count, never the
    command line -- a here-doc body or a credential in an argument never reaches the journal.
  - NEW: 'ai-tools --for <operator>' claims projects on behalf of another enrolled operator, so a
    passwordless service account that runs an agent can be given work. The entry lands in that
    account's registry, which is what makes the project theirs: files the agent writes are handed
    back to them, the per-project ACL grants them, and their next launch finds the project claimed
    instead of prompting for a password it cannot supply. You claim once, with your own password, and
    the account never meets a sudo prompt. Accepted on --project-claim/-create,
    --project-unclaim/-remove, --lockdown, --reclaim and --list; refused elsewhere rather than
    quietly ignored. Enrol the target first with 'sudo ai-tools-admin operator add <name>'.
  - NEW: Sandboxed agents now carry a shared standard for building and operating systems that act
    with autonomy -- what constrains a system, what watches it, and what a human does when a
    threshold is crossed -- which also binds the agent's own conduct in the sandbox. Seeded with the
    other shipped skills; nothing to configure.
  - FIX: On a DAC-only host, launch verified one path and started another: it checked the resolved
    entrypoint but handed systemd the launcher symlink, leaving the preflight as a window in which
    that link could be repointed. It now resolves once, uses that single path for both, and re-checks
    it immediately before starting. SELinux hosts were never exposed to this.
  - FIX: 'ai-tools --relabel' could report success while every launch stayed refused, exiting 0 with
    "entrypoint is not installed" for an agent that was in fact installed elsewhere than its package
    declares. That case now exits non-zero and names the real cause, and "not installed" is reported
    only when the agent genuinely is not provisioned.
  - FIX: A fresh install left the entrypoint relabel watcher enabled but not started, so
    'ai-tools --status' showed it DOWN until a reboot -- and a Node auto-upgrade in that window left
    the next launch refusing on an unlabelled entrypoint. It is started at install.
  - FIX: A fresh toolchain bootstrap no longer emits "warning: adding embedded git repository: .nvm".


author: dagnode <tools@dagnode.com> - 0.13.0-1
changelog:
  - CHANGE: "Operator" now names exactly two facts -- membership of ai-ops and a name in OPERATORS,
    both written by 'ai-tools-admin operator add'. A general sudo grant is a THIRD, independent axis
    this project never writes, records, or infers: the host's own sudoers decides it. An account
    holding only the two is a first-class operator -- it launches sessions and has projects claimed
    for it with --for -- rather than a half-configured one. The requirement that follows is now
    stated: a host needs at least one operator holding a general grant, or nothing can be claimed on
    it by any principal, root included. Nothing changes on an existing host; what changes is that a
    refusal now says which of the three you are missing.
  - NEW: A verb whose root helper you hold no sudo grant for is refused BEFORE sudo asks for a
    password. sudo authenticates before it decides whether a rule matches, so a restricted operator
    was made to authenticate and then turned away, for a decision that was knowable without asking.
    The refusal names the command that does work -- for most verbs the same command with
    '--for <you>', run by an operator who holds a grant. It reports rather than decides: an answer it
    cannot read falls through to sudo, so nobody loses a command they do hold the grant for.
  - NEW: Root may run the commands that write no operator-owned state -- --audit, --status, --list,
    --providers and --stop. --audit needs root by construction (its trail is 700 root:root) and was
    unreachable from both sides on a host whose only operator holds no general grant. Every command
    that writes a registry still refuses root, since a registry owned by root names an operator whose
    own launch gate cannot read it.
  - NEW: 'ai-tools --stop' runs with no password in its bare form, so a service that detects a session
    which must end immediately can escalate to a full stop with nobody present. A control unattended
    monitoring cannot exercise is unavailable during exactly the incidents it exists for. The grant is
    pinned to that bare form: --force (which drops the grace period, and the current turn's unsaved
    work with it) and --dry-run still prompt. It now also works on a host that is unprovisioned or
    that enables an agent other than Claude Code -- a stop must not depend on what it is stopping.
  - FIX: 'sudo ai-tools --stop', the form 0.12.0 documented, was refused on every host: --stop sat in
    the set of verbs the CLI refuses root for, though it writes no operator state. Run it as yourself
    -- 'ai-tools --stop' -- and root may now run it too.
  - NEW: 'install.sh --operator <account>' names the account to enrol instead of silently adopting
    SUDO_USER, and an interactive install asks. Root, the sandbox account, an unknown name and a name
    with no home are each refused by one rule whichever route the name arrived by, so an unattended
    from-source install can enrol an account other than SUDO_USER.
  - NEW: 'sudo ai-tools-admin operator add <name>' reports which of the two operator shapes it just
    created, by asking sudo about the enrolled account directly. Group membership cannot imply a
    sudoers rule, so asking is the only way to know -- and the administrator learns at the moment of
    the decision whether that account can claim projects or needs another operator to claim for it.
  - CHANGE: A shipped skill or agent is now UPDATED by default on upgrade. It was only ever replaced
    when an operator answered yes at a prompt, and a package scriptlet has no terminal, so every dnf
    upgrade silently took the keep default with its output discarded -- a host stayed on whatever
    version it first seeded and was never told. Assets seeded before this need ONE interactive
    './install.sh install' or 'sudo ai-tools-bootstrap' to move; after that, upgrades deliver new
    versions on their own. A withdrawn asset is moved to /opt/ai-tools/retired rather than deleted.
  - CHANGE: The four documentation skills (reference, usage, doc comments, changelogs) are replaced by
    one writing standard, ai-tools-technical-docs, which also covers commit messages, man pages, and
    error and log output. The four are removed from the live skills root on upgrade and kept under
    /opt/ai-tools/retired; a skill of your own under one of those names is left alone.
  - NEW: 'ai-tools --status' reports the LABELLING half of the entrypoint reconciliation beside the
    verification half. An upgrade whose relabel failed previously reported a healthy host -- the
    watcher OK and a fresh green VERIFIED line, both true, with the failure between them writing no
    record at all. ai-tools-relabel.service is reported too, judged by its last run rather than by
    is-active, which a healthy oneshot service always fails.
  - FIX: An upgrade could leave the entrypoint and config-directory SELinux rules unregistered. The
    agent package's %post and the relabel watcher the same transaction triggers ran at once, and
    semanage reports an error to whichever process finds the policy store held rather than waiting,
    so both runs failed on a store neither had broken. They now take a shared lock, and a refusal
    carries semanage's own reason instead of naming none -- a held store and a type the loaded policy
    does not define need different remedies.
  - FIX: A policy module that failed to load during install left no trace at the point it happened,
    surfacing much later as a relabel that cannot register its rules and a launch that fail-closes.
    The scriptlet now prints semodule's message and the command that repeats it.
  - FIX: A refusal that suggested a claim command could name a directory nobody typed. It scanned the
    arguments for one that exists and fell back to the current directory, so a mistyped project path
    produced a plausible-looking claim of whatever repository you were standing in -- observed live.
    A path you named now passes through as typed.
  - FIX: 'ai-tools-admin operator add' read a sudo that failed for its own reasons -- an unreachable
    sudoers backend, a host that refuses -l -- as proof the account holds no grant, sending an
    administrator to a --for workflow they did not need. It now reports "undetermined" unless a
    second probe confirms sudo is answering at all.
  - FIX: ai-tools(1) documents the exit codes it returns, including the three specific to --stop, and
    both man pages follow the shipped man-page guideline (ENVIRONMENT, EXAMPLES, and the lifecycle
    guide cited at the path the package installs it to).
  - Upgrading from 0.12.x needs no action beyond dnf, with one exception: shipped skills and agents
    seeded before this release move on the next INTERACTIVE './install.sh install' or
    'sudo ai-tools-bootstrap', not on the dnf upgrade itself.


author: dagnode <tools@dagnode.com> - 0.14.0-1
changelog:
  - NEW: 'ai-tools --project-create <path>' creates a project: one directory, an empty git
    repository, a README.md naming it, then the ordinary claim on the result. It was an alias for
    --project-claim, which refuses a path that does not exist, so the one thing its name promised was
    the one thing it could not do. It asks nothing it can answer for itself -- no proceed
    confirmation, no secret scan and no git-history question over a tree it just created -- which
    leaves the traverse grant as the only prompt, and it takes no -y because there is nothing to
    pre-answer. A path that already exists and a parent that does not are each refused, naming the
    command that applies.
  - NEW: 'ai-tools --project-remove [path]' releases a project AND deletes its directory.
    It was an alias for --project-unclaim, so the verb named after removal removed nothing;
    --project-unclaim stays the non-destructive reversal. An exact allowlist entry is the only thing
    that authorizes a deletion -- an ancestor, a path inside a project, an unregistered path, and an
    entry containing another claimed project are each refused -- and there is no --force. It confirms
    twice, a default-NO prompt and a typed project name with no default at all, so nothing is deleted
    without a terminal; only this verb's own -y pre-answers them, and -y requires an explicit path so
    an unattended run cannot delete the directory it started in.
  - FIX: --project-create now produces a project the agent can actually read on a host whose umask is
    077. The directory, the README and .git were born owner-only, which the claim that follows
    honours as a deliberate seal and skips -- so the verb registered a project whose README the agent
    cannot read, having just reported that it was normalizing it. It sets group-readable modes
    instead of inheriting them, and says what it set rather than asking.
  - FIX: --project-remove checks that it can unlink the project from its PARENT directory before it
    deletes anything. On a parent the operator cannot write, the deletion previously removed every
    file, failed on the top directory, and left an empty husk whose registry entries had already been
    dropped -- exactly the outcome the pre-flight exists to prevent.
  - FIX: --project-remove refuses when the project's allowlist entry cannot be dropped, and names the
    line to delete by hand. That entry is the agent's launch gate, so deleting the tree past a failed
    de-registration stranded the one entry the teardown order exists to remove.
  - NEW: 'ai-tools --project-disable [path]' parks a claimed project and '--project-enable [path]'
    brings it back. Parking takes a project out of service without unclaiming it: no session starts
    there and the ownership handback stops restoring files written under it, while group ownership,
    ACLs, setgid and the SELinux label are untouched -- so re-enabling grants nothing that was not
    already granted. The line is edited in place, so an allowed-projects you keep as an ordered,
    commented document comes back byte-identical after a park and a restore. --project-disable
    refuses a project nested inside another, since that line would be indistinguishable from a
    carve-out withholding a subtree, and --project-enable refuses a carve-out for the same reason.
  - NEW: 'ai-tools --project-unclaim --keep-entry' hands the files back with clean permissions and
    leaves the project's line parked in place. It serves the release cycle: seal a tree before a
    release, then claim it again for the next stage without its entry moving to the end of the file.
  - CHANGE: The CLI reads the parked state. A '!'-prefixed line is a workflow that predates any verb
    for it and the CLI could not see one, so a parked project was indistinguishable from a project
    never claimed: a claim appended a duplicate under an exclusion that still won and reported
    success, while --project-unclaim, --project-remove, --lockdown and --reclaim refused it as "not a
    claimed project". Entry state is now reported apart from effective reachability, since a project
    whose own line is clean can still be parked by an ancestor or a glob.
  - FIX: A launch refused in a parked project names --project-enable. Both a parked project and a
    subtree an operator withheld from an enclosing project were refused as "excluded by '!' rule",
    which left the operator to work out which of the two they had -- and the screen they reached next
    offered to claim a project that was already claimed. One is now one command, the other an edit.
  - FIX: './install.sh install' asks before de-registering its own checkout when that allowlist entry
    carries a comment. It matched only a line spelled exactly as the install directory, so a
    commented entry read as absent, the operator was never asked, and the entry stayed.
  - FIX: A claim no longer closes with a success mark over root steps that did not apply. With a
    mistyped password all four -- group, setgid, ACLs, label -- could fail, each printing its own
    warning, and the claim still reported "claimed" over a project the agent cannot enter. It now
    reports what is still pending and exits non-zero. The first failure also asks once, default NO,
    whether to attempt the remaining steps, instead of costing three password prompts per step on a
    host where sudo caches no credential.
  - FIX: --project-unclaim reports when the filesystem hand-back did not run, and exits non-zero.
    That step is what revokes the agent's access to the FILES; everything else the verb does is
    registry work, so an unclaim that under-applied told the operator access was removed while the
    tree stayed group-owned by the sandbox account. --sandbox-create no longer reports "sandbox
    ready" when git safe.directory could not be added, and --project-remove keeps its check mark for
    the deleted tree but names the cleanup that did not run.
  - FIX: A claim over a tree the resolved operator does not own is refused before its first registry
    write, naming the chown that makes it claimable. 'mkdir ~/proj && ai-tools --project-claim --for
    svc ~/proj' is the common way to reach that state: every inode failed the helpers' owner guard,
    so the claim applied its registries and its label, granted nothing, and finished with a check
    mark over a project that account cannot enter.
  - FIX: The setgid and ACL walks report how many paths they skipped because a third party owns them,
    with the project root stated in its own words -- every directory below an unreachable root
    inherits nothing, so that case is the outcome of the whole claim rather than one skipped path.
  - FIX: Claiming a project for a secondary operator, and every 'ai-tools --project-claim --for
    <operator>', now applies the SELinux label. The label step read a single operator's
    allowlist -- the primary's -- and refused a path absent from it, so on an enforcing host the
    claim finished registered, git-trusted and ACL'd but unlabelled, which is a project the agent
    cannot work in. It was reported ("1 step(s) that grant the agent access did not apply") rather
    than silent, but it could not succeed. The label's authorization now comes from whichever
    operator's registry holds the project.
  - FIX: 'ai-tools --project-claim --for <operator>' sets git's core.filemode as that operator. It
    ran git as the invoking user, so the claim either reported the target's tree as "not a git work
    tree" or was refused the write into a .git/config that account owns.
  - FIX: A project directly under your home -- /home/<you>/<project> -- is reachable again. Vetting
    an ancestor for the traverse grant reused the rule written for whole-tree targets, which refuses
    every home root, so such a project was reported permanently unreachable with a sandbox clone the
    only route in. Granting search permission on one directory is now its own rule: your own home
    root is allowed, while every system directory, /home itself and any other account's home root
    stay refused. Under --for the grant applies as the target operator, so a claim made for someone
    else can set it on ancestors that belong to them instead of warning on each one.
  - FIX: The checks that ask whether an SELinux policy module is loaded could report a loaded module
    as ABSENT, at random, and more often the more modules a host has. That silently cost an entrypoint
    relabel its file-context registration and the dotnet integration its label step. Five probes
    carried the fault; all five are fixed, and the same shape was costing the container test suite
    red runs on a man-page check that had nothing wrong with it.
  - CHANGE: An unattended entrypoint reconciliation answers from the pin already on disk when the
    installed version, the binary's bytes and every verification input are unchanged. A single dnf
    upgrade could otherwise re-fetch and re-verify the vendor's signed manifest several times, which
    on an air-gapped host meant two connection timeouts per agent per run to leave the pin exactly as
    it was. 'ai-tools --relabel' still re-verifies the signature every time, which is what an operator
    runs it for; a pin recording no digest is never reused.
  - FIX: A package upgrade reports honestly what it did with the shipped skills and agents. The four
    withdrawn documentation skills were reported "up to date" and retired seconds later in the same
    transaction, "seeded (vN)" carried the previous asset's version rather than the new one, and the
    update question was drawn on dnf's terminal where nothing could answer it. No host ends an
    upgrade in a different state; what an operator is shown while it happens is now correct.
  - CHANGE: The first-run screen an agent shows in a directory it cannot work in states each choice
    once, leads with the action rather than the refusal, and says which option does not start a
    session there. It has no default: an unattended or piped run cancels, and the cancel path prints
    both commands plainly below the frame where they can be copied.
  - CHANGE: 'ai-tools --help' is orientation -- the verbs grouped by what they are for, one line
    each, the three flags that cross verbs, and a pointer to ai-tools(1). The man page is now the one
    reference for options and gains full entries and examples for --project-create and
    --project-remove, which it had documented as aliases, and the runas grant a --for create or
    remove needs.
  - FIX: 'ai-tools --help' and 'ai-tools --version' run on a host whose install never finished. The
    provisioning gate refused them, so --help answered with a refusal naming ai-tools-bootstrap --
    the only place the command could still be found.
  - CHANGE: The project lifecycle guide follows the lifecycle: choose a model, create or claim, work,
    clone, park, release, delete, act for another operator, then the permission reference. Each
    section opens with a runnable command, and the guide now covers --for, the parked state and
    --keep-entry. It also separates --reclaim from unclaiming -- bringing a project back after an
    unclaim is --project-claim run again, not --reclaim -- and states which routes into the tree a
    release actually closes, in the order that matters. The README leads with the --project-create
    one-liner and keeps --project-claim beside it.
  - Upgrading from 0.13.x needs no action beyond dnf.


author: DagNode Package Signing <tools@dagnode.com> - 1.0.0-1
changelog:
  - Initial release: [dagnode] repository definition and the org signing key, so a new host
    installs the repo and its trust root in one `dnf install dagnode-release`.


