Skip to main content

agent-policy

Pure-function policy matrix for AI coding agents. Maps (repo, capability, context) to one of three modes: deny / require_approval / auto_allow.

Status: 0.1.16 alpha. The core evaluator API is stable for v0.1; additive wrapper helpers may still grow while the package is alpha.

Note: v0.1.10 is retained as a tag-only, unpublished release attempt; use 0.1.16 for installation and provenance verification.

Why

AI coding agents (Claude Code, Codex, Aider, and friends) need a single place to answer one question, the same way, every time:

"The agent wants to do X in repo Y — should I let it?"

agent-policy is that single place. It is deliberately tiny:

  • One pure functionevaluate(policy, repo, capability, context).
  • No I/O, no logging, no global state. The evaluator does not touch disk, network, or clocks. It is safe to call from a hook, a test, or a long-running daemon.
  • Fail-closed defaults. A missing default_mode is require_approval, unknown fields in policy files are rejected, and hard guardrails cannot be overridden by repo policy.

It does not parse shell commands, manage state, or send messages. Those belong in the wrapper layer that calls evaluate.

Optional runtime companion

agent-guard is the standalone static entry point for repositories touched by coding agents such as Codex, Claude Code, Aider, and similar tools. It answers the repository question:

"Does the repository content still obey the safety rules before hooks, CI, release, or publication?"

agent-policy is an optional advanced runtime companion for integrations that need action-level authorization:

"Given this repo, capability, and context, should the agent be denied, require approval, or be allowed?"

It is not required by agent-guard or a basic static setup. When an integration needs runtime admission control, it can call agent-policy in a hook or wrapper before an agent performs a side effect.

Role Tool Responsibility
Standalone static entry agent-guard Scan paths, text, API surfaces, and pinned digests for repository safety drift.
Optional advanced runtime companion agent-policy Decide whether a normalized agent action is deny, require_approval, or auto_allow.

agent-safety-toolkit-example is a reference integration for projects that intentionally use both tools; it is not required setup.

Install

pip install yui-agent-policy==0.1.16

From a source checkout, install the package in editable mode so both the library and examples/check.py can resolve import agent_policy:

pip install -e .

Requires Python 3.11+ (uses stdlib tomllib). The only runtime dependency is pydantic >= 2.

Quick start

from agent_policy import evaluate, PolicyMatrix, RepoPolicy

policy = PolicyMatrix(
    default_mode="require_approval",
    repo_policy=[
        RepoPolicy(
            repo="acme/app",
            ownership_class="internal",
            capabilities={
                "read": "auto_allow",
                "commit": "auto_allow",
                "push": "auto_allow",
                "shell": "require_approval",
            },
        ),
    ],
)

decision = evaluate(
    policy,
    repo="acme/app",
    capability="commit",
    context={"ownership_class": "internal"},
)

print(decision.mode)         # "auto_allow"
print(decision.reason)       # "repo_policy"
print(decision.matched_repo) # "acme/app"

Load the same policy from a TOML file:

from agent_policy import evaluate, load_policy_file

policy = load_policy_file("policy.toml")
decision = evaluate(policy, repo="acme/app", capability="commit")

evaluate also accepts a plain dict in the same shape as PolicyMatrix, which is convenient for tests and one-off scripts.

Decision model

Every call returns a frozen PolicyDecision with three fields:

Field Type Meaning
mode "deny" | "require_approval" | "auto_allow" What the caller should do.
reason "hard_guardrail" | "repo_policy" | "default_mode" | ... Which rule produced the decision.
matched_repo str | None The repo string that matched, or None.

Decisions are evaluated in this order:

  1. Hard guardrails — cannot be overridden by repo policy.
    • push.force → always deny.
    • merge.pr → always require_approval.
    • External first_write_to_repo on a mutating capability → require_approval. Read is not blocked.
  2. Repo policy match — every [[repo_policy]] entry for the requested repo is scanned (optionally gated by ownership_class). The first entry that declares the capability wins. Splitting a repo's policy across multiple entries is supported. Entries that can both match the same repo, ownership class, and capability must declare the same mode; contradictory overlaps fail validation instead of depending on list order.
  3. default_mode fallback — used when no repo policy declares the capability. Defaults to require_approval if unset.

HARD_GUARDRAILS remains an ordinary inspection dict for compatibility. Evaluation uses separate private immutable state, so mutating or rebinding the public copy cannot weaken the force-push guardrail.

Audit event schema

Wrappers that need logs or approval records can build a deterministic audit event from the same inputs they pass to evaluate:

from agent_policy import audit_event_to_json, build_audit_event, evaluate

context = {"ownership_class": "internal"}
decision = evaluate(policy, repo="acme/app", capability="shell", context=context)
event = build_audit_event(
    repo="acme/app",
    capability="shell",
    context=context,
    decision=decision,
    session_id="session-123",
    path="scripts/release.sh",
)

print(audit_event_to_json(event))

The event is an immutable value object with these fields:

Field Meaning
repo Repository identifier evaluated by the wrapper.
capability Normalized capability evaluated by the wrapper.
context Recursively copied, key-sorted JSON-compatible context used for the decision.
decision Nested PolicyDecision payload.
session_id, command, path Optional wrapper-supplied identifiers.

Installed wheels include two JSON Schema resources:

Resource Contract
agent_policy.schemas/agent-policy.audit_event.v1.schema.json Backward-compatible event shape. Optional wrapper strings remain unconstrained.
agent_policy.schemas/agent-policy.audit_event.v1.1.schema.json Opt-in stricter event shape with additive constraints for decision.matched_repo, session_id, command, and path.

Downstream wrappers can load either resource with importlib.resources to validate the event shape they write to logs, CI artifacts, or approval records:

from importlib import resources
import json

schema_text = (
    resources.files("agent_policy.schemas")
    .joinpath("agent-policy.audit_event.v1.1.schema.json")
    .read_text(encoding="utf-8")
)
schema = json.loads(schema_text)

The schemas describe the deterministic payload only; they do not prove that a human approval exists.

session_id, command, and path are serialized verbatim when supplied. The packaged .v1 schema intentionally accepts any string for those optional fields for backward compatibility. The packaged .v1.1 schema makes the recommended length and character constraints machine-checkable for consumers that opt in. Operators should still enforce public-safe values and keep them redacted before calling build_audit_event():

Field Recommended operator constraint
session_id 1-256 characters matching ^[A-Za-z0-9._:@/+~-]+$(?![\s\S]); the final lookahead requires true end-of-input.
command 1-4096 redacted characters with no control characters, using ^[^\x00-\x1f]+$(?![\s\S]).
path 1-1024 characters with no leading POSIX slash or control characters, using ^[^/\x00-\x1f][^\x00-\x1f]*$(?![\s\S]); producers should also reject parent traversal and alternate local-path syntax before treating it as repository-relative.

Do not pass private command transcripts, absolute local paths, secrets, or personal identifiers into these fields if the event may be stored or published. The bundled examples/check.py --audit-event producer enforces these stricter optional-field constraints before serialization, including rejection of parent traversal components, absolute POSIX paths, Windows drive or UNC paths, local home or environment shorthand, file URI syntax, empty strings, overlong values, and control characters.

Schema validation does not redact values, scan for secrets, reject parent traversal, reject alternate local-path syntax, or prove repository containment. Those checks remain producer-owned responsibilities.

agent-policy does not persist events, generate timestamps, create IDs, hash approvals, redact optional wrapper strings, normalize local paths, add a schema_version field, or verify approval records. Those remain wrapper-owned side effects so the evaluator stays pure and deterministic.

Policy file format

# policy.toml
default_mode = "require_approval"

[[repo_policy]]
repo = "acme/app"
ownership_class = "internal"

[repo_policy.capabilities]
read = "auto_allow"
commit = "auto_allow"
push = "auto_allow"

[[repo_policy]]
repo = "acme/app"                # same repo, extra constraint
[repo_policy.capabilities]
shell = "require_approval"

Unknown top-level fields or typos inside [[repo_policy]] fail loudly with a pydantic.ValidationError — there is no silent degradation. ownership_class is a closed optional gate: it accepts only internal or external; omit it (or use None in Python) to match either ownership class. Overlapping entries may split capabilities or repeat the same mode, but a wildcard or ownership-specific overlap that assigns different modes to the same capability is rejected.

