Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:

- name: Check the product's own text
run: |
./zig-out/bin/zag lint README.md AGENTS.md docs/architecture.md \
./zig-out/bin/zag-audit lint README.md AGENTS.md docs/architecture.md \
docs/standards-conformance.md docs/plain-language.md \
docs/competitive-position.md docs/roadmap.md

Expand All @@ -70,7 +70,7 @@ jobs:

- name: Write the conformance statement, and check it matches what is committed
run: |
./zig-out/bin/zag evidence
./zig-out/bin/zag-audit evidence
# The statement records when it was produced, so that field differs on
# every run. Everything else must not: the results, the subjects and
# the hashes of the artefacts they are about are what the committed
Expand All @@ -80,15 +80,15 @@ jobs:
strip_time < evidence/conformance.jsonl > /tmp/produced.jsonl
git show HEAD:evidence/conformance.jsonl | strip_time > /tmp/committed.jsonl
if ! diff -u /tmp/committed.jsonl /tmp/produced.jsonl; then
echo "The conformance statement has changed. Run './zig-out/bin/zag evidence' and commit the result."
echo "The conformance statement has changed. Run './zig-out/bin/zag-audit evidence' and commit the result."
exit 1
fi

- name: Show the accessibility statement
run: ./zig-out/bin/zag accessibility
run: ./zig-out/bin/zag-audit accessibility

- name: Show the lifecycle record
run: ./zig-out/bin/zag lifecycle
run: ./zig-out/bin/zag-audit lifecycle

cross-compile:
name: compile ${{ matrix.target }}
Expand Down
82 changes: 73 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,27 +72,91 @@ zag ask --stream "..." # the same, printed as the answer arrives
zag secrets .env # what would be taken out before anything is recorded
zag why .workspace/events.jsonl # what a failure depended on, and what it affected
zag run -- zig build test # run a command and record it as a block
zag lint README.md # check text against the plain-language rules
zag terms workspace # what a word means here
zag standards # the standards in force, and their editions
zag check # audit this repository
zag card model # the model card, generated from the system
zag accessibility # the accessibility statement and the themes

zag shell-hook bash # the shell integration to add to your shell
zag workflow # this repository's own workflow, as a task graph
zag lifecycle # the lifecycle record, and what has not started
zag evidence # the conformance statement, from recorded evidence
zag history "status:failed zig" # search recorded work, not a text file
zag knowledge # the knowledge under .workspace/, and what is overdue
zag objects --root . # audit stored command output without changing it
zag recover --root . # inspect a damaged log and print a recovery plan
```

Settings live in `.workspace/settings.toml`. `zag emit` writes an example with
Settings live in `.workspace/settings.toml`. `zag-audit emit` writes an example with
every value at its default. A setting this build cannot read is reported by
name, with what was found and what is being used instead, and the terminal
opens anyway — a mistake in that file never costs you a shell.

## Ask a model

Two steps. Set a key, then write a policy:

```
export ANTHROPIC_API_KEY=... # or any connector; run "zag providers"
zag policy --init # writes .workspace/policy.toml
zag ask "why did the build fail?"
```

`zag doctor` says which of the two is missing. If a key is set but no policy
allows reaching the provider, it names the host and tells you what to run —
that is the case where nothing is broken, nothing is misspelt, and the only
symptom is a refusal at the moment of asking.

With no policy file the built-in one is in force: it allows a model running on
your own computer and nothing else. So if you use Ollama, none of this is
needed.

### The policy file

`.workspace/policy.toml` is a list of rules. Each `[[rule]]` names **one**
effect — `allow`, `ask` or `deny` — and lists the capabilities it applies to:

```toml
[[rule]]
id = "ask-a-model"
allow = ["model.infer"]
because = "This workspace may ask a model questions."

[[rule]]
id = "reach-the-provider"
allow = ["network.connect"]
hosts = ["api.anthropic.com"] # scoped: nowhere else may be reached
because = "The model runs there."

