Skip to content

item 2: examples and sector policy templates - #2

Merged
arpanghoshal merged 2 commits into
mainfrom
item-2-examples-and-templates
Sep 3, 2026
Merged

arpanghoshal merged 2 commits into
mainfrom
item-2-examples-and-templates

Conversation

@arpanghoshal

Copy link
Copy Markdown
Member

Build-list item 2. SPEC-v0.2 §1.1, acceptance test T31.

This item sits early because it needs nothing this release adds: v0.1 primitives only, which is what lets it land before §3 changes the policy schema.

examples/

Four standalone scripts, one per failure scenario. Each has its own ctrlrun.yaml, runs the failure it exists to demonstrate, prints the refusal, and exits 0.

Scenario What it blocks
double-refund/ effect may already have committed; blind retry refused
approval-mutation/ approved action ≠ requested action (mismatch)
agent-race/ already reserved (in_progress)
approval-replay/ single-use approval already consumed

Three decisions worth naming:

  • Each keeps its own state directory, under .ctrlrun/examples/<scenario>/, and clears it first so the example repeats. An example that reserved effect keys in the store a real agent is using would block live work — the same reason ctrlrun demo keeps its evidence separate.
  • A scenario that is not blocked exits non-zero. Every script has an else: raise SystemExit(...) on the call that must be refused, so an example that silently starts working fails loudly instead of printing a line that says it worked.
  • agent-race is deterministic, not raced for. Agent B runs from inside agent A's remote call, which is exactly the window that matters and needs no sleeping to hit. The docstring says so, and says the cross-process guarantee is the one enforced by BEGIN IMMEDIATE and the unique constraint rather than by a lock in this interpreter.

examples/policies/

Nine sector templates: devops, payments, e-commerce, insurance, healthcare, legal, security, government, hr. Each declares schema: ctrlrun.policy/v1, uses no key §3 adds, carries the header Starting point on the v0.1 kernel. Adapt before use., and says no to something.

They are shaped around one asymmetry rather than around a sector's vocabulary: what is cheap to undo can be autonomous, what leaves the building needs a human, and what destroys the evidence is denied outright. security.yaml is the clearest case — firewall.add_deny_rule is containment and allowed, firewall.add_allow_rule is a hole and needs a human, audit_log.delete is denied.

No template describes itself as compliant with anything, per ROADMAP's sector rule. The three templates over regulated work say in a comment that the question of what may be delegated at all comes first and a template cannot answer it.

T31

77 tests. The one worth calling out: the "no network" claim is asserted, not trusted. Each script runs in a subprocess whose sitecustomize replaces socket.socket, create_connection and getaddrinfo with a refusal, so an example that grows a dependency on a live service fails in CI rather than on a reader's laptop.

Packaging

MANIFEST.in now carries examples/. CI's package job extracts the sdist and runs the suite from it, so a T31 that reads the source tree needs both halves to travel — the failure mode fix: the sdist shipped tests it could not run already fixed once. Verified by hand: the sdist carries 24 example paths and T31 passes from the extracted tree.

Mutation table

Every MUST in §1.1 and every clause of T31, broken one at a time:

# Rule Result
M1 a template MUST declare schema: ctrlrun.policy/v1 red
M2 a template MUST NOT use effect: / resource: / mcp: red
M3 each carries the "Adapt before use" header red
M4 no template claims compliance red
M5 a template that never says no teaches nothing red
M6 each script prints the refusal it demonstrates red
M7 each script runs with no network red
M8 each script keeps state under its own directory red
M9 each script repeats red
M10 all nine sectors present red
M11 all four scenarios present red

Verification

802 tests pass (725 → 802), mypy --strict src/ and ruff check clean, ruff format --check clean, ctrlrun demo still runs the four scenarios in 0.07s with no network. All four example scripts verified by hand from a scratch directory.

Four standalone scripts and nine sector templates, SPEC-v0.2 §1.1, test T31.

examples/<scenario>/ for double-refund, approval-mutation, agent-race and
approval-replay: each a policy file and one script that runs the failure it
exists to demonstrate, prints the refusal, and exits 0. Each keeps its own
state directory under .ctrlrun/examples/ — an example that reserved effect
keys in a live store would block real work — and clears it first, so the
example repeats.

examples/policies/<sector>.yaml for devops, payments, e-commerce, insurance,
healthcare, legal, security, government and hr. v0.1 primitives only: every
one declares ctrlrun.policy/v1 and uses no key §3 adds, which is what lets
this item land before the policy schema changes. Each carries the "Adapt
before use" header and says no to something.

T31 asserts the network rather than trusting it: each script runs in a
subprocess whose sitecustomize refuses every socket, so an example that grew
a dependency on a live service fails in CI instead of on a reader's laptop.

MANIFEST.in carries examples/ because CI builds the sdist and runs the suite
out of it; T31 reads both halves from the source tree.
The ignore rule for the operator's own policy was unanchored, so `ctrlrun.yaml`
matched at any depth and `git add -A` silently skipped
`examples/<scenario>/ctrlrun.yaml`. The examples ran locally, where the files
exist, and failed on the first CI run against a fresh checkout. The sdist did
not catch it either: setuptools builds from the working tree, not from the
index.

Anchored to `/ctrlrun.yaml`, which is what that line always meant.

T31 gains the guard for the class: every .py and .yaml under examples/ must
appear in `git ls-files`. Verified by dropping one file from the index and
watching it go red.
@arpanghoshal

Copy link
Copy Markdown
Member Author

Follow-up commit 944d13f — worth reading before merging, because the first CI run on this PR failed and the reason is more interesting than the fix.

.gitignore ignored ctrlrun.yaml unanchored, so it matched examples/<scenario>/ctrlrun.yaml at any depth. git add -A silently skipped all four, and the examples passed locally — where the files exist on disk — while being absent from every clone. check (3.11) caught it on a fresh checkout.

The sdist verification quoted in the description did not catch it either, and that is the part worth noting: python -m build --sdist resolves MANIFEST.in against the working tree, not the git index, so the untracked files were packaged and the extracted-tree run of T31 passed. A green sdist is not evidence that a file is tracked.

Fixed by anchoring to /ctrlrun.yaml, which is what that line always meant — the operator's own policy at the repo root, written by ctrlrun init.

T31 gains test_T31_every_file_an_example_needs_is_tracked_by_git: every .py and .yaml under examples/ must appear in git ls-files. Verified by dropping one file from the index and watching it go red, then restoring it. That is the deterministic guard for the class — CI on a fresh clone was the only thing standing between this failure and a merge.

@arpanghoshal
arpanghoshal merged commit 7979672 into main Sep 3, 2026
3 checks passed
@arpanghoshal
arpanghoshal deleted the item-2-examples-and-templates branch September 3, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant