From 3022ba1ac0289f945267113274ea7ffff1380098 Mon Sep 17 00:00:00 2001 From: Worker Date: Mon, 21 Sep 2026 18:42:22 +0800 Subject: [PATCH] docs: add architecture and stack-lock policy docs --- README.md | 4 ++ docs/architecture.md | 114 +++++++++++++++++++++++++++++++++++++++++++ docs/stack-lock.md | 79 ++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 docs/architecture.md create mode 100644 docs/stack-lock.md diff --git a/README.md b/README.md index c8a7929..36feeb5 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,10 @@ Browser artifacts are written to `test-results/` and `playwright-report/` - MandateBound’s prove step uses `simulate --scenario operator` as the dispute-oriented demo path. Full AP2 pack assemble/verify remains in MandateBound’s own CLI and docs. - This repo does not read or write any private GitHub repositories. +For deeper coverage of how the orchestrator sequences the three libraries and +how `stack-lock.json` is governed, see [docs/architecture.md](docs/architecture.md) +and [docs/stack-lock.md](docs/stack-lock.md). + ## License Apache-2.0 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..4c30220 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,114 @@ +# Orchestrator architecture + +Agent Action Stack is a thin orchestrator. It does not re-implement policy +evaluation, recourse rails, or dispute evidence simulation. It sequences three +public sibling libraries in a fixed order and persists one isolated run +bundle per invocation. + +This document describes the architecture the code in this repository actually +implements. It does not describe behavior owned by the sibling libraries; +their documentation is the source of truth for those. + +## Bounded responsibility + +- Orchestration only. Policy, recourse, and proof logic live in the three + sibling repositories. +- Synthetic connectors and scenarios only. No real account, merchant, + payment, or external provider integration. +- One run, one bundle. Each invocation writes an atomic bundle under + `.out/runs//` and updates `.out/latest.json` only on a complete + bundle; a failed or skipped stage cannot leave an older artifact looking + current. +- Public dependencies only. `stack-lock.json` pins the three sibling repos + to exact public commits. Nothing private is cloned or modified. + +## Stage flow: decide then act then prove + +The `runDemo` function in `bin/aas.mjs` is the orchestrator. It runs three +stages in order. Each stage returns a child result captured from a spawned +process, and the orchestrator records the stage status into the report. + +``` + decide (Constitutional Agent Testbench, Python) + | + | passed? + |--- no ---> stop (policy_failed) + v yes + act (Consequence Rail, Node) + | + | ok? + |--- no ---> stop (act_failed) + v ok + | outcome != "settled" or --dispute? + |--- no (and not --dispute) ---> skip (no_dispute) + v yes + prove (MandateBound, Node): dispute scenario or rail review +``` + +The `flow` field in the report records the actual path taken: + +- `decide -> stop (policy failed)` when decide returns non-ok +- `decide -> act -> stop (act failed)` when act returns non-ok +- `decide -> act` when act settles cleanly and `--dispute` was not passed +- `decide -> act -> prove` when the rail outcome needs review or `--dispute` + forces the prove path +- `decide -> error` or `decide -> act error` or `decide -> act -> prove + error` when a stage throws + +The prove stage has two modes, selected by `--prove`: + +- `simulate` (default): runs MandateBound's canned dispute scenario. For the + demo the scenario is `operator`. +- `rail`: re-opens the act-stage rail bundle, verifies it with the rail's + own verifier, and binds it into a MandateBound review record for the same + action id and digests. The review records the rail's verdict without + re-verifying rail signatures; source truth stays unknown and legal effect + stays not determined. + +## Child execution and timeouts + +Each decide, act, and prove child is spawned with a bounded timeout. Default +is 30000 ms (`AAS_CHILD_TIMEOUT_MS`); empty values keep the default and +invalid integers are rejected at parse time. A hung child fails the stage +with code `AAS_CHILD_TIMEOUT` instead of blocking the run. + +Child stdout is capped (`CHILD_JSON_LIMIT`) so a runaway tool cannot inflate +the run bundle. Captured stderr is clipped (`STDERR_LIMIT`) before it is +persisted with the stage record. + +The decide stage runs on the first Python 3.11+ interpreter found, because +the locked Constitutional Agent Testbench declares `requires-python >= 3.11`. +Set `AAS_PYTHON` to use a specific interpreter; a missing interpreter or one +below 3.11 fails with an actionable message. + +## Run bundle layout + +Each invocation writes one atomic bundle under `.out/runs//`: + +- `manifest.json`: stage status and component provenance +- `report.json`: user-facing run report +- `stages/.json`: captured output from each stage that ran + +`.out/latest.json` is an atomic pointer to the most recent complete bundle. + +## Provenance + +The orchestrator resolves component provenance from `stack-lock.json` and the +checked-out `deps/` directories. Provenance is recorded on every run: +repository URL, commit, detached checkout flag, clean checkout flag, and +expected entrypoints present. The orchestrator never re-verifies the +components; the sibling CLIs and the rail's own verifier do that. + +## Verify-only CI boundary + +GitHub Actions in `.github/workflows/ci.yml` runs three jobs and nothing +else: `test` (unit suite plus syntax check plus GUI smoke), `integration` +(clean checkout bootstrap plus the integrator examples), and `browser` +(Playwright real-browser workflow tests). CI has no publish, deploy, push, +or release step. It does not write to any registry, package index, or +hosted target. `contents: read` is the only permission requested. + +The same boundary holds locally: `npm test`, `npm run integration`, +`npm run example:review-handoff`, and `npm run test:browser` are read-only +with respect to anything outside `.out/`. The orchestrator writes only to +`.out/` for run bundles and to `.out/latest.json` for the latest pointer. \ No newline at end of file diff --git a/docs/stack-lock.md b/docs/stack-lock.md new file mode 100644 index 0000000..0c2058a --- /dev/null +++ b/docs/stack-lock.md @@ -0,0 +1,79 @@ +# stack-lock.json policy + +`stack-lock.json` is the single source of truth for which versions of the +three sibling libraries the orchestrator is allowed to compose. Schema: +`agent-action-stack.lock/v1`. It pins each sibling's public URL, exact +reviewed commit, expected entrypoints, and where applicable the build hooks. + +## What it pins + +One record per sibling. The current lock has three: + +- `constitutional-agent-testbench` (decide). Public URL, one reviewed + commit. Expected entrypoints `pyproject.toml` and + `src/constitutional_agent_testbench/cli.py`. No install or build step. +- `consequence-rail` (act). Public URL, one reviewed commit. Expected + entrypoints `package.json` and `cmd/crctl.js`. No install or build step. +- `mandatebound` (prove). Public URL, one reviewed commit. Expected + entrypoints `package.json`, `package-lock.json`, `src/cli.ts`. Post-build + entrypoint `dist/cli.js`; `install` hook `npm-ci`; `build` hook + `npm-run-build`. Bootstrap runs `npm ci --ignore-scripts` then the build. +The lock also pins, by construction, the public-only origin: every record +points at the public EauDoon GitHub repo. Substituting a private URL or +editing `deps/` directly is out of scope. + +## How the lock is enforced + +`scripts/bootstrap.mjs` loads the lock, asserts the full-stack Node version, +and prepares each dependency under `deps/`: + +1. If `deps/` is present, `inspectDependencyDirectory` verifies it + is a regular directory, detached (`HEAD` matches the pinned commit + exactly), clean, and contains every expected entrypoint. Substituted or + dirty pre-existing directories are rejected. +2. If absent, bootstrap clones the public URL at the pinned commit, checks + out detached, and verifies the entrypoints. +3. For components with an `install` hook, bootstrap runs the declared + command (`npm ci --ignore-scripts` for MandateBound). +4. For components with a `build` hook, bootstrap runs the declared command + (`npm-run-build` for MandateBound), then verifies post-build entrypoints. +The orchestrator records each component's provenance on every run bundle. A +run resolving components whose checkout disagrees with the lock surfaces the +disagreement in its manifest. + +## When to update + +Changing a pinned component is a coordinated cross-repo change. Do not bump +the lock on its own. + +1. Land the upstream change in the sibling repo first; it must pass that + sibling's own CI and review. +2. Bump the matching `commit` field to the reviewed merge SHA. +3. Update `expected_entrypoints`, `post_build_entrypoints`, `install`, or + `build` only if the sibling's published contract actually changed. +4. Run `npm run integration` locally on Ubuntu and Windows. The CI + integration job runs the same proof on every push and pull request. +Security fixes follow the same path. The schema version (`schema_version`) +is bumped only when the shape changes in a way that requires loader +changes; existing tools keep reading older versions until the bump lands +across all consumers. + +## Who can update + +The lock is owned by the Agent Action Stack maintainers. Updates land via +pull request on `imp/-` branches. PRs that change +`stack-lock.json` must cite the sibling repo, PR, and reviewed merge SHA; +show a passing `integration` job on both Ubuntu and Windows; and show a +passing `test` job (lock-load and dependency helpers live in the unit +suite). +Do not bypass the lockfile. Editing `deps/` directly, swapping a remote +URL, or relaxing the dirty-checkout rejection are out of scope. A PR that +needs any of those should propose a permanent fix in `scripts/bootstrap.mjs` +or in this policy document. + +## What is out of scope + +Real connectors, real secrets, real payment or merchant integrations; +changes that would read or write private repositories; policy or rail +rules that should live in their owning library. The lock pins reviewable +public artifacts. Anything else belongs in the sibling that owns it. \ No newline at end of file