[[rule]]
id = "run-commands"
ask = ["process.execute"] # stops for a person, each time
because = "A person decides each command before it runs."
```

The effect is the key, not a value: it is `allow = [...]`, not
`effect = "allow"`. `because` is required and is what a person is shown when a
decision is made. A rule may be scoped by `hosts`, `paths` or `agents`; an
unscoped rule applies to every resource of that capability.

Anything no rule allows is refused. A file with a mistake anywhere in it is
refused *whole* — nothing is granted by being written badly — and `zag policy`
says which rule and why.

Capabilities: `fs.read`, `fs.write`, `fs.delete`, `process.execute`,
`process.signal`, `network.connect`, `network.listen`, `git.read`,
`git.commit`, `git.push`, `credentials.use`, `credentials.read`, `mcp.invoke`,
`container.start`, `remote.execute`, `model.infer`.

`zag-audit` is the second binary. It checks a repository against the standards
it claims to meet, and nothing it does is needed to record work:

```
zag-audit check # audit this repository; the release gate
zag-audit lint README.md # check text against the plain-language rules
zag-audit terms workspace # what a word means here
zag-audit standards # the standards in force, and their editions
zag-audit emit # regenerate the schemas, vocabularies and docs
zag-audit evidence # the conformance statement, from recorded evidence
zag-audit card model # the model card, generated from the system
zag-audit accessibility # the accessibility statement and the themes
zag-audit lifecycle # the lifecycle record, and what has not started
```

`zagd` holds a workspace open for clients, remote people and background agents:

```
Expand Down
37 changes: 26 additions & 11 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,29 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
.imports = &.{.{ .name = "zag", .module = zag }},
});
// The standards registry is data, and it ships inside the binary so that
// `zag standards` and `zag check` work from any directory.
cli_module.addAnonymousImport("registry_language", .{ .root_source_file = b.path("standards/registry/language.toml") });
cli_module.addAnonymousImport("registry_accessibility", .{ .root_source_file = b.path("standards/registry/accessibility.toml") });
cli_module.addAnonymousImport("registry_knowledge", .{ .root_source_file = b.path("standards/registry/knowledge.toml") });
cli_module.addAnonymousImport("registry_interoperability", .{ .root_source_file = b.path("standards/registry/interoperability.toml") });
cli_module.addAnonymousImport("registry_governance", .{ .root_source_file = b.path("standards/registry/governance.toml") });
cli_module.addAnonymousImport("registry_requirements", .{ .root_source_file = b.path("standards/registry/requirements.toml") });

const cli = b.addExecutable(.{ .name = "zag", .root_module = cli_module });
b.installArtifact(cli);

