From 652359b1647c016769b451bfbe64bd9a1e519c46 Mon Sep 17 00:00:00 2001 From: Jay Shen Date: Sat, 22 Aug 2026 22:16:58 +0800 Subject: [PATCH] test: record M9-A0 pinned OpenClaw contract stop Signed-off-by: Jay Shen --- .../evidence/source-inspection.txt | 177 ++++++++++++++++++ .../plan.md | 70 +++++++ .../result.md | 61 ++++++ 3 files changed, 308 insertions(+) create mode 100644 smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/evidence/source-inspection.txt create mode 100644 smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/plan.md create mode 100644 smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/result.md diff --git a/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/evidence/source-inspection.txt b/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/evidence/source-inspection.txt new file mode 100644 index 0000000..c72ae3e --- /dev/null +++ b/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/evidence/source-inspection.txt @@ -0,0 +1,177 @@ +spike_layer=1-source-type-contract +image=tg-worker:dev +image_id=sha256:ac1cb183e5b2c82982f6473fef86ccf128763a31192d711526d843a79edb69ff +image_created=2026-08-21T13:00:21.057325912+08:00 +network=none +openclaw_version=OpenClaw 2026.4.14 (2f35b6f) +{ + "source": { + "hookTypes": { + "path": "src/plugins/hook-types.ts", + "bytes": 18405, + "lines": 694, + "sha256": "f4ae27785097debeed4e7aba3ee8b88d94e2e97e9d67e4ba39e55deccaf1948a" + }, + "hooks": { + "path": "src/plugins/hooks.ts", + "bytes": 36561, + "lines": 1160, + "sha256": "24abd7601d0ef490d43013630d5a8da6b8cd9a4e9e1e2deed1ac6a9b64f646cf" + }, + "hookRunnerGlobal": { + "path": "src/plugins/hook-runner-global.ts", + "bytes": 3022, + "lines": 106, + "sha256": "dff10b06b7b726ae353f787f15d3983a2effe3c6e9125d5b1bd0cbf6a87f30b9" + }, + "beforeToolCall": { + "path": "src/agents/pi-tools.before-tool-call.ts", + "bytes": 15826, + "lines": 464, + "sha256": "e96a13e0e23b8115087098f23d0e6222527cca98f3fa2e8f1a59b2237c2dc4b8" + }, + "promptHelpers": { + "path": "src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts", + "bytes": 8655, + "lines": 258, + "sha256": "9c9d4c161b5d0338b8fb87ece846f125e904bc967e2ffa29d687d4a14a097758" + }, + "attempt": { + "path": "src/agents/pi-embedded-runner/run/attempt.ts", + "bytes": 99293, + "lines": 2466, + "sha256": "1fbc0d09651a7378a0177667a3511c31ead8e87b29db14c8d9dd5cd1f95ba627" + }, + "persistGuard": { + "path": "src/agents/session-tool-result-guard.ts", + "bytes": 9880, + "lines": 271, + "sha256": "40a26a7b41ad68dc209727dda9da7e2c92d45df5fbe2f93d19f3f15462b21f43" + }, + "bootstrapFiles": { + "path": "src/agents/bootstrap-files.ts", + "bytes": 7651, + "lines": 243, + "sha256": "2f5210d60134de8a3235beb24f7993f11e9acec58ea799abb2e3017d6c5f901d" + }, + "bootstrapHelpers": { + "path": "src/agents/pi-embedded-helpers/bootstrap.ts", + "bytes": 8985, + "lines": 289, + "sha256": "46f9223f67612ce49d0d60fb2e2e84d079a7b38ecde314829935d76d0d568bf7" + }, + "workspace": { + "path": "src/agents/workspace.ts", + "bytes": 20054, + "lines": 648, + "sha256": "c675c2fc6015ed49fd919add48cefd86c57b8dd4a82c3fb25250ffcd60bff590" + } + }, + "before_tool_call": { + "typed_async": { + "line": 633, + "text": ") => Promise | PluginHookBeforeToolCallResult | void;" + }, + "runner_sequential": { + "line": 782, + "text": "return runModifyingHook<\"before_tool_call\", PluginHookBeforeToolCallResult>(" + }, + "caller_awaits_runner": { + "line": 203, + "text": "const hookResult = await hookRunner.runBeforeToolCall(" + }, + "failure_policy": { + "line": 44, + "text": "before_tool_call: \"fail-closed\"," + }, + "hook_runner_timeout_enforcement": false + }, + "tool_result_persist": { + "typed_sync_only": { + "line": 638, + "text": "tool_result_persist: (" + }, + "runner_sync_only": { + "line": 827, + "text": "* This hook is intentionally synchronous: it runs in hot paths where session" + }, + "promise_guard": { + "line": 852, + "text": "`[hooks] tool_result_persist handler from ${hook.pluginId} returned a Promise; ` +" + }, + "promise_error_policy": { + "line": 854, + "text": "if (shouldCatchHookErrors(\"tool_result_persist\")) {" + }, + "persistence_calls_transform_before_append": { + "line": 198, + "text": "const persisted = applyBeforeWriteHook(" + }, + "append_after_transform": { + "line": 208, + "text": "return originalAppend(persisted as never);" + }, + "default_failure_policy_expression": { + "line": 215, + "text": "catchErrors && (failurePolicyByHook[hookName] ?? \"fail-open\") === \"fail-open\";" + }, + "configured_global_policy_only": { + "line": 43, + "text": "failurePolicyByHook: {" + } + }, + "before_prompt_build": { + "typed_async": { + "line": 576, + "text": "before_prompt_build: (" + }, + "runner_sequential": { + "line": 534, + "text": "return runModifyingHook<\"before_prompt_build\", PluginHookBeforePromptBuildResult>(" + }, + "caller_catches_and_continues": { + "line": 52, + "text": "log.warn(`before_prompt_build hook failed: ${String(hookErr)}`);" + }, + "caller_returns_undefined_after_failure": { + "line": 53, + "text": "return undefined;" + }, + "missing_hook_returns_no_result": { + "line": 43, + "text": "? await params.hookRunner" + }, + "final_model_call_uses_effective_prompt": { + "line": 2011, + "text": "activeSession.prompt(effectivePrompt, { images: imageResult.images })," + } + }, + "bootstrap": { + "load_and_hook_override": { + "line": 208, + "text": "const updated = await applyBootstrapHookOverrides({" + }, + "context_build": { + "line": 236, + "text": "const contextFiles = buildBootstrapContextFiles(bootstrapFiles, {" + }, + "missing_file_becomes_prompt_text": { + "line": 225, + "text": "const missingText = `[MISSING] Expected at: ${pathValue}`;" + }, + "truncation_is_allowed": { + "line": 251, + "text": "`workspace bootstrap file ${file.name} is ${trimmed.originalLength} chars (limit ${trimmed.maxChars}); truncating in injected context`," + }, + "digest_or_checksum_in_bootstrap_sources": false + } +} + +current_tiangong_plugin_contract_lines +worker/agent/skills/runtime.mjs:34: return { prependContext: promptContext(runtime, ctx) }; +worker/agent/skills/runtime.mjs:60:export function registerAgentPackageRuntime(api, options = {}) { +worker/agent/gates/tool-result-capture.mjs:111: store.appendSync(record); +worker/plugin/index.mjs:17:import { registerAgentPackageRuntime } from "../agent/skills/runtime.mjs"; +worker/plugin/index.mjs:56: registerAgentPackageRuntime(api, { env: process.env }); +worker/plugin/index.mjs:57: api.on("tool_result_persist", createToolResultCaptureHook({ +worker/plugin/index.mjs:60: }), { priority: 100 }); diff --git a/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/plan.md b/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/plan.md new file mode 100644 index 0000000..a359c54 --- /dev/null +++ b/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/plan.md @@ -0,0 +1,70 @@ +# M9-A0 pinned OpenClaw spike + +## Scope + +- Issue: #110 +- Design: `docs/design/m9-professional-agent-runtime-and-project-knowledge.md`, §§16.1, 17.1, 20.1 +- Component under test: OpenClaw `2026.4.14` (`2f35b6f`) and the Tiangong control plugin path +- Run type: focused research/integration spike; the final layer is one Basic Matrix member turn +- Product boundary: this run does not implement or enable M9-A and does not authorize an OpenClaw upgrade + +## Contract and owners + +- OpenClaw owns the pinned hook registry, runner, prompt/session path, and official Matrix Channel Plane. +- Tiangong owns the control plugin, admission decisions, bootstrap contract decision, ToolResult capture decision, and sanitized report. +- Fake provider and fake coordination endpoint are test-only fixtures owned by this run. +- The disposable workspace and any final reserved Matrix fixture must be created by the run, recorded by stable identifier, and cleaned only by the run. + +## Provider and target identity + +- Layers 1–3: no external model provider; use a deterministic fake provider and capture the final request without credentials. +- Layer 4: use the official Channel Plane with one reserved safe member fixture. Record the exact owned fixture identity and current turn correlation in `result.md` before execution. +- No production Work, Task, room, repository, credential, or unrestricted transcript is in scope. + +## Prerequisites + +- Repository branch is based on merged `develop` and keeps the pinned OpenClaw version at `2026.4.14`. +- Actual candidate Worker image reports the pinned OpenClaw version and source/type paths before any hook runner test. +- Docker, Node, the repository deterministic checks, and the local AgentTeams/Matrix stack are available only for the layer that requires them. +- No layer may silently change the OpenClaw version, provider, model, prompt, fixture, timeout, or isolation policy. + +## Serial gates + +A later layer is blocked unless the previous layer has a passing direct machine result: + +1. **Source/type/contract inspection** — inspect hook types, registration, ordering, await/block, throw/timeout, priority, error policy, bootstrap loading, and final prompt assembly. +2. **Container hook runner + fake provider** — run the real runner in the actual image with no external network; verify blocking, final request, persistence ordering, replay, truncation, bootstrap placement, and corrupt/missing bootstrap behavior. +3. **Deterministic integration** — run the real plugin and gateway/session path with fake provider, fake coordination, synthetic Task and Work-Leader admission, runtime binding/claim fixtures, and disposable workspace. +4. **Basic Matrix turn** — use the official Channel Plane once to confirm the already-proven path; it cannot prove authorization, concurrency, atomicity, or recovery. + +If any layer fails, stop immediately, preserve sanitized evidence, and do not run later layers or begin M9-A implementation. + +## Required observations + +- `before_tool_call`: synchronous gate from the caller's perspective; await behavior, block result, thrown error, timeout, priority, and whether the tool executes. +- `tool_result_persist`: synchronous-only contract, exact ordering relative to transcript persistence and return to the model, replay behavior, truncation, and failure behavior. +- `before_prompt_build`: final request/system context, mutation fields, ordering, priority, and failure behavior. +- Immutable bootstrap: selected `AGENTS.md`/`SOUL.md` content reaches the final provider request; missing, corrupt, or digest-mismatched input prevents the model call rather than degrading to ordinary prompt context. +- Observation-only hooks remain observations and are not treated as fail-closed controls. + +## Evidence and sanitization + +- Store direct, bounded machine evidence under this run directory with pinned versions, source paths/line ranges, stable fixture IDs, event ordering, exit codes, and sanitized error codes. +- Do not store credentials, tokens, provider configuration, private session material, raw Matrix transcripts, unrestricted prompts, or unrestricted logs. +- Keep model/provider response claims separate from hook outcomes, persistent records, and external Matrix observations. + +## Timeout and cleanup + +- Each layer has an explicit command timeout recorded in `result.md`; no fixed sleep is used as readiness proof. +- Cleanup runs after every started layer and removes only this run's disposable workspace, fake services, containers, and reserved fixture resources. +- Cleanup failure keeps the run red and is reported with the exact owned resource residue. + +## Deliverables + +- `plan.md` — this pre-run scope and gate plan. +- `result.md` — per-layer pass/fail/blocked status, direct facts, limitations, and integration decision. +- Bounded evidence files referenced by `result.md`. + +## Decision rule + +The spike is research evidence only. A passing result selects the actual hook/bootstrap integration points for a later M9-A implementation PR. A failed or inconclusive premise requires a design revision and review before any implementation; no prompt, Skill, Gate, or manual process may be used as a substitute for a missing runtime safety boundary. diff --git a/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/result.md b/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/result.md new file mode 100644 index 0000000..5be4ab4 --- /dev/null +++ b/smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/result.md @@ -0,0 +1,61 @@ +# M9-A0 pinned OpenClaw spike result + +## Overall decision + +**STOPPED at layer 1.** The pinned source/type/contract inspection completed, but it found blocking contract mismatches. Layers 2–4 were not started, no M9-A runtime implementation was begun, and no OpenClaw upgrade was attempted. + +This result is research evidence, not evidence that M9-A is implemented. + +## Layer status + +| Layer | Status | Reason | +|---|---|---| +| 1. Source/type/contract inspection | **FAIL / stop** | The pinned runtime does not provide the required fail-closed immutable bootstrap and ToolResult capture contract at the inspected boundaries. | +| 2. Container hook runner + fake provider | **BLOCKED** | Required serial gate: layer 1 failed. | +| 3. Deterministic plugin/gateway/session integration | **BLOCKED** | Required serial gate: layer 2 was not started. | +| 4. Basic Matrix turn | **BLOCKED** | Required serial gate: layer 3 was not started; no Matrix fixture was used. | + +## Direct machine facts + +- Candidate image: `tg-worker:dev` +- Candidate image ID: `sha256:ac1cb183e5b2c82982f6473fef86ccf128763a31192d711526d843a79edb69ff` +- Pinned runtime observation: `OpenClaw 2026.4.14 (2f35b6f)` +- Inspection ran with Docker network disabled. +- Source file sizes and SHA-256 values, hook line references, and current Tiangong registration lines are in [`evidence/source-inspection.txt`](evidence/source-inspection.txt). + +## Blocking findings + +### 1. `before_prompt_build` is not fail-closed + +The typed hook is asynchronous and the hook runner executes it sequentially, but the embedded attempt prompt helper catches a hook error, logs a warning, returns `undefined`, and continues to the model path. A missing hook also produces no result. The final call still reaches `activeSession.prompt(effectivePrompt, ...)`. + +This cannot enforce the M9 requirement that a missing, corrupt, or invalid immutable bootstrap prevents any model call. + +### 2. Bootstrap input is ordinary mutable context, not an immutable verified boundary + +The inspected bootstrap path loads workspace files, applies internal hook overrides, and builds context files. Missing files are represented as `[MISSING] Expected at: ...`; oversized files may be truncated. No digest/checksum validation was found in the inspected bootstrap sources. + +The current Tiangong package runtime returns `prependContext`, and the current ToolResult capture writes a local `store.appendSync(record)`. Neither observation proves immutable bootstrap entry into the final system input or control-domain spool closure. + +### 3. `tool_result_persist` cannot provide the required awaited control-spool boundary as currently defined + +The typed hook and runner are synchronous-only. Promise returns are detected and ignored or handled according to the hook failure policy. The global runner configures `before_tool_call` as fail-closed but does not configure `tool_result_persist`; the default policy expression is fail-open. The transcript guard applies the transform before the local transcript append, but the hook cannot await a trusted control-domain ingest. + +Therefore the current source contract does not prove “closed in control domain before result returns to the model”; the current Tiangong implementation only appends a local capture record synchronously. + +### 4. No hook-level timeout contract was found for `before_tool_call` + +The inspected `runBeforeToolCall` runner block has no timeout/abort enforcement. Approval timeout is a separate path and is not a timeout contract for the admission hook itself. This remains unresolved until a later design revision or a pinned integration path proves the required behavior. + +## Cleanup and resource ownership + +- No fake provider, fake coordination endpoint, disposable workspace, Worker, Matrix room, or external resource was created. +- Docker inspection containers exited and the temporary copied source directory was removed after evidence generation. +- No credential, token, provider configuration, or raw session transcript was written to the repository. + +## Required follow-up + +1. Open a design revision issue/PR for the failed pinned contracts, preserving this result and direct evidence. +2. Decide whether the pinned OpenClaw source can support a Tiangong-owned fail-closed wrapper/control boundary without relying on fail-open prompt or synchronous observation hooks. +3. Re-review the revised contract before rerunning layer 1 or starting layers 2–4. +4. Do not begin M9-A implementation until a new spike passes all four layers in order.