44 KiB
V2R1 Authenticated Runtime Release Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Close exactly five reviewed blockers between the replayed V2R1 tree and an evidence-backed, authenticated, digest-bound release candidate.
Architecture: Use one canonical aios_v2r1 Python package; authenticate a Node-to-Python loopback envelope before server-side Weixin projection and authority resolution; make ACCEPTED terminal; dispatch through a durable outbox and the verified Hermes HTTP router; stage an isolated OpenClaw candidate with three services; promote only byte-identical artifacts after live reconciliation evidence.
Tech Stack: Python 3.11 and pytest, SQLite, Node.js, OpenClaw 2026.6.11, loopback HTTP, systemd credentials and units, SHA-256 manifests.
Global gates and dynamic blocker ledger
- B1 and B2 may proceed only after review accepts the corrected design and plan.
- B3, B4, and B5 are evidence-gated. Unresolved evidence stops that task before implementation, commit, or ledger append.
- Current outbound evidence is
EVIDENCE_REQUIRED; B4 and B5 remain stopped until a verified provider echo exactly bindsclient_id -> message_id. - Keep exactly five blocker commits, one each for B1 through B5. The docs-correction commit is the reviewed starting point, not a blocker.
- Do not create a tag, edit
VERSIONorbaseline/current.json, pushmain, deploy/restart a live service, modify active OpenClaw configuration, or invoke Fleet. - All OpenClaw loader work uses an isolated temporary or candidate config/state directory.
ACCEPTEDis terminal. Legacy handling is reachable only when bridge mode is explicitlydisabled.- Never claim exactly-once Hermes delivery or use time/approximation as message authority.
- D0 is a non-blocker documentation correction. Its reviewed commit is the R9
START; it does not consume a blocker label or relax any B3/B4/B5 evidence gate. - B2 is both the canonical-package blocker and the executable completeness gate for Ledger, Memory, Secret, Audit, Hermes Profile, Cell Manager/CLI, and Fabric. Existing correct behavior is recorded as characterization; a production edit requires an observed contract RED first.
After docs review, initialize the stable per-worktree ledger from the then-current tip rather than a hardcoded SHA:
test -z "$(git status --porcelain)"
test "$(git branch --show-current)" = "integration/v2r1-runtime-20260715-r9"
start_sha="$(git rev-parse HEAD)"
start_parent="$(git rev-parse HEAD^)"
start_tree="$(git rev-parse HEAD^{tree})"
ledger="$(git rev-parse --git-path v2r1-blocker-ledger.tsv)"
umask 077
test ! -e "$ledger"
printf 'START\t%s\t%s\t%s\n' "$start_sha" "$start_parent" "$start_tree" >"$ledger"
Expected: the clean reviewed docs-correction tip is START. At the beginning of every blocker, rehydrate and verify the latest ledger row before work:
ledger="$(git rev-parse --git-path v2r1-blocker-ledger.tsv)"
test -s "$ledger"
IFS=$'\t' read -r last_label last_sha last_parent last_tree < <(tail -n 1 "$ledger")
test "$(git rev-parse HEAD)" = "$last_sha"
test "$(git rev-parse HEAD^)" = "$last_parent"
test "$(git rev-parse HEAD^{tree})" = "$last_tree"
Each blocker appends label, SHA, parent, tree only after its review passes. The ledger stays in the worktree Git directory and is never staged.
Task B1: Prove the real OpenClaw plugin loader and permission contract
Files:
- Modify:
openclaw/plugins/aios-tenant-boundary/package.json - Modify:
openclaw/plugins/aios-tenant-boundary/openclaw.plugin.json - Modify only on demonstrated hook failure:
openclaw/plugins/aios-tenant-boundary/dist/index.js - Modify only on demonstrated policy failure:
openclaw/plugins/aios-tenant-boundary/dist/policy.js - Create:
openclaw/plugins/aios-tenant-boundary/test/compat.test.js - Create:
openclaw/plugins/aios-tenant-boundary/test/policy.test.js - Create:
openclaw/plugins/aios-tenant-boundary/test/loader.test.mjs
Exact contract: package/manifest versions 2.1.0-rc.1; blockUnknownAgentRuns boolean default false; approvalTimeoutMs integer min 1000, max 300000, default 60000; every runtime-read property declared. Default permission proves only before_tool_call is usable. before_agent_run and agent_end require plugins.entries.aios-tenant-boundary.hooks.allowConversationAccess=true. Fake API tests are supplemental; real runtime inspection is mandatory.
- Add Node tests for missing/blocked/allowlisted senders, automation, read-only tools, absent provenance, approval, exact timeout bounds, cleanup, age pruning, versions, and schema.
- Add
loader.test.mjsto parse real inspect JSON and assert linked path, version, diagnostics, hooks, and permission state. - Run initial RED:
cd openclaw/plugins/aios-tenant-boundary
npm test
Expected RED: current package has no tracked tests; version and missing-schema assertions fail.
- Normalize versions/schema; change runtime only when an executable test proves incompatibility. Capture TAP and prove at least one test executed:
tmp="$(mktemp -d)"
set -o pipefail
npm test -- --test-reporter=tap | tee "$tmp/plugin.tap"
test "$(grep -c '^ok [0-9]' "$tmp/plugin.tap")" -gt 0
test -z "$(grep '^not ok ' "$tmp/plugin.tap" || true)"
Expected GREEN: npm exits 0, TAP contains one or more ok test records, and contains no not ok record.
- Run the real loader twice in isolated state:
repo="$(git rev-parse --show-toplevel)"
tmp="$(mktemp -d)"
export OPENCLAW_STATE_DIR="$tmp/state"
export OPENCLAW_CONFIG_PATH="$tmp/state/openclaw.json"
mkdir -p "$OPENCLAW_STATE_DIR"
openclaw plugins install --link "$repo/openclaw/plugins/aios-tenant-boundary"
openclaw plugins inspect aios-tenant-boundary --runtime --json >"$tmp/default-inspect.json"
node "$repo/openclaw/plugins/aios-tenant-boundary/test/loader.test.mjs" default "$tmp/default-inspect.json"
openclaw config set plugins.entries.aios-tenant-boundary.hooks.allowConversationAccess true --strict-json
openclaw config validate
set +e
openclaw doctor --lint --json >"$tmp/doctor-lint.json"
doctor_rc=$?
set -e
test "$doctor_rc" = 0 || test "$doctor_rc" = 1
python3 - "$tmp/doctor-lint.json" <<'PY'
import json, sys
data = json.load(open(sys.argv[1]))
def walk(value):
if isinstance(value, dict):
yield value
for child in value.values(): yield from walk(child)
elif isinstance(value, list):
for child in value: yield from walk(child)
bad = [item for item in walk(data) if str(item.get("severity", "")).lower() in {"error", "fatal"} and "plugin" in json.dumps(item).lower()]
assert not bad, bad
PY
openclaw doctor --post-upgrade --json >"$tmp/doctor-plugin.json"
openclaw plugins inspect aios-tenant-boundary --runtime --json >"$tmp/opt-in-inspect.json"
node "$repo/openclaw/plugins/aios-tenant-boundary/test/loader.test.mjs" opt-in "$tmp/opt-in-inspect.json"
Expected GREEN: config validation and post-upgrade doctor exit 0; lint doctor may exit 0 or 1, warnings are allowed, parsed JSON contains zero error/fatal plugin findings; default inspection proves only before_tool_call; opt-in proves before_agent_run, before_tool_call, agent_end.
- Run carried-forward tests:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH=active-runtime/app python3 -m pytest tests/v2r1 tests/integration -q
Expected GREEN: 32 passed.
- Commit and append ledger:
git add openclaw/plugins/aios-tenant-boundary/package.json openclaw/plugins/aios-tenant-boundary/openclaw.plugin.json \
openclaw/plugins/aios-tenant-boundary/dist/index.js openclaw/plugins/aios-tenant-boundary/dist/policy.js \
openclaw/plugins/aios-tenant-boundary/test/compat.test.js openclaw/plugins/aios-tenant-boundary/test/policy.test.js \
openclaw/plugins/aios-tenant-boundary/test/loader.test.mjs
git commit -m "fix(v2r1): prove OpenClaw plugin compatibility"
Review B1
git diff --check HEAD^ HEADhas no output; commit contains only named B1 paths and unchanged optionaldistfiles do not appear.- Re-run Node tests, both real loader permission modes, validation/doctor, and 32 Python tests.
- Prove B1 parent equals ledger
STARTand, before the final append step, the ledger still contains onlySTART. - Stop if real inspection does not prove default-only
before_tool_calland explicit conversation-access hooks. - After every prior B1 review check passes, append B1 and verify the recorded commit identity:
ledger="$(git rev-parse --git-path v2r1-blocker-ledger.tsv)"
printf 'B1\t%s\t%s\t%s\n' "$(git rev-parse HEAD)" "$(git rev-parse HEAD^)" "$(git rev-parse HEAD^{tree})" >>"$ledger"
IFS=$'\t' read -r label sha parent tree < <(tail -n 1 "$ledger")
test "$label" = B1
test "$sha" = "$(git rev-parse HEAD)"
test "$parent" = "$(git rev-parse HEAD^)"
test "$tree" = "$(git rev-parse HEAD^{tree})"
Task B2: Package only aios_v2r1 and repair the exact legacy import set
Files:
- Create:
active-runtime/pyproject.toml - Create:
active-runtime/app/aios_v2r1/__init__.py - Create:
active-runtime/app/aios_v2r1/ingress_service.py - Modify:
active-runtime/app/aios_v2r1/cli.py - Create:
tests/v2r1/test_ingress_service.py - Create:
tests/test_v2r1_package_identity.py - Modify: the exact 23 Appendix A test files, and no unlisted legacy import file
- Modify only after an observed matrix RED:
active-runtime/app/aios_v2r1/message_ledger.py - Modify only after an observed matrix RED:
active-runtime/app/aios_v2r1/memory_scope.py - Modify only after an observed matrix RED:
active-runtime/app/aios_v2r1/secret_scope.py - Modify only after an observed matrix RED:
active-runtime/app/aios_v2r1/audit.py - Modify only after an observed matrix RED:
active-runtime/app/aios_v2r1/hermes_profile.py - Modify only after an observed matrix RED:
active-runtime/app/aios_v2r1/cell_manager.py - Modify only after an observed matrix RED:
active-runtime/app/aios_v2r1/hermes_fabric.py - Expand:
tests/v2r1/test_message_ledger_append.py - Create:
tests/v2r1/test_memory_scope.py - Create:
tests/v2r1/test_secret_scope.py - Create:
tests/v2r1/test_audit.py - Create:
tests/v2r1/test_hermes_profile.py - Create:
tests/v2r1/test_cell_manager.py - Expand:
tests/v2r1/test_hermes_fabric.py
Exact contract: V2R1 imports only as aios_v2r1; no active_runtime shim/alias/namespace extension/copied tree. Wheel includes aios_v2r1, excludes active_runtime and legacy app. Legacy repository tests change active_runtime.app.* to app.* and run with PYTHONPATH='active-runtime:active-runtime/app'. Mutation routes return bounded SERVICE_NOT_CONFIGURED until B3/B4 processors exist. Existing cell CLI stays compatible and serve is separate. B2 changes exactly 43 paths: the original 29 package/import paths plus the seven named runtime modules and seven named matrix-test paths above.
Executable completeness matrix: Message Ledger proves exact-ID query with no fallback, idempotency, collision conflict, tenant/cell/direction isolation, expiry/purge, restart, concurrency, and sanitized SQLite failures. Memory proves every namespace, cross-scope denial, shared-artifact read-only access, qualified keys, traversal rejection, and disabled search. Secret proves reference/path/scope/kind/version/identifier validation, no value leakage, and fail-closed broker boundaries. Audit proves genesis, tamper/delete/reorder detection, Unicode canonicalization, concurrency, restart, validation, tenant isolation, and sanitized SQLite failures. Hermes Profile proves exact environment, absolute roots, 0700 modes, traversal/symlink denial, scope, idempotent prepare, and no inherited environment. Cell Manager/CLI proves one cell per tenant, an explicit state graph, atomic/concurrent persistence, corrupt-registry fail-closed behavior, stable CLI exit codes/JSON, and restart. Fabric proves claimed jobs return running consistently with durable state, tenant/cell/profile isolation, one concurrent claimant, rejection of non-claimable states, restart, and sanitized SQLite failures.
- Rehydrate the stable blocker ledger using the global snippet and require its latest label is
B1. - Rehydrate the exact explorer manifest path and compare sorted actual/expected lists between exact marker lines. The marker names are assembled from split literals so this command cannot match itself:
actual="$(git rev-parse --git-path v2r1-active-runtime-imports.actual)"
expected="$(git rev-parse --git-path v2r1-active-runtime-imports.expected)"
plan=docs/superpowers/plans/2026-07-14-v2r1-runtime-release-plan.md
begin="$(printf '%s' 'APPENDIX_A_''BEGIN')"
end="$(printf '%s' 'APPENDIX_A_''END')"
tick="$(printf '\140')"
git grep -l -E '^(from|import) active_runtime\.app' -- tests | sort >"$actual"
awk -v begin="${tick}${begin}${tick}" -v end="${tick}${end}${tick}" '
$0 == begin {inside=1; next}
$0 == end {inside=0; next}
inside && /^- tests\// {sub(/^- /, ""); print}
' "$plan" | sort >"$expected"
test "$(wc -l < "$actual" | tr -d ' ')" = 23
test "$(wc -l < "$expected" | tr -d ' ')" = 23
cmp -s "$actual" "$expected"
Expected: exact match to 23 paths. Drift requires another docs review.
- Add package/service/CLI tests and the seven-component matrix before any production repair. Run each focused case and record the exact command plus its expected contract failure. If current behavior already satisfies the contract, record it as characterization and do not manufacture a failure or edit that production module.
- Run the aggregate initial RED/characterization command:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH='active-runtime:active-runtime/app' \
python3 -m pytest tests/v2r1/test_ingress_service.py tests/test_v2r1_package_identity.py \
tests/v2r1/test_message_ledger_append.py tests/v2r1/test_memory_scope.py \
tests/v2r1/test_secret_scope.py tests/v2r1/test_audit.py \
tests/v2r1/test_hermes_profile.py tests/v2r1/test_cell_manager.py \
tests/v2r1/test_hermes_fabric.py -q
Expected: package metadata, ingress service, and identity tests begin RED because their implementation is absent. Each matrix case is either a recorded contract RED or a recorded characterization; no production repair precedes its corresponding RED.
- Implement package/disabled processor boundary and replace imports in exactly Appendix A. Apply only the minimum runtime repairs demonstrated by matrix REDs; do not delete or weaken a matrix test. Prove no shim/old import:
test ! -e active_runtime
test -z "$(git grep -l -E '^(from|import) active_runtime\.app' -- tests || true)"
- Run full collect and full suite:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH='active-runtime:active-runtime/app' \
python3 -m pytest tests/v2r1 tests/integration tests/test_v2r1_package_identity.py -q
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH='active-runtime:active-runtime/app' python3 -m pytest --collect-only -q
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH='active-runtime:active-runtime/app' python3 -m pytest -q
PYTHONDONTWRITEBYTECODE=1 python3 -m compileall -q active-runtime/app/aios_v2r1
(cd openclaw/plugins/aios-tenant-boundary && npm test)
Expected GREEN: the focused carried suite reports at least 160 passed, including every matrix bullet; full collection has zero import errors; the complete pytest suite, 15 Node tests, and Python compileall pass. Re-run B1's real isolated default/opt-in loader sequence and require the same hook evidence. Pre-existing failures still block B2.
- Build and inspect isolated wheel:
tmp="$(mktemp -d)"
python3 -m pip wheel --no-deps --no-build-isolation active-runtime -w "$tmp/wheels"
wheel="$(find "$tmp/wheels" -name '*.whl' -print -quit)"
python3 -m venv "$tmp/venv"
"$tmp/venv/bin/python" -m pip install --no-deps "$wheel"
"$tmp/venv/bin/python" - "$wheel" <<'PY'
import sys, zipfile
names = zipfile.ZipFile(sys.argv[1]).namelist()
assert any(n.endswith("aios_v2r1/ingress_service.py") for n in names)
assert not any(n.startswith("active_runtime/") or n.startswith("app/") for n in names)
import aios_v2r1
assert aios_v2r1.__name__ == "aios_v2r1"
PY
Expected GREEN: one wheel, one V2R1 namespace, installed identity aios_v2r1.
- Stage the exact 43 paths (six package paths, Appendix A's 23 import paths, seven matrix runtime modules, and seven matrix-test paths), commit, append ledger:
actual="$(git rev-parse --git-path v2r1-active-runtime-imports.actual)"
git add active-runtime/pyproject.toml active-runtime/app/aios_v2r1/__init__.py \
active-runtime/app/aios_v2r1/ingress_service.py active-runtime/app/aios_v2r1/cli.py \
tests/v2r1/test_ingress_service.py tests/test_v2r1_package_identity.py \
active-runtime/app/aios_v2r1/message_ledger.py active-runtime/app/aios_v2r1/memory_scope.py \
active-runtime/app/aios_v2r1/secret_scope.py active-runtime/app/aios_v2r1/audit.py \
active-runtime/app/aios_v2r1/hermes_profile.py active-runtime/app/aios_v2r1/cell_manager.py \
active-runtime/app/aios_v2r1/hermes_fabric.py tests/v2r1/test_message_ledger_append.py \
tests/v2r1/test_memory_scope.py tests/v2r1/test_secret_scope.py tests/v2r1/test_audit.py \
tests/v2r1/test_hermes_profile.py tests/v2r1/test_cell_manager.py tests/v2r1/test_hermes_fabric.py
while IFS= read -r path; do git add -- "$path"; done <"$actual"
git commit -m "feat(v2r1): package canonical authenticated ingress"
Review B2
- Require
git diff --check, exactly 43 paths (the original 29 plus the exact 14 matrix paths), exact-list/no-shim checks, focused matrix/carried tests, full collect/full pytest, Node 15, real loader default/opt-in, compileall, and wheel identity. - Require B2 parent equals B1 in ledger.
- Prove mutation routes remain fail-closed until authenticated processors are installed.
- Prove every production matrix edit has a recorded pre-edit RED and every already-correct case is recorded as characterization. The partial R9 ledger must contain exactly
START,B1,B2; B3/B4/B5 remain absent. - After all prior B2 review checks pass, append B2 and verify the recorded commit identity:
ledger="$(git rev-parse --git-path v2r1-blocker-ledger.tsv)"
printf 'B2\t%s\t%s\t%s\n' "$(git rev-parse HEAD)" "$(git rev-parse HEAD^)" "$(git rev-parse HEAD^{tree})" >>"$ledger"
IFS=$'\t' read -r label sha parent tree < <(tail -n 1 "$ledger")
test "$label" = B2
test "$sha" = "$(git rev-parse HEAD)"
test "$parent" = "$(git rev-parse HEAD^)"
test "$tree" = "$(git rev-parse HEAD^{tree})"
Task B3: Implement authenticated Node transport and server-side Weixin authority
Evidence gate before code: create release/v2r1c0-rc1/weixin-bridge-evidence.json only after recording installed TS/JS paths and SHA-256, exact inbound bigint/raw fields, authenticated account/sender/conversation sources, authority-map owner/mode/schema, candidate credential contract, and disabled|enforced mode owner. Stop if any source is inferred, Node would need the authority map, or patch targets are not byte-idempotent.
Files after evidence acceptance:
- Modify:
active-runtime/app/aios_v2r1/identity.py - Modify:
active-runtime/app/aios_v2r1/tenant_context.py - Modify:
active-runtime/app/aios_v2r1/ingress_service.py - Create:
active-runtime/app/aios_v2r1/bridge_auth.py - Create:
active-runtime/app/aios_v2r1/weixin_projection.py - Create:
fixes/patch_openclaw_weixin_v2r1_ingress.py - Create:
tests/v2r1/test_bridge_auth.py - Create:
tests/v2r1/test_weixin_projection.py - Create:
tests/integration/test_v2r1_bridge_transport.py - Create:
tests/test_weixin_v2r1_ingress_bridge.py - Create: four
tests/fixtures/weixin_v2r1/*.jsonfiles named in the commit command - Create:
release/v2r1c0-rc1/weixin-bridge-evidence.json
Exact contract: Every authenticated POST has wire shape {"unsigned_body":{"schema_version":"aios.signed-request.v1","key_id":"bridge-current","issued_at_ms":1784000000000,"nonce":"base64url-16-byte-value","route_payload":{}},"signature":"lowercase-64-hex"}. unsigned_body has exactly those five fields; route_payload holds only route-specific content. The signing object is exactly {"method":"POST","path":"<exact route>","body":unsigned_body}. Canonical JSON is UTF-8 with recursive Unicode-codepoint-sorted object keys, no whitespace, no ASCII escaping, no floats, and string IDs; the outer signature is the lowercase 64-hex HMAC-SHA256 of those bytes. There is no authorization header, nested signature, or alternate route form. Python verifies before projection. Skew 30 seconds; replay five minutes; active+previous rotation; fresh nonce retry. Node reads bridge-ingress and the separate receipt-read keyring through distinct $CREDENTIALS_DIRECTORY files. Python owns projection/authority/internal signing. POST /v1/receipts/status accepts account_id plus exactly one of event_id|receipt_id, resolves only same-account/event scope, and uses receipt-read. Response max 4096 bytes. REPLAY_REJECTED is transport/service-only, never RuntimeIngressResponse. Accepted is terminal; legacy disabled-only. Date.now() and every local _ms timestamp are telemetry only.
- Rehydrate the stable ledger with the global snippet and require latest label
B2before evidence collection. - Add RED tests for the one exact wire/signing contract on
/v1/ingressand/v1/receipts/status, including rejection of extra/missing fields, signature-inside-body, authorization-header variants, nonce lengths, skew boundaries, replay, key rotation, fresh retry, roles, bounded routes, terminal accepted, disabled legacy, bigint, and exact quote/attachment. - Run initial RED:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH='active-runtime:active-runtime/app' \
python3 -m pytest tests/v2r1/test_bridge_auth.py tests/v2r1/test_weixin_projection.py \
tests/integration/test_v2r1_bridge_transport.py tests/test_weixin_v2r1_ingress_bridge.py -q
Expected RED: authenticator, server projection, processor, patcher absent.
- Implement authenticated transport/server projection and byte-idempotent TS/JS patch. Never embed authority or internal key in Node.
- Run focused, existing Weixin, full collect, and full pytest. Expected GREEN: all pass.
- Commit exactly 15 paths and append ledger:
git add active-runtime/app/aios_v2r1/identity.py active-runtime/app/aios_v2r1/tenant_context.py \
active-runtime/app/aios_v2r1/ingress_service.py active-runtime/app/aios_v2r1/bridge_auth.py \
active-runtime/app/aios_v2r1/weixin_projection.py fixes/patch_openclaw_weixin_v2r1_ingress.py \
tests/v2r1/test_bridge_auth.py tests/v2r1/test_weixin_projection.py \
tests/integration/test_v2r1_bridge_transport.py tests/test_weixin_v2r1_ingress_bridge.py \
tests/fixtures/weixin_v2r1/text.json tests/fixtures/weixin_v2r1/quoted-text.json \
tests/fixtures/weixin_v2r1/image-with-text.json tests/fixtures/weixin_v2r1/unresolved-reference.json \
release/v2r1c0-rc1/weixin-bridge-evidence.json
git commit -m "feat(v2r1): bridge authenticated Weixin authority"
Review B3
- Require exactly 15 named paths,
git diff --check, focused/full tests, and a second patcher pass with empty diff. - Prove Node transports evidence only; Python owns projection/authority; RuntimeWiring re-resolves.
- Prove fresh nonce on retry, terminal accepted, and legacy unreachable in
enforced. - Require B3 parent equals B2. Stop without commit/ledger append when evidence is unresolved.
- After all prior B3 review checks pass, append B3 and verify the recorded commit identity:
ledger="$(git rev-parse --git-path v2r1-blocker-ledger.tsv)"
printf 'B3\t%s\t%s\t%s\n' "$(git rev-parse HEAD)" "$(git rev-parse HEAD^)" "$(git rev-parse HEAD^{tree})" >>"$ledger"
IFS=$'\t' read -r label sha parent tree < <(tail -n 1 "$ledger")
test "$label" = B3
test "$sha" = "$(git rev-parse HEAD)"
test "$parent" = "$(git rev-parse HEAD^)"
test "$tree" = "$(git rev-parse HEAD^{tree})"
Task B4: Connect the durable outbox to the verified Hermes HTTP router
Evidence gate before code: create release/v2r1c0-rc1/hermes-http-contract-evidence.json from live listener/unit/source/schema inspection and release/v2r1c0-rc1/weixin-outbound-binding-evidence.json from raw protocol evidence. Hermes evidence resolves schemas, aios-hermes-router.service, source digest, exact 140-second timeout, tenant/user ownership, and repeated-route_id behavior as idempotent/non-idempotent. Outbound evidence must prove exact provider echo client_id -> message_id. Current ACK ret/errmsg plus reference ref_msg.message_item.msg_id/ref_msg.message_item.create_time_ms is EVIDENCE_REQUIRED and hard-stops B4; timestamp cannot unlock it.
Files only after both gates pass:
- Create:
active-runtime/app/aios_v2r1/outbox.py - Create:
active-runtime/app/aios_v2r1/hermes_http.py - Create:
active-runtime/app/aios_v2r1/hermes_worker.py - Create:
active-runtime/app/aios_v2r1/outbound_receipt.py - Modify:
active-runtime/app/aios_v2r1/ingress_service.py - Modify:
active-runtime/app/aios_v2r1/message_ledger.py - Create:
tests/v2r1/test_outbox.py - Create:
tests/v2r1/test_hermes_http.py - Create:
tests/v2r1/test_outbound_receipt.py - Create:
tests/integration/test_v2r1_delivery_flow.py - Modify:
fixes/patch_openclaw_weixin_outbound_quotes.py - Create:
fixes/patch_openclaw_weixin_outbound_receipts.py - Create:
openclaw/plugins/aios-tenant-boundary/dist/receipt-client.js - Create:
openclaw/plugins/aios-tenant-boundary/dist/outbound-receipt-journal.js - Create:
openclaw/plugins/aios-tenant-boundary/test/receipt-client.test.js - Modify:
tests/test_weixin_outbound_quote_timestamp.py - Create:
tests/test_weixin_outbound_receipt_boundary.py - Create: both evidence JSON files named above
Exact contract: readiness GET http://127.0.0.1:18791/health/ready; dispatch POST http://127.0.0.1:18791/internal/hermes/route; downstream 140 seconds; worker deadline 150 seconds; 120-second direct timeout forbidden. States pending|leased|delivered|delivery_unknown|dead. No exactly-once. Unknown idempotency stops; known false prevents ambiguous retry; known true retries same route_id/digest. After B4 evidence, the actual Node Weixin sender receives a separate outbound-receipt-write LoadCredential= and uses the same one POST signing contract for /v1/outbound-receipts: exact five-field unsigned_body, exact {method,path,body} signing object, canonical JSON, and outer lowercase 64-hex HMAC-SHA256 signature.
Node alone owns its callback journal at $STATE_DIRECTORY/outbound-receipts. The gateway unit supplies a gateway-specific StateDirectory= with StateDirectoryMode=0700; the directory and its mode-0600 files are owned and readable only by that gateway's service user. The send-boundary patch persists one sanitized per-client-ID JSON entry using a safe fixed-width digest filename: same-directory temporary file, file fsync, atomic rename to .pending, then directory fsync. Records contain only bounded IDs, send-success, attempt count, and next-attempt metadata—no bodies, quotes, attachments, signatures, credentials, key material, or provider payloads. Startup recovery and a background worker perform bounded authenticated callback retries without invoking Weixin send again. A pending record is deleted and the directory fsynced only after an authenticated application ACK. Python owns receipt/outbox/worker storage and has no second send journal. Missing exact binding records EVIDENCE_REQUIRED, null provider ID, hard stop; Date.now() and local _ms fields remain telemetry only.
- Rehydrate the stable ledger and require latest label
B3before evidence collection. - Execute evidence collectors first. Current expected result is
EVIDENCE_REQUIRED; stop with no runtime edit, B4 commit, or ledger append. - After future exact binding acceptance, add RED tests for replay/collision/lease/unknown/dead, HTTP schema, 140/150 budgets, idempotency branches, the exact receipt signing role, exact provider mapping, ledger quote,
$STATE_DIRECTORY/outbound-receiptsresolution, directory/file ownership and0700/0600modes, file/directoryfsync, atomic.pendingrename, startup/background bounded retry, no-body/no-secret persistence, no content resend, and authenticated-ACK-only delete plus directoryfsync. - Run initial RED:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH='active-runtime:active-runtime/app' \
python3 -m pytest tests/v2r1/test_outbox.py tests/v2r1/test_hermes_http.py \
tests/v2r1/test_outbound_receipt.py tests/integration/test_v2r1_delivery_flow.py \
tests/test_weixin_outbound_quote_timestamp.py tests/test_weixin_outbound_receipt_boundary.py -q
node --test openclaw/plugins/aios-tenant-boundary/test/receipt-client.test.js
Expected RED after evidence acceptance: modules absent.
- Implement outbox, bounded HTTP adapter/worker, authenticated callback client, Node-owned durable callback journal, actual Weixin send-boundary patch, role-authenticated receipt route, exact Python receipt store, and outbound ledger append only after exact binding. Run Python, Node, both patchers twice for byte-idempotence, actual send-boundary, full, and loader tests; expected GREEN, with callback failure journaled without resend, timeout ambiguity
delivery_unknown, and no exactly-once assertion. - Commit exactly 19 paths:
git add active-runtime/app/aios_v2r1/outbox.py active-runtime/app/aios_v2r1/hermes_http.py \
active-runtime/app/aios_v2r1/hermes_worker.py active-runtime/app/aios_v2r1/outbound_receipt.py \
active-runtime/app/aios_v2r1/ingress_service.py active-runtime/app/aios_v2r1/message_ledger.py \
tests/v2r1/test_outbox.py tests/v2r1/test_hermes_http.py tests/v2r1/test_outbound_receipt.py \
tests/integration/test_v2r1_delivery_flow.py fixes/patch_openclaw_weixin_outbound_quotes.py \
fixes/patch_openclaw_weixin_outbound_receipts.py openclaw/plugins/aios-tenant-boundary/dist/receipt-client.js \
openclaw/plugins/aios-tenant-boundary/dist/outbound-receipt-journal.js \
openclaw/plugins/aios-tenant-boundary/test/receipt-client.test.js tests/test_weixin_outbound_quote_timestamp.py \
tests/test_weixin_outbound_receipt_boundary.py release/v2r1c0-rc1/hermes-http-contract-evidence.json \
release/v2r1c0-rc1/weixin-outbound-binding-evidence.json
git commit -m "feat(v2r1): dispatch through evidenced Hermes outbox"
Review B4
- Require exactly 19 paths, accepted evidence digests,
git diff --check, Python/Node/patcher/send-boundary/full/loader tests. - Prove 150 exceeds exact 140 and no 120-second production deadline remains.
- Search changed files for exactly-once claims and timestamp/approximate binding; require none.
- Require B4 parent equals B3. If either evidence remains unresolved, correct result is STOP with no commit.
- After all prior B4 review checks pass, append B4 and verify the recorded commit identity:
ledger="$(git rev-parse --git-path v2r1-blocker-ledger.tsv)"
printf 'B4\t%s\t%s\t%s\n' "$(git rev-parse HEAD)" "$(git rev-parse HEAD^)" "$(git rev-parse HEAD^{tree})" >>"$ledger"
IFS=$'\t' read -r label sha parent tree < <(tail -n 1 "$ledger")
test "$label" = B4
test "$sha" = "$(git rev-parse HEAD)"
test "$parent" = "$(git rev-parse HEAD^)"
test "$tree" = "$(git rev-parse HEAD^{tree})"
Task B5: Build digest-bound isolated candidate staging and rollback
Evidence gate before code: accepted B3/B4 ledger entries. capture_live_preflight.py creates one evidence file containing a reconciled Gitea branch-consistency sentinel (status=healthy, drift_count=0, refs matching candidate) and the V1R4C6 live snapshot (host identity, capture time, Git SHA, units, commands, listeners, OpenClaw version, config/plugin digests, credential metadata, state/database paths, mounts, active artifact, rollback target). release_ctl.py --preflight-file validates the schema, exact current host, sentinel, all required fields, and freshness no older than 300 seconds. Stop if either record is missing, stale, host-mismatched, malformed, or inconsistent before staging or promotion.
Files:
- Create:
deploy/v2r1/release_ctl.py - Create:
deploy/v2r1/capture_live_preflight.py - Modify:
deploy/v2r1/install.sh,deploy/v2r1/validate.sh,deploy/v2r1/healthcheck.sh - Create:
deploy/v2r1/rollback.sh - Create:
deploy/v2r1/aios-v2r1-candidate-gateway.service - Create:
deploy/v2r1/aios-v2r1-production-gateway.service - Create:
deploy/v2r1/aios-v2r1-ingress.service - Create:
deploy/v2r1/aios-v2r1-hermes-worker.service - Create:
deploy/v2r1/openclaw-candidate.json5 - Create:
release/v2r1c0-rc1/FLEET_UNSUPPORTED - Create:
tests/deploy/test_v2r1_release_contract.py - Modify:
release/v2r1c0-rc1/RELEASE.md
Exact contract: python3 deploy/v2r1/release_ctl.py exposes only build, stage, verify-stage, promote, status, and rollback. Forward states are exactly BUILT -> STAGED -> STAGE_VERIFIED -> PRODUCTION; rollback states are ROLLBACK_STARTED -> ROLLED_BACK|ROLLBACK_FAILED. Forward commands accept --preflight-file; callers supply no artifact, reconciled-sentinel, or runtime-snapshot digest strings. The controller validates the evidence and computes/persists all three digests internally. verify-stage and promote each recapture live evidence with the collector, revalidate it, recompute the triplet, and reject any drift from the recorded candidate state. Deterministic same bytes stage/prod; isolated users/state/config/ports/credentials; LoadCredential= least-role delivery; linked candidate plugin with runtime inspect, allowConversationAccess=true, config validate, doctor, inspect; NOOP_DIGEST_MATCH zero mutation/controller calls. Candidate and production gateway units use distinct dedicated users and distinct StateDirectory= values (aios-v2r1-candidate-gateway versus aios-v2r1-production-gateway), both set StateDirectoryMode=0700, and resolve journals only below their own $STATE_DIRECTORY/outbound-receipts; sharing, symlinking, or bind-mounting journal state is forbidden. The current root-run production gateway remains root-owned only until its audited migration; candidate staging never reads it, and migrated mode-0600 records become readable only by the production gateway user. STOP blocks forward actions but permits only rollback --rollback-only --to-previous-recorded --reason-file "$reason_file"; exact prior digest/snapshot, no forward migration, cannot remove STOP, audited.
- Rehydrate the stable ledger and require latest label
B4before live evidence capture. - Add RED release tests for deterministic builds, dirty/secret rejection, rejection of caller digest arguments, preflight schema/required fields/exact-host/sentinel/freshness-at-300-seconds, tamper and recapture drift, isolated config, loader sequence, unit credentials, candidate/production service-user ownership, exact distinct
StateDirectory=names and paths,StateDirectoryMode=0700, journal file mode0600, no shared/symlink/bind-mounted journal state, root-production transition only during audited migration, no-op, STOP forward block, constrained rollback, readiness timeout, and prior integrity. - Run initial RED:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' python3 -m pytest tests/deploy/test_v2r1_release_contract.py -q
Expected RED: driver, collector, units/config, rollback wrapper, lifecycle absent.
- Implement deterministic artifact and the executable state machine; wrappers never switch branches/create tags/edit baseline/version/suppress errors. Exercise exact commands:
preflight_file="$(mktemp)"
python3 deploy/v2r1/capture_live_preflight.py --output "$preflight_file"
python3 deploy/v2r1/release_ctl.py build --source "$source_sha" --output "$build_dir" --preflight-file "$preflight_file"
python3 deploy/v2r1/release_ctl.py stage --artifact "$artifact" --preflight-file "$preflight_file" --root "$release_root"
python3 deploy/v2r1/release_ctl.py verify-stage --preflight-file "$preflight_file" --root "$release_root"
python3 deploy/v2r1/release_ctl.py promote --preflight-file "$preflight_file" --root "$release_root"
python3 deploy/v2r1/release_ctl.py status --root "$release_root"
python3 deploy/v2r1/release_ctl.py rollback --root "$release_root" --rollback-only --to-previous-recorded --reason-file "$reason_file"
Expected GREEN: the collector creates the evidence file; the controller accepts no caller digest strings, validates it, and computes the artifact/sentinel/runtime triplet internally. State records advance only through BUILT,STAGED,STAGE_VERIFIED,PRODUCTION; verify-stage and promote recapture current live state and require the same triplet; rollback records ROLLBACK_STARTED then ROLLED_BACK or ROLLBACK_FAILED.
- Execute candidate loader in isolated environment:
tmp="$(mktemp -d)"
candidate_release="$tmp/release/current"
export OPENCLAW_STATE_DIR="$tmp/openclaw-state"
export OPENCLAW_CONFIG_PATH="$tmp/openclaw-state/openclaw.json"
mkdir -p "$OPENCLAW_STATE_DIR"
openclaw plugins install --link "$candidate_release/openclaw/plugins/aios-tenant-boundary"
openclaw plugins inspect aios-tenant-boundary --runtime --json >"$tmp/plugin-before.json"
openclaw config set plugins.entries.aios-tenant-boundary.hooks.allowConversationAccess true --strict-json
openclaw config validate
set +e
openclaw doctor --lint --json >"$tmp/doctor-lint.json"
doctor_rc=$?
set -e
test "$doctor_rc" = 0 || test "$doctor_rc" = 1
python3 - "$tmp/doctor-lint.json" <<'PY'
import json, sys
data = json.load(open(sys.argv[1]))
def walk(value):
if isinstance(value, dict):
yield value
for child in value.values(): yield from walk(child)
elif isinstance(value, list):
for child in value: yield from walk(child)
bad = [item for item in walk(data) if str(item.get("severity", "")).lower() in {"error", "fatal"} and "plugin" in json.dumps(item).lower()]
assert not bad, bad
PY
openclaw doctor --post-upgrade --json >"$tmp/doctor-plugin.json"
openclaw plugins inspect aios-tenant-boundary --runtime --json >"$tmp/plugin-after.json"
Expected GREEN: plugin path inside candidate; config validation and post-upgrade doctor exit 0; lint doctor exits 0 or 1, warnings are allowed, and parsed JSON has zero error/fatal plugin findings; final inspect proves three hooks/permission; active V1R4C6 paths are absent.
- Run release/full Python/Node/loader tests, two byte-identical builds, and refreshed sentinel/snapshot comparisons. Expected GREEN: all pass.
- Commit exactly 14 paths and append ledger:
git add deploy/v2r1/release_ctl.py deploy/v2r1/capture_live_preflight.py deploy/v2r1/install.sh \
deploy/v2r1/validate.sh deploy/v2r1/healthcheck.sh deploy/v2r1/rollback.sh \
deploy/v2r1/aios-v2r1-candidate-gateway.service deploy/v2r1/aios-v2r1-production-gateway.service \
deploy/v2r1/aios-v2r1-ingress.service \
deploy/v2r1/aios-v2r1-hermes-worker.service deploy/v2r1/openclaw-candidate.json5 \
release/v2r1c0-rc1/FLEET_UNSUPPORTED tests/deploy/test_v2r1_release_contract.py release/v2r1c0-rc1/RELEASE.md
git commit -m "deploy(v2r1): bind candidate release to live evidence"
Review B5
- Require exactly 14 paths, no live secret/snapshot payload in Git,
git diff --check, complete tests, two identical builds. - Inspect both gateway units and acceptance-test distinct service users, distinct
StateDirectory=names/absolute paths,StateDirectoryMode=0700, mode-0600journal files, and no shared, symlinked, or bind-mounted receipt journal. - Prove candidate paths never resolve active V1R4C6; STOP blocks forward while rollback selects only recorded previous state and cannot remove STOP.
- Prove staging validates the collector-created file and
verify-stage/promoteeach recapture current live sentinel/runtime state, compute digests internally, and compare the same triplet; prove no caller-supplied digest option exists. - Require B5 parent equals B4.
- After all prior B5 review checks pass, append B5 and verify the recorded commit identity:
ledger="$(git rev-parse --git-path v2r1-blocker-ledger.tsv)"
printf 'B5\t%s\t%s\t%s\n' "$(git rev-parse HEAD)" "$(git rev-parse HEAD^)" "$(git rev-parse HEAD^{tree})" >>"$ledger"
IFS=$'\t' read -r label sha parent tree < <(tail -n 1 "$ledger")
test "$label" = B5
test "$sha" = "$(git rev-parse HEAD)"
test "$parent" = "$(git rev-parse HEAD^)"
test "$tree" = "$(git rev-parse HEAD^{tree})"
Final five-commit verification
Run only after all evidence/task reviews pass:
ledger="$(git rev-parse --git-path v2r1-blocker-ledger.tsv)"
python3 - "$ledger" <<'PY'
from pathlib import Path
import subprocess, sys
rows = [line.split("\t") for line in Path(sys.argv[1]).read_text().splitlines()]
assert [row[0] for row in rows] == ["START", "B1", "B2", "B3", "B4", "B5"]
for i in range(1, len(rows)):
label, sha, recorded_parent, recorded_tree = rows[i]
parent = subprocess.check_output(["git", "rev-parse", sha + "^"], text=True).strip()
tree = subprocess.check_output(["git", "rev-parse", sha + "^{tree}"], text=True).strip()
assert parent == rows[i - 1][1]
assert parent == recorded_parent and tree == recorded_tree, label
start, head = rows[0][1], rows[-1][1]
assert subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip() == head
assert subprocess.check_output(["git", "rev-list", "--count", f"{start}..{head}"], text=True).strip() == "5"
assert not subprocess.check_output(["git", "rev-list", "--merges", f"{start}..{head}"], text=True).strip()
PY
test -z "$(git status --porcelain)"
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH='active-runtime:active-runtime/app' python3 -m pytest --collect-only -q
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS='-p no:cacheprovider' PYTHONPATH='active-runtime:active-runtime/app' python3 -m pytest -q
(cd openclaw/plugins/aios-tenant-boundary && npm test)
Expected: labels exactly START,B1,B2,B3,B4,B5; current HEAD/parent/tree match recorded values; START..HEAD has exactly five commits and no merge; clean tree; full collect/full Python/Node/loader/release tests pass.
Review authority roles, status spelling, timeout units, routes, response bounds, terminal semantics, evidence states, types, and sentinel behavior. Scan unresolved markers with split literals:
marker_pattern="$(printf '%s' 'TO''DO|T''BD|FIX''ME|CHANGE''_ME|INSERT''_')"
git grep -nE "$marker_pattern" -- active-runtime deploy/v2r1 openclaw/plugins/aios-tenant-boundary release/v2r1c0-rc1 tests
Expected: no matches. Any contradiction, type mismatch, unresolved evidence, extra blocker commit, or missing review stops release preparation.
Appendix A: Exact legacy active_runtime.app import files
APPENDIX_A_BEGIN
- tests/test_agent_event_bus.py
- tests/test_agent_observability_loop.py
- tests/test_architect_governance.py
- tests/test_architect_research_timeline.py
- tests/test_architecture_change_classifier.py
- tests/test_architecture_owner_gate.py
- tests/test_architect_weekly_research.py
- tests/test_change_governance.py
- tests/test_governance_event_bridge.py
- tests/test_governance_timeline.py
- tests/test_search_agent_adapter.py
- tests/test_search_architect_integration.py
- tests/test_search_gateway_dashboard.py
- tests/test_search_gateway_general_capability.py
- tests/test_search_gateway_orchestrator.py
- tests/test_search_gateway.py
- tests/test_search_provider_registry.py
- tests/test_task_center.py
- tests/test_task_dashboard_view.py
- tests/test_task_event_and_statistics.py
- tests/test_task_lifecycle.py
- tests/test_task_orchestrator.py
- tests/test_v1r4c1_risk_fixes.py
APPENDIX_A_END