Wrapper pattern

agent-policy deliberately does not know how to parse git push --force or a shell command line. The intended shape is:

           ┌────────────────────────┐
agent ───▶ │ wrapper (hook / CLI)   │ ──▶ agent-policy.evaluate()
           │  - normalize capability│         │
           │  - build context       │         ▼
           │  - act on decision     │   PolicyDecision
           └────────────────────────┘

The wrapper owns: parsing the agent's intent, mapping it to one of the MVP capabilities (read, write, commit, push, push.force, merge.pr, shell), and executing whatever side effect the decision implies (block, prompt for approval, log and allow).

A runnable minimal wrapper lives in examples/check.py.

Approval wrapper checklist

For require_approval decisions, keep the approval layer outside agent-policy but make the wrapper contract explicit. Production wrappers should:

  • Bind approval records to the exact capability, session, path, and command being executed. A command change after approval should fail closed.
  • Record the serialized audit event or a downstream hash of it in the approval record, then verify that the referenced event still exists before running the approved command.
  • Treat approvals as single-use for side-effecting operations such as artifact.publish; reserve a local use marker before executing the command so retry races cannot reuse the same approval.
  • Keep bypass corpora, private logs, .env* files, and red-team transcripts outside tracked paths, and add an independent scanner such as yui-agent-guard to CI.

Wrapper contract summary

The stable wrapper contract is:

  • evaluate() performs no I/O and has no approval, logging, or storage side effects.
  • Wrappers own command parsing, capability normalization, approval storage, logging, redaction, audit-event schema validation, and any human prompt.
  • examples/check.py maps decisions to process exits: 0 for auto_allow, 1 for wrapper/program errors, 2 for require_approval, and 3 for deny.
  • Agent-specific hooks may translate both require_approval and deny to the hook platform's blocking exit code when the platform has no inline approval state.
  • The example hooks take first-write state only from AGENT_POLICY_FIRST_WRITE=true|false, never from a hook payload. For an external mutating capability, it is required: true supplies --first-write, while false does not. Read-only Claude tools are unaffected.
  • --audit-event emits deterministic evidence for the decision that was made; it is not itself an approval record.

These checks belong in the wrapper/admission layer rather than the pure evaluator. The ai_resilience_policy.toml example shows the capability vocabulary; downstream repositories can combine it with their own approval record schema and CI gates.

Examples

See examples/. To run them from this source checkout, install it with pip install -e .; public users who need the released library should use pip install yui-agent-policy==0.1.16:

  • policy.toml — a minimal fail-closed policy with two repos.
  • ai_resilience_policy.toml — a safety-oriented vocabulary example for publication, constitution, audit, secret-materialization, and scanner policy changes. These remain repo-policy capabilities rather than hard guardrails until downstream wrappers prove they are universal invariants.
  • check.py — a tiny CLI wrapper that maps PolicyDecision to JSON on stdout and a process exit code, suitable for PreToolUse hooks. Pass --audit-event to emit the wrapper-owned audit event schema instead of the bare decision payload.
  • claude_code_hook.sh — a Claude Code PreToolUse hook that reads the hook payload from stdin, maps the tool to a capability, and shells out to check.py. Set AGENT_POLICY_FILE and AGENT_POLICY_REPO in the hook's environment, then point ~/.claude/settings.json at it. Unknown tools and wrapper failures block with fixed sanitized stderr.
  • codex_hook.sh — a Codex CLI PreToolUse hook (block-style shell guardrail pilot). This wrapper only maps bounded Bash commands: visible force-push forms to push.force, gh pr merge to merge.pr, and a finite allowlist of simple commands to shell. Other forms become unknown and block. Codex hooks themselves can match more than Bash; this example is intentionally narrower.
  • codex_permission_request_hook.sh — a Codex CLI PermissionRequest hook (delegation shell pilot). It returns allow for auto_allow, returns deny for deny, and returns no decision for require_approval, which delegates to Codex's normal approval prompt. Any wrapper failure returns a fixed protocol-valid deny JSON payload instead of exposing an error.
  • capability_map.py — stdlib-only helper that turns a raw Bash command into push.force / merge.pr / shell / unknown. The hook wrappers shell out to it instead of doing substring matching, so quoted literals like printf '%s\n' 'git push --force' no longer produce a false push.force classification. Active arithmetic, active unquoted brace or pathname expansion, every shell or environment assignment, state-mutating and callback-bearing builtins, xtrace wrappers, selected visible dynamic argv execution, path-qualified or unlisted command heads, and Git subcommands outside the bounded builtin allowlist become unknown and are rejected before policy evaluation. See the file header for the exact bounded flow: heredoc stripping, expansion screening, shlex tokenization, statement classification, and recursive bash -c / eval handling.