// ---- Repository audit tool -------------------------------------------
// Split out of `zag` so the workbench binary does not carry the standards
// registry, the plain-language rules or the conformance generator. The
// registry data ships here now, because these are the commands that read it.
const audit_module = b.createModule(.{
.root_source_file = b.path("src/audit/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{.{ .name = "zag", .module = zag }},
});
audit_module.addAnonymousImport("registry_language", .{ .root_source_file = b.path("standards/registry/language.toml") });
audit_module.addAnonymousImport("registry_accessibility", .{ .root_source_file = b.path("standards/registry/accessibility.toml") });
audit_module.addAnonymousImport("registry_knowledge", .{ .root_source_file = b.path("standards/registry/knowledge.toml") });
audit_module.addAnonymousImport("registry_interoperability", .{ .root_source_file = b.path("standards/registry/interoperability.toml") });
audit_module.addAnonymousImport("registry_governance", .{ .root_source_file = b.path("standards/registry/governance.toml") });
audit_module.addAnonymousImport("registry_requirements", .{ .root_source_file = b.path("standards/registry/requirements.toml") });
const audit = b.addExecutable(.{ .name = "zag-audit", .root_module = audit_module });
b.installArtifact(audit);

// ---- Workspace daemon ------------------------------------------------
const daemon = b.addExecutable(.{
.name = "zagd",
Expand Down Expand Up @@ -79,25 +90,29 @@ pub fn build(b: *std.Build) void {
const daemon_tests = b.addTest(.{ .root_module = daemon.root_module });
const run_daemon_tests = b.addRunArtifact(daemon_tests);

const test_step = b.step("test", "Run the library, command-line and daemon tests");
const audit_tests = b.addTest(.{ .root_module = audit.root_module });
const run_audit_tests = b.addRunArtifact(audit_tests);

const test_step = b.step("test", "Run the library, command-line, audit and daemon tests");
test_step.dependOn(&run_lib_tests.step);
test_step.dependOn(&run_cli_tests.step);
test_step.dependOn(&run_audit_tests.step);
test_step.dependOn(&run_daemon_tests.step);

// ---- Conformance gate ------------------------------------------------
// `zig build check` is the release gate: it runs the tests and then makes
// the tool audit the repository's own documentation, standards registry and
// metadata. A build that cannot state its own conformance evidence fails.
const fmt = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--check", "build.zig", "src" });
const self_check = b.addRunArtifact(cli);
const self_check = b.addRunArtifact(audit);
self_check.addArgs(&.{ "check", "--repo", "." });
self_check.step.dependOn(test_step);
const check_step = b.step("check", "Run tests, then audit the repository against its standards profile");
check_step.dependOn(&fmt.step);
check_step.dependOn(&self_check.step);

// ---- Generated interchange artefacts ---------------------------------
const emit = b.addRunArtifact(cli);
const emit = b.addRunArtifact(audit);
emit.addArgs(&.{ "emit", "--out", "." });
const emit_step = b.step("emit", "Regenerate schemas, vocabularies and the OpenAPI description");
emit_step.dependOn(&emit.step);
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0006-generated-not-maintained.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ model cards all describe the system. Where should they live?
## The decision

They are generated from the code and the data that already exist, by
`zag emit`. None of them is maintained by hand.
`zag-audit emit`. None of them is maintained by hand.

## Why

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0008-knowledge-lives-in-the-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ than the entries that use a word.
## What follows

- An agent is offered only knowledge that is approved and is not overdue.
- `zag check` audits `.workspace/` like anything else, and `zag evidence`
- `zag-audit check` audits `.workspace/` like anything else, and `zag-audit evidence`
records what that audit established.
- This repository keeps its own rules there, so the product is its own first
user.
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ knowledge that is approved and is not overdue.

An entry links to concepts rather than to words, so a search finds the entries
about a concept, not the ones that happen to use the term. This repository keeps
its own rules there, and `zag check` audits them like anything else.
its own rules there, and `zag-audit check` audits them like anything else.

### The standards control plane

Expand Down
2 changes: 1 addition & 1 deletion docs/glossary.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Glossary

Generated by `zag emit` from the concept system in `src/knowledge`. Do not
Generated by `zag-audit emit` from the concept system in `src/knowledge`. Do not
edit this file: change the concept, and the definition changes everywhere it
is used, including in the product's own text checks.

Expand Down
2 changes: 1 addition & 1 deletion docs/risk-register.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Risk register

Generated by `zag emit` from `src/ai/risk.zig`. A treatment that claims to
Generated by `zag-audit emit` from `src/ai/risk.zig`. A treatment that claims to
reduce a risk must name a control that is enforced by code; an instruction to
a model is not a control, and the build says so.

Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ directories. An agent run had no finish at all, so a workspace of completed runs
displayed every one of them as still going.

Two more were claims rather than records. The daemon said it held a workspace
open for clients, and it has no socket. `zag check` never loaded the requirements
open for clients, and it has no socket. `zag-audit check` never loaded the requirements
register. Every profile naming a requirement was therefore checked against an
empty set, and eleven dangling identifiers went unnoticed.

Expand Down
Loading