@synovitec/trellis 0.0.0-stage → 0.6.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +406 -0
  2. package/LICENSE +190 -0
  3. package/README.md +206 -2
  4. package/SECURITY.md +105 -0
  5. package/bin/trellis.mjs +5 -0
  6. package/docs/attestation/v0.1.md +96 -0
  7. package/package.json +78 -4
  8. package/schema/trellis.config.schema.json +349 -0
  9. package/src/agents/adapters.mjs +155 -0
  10. package/src/agents/git.mjs +241 -0
  11. package/src/agents/glob.mjs +41 -0
  12. package/src/agents/hooks.mjs +780 -0
  13. package/src/agents/install-adapters.mjs +172 -0
  14. package/src/agents/install.mjs +302 -0
  15. package/src/agents/manifests.mjs +127 -0
  16. package/src/agents/rules.mjs +330 -0
  17. package/src/agents/shell.mjs +253 -0
  18. package/src/agents/state.mjs +233 -0
  19. package/src/agents/transcript.mjs +120 -0
  20. package/src/cli/main.mjs +191 -0
  21. package/src/commands/attest.mjs +208 -0
  22. package/src/commands/checkpoint-state.mjs +62 -0
  23. package/src/commands/doctor.mjs +98 -0
  24. package/src/commands/guide.mjs +62 -0
  25. package/src/commands/init.mjs +131 -0
  26. package/src/commands/key.mjs +31 -0
  27. package/src/commands/scan.mjs +51 -0
  28. package/src/commands/uninstall.mjs +36 -0
  29. package/src/commands/verify.mjs +510 -0
  30. package/src/core/abatty.mjs +25 -0
  31. package/src/core/agents-block.mjs +65 -0
  32. package/src/core/attestation.mjs +213 -0
  33. package/src/core/config.mjs +135 -0
  34. package/src/core/context.mjs +280 -0
  35. package/src/core/diff.mjs +189 -0
  36. package/src/core/evidence.mjs +167 -0
  37. package/src/core/mcp.mjs +51 -0
  38. package/src/index.mjs +11 -0
  39. package/src/mcp/client.mjs +164 -0
  40. package/src/mcp/launch.mjs +57 -0
  41. package/src/mcp/relay.mjs +111 -0
  42. package/src/mcp/server.mjs +210 -0
  43. package/src/mcp/trust.mjs +142 -0
  44. package/src/tools/registry.mjs +370 -0
  45. package/src/tools/roles.mjs +17 -0
  46. package/src/verify/deps.mjs +382 -0
  47. package/src/verify/gitleaks-default.toml +3211 -0
  48. package/src/verify/rules.mjs +177 -0
  49. package/src/verify/secrets.mjs +169 -0
  50. package/src/verify/test-commands.mjs +116 -0
package/README.md CHANGED
@@ -1,3 +1,207 @@
1
- # Temporary Holding Version
1
+ # Trellis
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Proof that a change got the checks your repository asks for, whoever made it.** Coding agents
4
+ (Claude Code, Cursor, Codex, Copilot, any agent that speaks MCP) and people alike: Trellis gives
5
+ them the repository's checks as tools, records what really ran, holds the commit to the rules, and
6
+ has CI verify every commit again on another machine.
7
+
8
+ ```console
9
+ $ npx @synovitec/trellis doctor
10
+ Trellis is not set up in this repository.
11
+ → npx @synovitec/trellis init --agents (add --dry-run to see what it writes first)
12
+ ```
13
+
14
+ An `AGENTS.md` file states what an agent should do; nothing checks that it did. Agents skip what
15
+ they are told to do, claim checks they never ran, and edit the tests that block them (see
16
+ [the evidence](#why-these-rules)). Trellis checks the actions, not the words: a check counts only
17
+ if Trellis saw it run, on the exact content being committed.
18
+
19
+ ## Start
20
+
21
+ ```bash
22
+ npm i -D @synovitec/trellis
23
+ npx @synovitec/trellis init --agents # --dry-run first to see every file it writes
24
+ npx @synovitec/trellis key --add # your public key, so CI trusts your commits' attestations
25
+ npx @synovitec/trellis doctor # what is in place, and the command for what is not
26
+ ```
27
+
28
+ Then in CI (GitHub Actions; the same command runs anywhere), as a workflow of its own,
29
+ `.github/workflows/trellis.yml`:
30
+
31
+ ```yaml
32
+ name: trellis
33
+ # pull_request_target: GitHub runs this file as it stands on the base branch, so a pull request
34
+ # cannot edit the check that judges it. Safe here because no step runs the pull request's code:
35
+ # Trellis reads it, with git and its own pinned code. Add no other step to this workflow.
36
+ on:
37
+ pull_request_target:
38
+ permissions:
39
+ contents: read
40
+ jobs:
41
+ verify:
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@<commit>
45
+ with:
46
+ fetch-depth: 0
47
+ ref: ${{ github.event.pull_request.head.sha }}
48
+ persist-credentials: false
49
+ - uses: Synovitec/trellis@<commit>
50
+ ```
51
+
52
+ In the default branch's ruleset, require a pull request and the `trellis / verify` check: then
53
+ every change is judged where it is made, on its pull request. The workflow does not run on
54
+ pushes to the default branch: a squash or rebase merge writes new commits there that no one
55
+ attested, by design. With `on: pull_request` instead, a pull request runs its own copy of the
56
+ workflow and can remove the check; if you must use it, protect `.github/` with CODEOWNERS and a
57
+ required review. A private repository needs nothing more: the action reads the attestations with
58
+ the workflow's token (input `token`), even with `persist-credentials: false`. A pull request from
59
+ a private fork is the exception: the base's token cannot read the fork, so its attestations do
60
+ not arrive and its commits read as unattested; with `verify.unattested: "judge"` they are judged
61
+ on their own.
62
+ GitHub's "Update branch" with rebase rewrites every commit of the pull request on GitHub, with
63
+ no attestation: use "Update with merge commit", or `verify.unattested: "judge"`.
64
+
65
+ Contributors without Trellis (a fork's contributor, Dependabot, Renovate, GitHub's web editor)
66
+ make commits with no attestation, which CI refuses by default. With `"verify": { "unattested":
67
+ "judge" }` (read at the base), such a commit is judged on its own instead, as an agent's: its
68
+ secrets, its dependencies, the protected tests and the net change still block; a lockfile bump
69
+ passes, an edit to an existing test or a workflow does not (Dependabot's GitHub Actions updates
70
+ edit workflows). For those, a maintainer takes the change over: check out the branch, read the
71
+ change, `git commit --amend --no-edit` (the hooks attest it as the maintainer's) and push.
72
+
73
+ ## How it works
74
+
75
+ ```
76
+ agent or person ──► the checks, as tools ──► evidence log ──► git commit ──► attestation ──► CI
77
+ (MCP gateway, CLI, (signed, (pre-commit: (git note, (verify --ci:
78
+ agent hooks) chained) the rules) signed) again, itself)
79
+ ```
80
+
81
+ 1. **The checks as tools.** `trellis mcp` is an MCP server every agent can use: the code graph,
82
+ the tests, an exit to a person, and every MCP server in `.mcp.json` relayed through it
83
+ (Context7, Postgres, Playwright...), each once a person trusted it with `trellis mcp trust`:
84
+ `.mcp.json` is a file a pull request can change. Trellis runs each call and records it itself. For Claude
85
+ Code, Cursor, Codex and Copilot, their hooks also refuse an edit made before its lookup.
86
+ 2. **The evidence log.** One append-only log per repository, each line signed and chained to the
87
+ one before, so an edited, removed or reordered line reads as tampering. A content check (tests,
88
+ a browser check) records the git blob of every file it ran on, and counts only while the file
89
+ still has that content.
90
+ 3. **The commit checkpoint.** The pre-commit hook runs `trellis verify` on the staged change: the
91
+ change by content (two trees compared, however the work arrived), Trellis's own secret scan,
92
+ each added dependency checked on its registry and against OSV at its exact version, and the
93
+ rules below. A blocking rule refuses the commit.
94
+ 4. **The attestation.** The post-commit hook signs an in-toto statement about the commit and its
95
+ tree (DSSE, the developer's ed25519 key): the checkpoint's verdict (run by the post-commit
96
+ itself when no pre-commit saw the commit: a cherry-pick, a revert, a conflicted rebase,
97
+ `--no-verify`), or `unverified` when no Trellis hook ran at all, and the checks seen: file paths, blob ids and the test command in clear,
98
+ queries and messages as digests, never a prompt or a file's content. It travels as a git note,
99
+ `refs/notes/trellis`, pushed by the pre-push hook. Format: [docs/attestation/v0.1.md](docs/attestation/v0.1.md).
100
+ 5. **CI, the authority.** `trellis verify --ci` checks every new commit: an attestation signed by a
101
+ key under `.trellis/keys/`, about that exact commit and tree; the secret and dependency checks
102
+ run again there; the rules judged again there from the commit and the recorded evidence. The
103
+ local verdict is read, never believed. The keys and the rules are read at the base commit, so
104
+ a pull request cannot add the key that signs it or relax the rules that judge it. The action
105
+ runs its own code, at the commit you pin, never a copy from the repository under check, and
106
+ installs gitleaks 8.30.1 checked by SHA-256 (Linux runners; elsewhere, put gitleaks on the
107
+ PATH first). It can write a SLSA verification summary
108
+ (`--summary`), which the action can sign with GitHub artifact attestations (`attest: true`).
109
+
110
+ ## The rules
111
+
112
+ Blocking rules are the deterministic ones; the rest warn. Two presets: `recommended` (default) and
113
+ `strict`.
114
+
115
+ | Rule | Who | recommended | Met by |
116
+ | --- | --- | --- | --- |
117
+ | `secrets-scanned` | everyone | block | Trellis's gitleaks over the content of every file the change adds or modifies (UTF-16 read as text, gzip unpacked), under neutral names so that no path is skipped; the team's committed allowlist applies, an inline `gitleaks:allow` or an allowlist the change writes does not. Archives other than gzip and Git LFS content (only its pointer is in git) are not opened |
118
+ | `deps-verified` | everyone | block | each added or moved dependency exists on its registry, at a published version, with no known vulnerability (OSV); a lockfile alias is checked as the package it installs |
119
+ | `deps-registry` | everyone | warn (block in strict) | every added dependency was checked on a registry: one installed from git, a URL or another registry, an ecosystem Trellis does not query (Cargo, Gem, Composer), a manifest or lockfile it does not read yet (setup.py, Pipfile, Maven, Gradle, NuGet, pubspec, Deno, conda, pnpm, yarn, bun and others), registry settings, overrides and included requirements files are named |
120
+ | `tests-protected` | agents | block | existing test, grader and CI files are not changed or deleted by an agent unless a person approves that exact content; adding tests is welcome |
121
+ | `escalation` | agents | block | an agent that ran `trellis escalate "<why>"` is answered by a person (`trellis approve`, in a terminal) before its change lands |
122
+ | `tests-run` | everyone | warn (block in strict) | a passing run of exactly the project's own test command (no filter of the agent's own), through Trellis, with an environment cleared of what steers a runner (`npm_config_*`, `PYTEST_ADDOPTS`, `NODE_OPTIONS`, ~/.npmrc) and no runner setting git ignores present, on exactly the tree committed (any file changed after the run, a test loosened and restored included, needs a new run); the command is `verify.tests`, or read from the project (its test script, pytest, go test ./..., cargo test), and CI reads it at the base. A project where none of these is found accepts any command until `verify.tests` names one |
123
+ | `graph-before-edit` | agents | warn | a code-graph query before a change spanning several files |
124
+ | `docs-looked-up` | agents | warn | a documentation lookup naming each newly added dependency |
125
+ | `ui-verified` | everyone | warn | a browser check of the current content of each changed UI file |
126
+ | `schema-before-change` | agents | warn | a schema lookup before a migration or model change |
127
+ | `review`, `plan-before-change` | agents | off (warn in strict) | an independent review; a plan before a large change |
128
+
129
+ ```json
130
+ {
131
+ "tools": { "graphify": true, "context7": true },
132
+ "verify": { "preset": "recommended", "rules": { "tests-run": "block" } }
133
+ }
134
+ ```
135
+
136
+ An unknown rule or tool is an error: a typo cannot switch a check off silently.
137
+
138
+ ### Why these rules
139
+
140
+ Each rule is grounded in published evidence; [docs/design/0001-evidence-v2.md](docs/design/0001-evidence-v2.md)
141
+ gives the sources. In short:
142
+
143
+ - Frontier models cheat on tasks whose tests contradict the spec (GPT-5 54% of the time on an
144
+ impossible SWE-bench variant); read-only tests stop test editing, and an "ask a person" exit cut
145
+ cheating from 54% to 9% (ImpossibleBench, 2025). Hence `tests-protected` and `escalation`.
146
+ - Models claim tool runs they never made (Transluce, 2025): checks count only from Trellis's own
147
+ record.
148
+ - Models invent packages (5 to 22% of package references, USENIX Security 2025) and pin vulnerable
149
+ versions: `deps-verified`.
150
+ - A blocking check must have almost no false positives, or developers stop trusting it (Google,
151
+ 2018): heuristic rules warn.
152
+ - Long context files do not raise agents' success and cost more (ETH Zurich, 2026): the
153
+ `AGENTS.md` block Trellis writes stays short.
154
+
155
+ ## Agents
156
+
157
+ | Agent | Tools (MCP gateway) | Edit refused before its lookup (hooks) | Commit checkpoint and CI |
158
+ | --- | --- | --- | --- |
159
+ | Claude Code | `.mcp.json` | `.claude/settings.json` | yes |
160
+ | Cursor | `.cursor/mcp.json` | `.cursor/hooks.json` (fails closed) | yes |
161
+ | Codex | `.codex/config.toml` | `.codex/hooks.json` (trusted projects) | yes |
162
+ | Copilot | `.vscode/mcp.json`, `.mcp.json` | `.github/hooks/trellis.json` | yes |
163
+ | any MCP agent | its own config | - | yes |
164
+ | a person | - | - | yes |
165
+
166
+ `init --agents` writes the files for Claude Code and for the agents it finds here (their folder in
167
+ the repository, or their command on the machine); `--agents=cursor,codex` names them.
168
+
169
+ ## Commands
170
+
171
+ | Command | What it does |
172
+ | --- | --- |
173
+ | `trellis init [--dry-run] [--agents[=list]]` | the config, MCP servers, the AGENTS.md block, the agent hooks, and the git hooks of the checkpoint |
174
+ | `trellis doctor [--strict] [--all] [--json]` | the checkpoint first, then every enabled tool: what is in place, and the command for what is not |
175
+ | `trellis verify` | the staged change against the rules (what the pre-commit runs) |
176
+ | `trellis verify --ci [--base <rev>] [--summary <file>] [--checksums <file>]` | every commit after the base, judged again |
177
+ | `trellis check tests -- <command>` | run the tests and record what they ran on |
178
+ | `trellis escalate "<why>"` | an agent stops and hands the change to a person |
179
+ | `trellis approve` | a person approves the change as it is now (refused without a terminal) |
180
+ | `trellis key [--add]` | this developer's public key; `--add` puts it under `.trellis/keys/` |
181
+ | `trellis mcp` | the MCP gateway |
182
+ | `trellis mcp trust` | a person trusts the `.mcp.json` servers the gateway may start, entry by entry (in a terminal) |
183
+ | `trellis uninstall [--dry-run]` | takes the hooks out; keeps the config |
184
+ | `trellis scan`, `guide`, `tools` | the scanners at once, which tool answers which question, every tool known |
185
+
186
+ Exit codes: 0 ok, 1 something to fix, 2 bad usage.
187
+
188
+ ## Limits
189
+
190
+ - Trellis checks actions, not reasoning: a poor graph query still counts as a query.
191
+ - On a developer's machine, an agent runs as the same user as Trellis: one determined to forge
192
+ evidence can. That is why CI re-runs the deterministic checks and re-judges the rules; see
193
+ [SECURITY.md](SECURITY.md). For an untrusted agent, run it in a sandbox or as another user.
194
+ - Each agent's hooks can be switched off by its user; a hook that did not run is never read as
195
+ "nothing to check": the commit checkpoint and CI do not depend on them.
196
+ - Git notes are not pushed or fetched by default: the pre-push hook pushes them (merging a
197
+ colleague's first, line by line), the action fetches them from origin and, for a pull request
198
+ from a fork, from the fork. A host that drops notes leaves the commit unattested, which CI
199
+ reports.
200
+ - A merge commit is checked by its own part (a resolved conflict, an edit made in the merge), and
201
+ CI refuses any file of the net change that no commit of the range produced, such as old content
202
+ a merge took from an ancestor; a
203
+ rebased commit keeps its attestation when its change is byte for byte the one attested, else
204
+ the checkpoint runs again on it. A squash merge made on the host is a new commit no
205
+ one attested: judge the pull request, not the squashed commit on the default branch.
206
+
207
+ No runtime dependency. Node 20 or later. Apache-2.0.
package/SECURITY.md ADDED
@@ -0,0 +1,105 @@
1
+ # Security
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report a vulnerability privately, through GitHub's "Report a vulnerability" on this
6
+ repository (Security tab), not in a public issue. Include the version, what an attacker can do,
7
+ and the smallest reproduction you have. You will get an answer within five working days.
8
+
9
+ A bypass of the rules (a way for an agent or a person to land a change that does not meet them, to
10
+ make a check count that did not run, or to make CI pass a commit it should refuse) is in scope and
11
+ welcome: each one becomes a test.
12
+
13
+ ## The trust model
14
+
15
+ Trellis has two places, and trusts them differently.
16
+
17
+ **On a developer's machine**, Trellis runs as the same user as the coding agent it checks. There it
18
+ is a seatbelt for an agent that cuts corners, not a sandbox for one that attacks:
19
+
20
+ - evidence is recorded by Trellis itself: the gateway runs the tool and sees the answer; the CLI
21
+ runs the tests; an agent hook records only a call the agent logged (Claude Code) or that passed
22
+ through the hook;
23
+ - the gateway starts a server of `.mcp.json` only once a person trusted that exact entry
24
+ (command, arguments, environment, folder) with `trellis mcp trust` in a terminal; an entry a
25
+ pull request adds or changes is held, so checking out a branch never runs its servers;
26
+ - the evidence log is append-only, each line signed (a per-user key outside the repository) and
27
+ chained to the one before: an edited, removed or reordered line reads as tampering;
28
+ - a change is read by content (two git trees compared), never by a commit history the agent can
29
+ shape: a push, a fetch from a side clone, a tag or a rebase does not hide anything;
30
+ - the shell guard (Claude Code, Cursor, Codex, Copilot hooks) refuses what would forge evidence or
31
+ remove Trellis: reading the key, writing the log or the transcripts, running a hook or
32
+ `trellis approve` / `checkpoint` / `mcp trust` / `uninstall` / `init` by hand, `git clean -x`, an install
33
+ without devDependencies, moving a remote-tracking ref, index flags. It reads paths as the shell
34
+ does, after quotes and escapes;
35
+ - every check fails closed: a log that does not verify, a scanner that could not run, a registry
36
+ that did not answer, a broken config, a hook whose Trellis is gone (it exits 2) is a finding,
37
+ never a pass. One is a warning rather than a block: a secret scan that could not run on a
38
+ developer's machine where the team did not switch gitleaks on; CI, with its own gitleaks, blocks;
39
+ - the attestation of each commit is signed with the developer's ed25519 key.
40
+
41
+ An agent determined to forge evidence with the user's own permissions can still do it: read the key
42
+ through an indirection the guard does not recognise, write a log line, sign an attestation, or
43
+ run a person's step (`trellis approve`) under a pseudo-terminal through a command the guard does
44
+ not parse (a name assembled at run time). The guard is a list of patterns, not a sandbox. Every hook fails closed when Trellis is missing or
45
+ crashes; Codex's Windows hook is itself a `node -e` line, so without node at all it cannot run,
46
+ and Codex reads that as an error, not a refusal. Code
47
+ that checks itself cannot catch every change to itself. That is why the machine is not the
48
+ authority.
49
+
50
+ **In CI**, on another machine, `trellis verify --ci` is the authority, provided the pull request
51
+ cannot edit the workflow that runs it: on GitHub, run it on `pull_request_target` (the base
52
+ branch's copy of the workflow runs) and make its check required, as the README's workflow does.
53
+ On `pull_request`, a pull request runs its own copy and can remove the step. No Trellis step runs
54
+ the pull request's code, which is what makes `pull_request_target` safe here; a step added to
55
+ that workflow that does would not be.
56
+
57
+ - every commit must carry an attestation signed by a key the repository holds (`.trellis/keys/`,
58
+ committed and reviewed like code), about that exact commit and tree; none, an unknown key, a
59
+ copied or an `unverified` attestation is refused;
60
+ - the deterministic checks are run again there: the secret scan and every dependency against its
61
+ registry and OSV;
62
+ - the rules are judged again there, from the commit's own diff and the recorded evidence (an event
63
+ counts for a file only while the blob it holds is the blob committed). The local verdict is read,
64
+ never believed.
65
+
66
+ What CI cannot know is whether a recorded graph query, documentation lookup or test run really
67
+ happened on the developer's machine: those are the heuristic and evidence rules, and the
68
+ attestation's signature only proves they were recorded by someone holding that developer's key.
69
+ They warn by default; the deterministic rules, which CI re-runs itself, are the ones that block.
70
+ The same holds for who made a commit: CI reads "agent" or "person" from the attestation, so an
71
+ agent holding the developer's key can sign its commit as a person's, and the agent rules
72
+ (`tests-protected`, `escalation`) then do not apply to it. The keys, the rules, the allowlist and
73
+ the test commands CI goes by are the base's, so a pull request cannot change them for itself.
74
+ For an untrusted agent, run it in a sandbox, a container or as a separate user, where the key and
75
+ Trellis's code are out of its reach.
76
+
77
+ **What Trellis does not do**: judge whether a tool was used *well*, see work outside the repository,
78
+ or stop a person from changing the rules (the config and `.trellis/keys/` are reviewed in pull
79
+ requests like the code they protect).
80
+
81
+ ## Privacy
82
+
83
+ The attestation travels with the repository, so it carries what the repository already shows,
84
+ and digests of the rest. In clear: the paths of the files each check ran on, git object ids, the
85
+ names of the tools called, the agent's self-reported name, the times, and the test command line
86
+ (CI checks it is one of the project's test commands). As SHA-256 only: what a check was about
87
+ (a query, package names, a URL) and each finding's message. The hash is not salted: it keeps the
88
+ text out of sight, not out of reach, since anyone can hash a guess (a package name, a common
89
+ query) and compare. Treat these as names, not secrets. Never: a prompt, a file's content or a
90
+ tool's answer. The local evidence log also holds query subjects in clear (to match a docs
91
+ lookup to a dependency) and stays in the git folder, never pushed.
92
+
93
+ ## Supply chain
94
+
95
+ - No runtime dependency. abatty is an optional peer dependency, run from the project's own copy.
96
+ - Hooks call the installed script with `node`, never a bare `npx trellis` (an unrelated package on
97
+ the registry). `.mcp.json` runs the project's copy by path (`node
98
+ node_modules/@synovitec/trellis/bin/trellis.mjs mcp`); the Cursor, Codex and Copilot configs use
99
+ `npx --no-install @synovitec/trellis mcp`, which runs the project's copy and never downloads
100
+ another.
101
+ - Every MCP server Trellis writes is pinned (the Postgres MCP by image digest). The Stop hook runs
102
+ the pinned AgentShield from an empty folder, so a copy planted in the repository is never run.
103
+ - The GitHub Action pins every action it uses to a commit.
104
+ - Releases are published from CI with npm provenance, by trusted publishing: no publish token is
105
+ stored anywhere.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../src/cli/main.mjs";
3
+
4
+ // Commands that use the network return a promise; the others an exit code.
5
+ process.exitCode = await main(process.argv.slice(2));
@@ -0,0 +1,96 @@
1
+ # Trellis check-evidence predicate, v0.1
2
+
3
+ Predicate type: `https://github.com/Synovitec/trellis/blob/main/docs/attestation/v0.1.md`
4
+
5
+ A Trellis attestation says, for one git commit, which of the repository's rules applied, what the
6
+ commit checkpoint concluded, and which checks were seen before it. It is an
7
+ [in-toto Statement v1](https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md) in a
8
+ [DSSE](https://github.com/secure-systems-lab/dsse) envelope (`payloadType`
9
+ `application/vnd.in-toto+json`), signed with the committing developer's ed25519 key, and stored as a
10
+ git note on the commit under `refs/notes/trellis`, one envelope per line (JSON lines). A note is
11
+ only ever added to: two developers' notes on one commit merge line by line
12
+ (`git notes merge -s cat_sort_uniq`), and each line stands or falls on its own signature.
13
+
14
+ It records what was checked, not who wrote what: it can be linked to attribution records (Git AI
15
+ notes, Agent Trace) and to OpenTelemetry traces, and does not replace them.
16
+
17
+ ## Subject
18
+
19
+ One resource descriptor: the commit, by its git object ids.
20
+
21
+ ```json
22
+ { "name": "git commit", "digest": { "gitCommit": "<commit id>", "gitTree": "<tree id>" } }
23
+ ```
24
+
25
+ `gitCommit` and `gitTree` are the in-toto DigestSet names for git object ids (SHA-1 or SHA-256).
26
+
27
+ ## Predicate
28
+
29
+ ```json
30
+ {
31
+ "verifier": { "name": "trellis", "version": "0.6.0" },
32
+ "actor": "agent",
33
+ "agent": "codex",
34
+ "preset": "recommended",
35
+ "verdict": "pass",
36
+ "findings": [{ "rule": "tests-run", "mode": "warn", "messageSha256": "<hex>" }],
37
+ "events": [
38
+ { "kind": "graph", "tool": "graphify", "agent": "codex", "at": 1793000000000, "subjectSha256": "<hex>" },
39
+ { "kind": "tests", "tool": "npm test", "agent": "codex", "at": 1793000060000, "result": 0, "blobs": { "src/a.js": "<blob id>" } }
40
+ ]
41
+ }
42
+ ```
43
+
44
+ | Field | Meaning |
45
+ | --- | --- |
46
+ | `verifier` | the Trellis version that wrote it |
47
+ | `actor` | `agent` when an agent's work led to the commit (named by a hook, the gateway, or the agent's environment), else `person` |
48
+ | `agent` | the agent's self-reported name, or null; a label, never a security decision |
49
+ | `preset` | the rule preset in force: `recommended`, `strict`, or `unknown` when the checkpoint did not run |
50
+ | `verdict` | `pass` or `fail`: the checkpoint's verdict on exactly the committed tree, from the pre-commit, or from the post-commit when no pre-commit saw the commit (a cherry-pick, a revert, a conflicted rebase, `--no-verify`); `unverified` when the evidence log no longer verified, so nothing could be judged |
51
+ | `findings` | the rules not met at the checkpoint, by rule id and mode, the message as a SHA-256 |
52
+ | `rewrittenFrom` | present on a rebased or amended commit: the commit it was rewritten from. The old attestation is carried over only when the change (each file and the blob it ends with) is exactly the old one's; otherwise the checkpoint runs again on the new commit |
53
+ | `events` | the checks recorded since the previous commit: `kind` (graph, docs, schema, browser, tests, review, plan, approval, escalation), the tool, the agent, the time (ms), an exit code where there is one, the git blob id of each file a content check ran on, and what it was about as a SHA-256 |
54
+
55
+ In clear: file paths, git object ids, tool names, the agent's name, times, exit codes and the test
56
+ command line (CI checks it is one of the project's test commands). As SHA-256, unsalted (a guess can be
57
+ hashed and compared, so these are names, not secrets): what a check was about and each finding's
58
+ message. Never included: a prompt, a file's content or a tool's answer.
59
+
60
+ ## Verification
61
+
62
+ `trellis verify --ci --base <commit> [--head <commit>]` checks, for every commit after the base
63
+ up to the head (a pull request's head, never the merge commit a CI checkout builds):
64
+
65
+ 1. a note under `refs/notes/trellis` holds an envelope whose signature verifies with a public key
66
+ committed under `.trellis/keys/` at the base (a key the range adds is trusted once merged);
67
+ an envelope that does not verify is skipped, never read as the commit's;
68
+ 2. the statement's subject is that commit and its tree;
69
+ 3. the verdict is `pass`: `unverified` (nothing could be judged) and `fail` (the checkpoint
70
+ refused the commit, which was made anyway) are refused;
71
+ 4. Trellis's own secret scan and dependency checks, run again on the commit's diff, pass;
72
+ 5. the rules, judged again from the commit's diff and the recorded events (an event counts for a
73
+ file only while the blob it holds is the blob committed), pass. A merge commit's diff is its
74
+ own part: the files that differ from every parent. What each side brought is judged on its
75
+ own commits, which are in the range;
76
+ 6. the range's net change (what merging the head into the base gives, by `git merge-tree`, git
77
+ 2.38 or later; else the head against every merge base) holds only content a
78
+ commit of the range produced: a file a merge took from elsewhere (an old ancestor of the base,
79
+ a branch outside the range) was judged by no one, and is refused;
80
+ 7. that net change, judged as one change against the base, meets the deterministic rules and
81
+ tests-protected (the agent rules over the files an agent's commit made, with the approvals the
82
+ range recorded): a branch from an old ancestor can "add" a test the base already has, or keep a
83
+ dependency the base upgraded, which against the base is an edit and a downgrade.
84
+
85
+ The local verdict is read, never believed: points 4 and 5 are the verifier's own judgement. The
86
+ result can be written as a SLSA `verification_summary/v1` (no SLSA level is claimed:
87
+ `SLSA_BUILD_LEVEL_UNEVALUATED`), whose subjects are each commit as the SHA-256 of its raw object
88
+ (`git cat-file commit <id> | sha256sum`), so that SHA-256-only tooling such as GitHub artifact
89
+ attestations can sign it.
90
+
91
+ ## Trust
92
+
93
+ A developer's signature proves the attestation was not altered after Trellis wrote it on that
94
+ developer's machine. It does not prove that an agent running as the same user did not write it:
95
+ that is why CI re-runs the deterministic checks and re-judges the rules. See
96
+ [SECURITY.md](../../SECURITY.md).
package/package.json CHANGED
@@ -1,6 +1,80 @@
1
1
  {
2
2
  "name": "@synovitec/trellis",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.6.0-rc.3",
4
+ "description": "Proof that a change got the checks your repository asks for, whoever made it: AI coding agents (Claude Code, Cursor, Codex, Copilot, any MCP agent) and people, held at the commit and verified again in CI with signed in-toto attestations.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Synovitec/trellis.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/Synovitec/trellis/issues"
12
+ },
13
+ "keywords": [
14
+ "developer-experience",
15
+ "coding-agents",
16
+ "mcp",
17
+ "tooling",
18
+ "doctor",
19
+ "gitleaks",
20
+ "osv-scanner",
21
+ "playwright",
22
+ "abatty",
23
+ "claude-code",
24
+ "agent-hooks",
25
+ "ai-agents",
26
+ "guardrails",
27
+ "graphify",
28
+ "context7",
29
+ "postgres-mcp",
30
+ "attestation",
31
+ "in-toto",
32
+ "slsa",
33
+ "provenance",
34
+ "mcp-server",
35
+ "cursor",
36
+ "codex",
37
+ "github-copilot",
38
+ "pre-commit",
39
+ "supply-chain"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "provenance": true
44
+ },
45
+ "type": "module",
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "bin": {
50
+ "trellis": "bin/trellis.mjs"
51
+ },
52
+ "exports": {
53
+ ".": "./src/index.mjs",
54
+ "./tools": "./src/tools/registry.mjs",
55
+ "./schema": "./schema/trellis.config.schema.json"
56
+ },
57
+ "files": [
58
+ "bin/",
59
+ "src/",
60
+ "schema/",
61
+ "docs/attestation/",
62
+ "README.md",
63
+ "CHANGELOG.md",
64
+ "SECURITY.md",
65
+ "LICENSE"
66
+ ],
67
+ "scripts": {
68
+ "test": "node scripts/test.mjs \"test/*.test.mjs\"",
69
+ "trellis": "node bin/trellis.mjs",
70
+ "doctor": "node bin/trellis.mjs doctor"
71
+ },
72
+ "peerDependencies": {
73
+ "abatty": ">=0.8.1"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "abatty": {
77
+ "optional": true
78
+ }
79
+ }
80
+ }