Codex CLI hooks — current contract notes

Codex hooks are default enabled. If you need an explicit feature setting, use features.hooks; older Codex-specific aliases should be avoided. Put hooks.json in ~/.codex/ or <repo>/.codex/.

Current PreToolUse matchers can target Bash, apply_patch, and MCP tool calls; apply_patch may also match through Edit / Write aliases. The examples here stay Bash-focused because they reuse capability_map.py, which only normalizes shell commands.

  • Block-style PreToolUse path. permissionDecision: "ask" is parsed but not supported by current Codex release behavior. A hook that returns it is reported as a hook run failure and the tool call continues. To stop execution from a PreToolUse hook, return permissionDecision: "deny", legacy decision: "block", or exit 2. examples/codex_hook.sh uses exit 2 for both deny and require_approval, with one fixed sanitized stderr message.
  • PermissionRequest delegation path. PermissionRequest runs just before Codex asks for approval. A hook can return allow or deny; if it returns no decision, Codex falls back to the normal approval flow. The codex_permission_request_hook.sh example uses that no-decision case for require_approval.
  • Heuristic command parsing. capability_map.py is shlex-based, not a full shell. It handles quoted literals, heredocs, compound statements, a finite simple-command allowlist, a bounded set of Git global options such as --git-dir=/path, and deliberate Git builtin and subcommand-option allowlists (status, add, commit, diff, fetch, push, and send-pack), and the common bash -c '...' / eval wrappers. Every push or send-pack without an explicit visible force option maps to unknown: command text cannot prove effective Git refspec, mirror, hook, helper, repository-selection, or TOCTOU state. Explicit visible force forms remain push.force. Active arithmetic is not interpreted, including $((...)), ((...)), legacy $[...], let, array/integer/nameref declarations, arithmetic parameter offsets, indexed-array or indirect parameter expansions, and variable-target builtins such as printf -v; these map to unknown because Bash may evaluate variable values and array subscripts recursively. Expanding heredocs are checked after active backslash-newline folding. Active unquoted brace or pathname expansion, selected visible xargs and find -exec-style argv generation, and Git subcommands outside that allowlist (including config alias mutation) also map to unknown. Unlisted options on a recognized Git subcommand also fail closed; this rejects command-bearing program selectors such as --receive-pack, --upload-pack, and --exec, including abbreviated forms. All leading assignments, assignments passed through env, assignment builtins, and set forms are rejected without a variable-name denylist. This covers startup selectors such as BASH_ENV, trace expansion through PS4, and Git transport selectors such as GIT_SSH_COMMAND. Callback-bearing builtins such as mapfile, readarray, and compgen are outside the allowlist. Interactive/login shell modes, xtrace, and explicit --rcfile / --init-file inputs are rejected because they can execute input before or around the inspected body. A modeled shell wrapper is accepted only when -c has exactly one inspected command body; trailing arguments or redirections are not modeled and therefore fail closed. Active output redirection at any statement position also fails closed, as do file-writing command heads such as tee, so one hook call cannot stage Git configuration, hooks, helpers, or policy state for another call. ANSI-C quoted words ($'...') are not modeled and fail closed because their escaped-quote rules differ from ordinary single quotes. Parameter-expanded builtin options fail closed, and expanding-heredoc delimiters are matched after backslash-newline folding so following commands cannot be mistaken for heredoc data. A standalone -exec, -execdir, -ok, or -okdir token in find argv is conservatively blocked even when it could be another primary's value; the example does not model full find expression arity. Unresolved parameter expansion anywhere in find argv is rejected for the same reason. A literal Git, gh, direct Git helper, or shell interpreter token behind an otherwise unmodeled command prefix is also conservatively blocked rather than treated as inert argument text. Leading input redirection, active output redirection anywhere, known execution prefixes such as exec, time, and nohup, grouping, and shell compound keywords also map to unknown. Forms such as git -c alias.p=push p --force, process substitution, or function definitions are not modeled. Command heads outside the finite allowlist, including general interpreters and build/test runners, map to unknown. Path-qualified heads such as /tmp/printf and ./cat also map to unknown; matching a basename is not proof that the referenced executable is the modeled command. Ambiguous, unbalanced, or unterminated parsing is rejected the same way. The helper classifies command text only and does not inspect the internals of bare allowlisted executables or inherited process state.

