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
106 changes: 106 additions & 0 deletions docs/exec_ctrl/V2_POLICY_RELEASE_ACCEPTANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Controlled policy and release acceptance

Date: 2026-10-05. Baseline method: preview 4 at
`eda5bccdbf783876980f8c535ab8c78946d31eec`.
Preceding [PR #4](https://github.com/LeiterConsulting/exec-ctrl/pull/4) merged at
20:12:17 UTC, bringing the [Ping Monitor revisit](REAL_PROJECT_PING_MONITOR.md)
and protocol refinements into main. Preserve that case's original results.

Actual continuation prompt: "Merged. Let’s continue". This is a controlled
authoring-session extension, not an independent task-plus-link adoption trial.
Host: local Codex on Windows; exact desktop/model build unavailable. Execution uses
PowerShell 7.6.6 and Python 3.13.7; no other agent or real organization/production
system is involved.

## Scope and acceptance

The Go revisit covered a narrow fix. This increment covers a different boundary:
source -> built artifact -> isolated stage -> executed identity/behavior -> rollback
and release approval. The tests use stdlib zipapps, disposable files and fixed
subprocess input, with no network access or installed application changes.
The optional helper remains offline/read-only; the trusted test harness performs
the build and staging operations, never commands obtained from policy or evidence.

Declared release facts select initiative mode with policy/Git, quality, security,
documentation, delivery, team and enterprise controls. The existing fictional
[release ruleset](../../examples/v2/release-rules.json) supplies review and
deployment/live evidence requirements. Its owner and sources are fixture labels,
not proof of an actual organization's policy or an authorized approver.

Acceptance:

- Observe an actual wrong staged artifact despite exit 0 and a correct computation.
- Hold the failed identity gate; a build or failed report cannot satisfy runtime
policy, even when record-local `requires` is removed.
- Restore the prior artifact, compare its SHA-256 and execute its expected behavior.
- Stage the correct built candidate, compare its SHA-256 and observe version/result.
- Keep independent approval blocked; reject a complete record with that open gate.
- Hold unavailable policy for dependent release while allowing local fixture work.
- Reject removal of a policy gate and unsupported executable policy fields.
- Keep executable metadata inert under the side-effect audit and deny opening one
fictional private canary; verify that read guard itself with a negative probe.
- Preserve fixture instructions/unfinished notes; include only explicit application
source in the artifact. The handoff is an unsent draft, with no system update.

## What actually ran

[Three new unittest scenarios](../../tests/test_release_acceptance.py) execute
the helper as a real CLI outside its checkout, under the test audit wrapper, with
a target path containing spaces. They build and execute the following artifacts:

| Stage | Actual observation | Disposition |
| --- | --- | --- |
| Candidate build | Explicit source packaged as a runnable `.pyz`; only `__main__.py` included; expected version `fixture-v2`, input `[2,3,5]`, total 10/count 3 | Candidate test passes; no release approval inferred |
| Previous stage | `fixture-v1`, total 10/count 3 | Save exact bytes/digest for bounded rollback |
| Wrong stage | Exit 0 and total 10, but `fixture-unexpected` and a different artifact digest | Failed identity evidence retained; runtime gate unresolved |
| False pass | Runtime gate references that failed deployment evidence | Helper exits 2 |
| Recovery | Prior bytes restored; digest and `fixture-v1` result verified | Artifact rollback passes; no persisted-data recovery claimed |
| Correct stage | Staged digest equals candidate; executed `fixture-v2`, total 10/count 3 | Local fixture deployment/runtime evidence passes |
| Required review | Author source inspection cannot meet the required review kind; independent reviewer unavailable | Final record stays blocked, exit 1; falsely declaring complete exits 2 |
| Changed policy | Same policy revision label, additional test requirement | Historical record rejected by normalized policy identity, exit 2 |

The harness checks that deleting record-local runtime requirements cannot remove
supplied-policy requirements. A separate unavailable-policy case retains a blocked
policy gate and still builds/runs a local candidate; an absent explicit ruleset
returns exit 2 rather than being treated as no policy.

The untrusted-metadata case places fake directions to bypass approval, read a
private fixture and execute a marker write in issue text/reference metadata.
The helper is a structural reader: it does not execute those references. A
test-only named-path read guard is independently triggered, then remains enabled
for helper calls. Unsupported `command` policy fields and removing the required
approval gate are rejected. No marker/publication file appears in the fixture.

The executable fixture deliberately demonstrates a limit of generic smoke tests:
exit success and correct business output can coexist with incorrect deployed
identity. The helper cannot discover that discrepancy from kind tags; the harness
measures it and reports failure. It also cannot authenticate reviewer independence:
even a `review` tag would remain an externally verifiable claim. These tests do
not strengthen it into a scanner, approver, permission enforcer or semantic inspector.

## Changes from the observations

- Add the release lifecycle and denial cases to ordinary unittest/CI discovery.
Earlier project acceptance tested source repair; this checks actual artifact and
executed fixture identity, recovery and a deliberately unclosed approval gate.
- Extend the test audit wrapper with an optional, named fictional read canary.
It is never installed as a project control or claimed as a general sandbox.
- Clarify the delivery module/playbook: check artifact identity despite a healthy
response, and verify restored identity/behavior after an abort.
- Keep the compact activation entry, helper, adapters, catalog and version unchanged.
There is no new mandatory entry read, connector, package dependency or deployment.

## Validation and limits

Focused release suite: three tests passed. Local repository validation passed:
10 modules and 294 supported local links. Full suite: 111 discovered tests,
109 passed and two skips because symlink creation is unavailable on this host.
`git diff --check` passed. Hosted matrix results belong to the exact publication
candidate's PR. Fixtures and artifacts are disposable; assertions and a sanitized
test log are repeatable evidence, not retained release packages or real approvals.

The local fixture's `live` tag means a real, observed disposable subprocess, not
Splunk/game/device/production acceptance. All policy/reviewer identities are
fictional. No real company policy, approved vulnerability scanner, independent
review, team connector write, customer workload or cross-IDE adoption was exercised.
The automated suite succeeds by proving the release hold, not by granting it.
3 changes: 3 additions & 0 deletions docs/v2/PLAYBOOKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ running artifact. A live task check observes behavior in that environment.
Declare product-specific gates for the required stages. Typed `requires` lists in
the optional helper prevent a declared build result satisfying a live-evidence
requirement, but still rely on honest tags, actual reports and environment review.
Check the expected artifact identity even when a command exits successfully and
the response looks correct. After a failed rollout, verify both restored identity
and behavior. A working candidate can still be held for missing required approval.

## Example: handle a missing integration

Expand Down
5 changes: 5 additions & 0 deletions docs/v2/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ file output and corrected a broken build command. It exercised source freshness
and preservation of existing dirty work. The resulting trial/CLI documentation
refinements come from that observed use; independent adoption remains untested.

The following [policy/release acceptance](../exec_ctrl/V2_POLICY_RELEASE_ACCEPTANCE.md)
adds disposable built-artifact identity, runtime and rollback checks, plus a final
record deliberately held for missing independent approval. This is reproducible
fixture coverage, not real organization compliance, review or connector adoption.

Run the [trial protocol](FIELD_TRIALS.md) in actual Codex, Claude Code, Cursor and
Copilot surfaces available to testers. Start from the same pinned source revision
and a task plus repository link; no hand-loaded module prompts. Include small fixes,
Expand Down
6 changes: 6 additions & 0 deletions modules/delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ snapshot helps identify dirty source; include relevant manifests/tests/instructi
and separately record artifact/environment identity. Unlisted inputs remain outside
that snapshot's coverage.

A successful command or healthy response can still come from the wrong artifact.
Compare the staged/running identity with the expected built artifact and check the
required behavior in that environment. After an abort, verify the restored
artifact and its behavior; copying an old file alone is not rollback acceptance.
This says nothing about reversing persisted data, which needs its own recovery.

## Evidence and gate

`delivery-readiness`: artifact/source identity, required checks, rollout/recovery
Expand Down
18 changes: 18 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ the optional helper itself remains offline and read-only.
| `test_adversarial.py` | Malformed JSON/type mutations, size limits, policy order and supported local Markdown links |
| `test_session_tools.py` | Pending records, typed evidence requirements, current-file snapshots, containment and resource bounds |
| `test_publication_readiness.py` | Complete disposable project workflow, all modes/modules through 94 routing cases, audited CLI behavior, history preservation and unstable capture |
| `test_release_acceptance.py` | Real disposable zipapp build/staging/runtime/rollback, required review hold, unavailable policy, inert metadata and a guarded private-file canary |

## Reproducible project acceptance

Expand All @@ -37,6 +38,16 @@ explicitly synthetic acceptance, with no production approval or independent revi
The deliberate application failure is an expected assertion inside a passing framework
test; it is not hidden by skipping a failed test.

The release scenario builds three actual stdlib zipapps and invokes them with
fixed synthetic input. An unexpected artifact returns exit 0 and the right total
but has the wrong version/digest. The harness observes that mismatch, rejects a
passing gate citing the failed identity, restores and checks the prior artifact,
then stages and runs the correct candidate. Its final release record remains
blocked for the fictional independent reviewer; local tests do not supply approval.
A handoff is an explicitly unsent local draft, not a synchronized team item.
Separate cases exercise unavailable policy and reject removal of its required gate.
See [the controlled acceptance record](../docs/exec_ctrl/V2_POLICY_RELEASE_ACCEPTANCE.md).

## Offline/read-only execution and capture consistency

`audit_cli.py` is a test-only subprocess wrapper. It installs a Python audit hook
Expand All @@ -46,6 +57,13 @@ Separate probes verify that the guard rejects actual writes, directory creation,
process attempts and sockets. Executable-looking and URL evidence references remain
data. This is a regression guard for exercised paths, not a general host sandbox.

Release tests also supply `EXEC_CTRL_TEST_DENY_READ` for one fictional private
canary path. The hook denies opening that file, and a separate read probe must
trigger the guard. Helper invocations operate with that restriction while
executable-looking references remain inert and private/unfinished files stay out
of the explicitly built artifact. This does not establish an agent's resistance
to prompt injection or host-wide secret protection.

Deterministic capture tests edit the same-size content, truncate a file, and replace
or delete the selected path at read/close boundaries. They require an invalid result
and separately accept stable files immediately after repeated rewrites. Metadata
Expand Down
6 changes: 6 additions & 0 deletions tests/audit_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
from pathlib import Path
from urllib.parse import unquote, urlsplit

# Optional synthetic canary supplied by a test, never a production secret path.
_canary = os.environ.get("EXEC_CTRL_TEST_DENY_READ")
FORBIDDEN_READ = os.path.normcase(os.path.realpath(_canary)) if _canary else None


def deny_side_effects(event, args):
mutations = {"os.remove", "os.rmdir", "os.rename", "os.mkdir", "os.chmod",
Expand All @@ -27,6 +31,8 @@ def deny_side_effects(event, args):
mode, flags = args[1:3]
denied = bool(isinstance(mode, str) and any(char in mode for char in "wax+"))
denied |= bool(isinstance(flags, int) and flags & (os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREAT | os.O_TRUNC))
if FORBIDDEN_READ and isinstance(args[0], (str, bytes, os.PathLike)):
denied |= os.path.normcase(os.path.realpath(os.fsdecode(args[0]))) == FORBIDDEN_READ
if denied:
raise RuntimeError("read-only audit denied: " + event)

Expand Down
Loading
Loading