Releases

Tag-driven. Pushing a vX.Y.Z annotated tag triggers .github/workflows/release.yml, which verifies that the tag matches [project].version, points at the current master commit, and has a successful completed master push CI run. It also checks that the version is not already present on PyPI, then builds the sdist + wheel and publishes through PyPI Trusted Publishing (OIDC). No maintainer-side PyPI token is required. Manual workflow_dispatch with publish=false is a build-only dry run; it skips attestation and publication. Manual publish=true must run against a v* tag ref; running it from a branch fails before build.

After PyPI publication succeeds, the separate github-release workflow creates the GitHub Release notes from the verified tag commit, or verifies that an existing Release is already public and asset-free. The automatic path proceeds only after a fully successful tag-push release.yml run for the same annotated tag object and peeled commit.

Manual recovery must be dispatched from the repository's default branch for an exact vX.Y.Z tag. Successful 0.1.1 through 0.1.9 tag-push runs use the bounded historical path when their build and publish jobs succeeded and PyPI exposes the exact expected, non-yanked wheel and sdist. Historical releases do not require retained workflow artifacts or an attestation job that did not yet exist; the historical path also accepts the repository's v0.1.6 legacy lightweight tag when its exact commit is unchanged. Other successful versions must match the full current job topology and use an annotated tag. If a historical tagged tree predates CHANGELOG.md, a newly created Release uses a fixed provenance-only recovery note rather than invented feature notes. A failed current tag-push run is recoverable only from an annotated tag and only when the build, attestation, publisher step, retained artifact, and matching PyPI wheel/sdist byte evidence are all proven. When automatic run selection is ambiguous, the optional release_run_id input selects the exact run. Recovery fails closed when evidence required for the selected path is unavailable, ambiguous, or inconsistent. It never moves tags or reuploads distributions; immediately before creating or accepting the GitHub Release, it rechecks that the remote tag is unchanged.

The release build creates GitHub artifact attestations for dist/* before the publish job downloads those files. PyPI Trusted Publishing and the PyPA publish action also provide PyPI-side distribution attestations. These are provenance and integrity evidence for a specific artifact and workflow identity. They do not prove code correctness, dependency safety, maintainer approval, absence of secrets, or policy compliance.

To verify the GitHub provenance for downloaded 0.1.16 artifacts, check the tag, repository, and signer workflow explicitly:

(
set -euo pipefail
verify_dir="$(mktemp -d "${TMPDIR:-/tmp}/agent-policy-dist-verify.XXXXXX")"
trap 'rm -rf -- "$verify_dir"' EXIT
python - "$verify_dir" <<'PY'
import json
import shutil
import sys
import urllib.request
from pathlib import Path
from urllib.parse import urlparse

version = "0.1.16"
target = Path(sys.argv[1])
request_timeout_seconds = 20
metadata_url = f"https://pypi.org/pypi/yui-agent-policy/{version}/json"
with urllib.request.urlopen(metadata_url, timeout=request_timeout_seconds) as response:
    final_metadata_url = urlparse(response.geturl())
    if final_metadata_url.scheme != "https" or final_metadata_url.hostname != "pypi.org":
        raise SystemExit("PyPI release metadata URL is not an expected HTTPS host")
    release = json.load(response)
if not isinstance(release, dict):
    raise SystemExit("PyPI release metadata is malformed")
expected = {
    f"yui_agent_policy-{version}-py3-none-any.whl": "bdist_wheel",
    f"yui_agent_policy-{version}.tar.gz": "sdist",
}
files = release.get("urls")
if not isinstance(files, list) or len(files) != len(expected):
    raise SystemExit("PyPI release does not contain the exact expected artifact set")
by_name = {}
for file_info in files:
    if not isinstance(file_info, dict):
        raise SystemExit("PyPI release metadata is malformed")
    filename = file_info.get("filename")
    url = file_info.get("url")
    if (
        not isinstance(filename, str)
        or filename not in expected
        or file_info.get("packagetype") != expected[filename]
        or file_info.get("yanked") is not False
        or not isinstance(url, str)
    ):
        raise SystemExit("PyPI release metadata does not match the expected artifact contract")
    parsed = urlparse(url)
    if parsed.scheme != "https" or parsed.hostname != "files.pythonhosted.org":
        raise SystemExit("PyPI release artifact URL is not an expected HTTPS host")
    if filename in by_name:
        raise SystemExit("PyPI release metadata contains duplicate artifacts")
    by_name[filename] = url
if set(by_name) != set(expected):
    raise SystemExit("PyPI release does not contain the exact expected artifact set")
for filename in sorted(expected):
    with urllib.request.urlopen(
        by_name[filename], timeout=request_timeout_seconds
    ) as response:
        final_artifact_url = urlparse(response.geturl())
        if (
            final_artifact_url.scheme != "https"
            or final_artifact_url.hostname != "files.pythonhosted.org"
        ):
            raise SystemExit("Downloaded artifact URL is not an expected HTTPS host")
        with (target / filename).open("xb") as destination:
            shutil.copyfileobj(response, destination)
PY
gh attestation verify "$verify_dir/yui_agent_policy-0.1.16-py3-none-any.whl" \
  --repo yui-stingray/agent-policy \
  --signer-workflow yui-stingray/agent-policy/.github/workflows/release.yml \
  --source-ref refs/tags/v0.1.16
gh attestation verify "$verify_dir/yui_agent_policy-0.1.16.tar.gz" \
  --repo yui-stingray/agent-policy \
  --signer-workflow yui-stingray/agent-policy/.github/workflows/release.yml \
  --source-ref refs/tags/v0.1.16
)

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yui_agent_policy-0.1.16.tar.gz (131.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

yui_agent_policy-0.1.16-py3-none-any.whl (22.8 kB view details)

Uploaded Python 3

File details

Details for the file yui_agent_policy-0.1.16.tar.gz.

File metadata

  • Download URL: yui_agent_policy-0.1.16.tar.gz
  • Upload date:
  • Size: 131.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for yui_agent_policy-0.1.16.tar.gz
Algorithm Hash digest
SHA256 b33cb9d8664d400ec785ff5093ecec54eba70c68cc30437977095aa60e5f4b5b
MD5 4e8c34d4843ba8974107779f3ef7e754
BLAKE2b-256 5a008ad5dfc3557090bcb2a381f52fa9a139ab246bca30379cf6be4a942b4c74

See more details on using hashes here.

Provenance

The following attestation bundles were made for yui_agent_policy-0.1.16.tar.gz:

Publisher: release.yml on yui-stingray/agent-policy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file yui_agent_policy-0.1.16-py3-none-any.whl.

File metadata

File hashes

Hashes for yui_agent_policy-0.1.16-py3-none-any.whl
Algorithm Hash digest
SHA256 5f41b62fc8fc113470c84e2b6b92689aeb03046323674d52b010d46991010098
MD5 379b418d0dac0fe4ea40f11ae1b94f87
BLAKE2b-256 f1dbd0940be41a47d2f5d3e4897c9982802f57c369a7607cf4774d7382d9a9ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for yui_agent_policy-0.1.16-py3-none-any.whl:

Publisher: release.yml on yui-stingray/agent-policy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.16 This release

2 files

0.1.15

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

0.1.11

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page