From 5aecf8cb2ece3785f92c8649f765af513a65ef76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Sun, 25 Oct 2026 23:18:27 +0200 Subject: [PATCH 01/42] feat: add kimi pinned project research collector Integrates independently reviewed client leaf 7f368088eb57da962959741112095b05182fa4b1. Native runtime qualification remains separate. --- scripts/project_native_collectors/kimi.py | 79 +++++++++++++++++++ scripts/test_project_native_kimi.py | 94 +++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 scripts/project_native_collectors/kimi.py create mode 100644 scripts/test_project_native_kimi.py diff --git a/scripts/project_native_collectors/kimi.py b/scripts/project_native_collectors/kimi.py new file mode 100644 index 000000000..ecec1084a --- /dev/null +++ b/scripts/project_native_collectors/kimi.py @@ -0,0 +1,79 @@ +"""Pinned Kimi help observations; project discovery and runtime stay unqualified. + +The shared frontend owns acquisition, genuine hosted admission, child bounds, +private capture and artifact publication. This leaf never starts a web/ACP +service, grants trust, configures a provider or reads a native bearer token. +""" +import re + +CLIENT = "kimi" +VERSION = "2.3.2" +AVAILABLE_PHASES = ("acquire", "help") +ACQUISITION = { + "url": "https://github.com/MoonshotAI/kimi-code/releases/download/" + "%40moonshot-ai/kimi-code%402.3.2/kimi-code-linux-x64.tar.gz", + "integrity": "sha256:4715ccd1a591b64683929a14b26b3d07ac4c484e4da3881b22a2ccca56304042", + "max_bytes": 96 << 20, + "expected_bytes": 65_938_009, + "format": "tar.gz", + "entrypoint": "kimi", + "runtime": "native", +} + +# The exact release's produce-manifest.mjs packages the sole executable as +# `tar -C -czf kimi`. The frontend must verify the archive pin +# and regular entry before executing it. Never reuse a 2.3.1 binary digest. +SOURCE_COMMIT = "b8dd3a134060c6e80ba4a1659335cd63b68483f2" +PROBES = ( + ("kimi-version", ("--version",), None), + ("kimi-help", ("--help",), ""), + ("kimi-doctor-help", ("doctor", "--help"), "doctor"), + ("kimi-acp-help", ("acp", "--help"), "acp"), + ("kimi-web-help", ("web", "--help"), "web"), +) + + +def _accepted_streams(record, stdout, stderr): + if (not isinstance(record, dict) or type(record.get("exit")) is not int + or record["exit"] != 0 or "error" not in record + or record["error"] not in (None, "")): + raise ValueError("Kimi help probe failed; bounded capture retained") + if not isinstance(stdout, bytes) or not isinstance(stderr, bytes) or stderr: + raise ValueError("Kimi help probe emitted unexpected stream data") + try: + return stdout.decode("utf-8", errors="strict") + except UnicodeError as error: + raise ValueError("Kimi help probe is not UTF-8") from error + + +def _validate_help(text, command): + name = "kimi" + (" " + command if command else "") + if not re.search(r"^Usage: " + re.escape(name) + r"(?:[ \t]+\[|[ \t]*$)", text, re.MULTILINE): + raise ValueError("Kimi help usage does not identify the requested command") + if "--help" not in text: + raise ValueError("Kimi command help is missing its help option") + if not command and any(word not in text for word in ("--version", "doctor", "acp", "web")): + raise ValueError("Kimi main help is missing the selected command surface") + + +def collect(ctx): + """Observe only fixed read-only version/help commands in project A.""" + observations = {} + for label, arguments, command in PROBES: + record, stdout, stderr = ctx.probe(label, list(arguments), project="a", timeout=30) + text = _accepted_streams(record, stdout, stderr) + if command is None: + if stdout not in (b"2.3.2", b"2.3.2\n", b"2.3.2\r\n"): + raise ValueError("Kimi executable version differs from the frozen 2.3.2 pin") + else: + _validate_help(text, command) + observations[label] = {"surface": "version" if command is None else "help", + "arguments": list(arguments)} + return { + "observations": observations, + "cases": [{"id": "kimi.help", "status": "passed", + "reason": "Exact pinned Kimi 2.3.2 version and fixed main/doctor/ACP/web help observed; no service or project discovery was invoked"}], + "unobserved": ["kimi.scope-observation", "kimi.project-lifecycle", + "kimi.native-restart-removal", "kimi.mcp-initialize", + "kimi.mcp-tools-list", "kimi.mcp-tools-call"], + } diff --git a/scripts/test_project_native_kimi.py b/scripts/test_project_native_kimi.py new file mode 100644 index 000000000..393facecb --- /dev/null +++ b/scripts/test_project_native_kimi.py @@ -0,0 +1,94 @@ +"""Inert Kimi collector contracts; never acquires or executes vendor bytes.""" +import importlib.util +from pathlib import Path +import unittest + +SPEC = importlib.util.spec_from_file_location( + "kimi_project_collector", Path(__file__).with_name("project_native_collectors") / "kimi.py") +kimi = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(kimi) + + +def help_body(command): + name = "kimi" + (" " + command if command else "") + return ("Usage: " + name + " [options]\nOptions:\n --help\n" + + (" --version\nCommands:\n doctor\n acp\n web\n" if not command else "")).encode() + + +class FakeContext: + def __init__(self, replacement=None): + self.calls = [] + self.replacement = replacement or {} + + def probe(self, label, arguments, project="a", extra_env=None, timeout=30): + self.calls.append((label, arguments, project, extra_env, timeout)) + command = next(command for name, _, command in kimi.PROBES if name == label) + default = ({"exit": 0, "error": None}, b"2.3.2\n" if command is None else help_body(command), b"") + return self.replacement.get(label, default) + + +class KimiCollectorTests(unittest.TestCase): + def test_frozen_archive_is_current_official_native_entry(self): + self.assertEqual(kimi.CLIENT, "kimi") + self.assertEqual(kimi.VERSION, "2.3.2") + self.assertEqual(kimi.ACQUISITION, { + "url": "https://github.com/MoonshotAI/kimi-code/releases/download/" + "%40moonshot-ai/kimi-code%402.3.2/kimi-code-linux-x64.tar.gz", + "integrity": "sha256:4715ccd1a591b64683929a14b26b3d07ac4c484e4da3881b22a2ccca56304042", + "max_bytes": 96 << 20, "expected_bytes": 65_938_009, + "format": "tar.gz", "entrypoint": "kimi", "runtime": "native", + }) + + def test_only_fixed_help_surface_is_observed_without_scope_claim(self): + ctx = FakeContext() + result = kimi.collect(ctx) + self.assertEqual(kimi.AVAILABLE_PHASES, ("acquire", "help")) + self.assertEqual(ctx.calls, [ + ("kimi-version", ["--version"], "a", None, 30), + ("kimi-help", ["--help"], "a", None, 30), + ("kimi-doctor-help", ["doctor", "--help"], "a", None, 30), + ("kimi-acp-help", ["acp", "--help"], "a", None, 30), + ("kimi-web-help", ["web", "--help"], "a", None, 30), + ]) + self.assertEqual([row["id"] for row in result["cases"]], ["kimi.help"]) + self.assertEqual(result["cases"][0]["status"], "passed") + self.assertIn("kimi.scope-observation", result["unobserved"]) + self.assertIn("kimi.mcp-tools-call", result["unobserved"]) + self.assertEqual(list(result["observations"]), [call[0] for call in ctx.calls]) + self.assertEqual(result["observations"]["kimi-version"]["surface"], "version") + + def test_wrong_or_ambiguous_version_stops_before_any_help(self): + for body in (b"2.3.1\n", b"2.3.20\n", b"2.3.2-alpha\n", b"inert 2.3.2\n", b"2.3.2\nextra\n"): + with self.subTest(body=body): + ctx = FakeContext({"kimi-version": ({"exit": 0, "error": None}, body, b"")}) + with self.assertRaisesRegex(ValueError, "version differs"): + kimi.collect(ctx) + self.assertEqual(len(ctx.calls), 1) + + def test_failed_or_missing_process_result_never_becomes_boundary_or_pass(self): + for record in ({"exit": 1, "error": None}, {"exit": 0, "error": "deadline"}, + {"exit": False, "error": None}, {"exit": 0}, {}): + with self.subTest(record=record): + ctx = FakeContext({"kimi-web-help": (record, help_body("web"), b"")}) + with self.assertRaisesRegex(ValueError, "probe failed"): + kimi.collect(ctx) + + def test_wrong_command_empty_non_utf8_and_stderr_help_fail(self): + for stdout, stderr in ((b"", b""), (help_body("doctor"), b""), + (b"Usage: kimi web [options]\n", b""), + (b"\xff", b""), (help_body("web"), b"auth required")): + with self.subTest(stdout=stdout, stderr=stderr): + ctx = FakeContext({"kimi-web-help": ({"exit": 0, "error": None}, stdout, stderr)}) + with self.assertRaises(ValueError): + kimi.collect(ctx) + + def test_subcommand_help_cannot_satisfy_main_help_by_mentioning_commands(self): + body = help_body("doctor") + b"--version acp web\n" + ctx = FakeContext({"kimi-help": ({"exit": 0, "error": None}, body, b"")}) + with self.assertRaisesRegex(ValueError, "requested command"): + kimi.collect(ctx) + self.assertEqual(len(ctx.calls), 2) + + +if __name__ == "__main__": + unittest.main() From 717f041e0eedf86a53bc7ce54c4e4cd0cf5d0173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Mon, 26 Oct 2026 16:49:39 +0200 Subject: [PATCH 02/42] fix: admit the observed Kimi Code 2.3.2 project path --- .../agentplugins/clients/kimi/scope.go | 4 ++-- .../providers/project_kimi_admission_test.go | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/install/integrationctl/agentplugins/clients/kimi/scope.go b/install/integrationctl/agentplugins/clients/kimi/scope.go index f051ef4e7..4039c9993 100644 --- a/install/integrationctl/agentplugins/clients/kimi/scope.go +++ b/install/integrationctl/agentplugins/clients/kimi/scope.go @@ -55,8 +55,8 @@ func (*Adapter) CheckPlanPrecondition(in clients.PlanInput, plan *domain.Deliver func currentKimi(client domain.DetectedClient) error { name := filepath.Base(client.ExecutablePath) - if (name != "kimi" && name != "kimi.exe") || !filepath.IsAbs(client.ExecutablePath) || filepath.Clean(client.ExecutablePath) != client.ExecutablePath || client.Version != "2.3.1" { - return fmt.Errorf("kimi project components require the explicitly selected current Kimi Code 2.3.1 executable/version; archived or unknown harnesses are unqualified") + if (name != "kimi" && name != "kimi.exe") || !filepath.IsAbs(client.ExecutablePath) || filepath.Clean(client.ExecutablePath) != client.ExecutablePath || (client.Version != "2.3.1" && client.Version != "2.3.2") { + return fmt.Errorf("kimi project components require the explicitly selected current Kimi Code 2.3.1 or 2.3.2 executable/version; archived or unknown harnesses are unqualified") } return nil } diff --git a/install/integrationctl/agentplugins/providers/project_kimi_admission_test.go b/install/integrationctl/agentplugins/providers/project_kimi_admission_test.go index e96fd1ed4..ae1a9171f 100644 --- a/install/integrationctl/agentplugins/providers/project_kimi_admission_test.go +++ b/install/integrationctl/agentplugins/providers/project_kimi_admission_test.go @@ -17,17 +17,21 @@ import ( func TestProjectKimiRequiresCurrentObservedHarnessAndNearestGit(t *testing.T) { f := newKimiProjectFixture(t, false, false) p := planner.Planner{Registry: f.registry, Paths: pathpolicy.Policy{}, ProjectRoot: f.project, ManagedRoot: filepath.Join(f.project, ".uap", "managed")} - qualified, err := p.Plan(context.Background(), domain.PlanRequest{Envelope: f.envelope, Client: f.request.Client, Scope: domain.ScopeProject, ProjectRoot: f.project, PhysicalArtifactID: f.request.Plan.PhysicalArtifactID}) - if err != nil || qualified.Status != domain.PlanReady { - t.Fatalf("current observed Kimi harness did not qualify: %+v, %v", qualified, err) + for _, version := range []string{"2.3.1", "2.3.2"} { + client := f.request.Client + client.Version = version + qualified, err := p.Plan(context.Background(), domain.PlanRequest{Envelope: f.envelope, Client: client, Scope: domain.ScopeProject, ProjectRoot: f.project, PhysicalArtifactID: f.request.Plan.PhysicalArtifactID}) + if err != nil || qualified.Status != domain.PlanReady { + t.Fatalf("current observed Kimi %s harness did not qualify: %+v, %v", version, qualified, err) + } } for _, change := range []func(*domain.DetectedClient){ - func(c *domain.DetectedClient) { c.Version = "" }, func(c *domain.DetectedClient) { c.Version = "2.3.0" }, func(c *domain.DetectedClient) { c.Version = "2.3.1-alpha" }, func(c *domain.DetectedClient) { c.ExecutablePath = filepath.Join(f.base, "kimi-cli") }, + func(c *domain.DetectedClient) { c.Version = "" }, func(c *domain.DetectedClient) { c.Version = "2.3.0" }, func(c *domain.DetectedClient) { c.Version = "2.3.1-alpha" }, func(c *domain.DetectedClient) { c.Version = "2.3.3" }, func(c *domain.DetectedClient) { c.Version = "2.3.2-alpha" }, func(c *domain.DetectedClient) { c.Version = "2.3.20" }, func(c *domain.DetectedClient) { c.Version = "v2.3.2" }, func(c *domain.DetectedClient) { c.Version = "2.3.2 " }, func(c *domain.DetectedClient) { c.Version = "2.3.2\n" }, func(c *domain.DetectedClient) { c.ExecutablePath = filepath.Join(f.base, "kimi-cli") }, func(c *domain.DetectedClient) { c.ExecutablePath = "kimi" }, func(c *domain.DetectedClient) { c.ExecutablePath = f.base + "/./kimi" }, } { client := f.request.Client change(&client) before := projectGeminiInventory(t, f.base) - if _, err := p.Plan(context.Background(), domain.PlanRequest{Envelope: f.envelope, Client: client, Scope: domain.ScopeProject, ProjectRoot: f.project, PhysicalArtifactID: f.request.Plan.PhysicalArtifactID}); err == nil || !strings.Contains(err.Error(), "require the explicitly selected current Kimi Code 2.3.1 executable/version") { + if _, err := p.Plan(context.Background(), domain.PlanRequest{Envelope: f.envelope, Client: client, Scope: domain.ScopeProject, ProjectRoot: f.project, PhysicalArtifactID: f.request.Plan.PhysicalArtifactID}); err == nil || !strings.Contains(err.Error(), "require the explicitly selected current Kimi Code 2.3.1 or 2.3.2 executable/version") { t.Fatalf("unobserved current Kimi harness refusal = %v", err) } if !reflect.DeepEqual(before, projectGeminiInventory(t, f.base)) { From 1d80da06a1d92866973373dd82dbbe509e8f9458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Mon, 26 Oct 2026 17:30:20 +0200 Subject: [PATCH 03/42] test(kimi): exercise installed project lifecycle through native API --- .github/workflows/project-native-runtime.yml | 125 +++ .../remaining-native-qualification.md | 97 +++ ...ugins_kimi_project_native_contract_test.go | 116 +++ ...gins_kimi_project_native_lifecycle_test.go | 734 ++++++++++++++++++ ...gentplugins_project_native_runtime_test.go | 269 +++++++ scripts/project_native_collectors/kimi.py | 247 ++++++ scripts/project_native_runtime.py | 417 ++++++++++ scripts/test_project_native_kimi.py | 78 ++ scripts/test_project_native_runtime.py | 115 +++ 9 files changed, 2198 insertions(+) create mode 100644 .github/workflows/project-native-runtime.yml create mode 100644 repotests/agentplugins_kimi_project_native_contract_test.go create mode 100644 repotests/agentplugins_kimi_project_native_lifecycle_test.go create mode 100644 repotests/agentplugins_project_native_runtime_test.go create mode 100644 scripts/project_native_runtime.py create mode 100644 scripts/test_project_native_runtime.py diff --git a/.github/workflows/project-native-runtime.yml b/.github/workflows/project-native-runtime.yml new file mode 100644 index 000000000..338984cb2 --- /dev/null +++ b/.github/workflows/project-native-runtime.yml @@ -0,0 +1,125 @@ +name: Project Native Runtime +on: + pull_request: + paths: + - '.github/workflows/project-native-runtime.yml' + - 'scripts/project_native_runtime.py' + - 'scripts/project_native_research.py' + - 'scripts/project_native_service.py' + - 'scripts/project_native_collectors/**' + - 'scripts/test_project_native_runtime.py' + - 'repotests/agentplugins_*project_native_*test.go' + - 'repotests/testdata/agentplugins_native_probe/**' + workflow_dispatch: +permissions: + contents: read +concurrency: + group: project-native-runtime-${{ github.ref }} + cancel-in-progress: false +jobs: + contracts: + name: Runtime admission contracts and exact selection + runs-on: ubuntu-24.04 + timeout-minutes: 5 + outputs: + matrix: ${{ steps.selection.outputs.matrix }} + count: ${{ steps.selection.outputs.count }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Verify inert envelope and selection contracts + run: python3 -m unittest discover -s scripts -p 'test_project_native_runtime.py' + - name: Select only implemented exact native tests + id: selection + run: | + python3 - <<'PYTHON' + import json, os, subprocess + value = subprocess.check_output(['python3', 'scripts/project_native_runtime.py', '--matrix'], text=True).strip() + matrix = json.loads(value) + with open(os.environ['GITHUB_OUTPUT'], 'a') as output: + output.write('matrix=' + value + '\n') + output.write('count=' + str(len(matrix['include'])) + '\n') + print(value) + PYTHON + native: + name: ${{ matrix.client }} / ${{ matrix.phase }} / installed public CLI + needs: contracts + if: ${{ needs.contracts.outputs.count != '0' }} + strategy: + fail-fast: false + max-parallel: 3 + matrix: ${{ fromJSON(needs.contracts.outputs.matrix) }} + runs-on: ubuntu-24.04 + timeout-minutes: 26 + env: + AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED: '1' + EXPECTED_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Admit actual hosted environment and clean source before toolchain setup + run: | + python3 - <<'PYTHON' + import os, platform, re, sys + from pathlib import Path + sys.path.insert(0, str(Path.cwd() / 'scripts')) + from native_client_research import run_probe + required = {'GITHUB_ACTIONS': 'true', 'RUNNER_ENVIRONMENT': 'github-hosted', 'AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED': '1'} + if any(os.environ.get(key) != value for key, value in required.items()) or platform.system() != 'Linux' or platform.machine() != 'x86_64': + raise SystemExit('actual opted-in GitHub-hosted Linux x64 required') + expected = os.environ['EXPECTED_COMMIT'] + env = {'PATH': '/usr/bin:/bin', 'LC_ALL': 'C', 'GIT_CONFIG_NOSYSTEM': '1', 'GIT_CONFIG_GLOBAL': '/dev/null', 'GIT_TERMINAL_PROMPT': '0'} + def git(*args): + record, stdout, stderr = run_probe(['/usr/bin/git', *args], Path.cwd(), env, Path('/tmp'), 'source-guard', timeout=10, limit=65536, memory_only=True) + if record['error'] is not None or record['exit'] != 0 or stderr: + raise SystemExit('bounded source identity query failed') + return stdout.decode('ascii', errors='strict').strip() + if not re.fullmatch('[0-9a-f]{40}', expected) or git('rev-parse', 'HEAD') != expected or not re.fullmatch('[0-9a-f]{40}', git('rev-parse', 'HEAD^{tree}')) or git('status', '--porcelain', '--untracked-files=normal'): + raise SystemExit('exact clean current-source checkout required') + PYTHON + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: '1.26.10' + cache: false + - name: Run the selected bounded current-source native test + run: python3 scripts/project_native_runtime.py --client '${{ matrix.client }}' --output '${{ runner.temp }}/project-runtime-${{ matrix.client }}' + - name: Admit exact closed output files + id: admission + if: ${{ always() }} + env: + EVIDENCE: ${{ runner.temp }}/project-runtime-${{ matrix.client }}/upload + run: | + /usr/bin/python3 -I - <<'PYTHON' + import os, stat + from pathlib import Path + root = Path(os.environ['EVIDENCE']) + for label in ('frontend', 'runtime'): + path = root / (label + '.zip') + if not path.exists(): + continue + info = path.lstat() + if path.resolve() != path or not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_uid != os.getuid() or info.st_size > 32 * 1024 * 1024: + raise SystemExit('refusing aliased or unbounded closed evidence') + with open(os.environ['GITHUB_OUTPUT'], 'a') as output: + output.write(label + '=true\n') + PYTHON + - name: Upload source, build and test receipt + if: ${{ always() && steps.admission.outputs.frontend == 'true' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.0 + with: + name: project-runtime-${{ matrix.client }}-${{ matrix.phase }}-frontend + path: ${{ runner.temp }}/project-runtime-${{ matrix.client }}/upload/frontend.zip + if-no-files-found: error + retention-days: 14 + - name: Upload admitted native runtime evidence + if: ${{ always() && steps.admission.outputs.runtime == 'true' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.0 + with: + name: project-runtime-${{ matrix.client }}-${{ matrix.phase }}-native + path: ${{ runner.temp }}/project-runtime-${{ matrix.client }}/upload/runtime.zip + if-no-files-found: error + retention-days: 14 diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index 1c68096ba..054506c3c 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -733,6 +733,103 @@ State stays Prepared. Label `provider_kind=scripted_loopback`, vendor authentication false and real-model use false. Raw bounded evidence stays in closed CI artifacts, not Git or recursively uploaded native profiles. +## Kimi installed local lifecycle amendment + +Genuine Kimi API run 40238495966 at `ac3a7e5da565590fd85c4ff9795cae6b312ed6b1`, +artifact 12615878095 (ZIP SHA256 +`cb14eec0bd7fc5040a43d4314255055a59ad1729a24dacb56ffcc894607381ef`), +confirmed the pinned 2.3.2 native bearer, A/B trust and separate A/B/C/user +catalogs with named initialize/tools-list and untrusted C refusal. Its +tools-call count was zero. Admit only exact product versions `{2.3.1, 2.3.2}`; +all existing executable, original-user and nearest-Git checks remain intact. + +Select `TestAgentpluginsKimiNativeProjectComponentsLifecycle`, opt-in +`AGENTPLUGINS_KIMI_PROJECT_NATIVE_E2E=1`. Public UAP installs independent local +user/A/B/C packages. Seven fresh authenticated native web processes observe the +empty, installed, unchanged, updated, missing-repaired, restarted and removed +stages. Native API trust authorizes only A/B during the empty baseline; C stays +untrusted. Receipt-selected skills and independently reconstructed exact +stdio command/argv/environment/package/data/cwd authority must match the +unchanged installed native files before the API reads them. B/C/user managed +package inventories and their original receipts/native bytes remain exact. + +This is the supported imperative local-directory route, with `--no-save` and +no saved dependency agreement. Missing local repair keeps its selected source +at A-two: it proves reconstruction from unchanged selected local input. Moving +Git/ZIP sources and repair from their recorded immutable pin remain separate +required gates, `TestProjectComposedSourceLifecycleSeparatesUserAndProjects`, +`TestProjectKimiComposedSavedLifecycle` and +`TestProjectKimiColdSavedImportPreservesPinnedAgreement`. Do not attribute those +source-resolution results to the native local-directory test or change product +repair semantics to fit a stronger local-source claim. + +The native test may satisfy project-lifecycle, native-restart-removal, +mcp-initialize and mcp-tools-list only after the complete sequence and cleanup. +It keeps mcp-tools-call false, activation Prepared, vendor authentication false +and real-model use false. Provider-driven tool calls require their own observed +and independently reviewed amendment. Reuse existing private service capture, +token quarantine/redaction and closed artifact admission; never upload a native +profile or token file. The original total/work/cleanup budgets are 900/840/60 +seconds, with no native children across UAP mutations. + +## Current-source runtime CI handoff + +The October 26 continuation uses a separate `project-native-runtime.yml` job +and the small `project_native_runtime.py` frontend. Two independent plan rounds +and independent code reviews by the Gemini/Copilot and Kimi/Cline implementers +admitted the closed first Copilot attempt. No existing baseline gate is changed. + +Only a fixed implemented test name, opt-in, source filename and deadline can be +selected. The workflow verifies actual GitHub-hosted Linux x64 and exact clean +source before setup-go; the frontend repeats admission before any compiler or +vendor child. It reuses the reviewed archive acquisition/materialization, +immutable file checks, capture budgets and closed ZIP writer. Build the public +installer, existing probe and repository test binary with exact Go 1.26.10, +checking held compiler identity before and after each build. Provision the same +pinned lintai release through bounded verified archive acquisition. No inherited +profile, authentication, proxy or loader environment enters the test. + +Acquisition and compilation have one 480-second budget. The initial Copilot +attempt has its own original 180-second total, with five seconds reserved by +the outer process capture for reaping, followed by a bounded publication phase. +Future full lifecycle registrations require their separately reviewed exact +tests and original deadlines; this is not an arbitrary timeout override. + +The frontend records source/build identity separately in `frontend.zip`. A +native `runtime.zip` is normalized only from bounded held bytes after checking +the fixed client's exact member inventory, command triples, source identity, +UTF-8, duplicate/special/oversized entries and explicit qualification state. +Copilot's initial schema admits its named installed snapshots, bounded provider +requests, probe events and fixed command table. Unknown native JSONL semantics +remain an intentional failed research attempt until a genuine artifact has been +reviewed. A closed artifact or successful build cannot qualify a failed, +skipped, absent or otherwise unobserved native test. Raw profiles are never +recursively uploaded, and the workflow publishes only these two named ZIPs. + +The reviewed Gemini registration uses the exact +`TestAgentpluginsGeminiNativeProjectRuntimeAttempt` with the same 180-second +attempt bound. A successful attempt additionally requires all command triples, +both installed snapshots, provider requests, probe events, actual native +session/call identifiers and returned probe facts. Its five lifecycle +qualification flags remain false. Pinned Gemini source adds only the exact +`wait_for_previous` boolean property to the provider-visible tool schema; +validate that property exactly before comparing the original probe schema. +Never add it to the emitted tool arguments. The fixture API key is registered, +redacted and checked again at closed artifact admission. + +The Kimi registration selects only +`TestAgentpluginsKimiNativeProjectComponentsLifecycle` and its original +900-second total/840-second work budget. It observes public local-source +installation, reconciliation, update, missing-component repair, fresh native +processes and removal through Kimi's authenticated local API. Named native MCP +tests prove initialize/list; tools-call stays false. As with the reviewed local +Gemini/Copilot sequence, repair uses unchanged selected local input. Moving-source +and recorded-pin behavior remains separately required in +`TestProjectComposedSourceLifecycleSeparatesUserAndProjects` and +`TestProjectCopilotComposedSavedLifecycle`; it is not attributed to these native +local-directory observations. The checked source-root override and canonical +Python executable are explicit inputs, with no ambient auth or loader variables. + ## Completion gate For each client, retain the exact source commit/tree, archive and executable diff --git a/repotests/agentplugins_kimi_project_native_contract_test.go b/repotests/agentplugins_kimi_project_native_contract_test.go new file mode 100644 index 000000000..1fd00d443 --- /dev/null +++ b/repotests/agentplugins_kimi_project_native_contract_test.go @@ -0,0 +1,116 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" +) + +func TestKimiProjectNativeWholeJSONRemainsBoundedAndExact(t *testing.T) { + for _, body := range [][]byte{ + []byte(`{"stage":"a","stage":"b"}`), []byte(`{"a":{"x":1,"x":2}}`), + []byte(`{"x":1e999}`), []byte(`{"x":NaN}`), []byte(`{} {}`), []byte(`[]`), + {0xff}, bytes.Repeat([]byte(" "), kimiProjectNativeLimit+1), + []byte(`{"nested":` + strings.Repeat("[", 34) + strings.Repeat("]", 34) + `}`), + } { + if _, err := kimiProjectObject(body); err == nil { + t.Fatalf("unsafe Kimi stage JSON admitted: %.80s", body) + } + } + value, err := kimiProjectObject([]byte(`{"schema_version":1,"tools_call_count":0,"provider_configured":false}`)) + if err != nil || value["schema_version"] != json.Number("1") || value["tools_call_count"] != json.Number("0") || value["provider_configured"] != false { + t.Fatalf("exact native count/version types lost: %v (%v)", value, err) + } +} + +func TestKimiProjectNativeMCPRequiresIndependentReceiptAndSourceAuthority(t *testing.T) { + p := &kimiProjectNativePackage{Revision: "a-two", Data: "/owned/project/.uap/plugin-data/a", Binding: domain.ClientBinding{TargetLocator: "/owned/project/.uap/managed/a"}} + active := p.Binding.TargetLocator + good := map[string]any{"transport": "stdio", "command": filepath.Join(active, "io.github.777genius.agentplugins", "managed-stdio-v1", "agentplugins"), "args": []any{"--internal-stdio-v1", active, p.Data, active, "plugin", "--", "./bin/probe", "Kimi owned a", active + "/argument"}, "env": map[string]any{"PLUGIN_ROOT": active, "PLUGIN_DATA": p.Data, "UAP_TEST_ROOT": "/owned", "UAP_TEST_EVENTS": "/owned/a-events.jsonl", "UAP_TEST_NONCE": "nonce-a", "UAP_TEST_REVISION": "a-two"}} + body, _ := json.Marshal(good) + if err := kimiProjectValidateServer(good, p, "a", "/owned", "nonce"); err != nil { + t.Fatal(err) + } + for _, kind := range []string{"launcher", "package", "data", "cwd", "anchor", "source-command", "source-argument", "nonce", "revision", "environment", "extra"} { + actual, err := kimiProjectObject(body) + if err != nil { + t.Fatal(err) + } + args := actual["args"].([]any) + env := actual["env"].(map[string]any) + switch kind { + case "launcher": + actual["command"] = "/foreign/probe" + case "package": + args[1] = "/foreign/package" + case "data": + args[2] = "/foreign/data" + case "cwd": + args[3] = "/foreign/cwd" + case "anchor": + args[4] = "data" + case "source-command": + args[6] = "./bin/other" + case "source-argument": + args[7] = "wrong" + case "nonce": + env["UAP_TEST_NONCE"] = "wrong" + case "revision": + env["UAP_TEST_REVISION"] = "a-three" + case "environment": + env["PLUGIN_DATA"] = "/foreign/data" + case "extra": + actual["cwd"] = "/foreign" + } + if err := kimiProjectValidateServer(actual, p, "a", "/owned", "nonce"); err == nil { + t.Fatalf("wrong Kimi %s projection accepted", kind) + } + } +} + +func TestKimiProjectNativeManagedTreeSnapshotCannotAdoptCorruption(t *testing.T) { + root := t.TempDir() + path := filepath.Join(root, "bin", "probe") + projectNativeRuntimeWrite(t, path, []byte("original inert probe bytes"), 0700) + before := kimiProjectTree(t, root) + if err := os.WriteFile(path, []byte("corrupt inert probe bytes"), 0700); err != nil { + t.Fatal(err) + } + if reflect.DeepEqual(before, kimiProjectTree(t, root)) { + t.Fatal("managed probe corruption adopted into preservation baseline") + } + if err := os.WriteFile(path, []byte("original inert probe bytes"), 0700); err != nil { + t.Fatal(err) + } + if err := os.Chmod(path, 0600); err != nil { + t.Fatal(err) + } + if reflect.DeepEqual(before, kimiProjectTree(t, root)) { + t.Fatal("managed probe permission drift adopted") + } + if err := os.Chmod(path, 0700); err != nil { + t.Fatal(err) + } + empty := filepath.Join(root, "empty-foreign-dir") + if err := os.Mkdir(empty, 0700); err != nil { + t.Fatal(err) + } + if reflect.DeepEqual(before, kimiProjectTree(t, root)) { + t.Fatal("empty managed directory addition adopted") + } + if err := os.Remove(empty); err != nil { + t.Fatal(err) + } + projectNativeRuntimeWrite(t, filepath.Join(root, "extra.json"), []byte("{}"), 0600) + if reflect.DeepEqual(before, kimiProjectTree(t, root)) { + t.Fatal("unrecorded managed file addition preserved") + } +} diff --git a/repotests/agentplugins_kimi_project_native_lifecycle_test.go b/repotests/agentplugins_kimi_project_native_lifecycle_test.go new file mode 100644 index 000000000..086b39716 --- /dev/null +++ b/repotests/agentplugins_kimi_project_native_lifecycle_test.go @@ -0,0 +1,734 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "archive/zip" + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "reflect" + "runtime" + "sort" + "strings" + "testing" + "time" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/adapters/process" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/managedstdio" + "github.com/777genius/plugin-kit-ai/install/integrationctl/ports" +) + +const kimiProjectNativeLimit = 1 << 20 +const kimiProjectArchiveSHA = "4715ccd1a591b64683929a14b26b3d07ac4c484e4da3881b22a2ccca56304042" +const kimiProjectExecutableSHA = "1cc5979fca0ea8c01c0a4d53806df8e65573cfbe7daf17ccff86db392807e4fa" + +type kimiProjectNativePackage struct { + Source, Name, Revision, Skill, Data string + Installation domain.Installation + Binding domain.ClientBinding +} + +type kimiProjectNativeFixture struct { + base *nativeFixture + client, installer, python, git, output string + projects map[string]string + packages map[string]*kimiProjectNativePackage + immutable, protected map[string]string + files map[string][]byte + ctx context.Context + cancel context.CancelFunc + started time.Time + evidence map[string]any + serverIDs map[string]bool + foreignMCP map[string]any + protectedTrees map[string]map[string]string +} + +func (f *kimiProjectNativeFixture) keep(t *testing.T, name string, body []byte) { + t.Helper() + total := len(body) + for _, b := range f.files { + total += len(b) + } + if filepath.Base(name) != name || f.files[name] != nil || len(f.files) >= 120 || len(body) > kimiProjectNativeLimit || total > 24<<20 { + t.Fatal("Kimi closed evidence bounds refused") + } + f.files[name] = bytes.Clone(body) +} + +func (f *kimiProjectNativeFixture) check(t *testing.T) { + t.Helper() + for _, inventory := range []map[string]string{f.immutable, f.protected} { + for path, want := range inventory { + got, err := projectNativeRuntimeDigest(path) + if err != nil || got != want { + t.Fatalf("Kimi authority identity changed: %s (%v)", path, err) + } + } + } + for root, expected := range f.protectedTrees { + if !reflect.DeepEqual(kimiProjectTree(t, root), expected) { + t.Fatal("Kimi unrelated managed package inventory changed") + } + } + for path, expected := range f.foreignMCP { + body, err := projectNativeRuntimeRead(path, kimiProjectNativeLimit) + if err != nil { + t.Fatal(err) + } + object, err := kimiProjectObject(body) + if err != nil { + t.Fatal(err) + } + entries, ok := object["mcpServers"].(map[string]any) + if !ok || !reflect.DeepEqual(entries["foreign-preserve"], expected) { + t.Fatal("Kimi unrelated native MCP member changed") + } + } +} + +func (f *kimiProjectNativeFixture) env() []string { + return append(f.base.env(filepath.Dir(f.client)), "NO_COLOR=1", "GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL="+os.DevNull, "GIT_TERMINAL_PROMPT=0") +} + +func (f *kimiProjectNativeFixture) command(t *testing.T, label, cwd string, env, argv []string, timeout time.Duration, absent bool) ports.CommandResult { + t.Helper() + f.check(t) + ctx, cancel := context.WithTimeout(f.ctx, timeout) + defer cancel() + r, err := (process.OS{}).RunWithDescendantExitGrace(ctx, ports.Command{Argv: argv, Env: env, Dir: cwd, StdoutLimitBytes: kimiProjectNativeLimit}, 5*time.Second) + f.keep(t, label+".stdout.log", r.Stdout) + f.keep(t, label+".stderr.log", r.Stderr) + meta, _ := json.Marshal(map[string]any{"argv": argv, "cwd": cwd, "exit": r.ExitCode, "error": err != nil, "deadline": ctx.Err() != nil, "cleanup": "ordinary full-descendant supervision; forced termination fails"}) + f.keep(t, label+".command.json", meta) + f.check(t) + if ctx.Err() != nil || len(r.Stdout) > kimiProjectNativeLimit || bytes.Contains(r.Stderr, []byte("stderr bytes omitted")) || (!absent && (err != nil || r.ExitCode != 0)) || (absent && (err != nil || r.ExitCode != 1)) { + t.Fatalf("Kimi command %s failed; bounded evidence retained", label) + } + return r +} + +func newKimiProjectNativeFixture(t *testing.T) *kimiProjectNativeFixture { + t.Helper() + started := time.Now() + if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" || os.Getenv("GITHUB_ACTIONS") != "true" || os.Getenv("RUNNER_ENVIRONMENT") != "github-hosted" || os.Getenv("AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED") != "1" { + t.Fatal("Kimi native execution requires actual opted-in GitHub-hosted Linux amd64") + } + f := &kimiProjectNativeFixture{started: started, packages: map[string]*kimiProjectNativePackage{}, immutable: map[string]string{}, protected: map[string]string{}, files: map[string][]byte{}, serverIDs: map[string]bool{}, foreignMCP: map[string]any{}, protectedTrees: map[string]map[string]string{}, evidence: map[string]any{"client": "kimi", "activation": "prepared", "qualification": map[string]bool{"project-lifecycle": false, "native-restart-removal": false, "mcp-initialize": false, "mcp-tools-list": false, "mcp-tools-call": false}, "vendor_authentication_qualified": false, "real_model_qualified": false}} + f.ctx, f.cancel = context.WithDeadline(context.Background(), started.Add(840*time.Second)) + f.client, f.installer = nativeBinary(t, "AGENTPLUGINS_KIMI_BIN"), nativeBinary(t, "AGENTPLUGINS_INSTALLER_BIN") + f.python = nativeBinary(t, "AGENTPLUGINS_PROJECT_NATIVE_PYTHON") + var err error + f.git, err = exec.LookPath("git") + if err != nil { + t.Fatal(err) + } + f.git, err = filepath.EvalSymlinks(f.git) + if err != nil { + t.Fatal(err) + } + f.output = os.Getenv("AGENTPLUGINS_PROJECT_NATIVE_OUTPUT") + if err := projectNativeRuntimeParents(filepath.Join(f.output, "leaf")); err != nil { + t.Fatal(err) + } + entries, err := os.ReadDir(f.output) + if err != nil || len(entries) != 0 { + t.Fatal("Kimi output must be an existing empty ordinary directory") + } + body, err := projectNativeRuntimeRead(os.Getenv("AGENTPLUGINS_PROJECT_NATIVE_IDENTITY"), kimiProjectNativeLimit) + if err != nil { + t.Fatal(err) + } + if _, err := kimiProjectObject(body); err != nil { + t.Fatal(err) + } + var identity struct { + Client string `json:"client"` + Acquisition struct { + SHA string `json:"sha256"` + Pin bool `json:"pin_verified"` + Members []struct { + Name, Kind string + Bytes int64 + SHA string `json:"sha256"` + } `json:"members"` + } `json:"acquisition"` + RuntimeFiles []struct { + Path string `json:"path"` + SHA string `json:"sha256"` + } `json:"runtime_files"` + } + if json.Unmarshal(body, &identity) != nil || identity.Client != "kimi" || identity.Acquisition.SHA != kimiProjectArchiveSHA || !identity.Acquisition.Pin || len(identity.Acquisition.Members) != 1 || len(identity.RuntimeFiles) != 1 { + t.Fatal("Kimi exact immutable acquisition receipt differs") + } + member := identity.Acquisition.Members[0] + if member.Name != "kimi" || member.Kind != "file" || member.Bytes != 192416960 || member.SHA != kimiProjectExecutableSHA || identity.RuntimeFiles[0].Path != f.client || identity.RuntimeFiles[0].SHA != member.SHA || filepath.Base(f.client) != "kimi" { + t.Fatal("Kimi exact executable member differs") + } + f.immutable[f.client] = member.SHA + for _, path := range []string{f.installer, f.python, f.git, nativeBinary(t, "AGENTPLUGINS_NATIVE_PROBE_BIN"), nativeBinary(t, "AGENTPLUGINS_LINTAI_BIN"), filepath.Join(RepoRoot(t), "scripts", "project_native_collectors", "kimi.py"), filepath.Join(RepoRoot(t), "scripts", "project_native_service.py")} { + digest, err := projectNativeRuntimeDigest(path) + if err != nil { + t.Fatal(err) + } + f.immutable[path] = digest + } + identitySource, err := nativeSourceIdentity(os.Getenv("AGENTPLUGINS_INSTALLER_COMMIT"), os.Getenv("AGENTPLUGINS_INSTALLER_TREE"), os.Getenv("AGENTPLUGINS_INSTALLER_PATCH_SHA256")) + if err != nil { + t.Fatal(err) + } + f.evidence["source_identity"], f.evidence["acquisition"] = identitySource, identity + f.check(t) + f.base = newNativeFixture(t, false) + f.base.ClientBinDir = filepath.Dir(f.client) + f.projects = map[string]string{"a": f.base.Project, "b": filepath.Join(f.base.Root, "project-b"), "c": filepath.Join(f.base.Root, "project-c"), "user": filepath.Join(f.base.Root, "outside")} + for key, path := range f.projects { + if key != "a" { + if err := os.Mkdir(path, 0700); err != nil { + t.Fatal(err) + } + } + } + f.immutable[f.base.Probe] = nativeSHA(t, f.base.Probe) + for _, name := range []string{"project_native_research.py", "native_client_research.py", "native_client_project_artifacts.py", "research-grok-native.py", "run-native-client-matrix.py", "native_client_draft.py"} { + path := filepath.Join(RepoRoot(t), "scripts", name) + digest, err := projectNativeRuntimeDigest(path) + if err != nil { + t.Fatal(err) + } + f.immutable[path] = digest + } + f.evidence["scanner"] = nativeProvisionScanner(t, f.base) + return f +} + +func (f *kimiProjectNativeFixture) source(t *testing.T, scope, revision string) string { + t.Helper() + name := "uap-kimi-native-" + scope + root := filepath.Join(f.base.Root, "sources", scope) + write := func(path string, body []byte, mode os.FileMode) { + if _, err := os.Lstat(path); err == nil { + if _, err := projectNativeRuntimeRead(path, 256<<20); err != nil { + t.Fatal(err) + } + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + } else if !os.IsNotExist(err) { + t.Fatal(err) + } + projectNativeRuntimeWrite(t, path, body, mode) + } + version := "1.0.0" + if revision == "a-two" { + version = "1.0.1" + } + manifest, _ := json.Marshal(map[string]any{"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": name, "version": version}) + write(filepath.Join(root, "plugin.json"), manifest, 0600) + skill := []byte(fmt.Sprintf("---\nname: %s\ndescription: Kimi installed %s\n---\nUAP_KIMI_%s_%s\n", name, revision, revision, f.base.Nonce)) + write(filepath.Join(root, "skills", name, "SKILL.md"), skill, 0600) + if scope != "user" { + probe, err := projectNativeRuntimeRead(f.base.Probe, 256<<20) + if err != nil { + t.Fatal(err) + } + write(filepath.Join(root, "bin", "probe"), probe, 0700) + server := map[string]any{"type": "stdio", "command": "./bin/probe", "args": []string{"Kimi owned " + scope, "${PLUGIN_ROOT}/argument"}, "cwd": "${PLUGIN_ROOT}", "env": map[string]string{"UAP_TEST_ROOT": f.base.Root, "UAP_TEST_EVENTS": filepath.Join(f.base.Root, scope+"-events.jsonl"), "UAP_TEST_NONCE": f.base.Nonce + "-" + scope, "UAP_TEST_REVISION": revision}} + mcp, _ := json.Marshal(map[string]any{"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", "mcpServers": map[string]any{name: server}}) + write(filepath.Join(root, "mcp.json"), mcp, 0600) + } + f.packages[scope] = &kimiProjectNativePackage{Source: root, Name: name, Revision: revision} + return root +} + +func (f *kimiProjectNativeFixture) uap(t *testing.T, label, scope string, mutated, absent bool, args ...string) { + t.Helper() + argv := append([]string{f.installer}, args...) + argv = append(argv, "--target", "kimi", "--format", "json") + cwd := f.projects["user"] + if scope == "user" { + argv = append(argv, "--scope", "user") + } else { + cwd = f.projects[scope] + argv = append(argv, "--scope", "project", "--project-root", cwd) + } + r := f.command(t, label, cwd, f.env(), argv, 30*time.Second, absent) + if absent { + first := "agentplugins: installation \"" + f.packages[scope].Name + "\" was not found\n" + second := "agentplugins: plugin is not installed for target \"kimi\" in project scope; no target was changed\n" + if len(r.Stdout) != 0 || (string(r.Stderr) != first && string(r.Stderr) != second) { + t.Fatal("Kimi repeated removal is not the exact absent-installation refusal") + } + return + } + value, err := kimiProjectObject(r.Stdout) + if err != nil { + t.Fatal(err) + } + if err := nativeValidateInstallerJSON(r.Stdout); err != nil { + t.Fatal(err) + } + commandsRequireMutated(t, label, value, mutated) + var progress string + switch args[0] { + case "add": + progress = "Resolving and validating one Agent Plugin package for every selected target...\n" + if strings.HasPrefix(label, "repeat-") { + progress += "Applying transactional client package...\n" + } else { + progress += "Installing " + f.packages[scope].Name + " for 1 selected clients...\n" + } + case "update": + progress = "Resolving and validating one updated Agent Plugin package for every selected target...\n" + case "repair": + progress = "Resolving and validating each unique exact installed package revision once...\n" + } + if string(r.Stderr) != progress { + t.Fatal("Kimi public CLI emitted unexpected diagnostics") + } +} + +func (f *kimiProjectNativeFixture) receipt(t *testing.T, scope string) { + t.Helper() + p := f.packages[scope] + statePath := filepath.Join(f.projects[scope], ".uap", "state-v2.json") + if scope == "user" { + statePath = filepath.Join(f.base.Root, "installer-state", "state-v2.json") + } + body, err := projectNativeRuntimeRead(statePath, kimiProjectNativeLimit) + if err != nil { + t.Fatal(err) + } + if _, err := kimiProjectObject(body); err != nil { + t.Fatal(err) + } + var state domain.StateFileV2 + if json.Unmarshal(body, &state) != nil { + t.Fatal("Kimi installed state malformed") + } + var selected []domain.Installation + for _, installation := range state.Installations { + if installation.DeclaredName == p.Name { + selected = append(selected, installation) + } + } + if len(selected) != 1 || len(selected[0].Clients) != 1 { + t.Fatal("Kimi exact package/binding receipt missing") + } + i := selected[0] + if i.Source.CanonicalSource != p.Source || i.Source.SourceBindingID == "" || i.Source.TreeDigest == "" || i.Package.DeclaredName != p.Name || i.Package.ManifestDigest == "" { + t.Fatal("Kimi selected source/package identity differs") + } + for _, binding := range i.Clients { + if binding.ClientID != "kimi" || binding.Scope != map[bool]string{true: "user", false: "project"}[scope == "user"] || binding.TargetLocator == "" { + t.Fatal("Kimi receipt scope differs") + } + if scope != "user" { + if binding.Activation != domain.ActivationPrepared || binding.Verification != domain.VerificationInstalled || binding.NativeProfileRoot != filepath.Join(f.projects[scope], ".kimi-code") { + t.Fatal("Kimi project authority/status changed") + } + data := i.DataReceipts[binding.DataReceiptID] + if data.DataReceiptID == "" || data.State != domain.DataReceiptOwned || data.OwnershipDigest == "" || data.Scope != "project" || data.PhysicalBackend != binding.PhysicalArtifact { + t.Fatal("Kimi persistent data receipt missing") + } + nativeRequireContained(t, filepath.Join(f.projects[scope], ".uap", "managed"), binding.TargetLocator) + nativeRequireContained(t, filepath.Join(f.projects[scope], ".uap", "plugin-data"), data.Locator) + p.Data = data.Locator + owned := map[string]string{} + for _, object := range binding.NativeObjects { + if object.Kind == "kimi_project_skill_directory" || object.Kind == "kimi_project_mcp_server" { + if object.LogicalName != p.Name || object.ManagedDigest == "" || owned[object.Kind] != "" { + t.Fatal("Kimi native ownership differs") + } + owned[object.Kind] = object.Path + } + } + if owned["kimi_project_skill_directory"] != filepath.Join(f.projects[scope], ".kimi-code", "skills", p.Name) || owned["kimi_project_mcp_server"] != filepath.Join(f.projects[scope], ".kimi-code", "mcp.json") { + t.Fatal("Kimi native ownership paths differ") + } + p.Skill = filepath.Join(owned["kimi_project_skill_directory"], "SKILL.md") + } else { + p.Skill = filepath.Join(binding.TargetLocator, "skills", p.Name, "SKILL.md") + } + p.Binding = binding + } + p.Installation = i + got, err := projectNativeRuntimeRead(p.Skill, kimiProjectNativeLimit) + if err != nil { + t.Fatal(err) + } + want, err := projectNativeRuntimeRead(filepath.Join(p.Source, "skills", p.Name, "SKILL.md"), kimiProjectNativeLimit) + if err != nil || !bytes.Equal(got, want) { + t.Fatal("Kimi selected native skill bytes differ") + } +} + +func (f *kimiProjectNativeFixture) observe(t *testing.T, stage string, trust bool, installed ...string) { + t.Helper() + if time.Until(f.started.Add(840*time.Second)) < 75*time.Second { + t.Fatal("Kimi cannot start native stage inside cleanup reserve") + } + selected := map[string]bool{} + for _, scope := range installed { + selected[scope] = true + } + skills, servers := map[string][]any{}, map[string][]any{} + for _, scope := range []string{"a", "b", "c"} { + skills[scope], servers[scope] = []any{}, []any{} + for _, key := range []string{"user", scope} { + if !selected[key] { + continue + } + p := f.packages[key] + f.receipt(t, key) + source := "project" + if key == "user" { + source = "extra" + } + skills[scope] = append(skills[scope], map[string]any{"name": p.Name, "description": "Kimi installed " + p.Revision, "path": p.Skill, "source": source, "sha256": nativeSHA(t, p.Skill)}) + } + if selected[scope] { + p := f.packages[scope] + origin := filepath.Join(f.projects[scope], ".kimi-code", "mcp.json") + body, err := projectNativeRuntimeRead(origin, kimiProjectNativeLimit) + if err != nil { + t.Fatal(err) + } + object, err := kimiProjectObject(body) + if err != nil { + t.Fatal(err) + } + entries, ok := object["mcpServers"].(map[string]any) + if !ok { + t.Fatal("Kimi native MCP collection missing") + } + config, ok := entries[p.Name].(map[string]any) + if !ok { + t.Fatal("Kimi native owned MCP missing") + } + if err := kimiProjectValidateServer(config, p, scope, f.base.Root, f.base.Nonce); err != nil { + t.Fatal(err) + } + servers[scope] = append(servers[scope], map[string]any{"name": p.Name, "origin": origin, "config": config, "sha256": nativeSHA(t, origin), "event": filepath.Join(f.base.Root, scope+"-events.jsonl"), "nonce": f.base.Nonce + "-" + scope, "cwd": f.projects[scope]}) + } + } + observerIDs := map[string]string{} + for path, digest := range f.immutable { + if path != f.python && path != f.git { + observerIDs[path] = digest + } + } + for _, scope := range installed { + if scope != "user" { + p := f.packages[scope] + for _, path := range []string{filepath.Join(p.Binding.TargetLocator, "bin", "probe"), filepath.Join(p.Binding.TargetLocator, filepath.FromSlash(managedstdio.RelativeDirectory), managedstdio.ExecutableName)} { + digest, err := projectNativeRuntimeDigest(path) + if err != nil { + t.Fatal(err) + } + observerIDs[path] = digest + } + } + } + request := map[string]any{"schema_version": 1, "root": f.base.Root, "home": f.base.Home, "projects": f.projects, "client": f.client, "identities": observerIDs, "observation": map[string]any{"stage": stage, "setup_trust": trust, "skills": skills, "servers": servers}} + body, _ := json.Marshal(request) + path := filepath.Join(f.base.Root, stage+"-private-request.json") + projectNativeRuntimeWrite(t, path, body, 0600) + env := append(f.env(), "GITHUB_ACTIONS="+os.Getenv("GITHUB_ACTIONS"), "RUNNER_ENVIRONMENT="+os.Getenv("RUNNER_ENVIRONMENT"), "AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED="+os.Getenv("AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED")) + r := f.command(t, "observe-"+stage, f.projects["user"], env, []string{f.python, filepath.Join(RepoRoot(t), "scripts", "project_native_collectors", "kimi.py"), "--installed-request", path}, 70*time.Second, false) + if len(r.Stderr) != 0 { + t.Fatal("Kimi observer diagnostics refused") + } + value, err := kimiProjectObject(r.Stdout) + if err != nil { + t.Fatal(err) + } + files, ok := value["files"].(map[string]any) + if !ok || value["schema_version"] != json.Number("1") { + t.Fatal("Kimi closed observer envelope malformed") + } + summaryText, ok := files["research.json"].(string) + if !ok { + t.Fatal("Kimi admitted native summary missing") + } + summary, err := kimiProjectObject([]byte(summaryText)) + if err != nil { + t.Fatal(err) + } + observation, ok := summary["observations"].(map[string]any) + if !ok || observation["stage"] != stage || observation["tools_call_count"] != json.Number("0") || observation["provider_configured"] != false { + t.Fatal("Kimi native stage outcome differs") + } + id, ok := observation["server_id"].(string) + if !ok || id == "" || f.serverIDs[id] { + t.Fatal("Kimi fresh native restart identity missing") + } + f.serverIDs[id] = true + f.evidence[stage] = observation +} + +func (f *kimiProjectNativeFixture) finish(t *testing.T) { + t.Helper() + defer f.cancel() + if time.Since(f.started) > 900*time.Second { + t.Error("Kimi original total deadline exceeded") + } + f.evidence["test_failed"], f.evidence["elapsed_ms"] = t.Failed(), time.Since(f.started).Milliseconds() + if t.Failed() { + for key := range f.evidence["qualification"].(map[string]bool) { + f.evidence["qualification"].(map[string]bool)[key] = false + } + } + body, _ := json.MarshalIndent(f.evidence, "", " ") + f.keep(t, "lifecycle.json", body) + file, err := os.OpenFile(filepath.Join(f.output, "research.zip"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if err != nil { + t.Error(err) + return + } + archive := zip.NewWriter(file) + names := make([]string, 0, len(f.files)) + for name := range f.files { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + w, err := archive.Create(name) + if err != nil { + t.Error(err) + break + } + if _, err = w.Write(f.files[name]); err != nil { + t.Error(err) + break + } + } + if err := archive.Close(); err != nil { + t.Error(err) + } + if err := file.Close(); err != nil { + t.Error(err) + } +} + +func TestAgentpluginsKimiNativeProjectComponentsLifecycle(t *testing.T) { + if os.Getenv("AGENTPLUGINS_KIMI_PROJECT_NATIVE_E2E") != "1" { + t.Skip("opt-in current-source Kimi installed native lifecycle") + } + f := newKimiProjectNativeFixture(t) + defer f.finish(t) + for _, scope := range []string{"a", "b", "c"} { + r := f.command(t, "git-"+scope, f.projects[scope], f.env(), []string{f.git, "-c", "core.hooksPath=/dev/null", "init", "--quiet"}, 30*time.Second, false) + if len(r.Stdout) != 0 || len(r.Stderr) != 0 { + t.Fatal("Kimi Git boundary setup failed") + } + } + r := f.command(t, "version", f.projects["user"], f.env(), []string{f.client, "--version"}, 30*time.Second, false) + if string(r.Stdout) != "2.3.2\n" || len(r.Stderr) != 0 { + t.Fatal("Kimi exact native version differs") + } + for _, scope := range []string{"user", "a", "b", "c"} { + f.source(t, scope, scope+"-one") + } + for _, root := range []string{f.base.Home, f.projects["a"], f.projects["b"], f.projects["c"]} { + path := filepath.Join(root, ".kimi-code", "foreign-keep.txt") + projectNativeRuntimeWrite(t, path, []byte("original foreign authority\n"), 0600) + f.protected[path] = nativeSHA(t, path) + skill := filepath.Join(root, ".kimi-code", "skills", "foreign-preserve", "SKILL.md") + projectNativeRuntimeWrite(t, skill, []byte("---\nname: foreign-preserve\ndescription: Retained foreign marker\n---\nRetain exact original bytes.\n"), 0600) + f.protected[skill] = nativeSHA(t, skill) + entry := map[string]any{"transport": "http", "url": "http://127.0.0.1:9/foreign", "enabled": false} + mcp := filepath.Join(root, ".kimi-code", "mcp.json") + body, _ := json.Marshal(map[string]any{"mcpServers": map[string]any{"foreign-preserve": entry}}) + projectNativeRuntimeWrite(t, mcp, body, 0600) + f.foreignMCP[mcp] = entry + } + preferences := filepath.Join(f.base.Home, ".kimi-code", "config.toml") + projectNativeRuntimeWrite(t, preferences, []byte("telemetry = false\n"), 0600) + f.protected[preferences] = nativeSHA(t, preferences) + userMCP := filepath.Join(f.base.Home, ".kimi-code", "mcp.json") + f.protected[userMCP] = nativeSHA(t, userMCP) + f.observe(t, "empty", true) + for _, scope := range []string{"user", "a", "b", "c"} { + f.uap(t, "add-"+scope, scope, true, false, "add", f.packages[scope].Source, "--no-save") + f.receipt(t, scope) + } + for _, scope := range []string{"a", "b", "c"} { + p := f.packages[scope] + path := filepath.Join(p.Data, "keep.txt") + projectNativeRuntimeWrite(t, path, []byte("retained-"+f.base.Nonce+"-"+scope), 0600) + f.protected[path] = nativeSHA(t, path) + } + f.observe(t, "installed", false, "user", "a", "b", "c") + userState := filepath.Join(f.base.Root, "installer-state", "state-v2.json") + f.protected[userState] = nativeSHA(t, userState) + for _, scope := range []string{"user", "b", "c"} { + f.protected[f.packages[scope].Skill] = nativeSHA(t, f.packages[scope].Skill) + f.protectedTrees[f.packages[scope].Binding.TargetLocator] = kimiProjectTree(t, f.packages[scope].Binding.TargetLocator) + if scope != "user" { + for _, path := range []string{filepath.Join(f.projects[scope], ".uap", "state-v2.json"), filepath.Join(f.projects[scope], ".kimi-code", "mcp.json")} { + f.protected[path] = nativeSHA(t, path) + } + } + } + userRegistry := filepath.Join(f.base.Home, ".kimi-code", "plugins", "installed.json") + f.protected[userRegistry] = nativeSHA(t, userRegistry) + state := filepath.Join(f.projects["a"], ".uap", "state-v2.json") + stable := nativeSHA(t, state) + nativeStable := nativeSHA(t, f.packages["a"].Skill) + aMCP := filepath.Join(f.projects["a"], ".kimi-code", "mcp.json") + nativeMCPStable := nativeSHA(t, aMCP) + f.uap(t, "repeat-add-a", "a", false, false, "add", f.packages["a"].Source, "--no-save") + f.uap(t, "unchanged-update-a", "a", false, false, "update", f.packages["a"].Name) + if nativeSHA(t, state) != stable || nativeSHA(t, f.packages["a"].Skill) != nativeStable || nativeSHA(t, aMCP) != nativeMCPStable { + t.Fatal("Kimi unchanged reconciliation rewrote state/native bytes") + } + f.uap(t, "intact-repair-a", "a", true, false, "repair", f.packages["a"].Name) + if nativeSHA(t, f.packages["a"].Skill) != nativeStable || nativeSHA(t, aMCP) != nativeMCPStable { + t.Fatal("Kimi intact repair changed native bytes") + } + f.observe(t, "stable", false, "user", "a", "b", "c") + old := f.packages["a"] + f.source(t, "a", "a-two") + f.uap(t, "update-a", "a", true, false, "update", f.packages["a"].Source) + f.receipt(t, "a") + if f.packages["a"].Installation.Source.TreeDigest == old.Installation.Source.TreeDigest || f.packages["a"].Data != old.Data { + t.Fatal("Kimi update source/data identity differs") + } + f.observe(t, "updated", false, "user", "a", "b", "c") + skillDir := filepath.Dir(f.packages["a"].Skill) + if skillDir != filepath.Join(f.projects["a"], ".kimi-code", "skills", f.packages["a"].Name) { + t.Fatal("Kimi missing repair removal is outside exact owned skill") + } + if _, err := projectNativeRuntimeRead(f.packages["a"].Skill, kimiProjectNativeLimit); err != nil { + t.Fatal(err) + } + if err := os.Remove(f.packages["a"].Skill); err != nil { + t.Fatal(err) + } + if err := os.Remove(skillDir); err != nil { + t.Fatal(err) + } + f.uap(t, "missing-repair-a", "a", true, false, "repair", f.packages["a"].Name) + f.receipt(t, "a") + f.observe(t, "repaired", false, "user", "a", "b", "c") + f.observe(t, "restart", false, "user", "a", "b", "c") + beforeEvent, err := projectNativeRuntimeRead(filepath.Join(f.base.Root, "a-events.jsonl"), 2<<20) + if err != nil { + t.Fatal(err) + } + f.uap(t, "remove-a", "a", true, false, "remove", f.packages["a"].Name) + if _, err := os.Lstat(skillDir); !os.IsNotExist(err) { + t.Fatal("Kimi removed native skill survives") + } + f.observe(t, "removed", false, "user", "b", "c") + afterEvent, err := projectNativeRuntimeRead(filepath.Join(f.base.Root, "a-events.jsonl"), 2<<20) + if err != nil || !bytes.Equal(beforeEvent, afterEvent) { + t.Fatal("Kimi removed A started native probe") + } + removedState := nativeSHA(t, state) + retainedA := map[string]map[string]string{} + for _, root := range []string{filepath.Join(f.projects["a"], ".uap"), filepath.Join(f.projects["a"], ".kimi-code")} { + retainedA[root] = kimiProjectTree(t, root) + } + f.uap(t, "repeat-remove-a", "a", false, true, "remove", f.packages["a"].Name) + if nativeSHA(t, state) != removedState { + t.Fatal("Kimi repeat removal changed state") + } + for root, before := range retainedA { + if !reflect.DeepEqual(before, kimiProjectTree(t, root)) { + t.Fatal("Kimi recognized repeated removal changed retained A authority") + } + } + f.check(t) + qualification := f.evidence["qualification"].(map[string]bool) + for _, key := range []string{"project-lifecycle", "native-restart-removal", "mcp-initialize", "mcp-tools-list"} { + qualification[key] = true + } + f.evidence["boundary"] = "Native authenticated local API discovery and named initialize/tools-list only; no tools/call, model, vendor OAuth or released-client qualification" +} + +func kimiProjectObject(body []byte) (map[string]any, error) { + value, err := projectNativeRuntimeJSON(body) + if err != nil { + return nil, err + } + object, ok := value.(map[string]any) + if !ok { + return nil, fmt.Errorf("Kimi JSON object required") + } + return object, nil +} + +// The expected projection is fixed by this source package and the selected +// receipt, independently of the bytes that the native API is about to read. +func kimiProjectValidateServer(actual map[string]any, p *kimiProjectNativePackage, scope, root, nonce string) error { + active := p.Binding.TargetLocator + expected := map[string]any{ + "transport": "stdio", + "command": filepath.Join(active, filepath.FromSlash(managedstdio.RelativeDirectory), managedstdio.ExecutableName), + "args": []any{"--internal-stdio-v1", active, p.Data, active, "plugin", "--", "./bin/probe", "Kimi owned " + scope, active + "/argument"}, + "env": map[string]any{"PLUGIN_ROOT": active, "PLUGIN_DATA": p.Data, "UAP_TEST_ROOT": root, "UAP_TEST_EVENTS": filepath.Join(root, scope+"-events.jsonl"), "UAP_TEST_NONCE": nonce + "-" + scope, "UAP_TEST_REVISION": p.Revision}, + } + if !reflect.DeepEqual(actual, expected) { + return fmt.Errorf("Kimi native stdio projection differs from selected receipt/package/data/cwd/source arguments") + } + return nil +} + +func kimiProjectTree(t *testing.T, root string) map[string]string { + t.Helper() + if err := projectNativeRuntimeParents(filepath.Join(root, "leaf")); err != nil { + t.Fatal(err) + } + inventory := map[string]string{} + var total int64 + entries := 0 + err := filepath.WalkDir(root, func(path string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + rel, err := filepath.Rel(root, path) + if err != nil { + return err + } + if len(strings.Split(rel, string(os.PathSeparator))) > 32 { + return fmt.Errorf("owned package depth limit") + } + entries++ + if entries > 4096 { + return fmt.Errorf("owned package entry count limit") + } + info, err := entry.Info() + if err != nil { + return err + } + if entry.IsDir() { + if err := projectNativeRuntimeParents(filepath.Join(path, "leaf")); err != nil { + return err + } + inventory[rel] = fmt.Sprintf("%#o:directory", info.Mode()) + return nil + } + total += info.Size() + if total > 256<<20 { + return fmt.Errorf("owned package byte limit") + } + digest, err := projectNativeRuntimeDigest(path) + if err != nil { + return err + } + inventory[rel] = fmt.Sprintf("%#o:%s", info.Mode(), digest) + return nil + }) + if err != nil { + t.Fatal(err) + } + return inventory +} diff --git a/repotests/agentplugins_project_native_runtime_test.go b/repotests/agentplugins_project_native_runtime_test.go new file mode 100644 index 000000000..9e1fcc204 --- /dev/null +++ b/repotests/agentplugins_project_native_runtime_test.go @@ -0,0 +1,269 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "math" + "os" + "path/filepath" + "reflect" + "strings" + "syscall" + "testing" + "unicode/utf8" +) + +// Shared held-file mechanics for the source-only native project suites. +// Extracted unchanged from the reviewed Copilot runtime attempt. +func projectNativeRuntimeParents(path string) error { + if !filepath.IsAbs(path) || filepath.Clean(path) != path { + return fmt.Errorf("noncanonical path") + } + parent := filepath.Dir(path) + for { + info, err := os.Lstat(parent) + if err != nil { + return err + } + if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("parent alias/non-directory") + } + next := filepath.Dir(parent) + if next == parent { + break + } + parent = next + } + return nil +} +func projectNativeRuntimeRead(path string, limit int64) ([]byte, error) { + if err := projectNativeRuntimeParents(path); err != nil { + return nil, err + } + before, err := os.Lstat(path) + if err != nil { + return nil, err + } + stat, ok := before.Sys().(*syscall.Stat_t) + if !ok || stat.Nlink != 1 || stat.Uid != uint32(os.Getuid()) { + return nil, fmt.Errorf("non-owned/single-link leaf") + } + return readProjectNativeSnapshot(path, before, limit) +} +func projectNativeRuntimeStableStat(left, right any) bool { + a, okA := left.(*syscall.Stat_t) + b, okB := right.(*syscall.Stat_t) + return okA && okB && a.Dev == b.Dev && a.Ino == b.Ino && a.Mode == b.Mode && a.Nlink == b.Nlink && a.Uid == b.Uid && a.Gid == b.Gid && a.Size == b.Size && a.Mtim == b.Mtim && a.Ctim == b.Ctim +} +func projectNativeRuntimeDigest(path string) (string, error) { + if err := projectNativeRuntimeParents(path); err != nil { + return "", err + } + before, err := os.Lstat(path) + if err != nil { + return "", err + } + stat, ok := before.Sys().(*syscall.Stat_t) + if !ok || !before.Mode().IsRegular() || stat.Nlink != 1 || (stat.Uid != uint32(os.Getuid()) && stat.Uid != 0) || before.Size() > 512<<20 { + return "", fmt.Errorf("immutable file type/size/ownership") + } + fd, err := syscall.Open(path, syscall.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0) + if err != nil { + return "", err + } + file := os.NewFile(uintptr(fd), path) + defer file.Close() + same := func(info os.FileInfo) bool { + return info != nil && os.SameFile(before, info) && info.Mode() == before.Mode() && info.Size() == before.Size() && info.ModTime().Equal(before.ModTime()) && projectNativeRuntimeStableStat(info.Sys(), before.Sys()) + } + opened, err := file.Stat() + if err != nil || !same(opened) { + return "", fmt.Errorf("immutable held identity changed") + } + hash := sha256.New() + n, err := io.Copy(hash, io.LimitReader(file, (512<<20)+1)) + if err != nil || n != before.Size() { + return "", fmt.Errorf("immutable read changed") + } + after, err := file.Stat() + if err != nil || !same(after) { + return "", fmt.Errorf("immutable held identity drift") + } + current, err := os.Lstat(path) + if err != nil || !same(current) { + return "", fmt.Errorf("immutable path identity drift") + } + return hex.EncodeToString(hash.Sum(nil)), nil +} +func projectNativeRuntimeWrite(t *testing.T, path string, body []byte, mode os.FileMode) { + t.Helper() + parent := filepath.Dir(path) + existing := parent + for { + _, err := os.Lstat(existing) + if err == nil { + break + } + if !os.IsNotExist(err) { + t.Fatal(err) + } + existing = filepath.Dir(existing) + } + if err := projectNativeRuntimeParents(filepath.Join(existing, "leaf")); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(parent, 0700); err != nil { + t.Fatal(err) + } + if err := projectNativeRuntimeParents(path); err != nil { + t.Fatal(err) + } + file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode) + if err != nil { + t.Fatal(err) + } + _, writeErr := file.Write(body) + closeErr := file.Close() + if writeErr != nil || closeErr != nil { + t.Fatal("authored file write failed") + } +} + +const projectNativeRuntimeBodyLimit = 1 << 20 + +// This decoder rejects duplicate keys before typed validation. No request or +// nested tool argument may rely on encoding/json's last-key-wins behavior. +func projectNativeRuntimeJSON(body []byte) (any, error) { + if len(body) == 0 || len(body) > projectNativeRuntimeBodyLimit || !utf8.Valid(body) { + return nil, fmt.Errorf("JSON body limit") + } + d := json.NewDecoder(bytes.NewReader(body)) + d.UseNumber() + nodes := 0 + var value func(int) (any, error) + value = func(depth int) (any, error) { + nodes++ + if depth > 32 || nodes > 16384 { + return nil, fmt.Errorf("JSON structure limit") + } + token, err := d.Token() + if err != nil { + return nil, err + } + if delimiter, ok := token.(json.Delim); ok { + switch delimiter { + case '{': + m := map[string]any{} + for d.More() { + key, err := d.Token() + if err != nil { + return nil, err + } + name, ok := key.(string) + if !ok || len(name) > 4096 { + return nil, fmt.Errorf("JSON key") + } + if _, exists := m[name]; exists { + return nil, fmt.Errorf("duplicate JSON key") + } + v, err := value(depth + 1) + if err != nil { + return nil, err + } + m[name] = v + } + end, err := d.Token() + if err != nil || end != json.Delim('}') { + return nil, fmt.Errorf("JSON object end") + } + return m, nil + case '[': + a := []any{} + for d.More() { + v, err := value(depth + 1) + if err != nil { + return nil, err + } + a = append(a, v) + } + end, err := d.Token() + if err != nil || end != json.Delim(']') { + return nil, fmt.Errorf("JSON array end") + } + return a, nil + default: + return nil, fmt.Errorf("unexpected JSON delimiter") + } + } + if number, ok := token.(json.Number); ok { + n, err := number.Float64() + if err != nil || math.IsInf(n, 0) || math.IsNaN(n) { + return nil, fmt.Errorf("nonfinite JSON number") + } + } + return token, nil + } + v, err := value(0) + if err != nil { + return nil, err + } + if _, err := d.Token(); err != io.EOF { + return nil, fmt.Errorf("JSON trailing data") + } + return v, nil +} + +func projectNativeRuntimeKeys(object map[string]any, required, optional string) error { + allowed := map[string]bool{} + for _, key := range strings.Fields(required + " " + optional) { + allowed[key] = true + } + for _, key := range strings.Fields(required) { + if _, exists := object[key]; !exists { + return fmt.Errorf("missing field %s", key) + } + } + for key := range object { + if !allowed[key] { + return fmt.Errorf("unknown field %s", key) + } + } + return nil +} +func projectNativeRuntimeText(v any, limit int) bool { + s, ok := v.(string) + return ok && len(s) <= limit && !strings.ContainsRune(s, 0) +} +func projectNativeRuntimeNumber(v any, low, high float64, integer bool) bool { + n, ok := v.(json.Number) + if !ok { + return false + } + f, err := n.Float64() + return err == nil && f >= low && f <= high && (!integer || math.Trunc(f) == f) +} +func projectNativeRuntimeSchema(v any) error { + m, ok := v.(map[string]any) + if !ok || projectNativeRuntimeKeys(m, "type properties required additionalProperties", "") != nil || m["type"] != "object" || m["additionalProperties"] != false { + return fmt.Errorf("probe argument schema") + } + properties, ok := m["properties"].(map[string]any) + if !ok || len(properties) != 3 { + return fmt.Errorf("probe properties") + } + for _, name := range []string{"nonce", "operation", "marker"} { + if !reflect.DeepEqual(properties[name], map[string]any{"type": "string"}) { + return fmt.Errorf("probe property %s", name) + } + } + if !reflect.DeepEqual(m["required"], []any{"nonce"}) { + return fmt.Errorf("probe required arguments") + } + return nil +} diff --git a/scripts/project_native_collectors/kimi.py b/scripts/project_native_collectors/kimi.py index 461cfc779..c8768ffb4 100644 --- a/scripts/project_native_collectors/kimi.py +++ b/scripts/project_native_collectors/kimi.py @@ -616,3 +616,250 @@ def collect(ctx): result["unobserved"].remove("kimi.mcp-initialize") result["unobserved"].remove("kimi.mcp-tools-list") return result + + +def _installed_skills(data, expected): + """Compare native rows to receipt-selected files, never replacement fixtures.""" + _object(data, ("skills",)) + rows = data["skills"] + if not isinstance(rows, list) or len(rows) > 512: + raise ValueError("Kimi installed skill catalog count refused") + found = {} + for row in rows: + _object(row, ("name", "description", "path", "source"), + ("type", "disable_model_invocation", "scopes")) + for field in ("name", "description", "path", "source"): + _string(row[field], empty=field == "description", multiline=field == "description") + if row["source"] not in ("project", "user", "extra", "builtin"): + raise ValueError("Kimi installed skill source shape differs") + if "type" in row: + _string(row["type"], empty=True) + if "disable_model_invocation" in row and type(row["disable_model_invocation"]) is not bool: + raise ValueError("Kimi installed skill invocation flag differs") + if "scopes" in row and (not isinstance(row["scopes"], list) or len(row["scopes"]) > 2 + or any(value not in ("tui", "web") for value in row["scopes"])): + raise ValueError("Kimi installed skill scope shape differs") + if row["name"].startswith("uap-kimi-native-"): + if row["name"] in found: + raise ValueError("Kimi installed skill is duplicated") + found[row["name"]] = row + if set(found) != {row["name"] for row in expected}: + raise ValueError("Kimi installed skill selection differs") + for item in expected: + row = found[item["name"]] + if any(row[key] != item[key] for key in ("name", "description", "path", "source")): + raise ValueError("Kimi installed skill origin differs") + return sorted(found) + + +def _installed_catalog(data, expected): + if not isinstance(data, list) or len(data) > 256: + raise ValueError("Kimi installed MCP catalog count refused") + found = {} + for row in data: + _object(row, ("name", "config", "source", "origin", "mutable"), ("plugin",)) + _string(row["name"]) + _string(row["origin"]) + if not isinstance(row["config"], dict) or row["source"] not in ("global", "plugin", "caller") or type(row["mutable"]) is not bool: + raise ValueError("Kimi installed MCP catalog shape differs") + if row["name"].startswith("uap-kimi-native-"): + if row["name"] in found: + raise ValueError("Kimi installed MCP is duplicated") + found[row["name"]] = row + if set(found) != {row["name"] for row in expected}: + raise ValueError("Kimi installed MCP selection differs") + for item in expected: + row, config = found[item["name"]], dict(item["config"]) + if "env" in config: + config["envKeys"] = sorted(config.pop("env")) + if (row["source"] != "global" or row["origin"] != item["origin"] + or row["mutable"] is not False or "plugin" in row or row["config"] != config): + raise ValueError("Kimi installed MCP origin/config differs") + return sorted(found) + + +def observe_installed(ctx, request): + """One fresh web process over already-installed public UAP authority. + + The Go lifecycle owns the installer and builds this exact expectation from + committed receipts/native bytes. This observer never authors components. + Only the empty initial stage may author native A/B trust through its API. + """ + _object(request, ("stage", "setup_trust", "skills", "servers")) + if (not re.fullmatch(r"[a-z][a-z0-9-]{0,40}", request["stage"]) + or type(request["setup_trust"]) is not bool + or set(request["skills"]) != set(WORKSPACES) + or set(request["servers"]) != set(WORKSPACES)): + raise ValueError("Kimi installed stage contract refused") + if (any(not isinstance(request[key][scope], list) or len(request[key][scope]) > (2 if key == "skills" else 1) + for key in ("skills", "servers") for scope in WORKSPACES) + or (request["setup_trust"] and (request["stage"] != "empty" + or any(request[key][scope] for key in ("skills", "servers") for scope in WORKSPACES)))): + raise ValueError("Kimi installed trust setup requires the empty baseline") + protected = {} + for scope in WORKSPACES: + for skill in request["skills"][scope]: + _object(skill, ("name", "description", "path", "source", "sha256")) + path = Path(skill["path"]) + body = _read(ctx.root, path, 1 << 20) + if hashlib.sha256(body).hexdigest() != skill["sha256"]: + raise ValueError("Kimi receipt-selected skill bytes differ") + protected[path] = body + + for item in request["servers"][scope]: + _object(item, ("name", "origin", "config", "sha256", "event", "nonce", "cwd")) + path = Path(item["origin"]) + body = _read(ctx.root, path, 1 << 20) + if (hashlib.sha256(body).hexdigest() != item["sha256"] + or _json(body).get("mcpServers", {}).get(item["name"]) != item["config"]): + raise ValueError("Kimi receipt-selected MCP bytes differ") + protected[path] = body + + def preserved(): + for path, body in protected.items(): + if _read(ctx.root, path) != body: + raise ValueError("Kimi installed authority changed during observation") + # Every fresh process verifies the same native workspace identities/trust. + # No trust mutation occurs in any installed or removed stage. + def observe(service): + token, count = _ready(ctx, service), 0 + + def api(label, method, path, body=None, code=0): + nonlocal count + count += 1 + if count > 60: + raise ValueError("Kimi installed API request budget exhausted") + return _envelope(*service.http(label, method, path, token=token, + json_body=body, timeout=min(5, service.remaining())), code=code) + + meta = api("meta", "GET", "/api/v1/meta") + _meta(meta) + ids = {} + for scope in WORKSPACES: + root = ctx.projects[scope] + fixture = {"root": root, "name": "uap-kimi-native-" + scope} + data = api("workspace-" + scope, "POST", "/api/v1/workspaces", + {"root": str(root), "name": fixture["name"]}) + ids[scope] = _workspace(data, fixture) + trust_path = "/api/v1/workspaces/" + ids[scope] + "/trust" + expected_trust = scope in ("a", "b") and not request["setup_trust"] + _trust(api("trust-before-" + scope, "GET", trust_path), expected_trust) + if request["setup_trust"] and scope in ("a", "b"): + _trust(api("trust-set-" + scope, "POST", trust_path, {}), True) + _trust(api("trust-after-" + scope, "GET", trust_path), True) + if len(set(ids.values())) != 3: + raise ValueError("Kimi installed workspace identities are not distinct") + skill_rows, server_rows, probe_rows = {}, {}, {} + seen = set() + _installed_catalog(api("mcp-outside", "GET", "/api/v2/mcp/servers?" + + urlencode({"cwd": str(ctx.projects["user"])})), []) + for scope in WORKSPACES: + skill_rows[scope] = _installed_skills(api("skills-" + scope, "GET", + "/api/v1/workspaces/" + ids[scope] + "/skills"), request["skills"][scope]) + entries = request["servers"][scope] if scope != "c" else [] + server_rows[scope] = _installed_catalog(api("mcp-" + scope, "GET", + "/api/v2/mcp/servers?" + urlencode({"cwd": str(ctx.projects[scope])})), entries) + for item in request["servers"][scope]: + event_fixture = {"event": Path(item["event"]), "nonce": item["nonce"]} + before, _ = _events(ctx, event_fixture) + if scope == "c": + if before: + raise ValueError("Kimi untrusted installed C already ran a probe") + api("test-refused-c", "POST", "/api/v2/mcp/servers:test", + {"name": item["name"], "cwd": str(ctx.projects[scope])}, code=40408) + if _events(ctx, event_fixture)[0] != before: + raise ValueError("Kimi untrusted installed C started a probe") + continue + started = datetime.now(timezone.utc) + result = api("test-" + scope, "POST", "/api/v2/mcp/servers:test", + {"name": item["name"], "cwd": str(ctx.projects[scope])}) + ended = datetime.now(timezone.utc) + expected_output = ('Connected to MCP server "' + item["name"] + '".\nAvailable tools: 1\n' + '- inspect_runtime: Inspect only disposable fixture runtime facts.') + if result != {"success": True, "output": expected_output}: + raise ValueError("Kimi installed named MCP test failed") + after, rows = _events(ctx, event_fixture) + fresh = rows[len(before.splitlines()):] + pairs = {(row["pid"], row["session"]) for row in fresh} + if (not after.startswith(before) or not fresh or fresh[0]["method"] != "initialize" + or {row["method"] for row in fresh} != {"initialize", "tools/list"} + or sum(row["method"] == "initialize" for row in fresh) != 1 + or len(pairs) != 1 or pairs & seen + or any(not started <= _instant(row["timestamp"]) <= ended for row in fresh)): + raise ValueError("Kimi installed named test lacks fresh correlated events") + seen.update(pairs) + probe_rows[scope] = fresh + preserved() + if _object(api("shutdown", "POST", "/api/v1/shutdown", {}), ("ok",))["ok"] is not True: + raise ValueError("Kimi installed service shutdown failed") + service.mark_shutdown() + return {"stage": request["stage"], "server_id": meta["server_id"], "workspace_ids": ids, + "skill_names": skill_rows, "server_names": server_rows, "probe_events": probe_rows, + "tools_call_count": 0, "provider_configured": False, "request_count": count} + + result = ctx.service("kimi-" + request["stage"], + ["web", "--no-open", "--host", "127.0.0.1", "--port", "0"], observe, project="a", timeout=60) + preserved() + return result + + +def _installed_main(request_path): + """Client-specific adapter to the existing private service/capture owner.""" + import sys + sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + import project_native_research as shared + + # This guard precedes parsing paths, creating files, or any child launch. + shared.native.require_hosted("linux-amd64") + path = Path(request_path) + value = _json(_read(path.parent, path, 1 << 20, mode=0o600)) + _object(value, ("schema_version", "root", "home", "projects", "client", "identities", "observation")) + if value["schema_version"] != 1 or type(value["schema_version"]) is not int: + raise ValueError("Kimi installed request version refused") + root, home, client = (Path(value[key]) for key in ("root", "home", "client")) + projects = {key: Path(item) for key, item in value["projects"].items()} + if (set(projects) != set((*WORKSPACES, "user")) or root.resolve() != root + or client.name != "kimi" or not client.is_absolute() + or any(item.resolve() != item or not item.is_relative_to(root) or item == root + for item in (home, *projects.values())) + or len({home, *projects.values()}) != 5 + or any(left.is_relative_to(right) for left in (home, *projects.values()) + for right in (home, *projects.values()) if left != right)): + raise ValueError("Kimi installed profile/project authority refused") + identities = {Path(key): digest for key, digest in value["identities"].items()} + if (client not in identities or len(identities) < 3 or len(identities) > 128 + or any(not isinstance(digest, str) or not re.fullmatch(r"[0-9a-f]{64}", digest) + for digest in identities.values())): + raise ValueError("Kimi installed immutable closure refused") + # Reuse the existing Context's service, environment, capture, redaction and + # cleanup implementation without acquisition or fixture creation. + ctx = shared.Context.__new__(shared.Context) + ctx.root, ctx.home, ctx.projects, ctx.identities = root, home, projects, identities + ctx.argv, ctx.secrets, ctx.captures = [str(client)], [], {} + ctx.summary = {"schema_version": 1, "client": "kimi", "phase": "installed-api", + "commands": [], "qualification": {"mcp-tools-call": False}} + ctx.env = shared.legacy.runtime_environment(home, root / "tmp") + ctx.env.update({"NO_COLOR": "1", "TERM": "dumb"}) + with shared.Budget(65) as budget: + ctx.budget = budget + ctx.summary["observations"] = observe_installed(ctx, value["observation"]) + bodies = shared.evidence(ctx.summary, ctx.captures, ctx.secrets) + # Shared evidence() admits only closed redacted members. Recheck the whole + # serialization too; a token cannot leak through an added summary field. + output = json.dumps({"schema_version": 1, + "files": {name: body.decode("utf-8", "strict") for name, body in bodies.items()}}, + allow_nan=False, sort_keys=True).encode() + if len(output) > 1 << 20 or any(secret in output for secret in ctx.secrets): + raise ValueError("Kimi installed closed evidence refused") + sys.stdout.buffer.write(output + b"\n") + + +if __name__ == "__main__": + import sys + if len(sys.argv) != 3 or sys.argv[1] != "--installed-request": + raise SystemExit("Kimi installed observer requires its exact private request") + try: + _installed_main(sys.argv[2]) + except Exception: + # Never render exception text from bearer-bearing startup/API activity. + raise SystemExit("Kimi installed API observation failed; private capture quarantined") from None diff --git a/scripts/project_native_runtime.py b/scripts/project_native_runtime.py new file mode 100644 index 000000000..37d8f11a4 --- /dev/null +++ b/scripts/project_native_runtime.py @@ -0,0 +1,417 @@ +"""Run a closed current-source native test on an actual disposable hosted runner. + +Acquisition and bounded process capture reuse the existing research runner. A +valid artifact never changes a failed test into a passed qualification. +""" +import argparse +import hashlib +import io +import json +import math +import os +from pathlib import Path +import re +import shutil +import stat +import sys +import zipfile + +import project_native_research as shared + +SOURCE = shared.SOURCE +CASES = { + "copilot": {"test": "TestAgentpluginsCopilotNativeProjectRuntimeAttempt", + "file": "repotests/agentplugins_copilot_project_native_attempt_test.go", + "optin": "AGENTPLUGINS_COPILOT_PROJECT_RUNTIME_ATTEMPT", "seconds": 180, + "phase": "runtime-attempt"}, + "gemini": {"test": "TestAgentpluginsGeminiNativeProjectRuntimeAttempt", + "file": "repotests/agentplugins_gemini_project_native_attempt_test.go", + "optin": "AGENTPLUGINS_GEMINI_PROJECT_RUNTIME_ATTEMPT", "seconds": 180, + "phase": "runtime-attempt"}, + "kimi": {"test": "TestAgentpluginsKimiNativeProjectComponentsLifecycle", + "file": "repotests/agentplugins_kimi_project_native_lifecycle_test.go", + "optin": "AGENTPLUGINS_KIMI_PROJECT_NATIVE_E2E", "seconds": 900, + "phase": "installed-components-lifecycle"}, +} +MAX_ZIP = 32 << 20 +MAX_MEMBER = 2 << 20 +COMMANDS = ("git-A", "git-B", "version", "add-A", "native-attempt") +KIMI_COMMANDS = ("git-a", "git-b", "git-c", "version", "add-user", "add-a", "add-b", "add-c", + "observe-empty", "observe-installed", "observe-stable", "observe-updated", "observe-repaired", + "observe-restart", "observe-removed", "repeat-add-a", "unchanged-update-a", "intact-repair-a", + "update-a", "missing-repair-a", "remove-a", "repeat-remove-a") + + +def strict_json(body): + def pairs(rows): + value = {} + for key, item in rows: + if key in value: + raise ValueError("duplicate JSON member") + value[key] = item + return value + def finite(value): + result = float(value) + if not math.isfinite(result): + raise ValueError("nonfinite JSON") + return result + return json.loads(body.decode("utf-8", errors="strict"), object_pairs_hook=pairs, parse_float=finite, + parse_constant=lambda _: (_ for _ in ()).throw(ValueError("nonfinite JSON"))) + + +def ordinary_bytes(path, limit, *, owners=None): + path = Path(path) + before = path.lstat() + if (not path.is_absolute() or path.resolve() != path or not stat.S_ISREG(before.st_mode) + or before.st_nlink != 1 or before.st_uid not in (owners if owners is not None else (os.getuid(),)) + or not 0 <= before.st_size <= limit): + raise ValueError("bounded ordinary owned file required") + descriptor = shared.artifacts.open_member(path) + try: + held = os.fstat(descriptor) + if not shared.artifacts.same_opened(before, held): + raise ValueError("opened file identity differs") + with os.fdopen(os.dup(descriptor), "rb") as stream: + body = shared.bounded_read(stream, limit) + if shared.artifacts.identity(os.fstat(descriptor)) != shared.artifacts.identity(held): + raise ValueError("held file changed") + finally: + os.close(descriptor) + if len(body) != before.st_size or shared.artifacts.identity(path.lstat()) != shared.artifacts.identity(before): + raise ValueError("file changed while reading") + return body + + +def write_new(path, body, mode=0o600): + with path.open("xb") as stream: + stream.write(body) + path.chmod(mode) + + +def attempt_members(bodies, source, client="copilot"): + required = {"runtime-attempt.json"} + installed = {"copilot": {"mcp.json.installed", "uapcopilota-SKILL.md.installed"}, + "gemini": {".gemini-settings.json.installed", "uapgeminia-SKILL.md.installed"}} + if client not in installed: + raise ValueError("unknown runtime attempt schema") + allowed = required | {"probe-events.jsonl"} | installed[client] + allowed |= {"provider-request-" + str(i) + ".json" for i in range(1, 9)} + for label in COMMANDS: + triple = {label + suffix for suffix in (".command.json", ".stdout.log", ".stderr.log")} + allowed |= triple + if set(bodies) & triple and not triple <= set(bodies): + raise ValueError("incomplete command evidence triple") + if triple <= set(bodies): + row = strict_json(bodies[label + ".command.json"]) + if (not isinstance(row, dict) or set(row) != {"argv", "cwd", "exit", "elapsed_ms", "error", "context_error", "cleanup"} + or not isinstance(row["argv"], list) or not row["argv"] + or any(not isinstance(arg, str) for arg in row["argv"]) + or not isinstance(row["cwd"], str) or type(row["exit"]) is not int + or type(row["elapsed_ms"]) is not int or type(row["error"]) is not bool + or type(row["context_error"]) is not bool): + raise ValueError("invalid command receipt") + if not required <= set(bodies) or set(bodies) - allowed: + raise ValueError("unrecorded native artifact member") + result = strict_json(bodies["runtime-attempt.json"]) + if (not isinstance(result, dict) or result.get("client") != client + or result.get("provider_kind") != "scripted_loopback" or result.get("activation") != "prepared" + or type(result.get("runtime_attempt_complete")) is not bool + or (client == "copilot" and result["runtime_attempt_complete"] is not False) + or result.get("vendor_authentication_qualified") is not False + or result.get("real_model_qualified") is not False + or result.get("qualification") != {key: False for key in shared.QUALIFICATION_CASES} + or type(result.get("test_failed")) is not bool): + raise ValueError("attempt must retain explicit unqualified state") + identity = result.get("source_identity") + if identity is not None and identity != {"installer_base_commit": source["source_commit"], + "installer_tree": source["source_tree"], + "installer_source_state": "committed", "installer_patch_sha256": []}: + raise ValueError("native evidence source differs") + numbers = sorted(int(name.removeprefix("provider-request-").removesuffix(".json")) + for name in bodies if name.startswith("provider-request-")) + if numbers != list(range(1, len(numbers) + 1)): + raise ValueError("provider evidence sequence has gaps") + if client == "gemini" and not result["test_failed"]: + successful = required | installed[client] | {"probe-events.jsonl"} + successful |= {label + suffix for label in COMMANDS for suffix in (".command.json", ".stdout.log", ".stderr.log")} + if (result["runtime_attempt_complete"] is not True or not successful <= set(bodies) or len(numbers) < 2 + or identity is None or result.get("provider_returned") is not True + or not isinstance(result.get("native_session_id"), str) or not result["native_session_id"] + or not isinstance(result.get("provider_call_id"), str) or not result["provider_call_id"] + or not isinstance(result.get("probe_facts"), dict) or not result["probe_facts"]): + raise ValueError("successful Gemini attempt is missing required runtime observations") + for label in COMMANDS: + row = strict_json(bodies[label + ".command.json"]) + if row["exit"] != 0 or row["error"] or row["context_error"]: + raise ValueError("successful Gemini attempt contains a failed command") + return result + + +def kimi_members(bodies, source): + allowed = {"lifecycle.json"} + for label in KIMI_COMMANDS: + triple = {label + suffix for suffix in (".command.json", ".stdout.log", ".stderr.log")} + allowed |= triple + if set(bodies) & triple and not triple <= set(bodies): + raise ValueError("incomplete Kimi command evidence triple") + if triple <= set(bodies): + row = strict_json(bodies[label + ".command.json"]) + if (not isinstance(row, dict) or set(row) != {"argv", "cwd", "exit", "error", "deadline", "cleanup"} + or not isinstance(row["argv"], list) or not row["argv"] + or any(not isinstance(arg, str) for arg in row["argv"]) + or not isinstance(row["cwd"], str) or type(row["exit"]) is not int + or type(row["error"]) is not bool or type(row["deadline"]) is not bool): + raise ValueError("invalid Kimi command receipt") + if "lifecycle.json" not in bodies or set(bodies) - allowed: + raise ValueError("unrecorded Kimi artifact member") + result = strict_json(bodies["lifecycle.json"]) + if (not isinstance(result, dict) or result.get("client") != "kimi" or result.get("activation") != "prepared" + or result.get("vendor_authentication_qualified") is not False or result.get("real_model_qualified") is not False + or type(result.get("test_failed")) is not bool): + raise ValueError("Kimi lifecycle evidence state differs") + qualification = result.get("qualification") + if (not isinstance(qualification, dict) or set(qualification) != set(shared.QUALIFICATION_CASES) + or any(type(value) is not bool for value in qualification.values()) + or qualification["mcp-tools-call"] is not False): + raise ValueError("Kimi cannot infer a tools-call qualification") + if result["test_failed"] and any(qualification.values()): + raise ValueError("failed Kimi lifecycle cannot qualify stages") + if not result["test_failed"]: + if set(bodies) != allowed or any(not qualification[key] for key in shared.QUALIFICATION_CASES if key != "mcp-tools-call"): + raise ValueError("successful Kimi lifecycle must contain all selected stages") + identity = result.get("source_identity") + if identity is not None and identity != {"installer_base_commit": source["source_commit"], "installer_tree": source["source_tree"], + "installer_source_state": "committed", "installer_patch_sha256": []}: + raise ValueError("Kimi evidence source differs") + return result + + +def admit_runtime(path, client, source): + body = ordinary_bytes(path, MAX_ZIP) + bodies, total = {}, 0 + with zipfile.ZipFile(io.BytesIO(body)) as archive: + entries = archive.infolist() + if not 1 <= len(entries) <= 80: + raise ValueError("native ZIP member count refused") + for member in entries: + mode = member.external_attr >> 16 + name = member.filename + if (name in bodies or Path(name).name != name or not re.fullmatch(r"[A-Za-z0-9_.-]{1,128}", name) + or member.is_dir() or stat.S_IFMT(mode) not in (0, stat.S_IFREG) or member.flag_bits & 1 + or member.compress_type not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED) + or member.file_size > MAX_MEMBER or total + member.file_size > (24 if client == "kimi" else 16) << 20): + raise ValueError("native ZIP member type/name/size refused") + with archive.open(member) as stream: + value = shared.bounded_read(stream, MAX_MEMBER) + if len(value) != member.file_size: + raise ValueError("native ZIP size differs") + value.decode("utf-8", errors="strict") + bodies[name] = value + total += len(value) + result = kimi_members(bodies, source) if client == "kimi" else attempt_members(bodies, source, client) + return bodies, result + + +def selected_cases(): + rows = [] + for client, case in CASES.items(): + leaf, test = SOURCE / "scripts/project_native_collectors" / (client + ".py"), SOURCE / case["file"] + if not leaf.exists() or not test.exists(): + continue + if not re.search(r"(?m)^func " + re.escape(case["test"]) + r"\(t \*testing.T\) \{", test.read_text()): + raise ValueError("declared exact native test is absent") + rows.append({"client": client, "phase": case["phase"]}) + return {"include": rows} + + +def capture(ctx, label, argv, cwd, env, seconds): + record, stdout, stderr = ctx.command(label, argv, cwd, env, seconds) + if record["exit"] != 0: + raise ValueError(label + " failed; bounded streams retained") + return stdout, stderr + + +def freeze(path, identities, limit=256 << 20): + body = ordinary_bytes(path, limit) + if not body: + raise ValueError("empty executable/input refused") + digest = hashlib.sha256(body).hexdigest() + identities[path] = digest + return {"path": str(path), "bytes": len(body), "sha256": digest} + + +def prepare(client, ctx): + # This method performs only Git source queries before the checked compiler + # or any acquired client can be executed. + ctx.source_and_projects() + leaf = shared.adjacent("project_native_runtime_leaf_" + client, "project_native_collectors/" + client + ".py") + if leaf.CLIENT != client or getattr(leaf, "ACQUISITION_ONLY", False): + raise ValueError("unreviewed native client") + receipt, contents = shared.acquire(leaf.ACQUISITION, ctx.budget) + binary, client_ids = shared.materialize(ctx.root / "client", leaf.ACQUISITION, receipt, contents) + identity = {"client": client, "acquisition": receipt} + node = None + if leaf.ACQUISITION["runtime"] == "node": + node_receipt, node_contents = shared.acquire(shared.NODE, ctx.budget, node=True) + node, node_ids = shared.materialize(ctx.root / "node", shared.NODE, node_receipt, node_contents, selected_only=True) + client_ids.update(node_ids) + identity["node"] = shared.selected_node_receipt(node_receipt) + elif leaf.ACQUISITION["runtime"] != "native": + raise ValueError("unreviewed client runtime") + identity["runtime_files"] = [{"path": str(path), "sha256": digest} for path, digest in client_ids.items()] + ctx.identities.update(client_ids) + del contents + kind, repository, version, archive, pin, basename = shared.native.PINS["linux-amd64"]["lintai"] + if kind != "github" or not archive.endswith(".tar.gz"): + raise ValueError("pinned scanner acquisition differs") + spec = {"url": "https://github.com/" + repository + "/releases/download/" + version + "/" + archive, + "integrity": pin, "format": "tar.gz", "max_bytes": 96 << 20, "max_expanded_bytes": 256 << 20, + "entrypoint": basename, "runtime": "native"} + scanner_receipt, scanner_contents = shared.acquire(spec, ctx.budget) + candidates = [row["name"] for row in scanner_receipt["members"] if row["kind"] == "file" and Path(row["name"]).name == basename] + if len(candidates) != 1: + raise ValueError("exact scanner basename is absent or ambiguous") + spec["entrypoint"] = candidates[0] + scanner, scanner_ids = shared.materialize(ctx.root / "scanner", spec, scanner_receipt, scanner_contents, selected_only=True) + ctx.identities.update(scanner_ids) + del scanner_contents + selected = shutil.which("go") + if not selected: + raise ValueError("pinned Go compiler not supplied") + go = Path(selected).resolve(strict=True) + # setup-go owns the compiler installation, which may be root-owned. Copy + # its hash using its existing ordinary file identity; do not run a wrapper. + before = go.lstat() + if not stat.S_ISREG(before.st_mode) or before.st_nlink != 1 or before.st_size > 64 << 20: + raise ValueError("ordinary Go compiler required") + go_digest = hashlib.sha256(ordinary_bytes(go, 64 << 20, owners=(0, os.getuid()))).hexdigest() + def verify_compiler(): + if (shared.artifacts.identity(go.lstat()) != shared.artifacts.identity(before) + or hashlib.sha256(ordinary_bytes(go, 64 << 20, owners=(0, os.getuid()))).hexdigest() != go_digest): + raise ValueError("Go compiler changed during build") + env = dict(ctx.env) + env.update({"PATH": str(go.parent) + ":/usr/bin:/bin", "GOTOOLCHAIN": "local", "GOMAXPROCS": "2", + "GOCACHE": str(ctx.root / "go-cache"), "GOMODCACHE": str(ctx.root / "go-modules"), + "GOPROXY": "https://proxy.golang.org", "GOSUMDB": "sum.golang.org"}) + verify_compiler() + stdout, stderr = capture(ctx, "build-go-version", [go, "version"], SOURCE, env, 10) + verify_compiler() + if stdout != b"go version go1.26.10 linux/amd64\n" or stderr: + raise ValueError("exact Go 1.26.10 Linux amd64 compiler required") + tools = ctx.root / "bin" + tools.mkdir(mode=0o700) + installer, probe, tests = (tools / name for name in ("agentplugins", "native-probe", "project-native.test")) + builds = (("build-installer", [go, "build", "-p=1", "-trimpath", "-o", installer, "./cmd/agentplugins"], SOURCE / "cli"), + ("build-probe", [go, "build", "-p=1", "-trimpath", "-o", probe, "./repotests/testdata/agentplugins_native_probe"], SOURCE), + ("build-tests", [go, "test", "-p=1", "-c", "-trimpath", "-o", tests, "./repotests"], SOURCE)) + for label, argv, cwd in builds: + verify_compiler() + capture(ctx, label, argv, cwd, env, 240) + verify_compiler() + ctx.summary["compiler"] = {"version": "1.26.10", "sha256": go_digest} + ctx.summary["builds"] = {path.name: freeze(path, ctx.identities) for path in (installer, probe, tests)} + identity_path = ctx.root / "runtime-identity.json" + write_new(identity_path, json.dumps(identity, sort_keys=True).encode() + b"\n") + freeze(identity_path, ctx.identities, 1 << 20) + ctx.summary["client_identity"] = identity + ctx.summary["scanner_archive_sha256"] = scanner_receipt["sha256"] + ctx.summary["build_complete"] = True + native_output = ctx.root / "native-evidence" + native_output.mkdir(mode=0o700) + selected_env = dict(ctx.env) + selected_env.update(shared.native.disposable_runtime_environment("linux-amd64")) + selected_env.update({"AGENTPLUGINS_INSTALLER_BIN": str(installer), + "AGENTPLUGINS_INSTALLER_COMMIT": ctx.summary["source_commit"], + "AGENTPLUGINS_INSTALLER_TREE": ctx.summary["source_tree"], + "AGENTPLUGINS_NATIVE_PROBE_BIN": str(probe), + "AGENTPLUGINS_LINTAI_BIN": str(scanner), + "AGENTPLUGINS_LINTAI_SHA256": scanner_ids[scanner], + "AGENTPLUGINS_LINTAI_ARCHIVE_SHA256": scanner_receipt["sha256"], + "AGENTPLUGINS_" + client.upper() + "_BIN": str(binary), + "AGENTPLUGINS_PROJECT_NATIVE_IDENTITY": str(identity_path), + "AGENTPLUGINS_PROJECT_NATIVE_OUTPUT": str(native_output), CASES[client]["optin"]: "1"}) + selected_env["PLUGIN_KIT_AI_REPO_ROOT"] = str(SOURCE) + if client == "kimi": + python = Path("/usr/bin/python3").resolve(strict=True) + python_body = ordinary_bytes(python, 64 << 20, owners=(0, os.getuid())) + ctx.summary["python"] = {"path": str(python), "sha256": hashlib.sha256(python_body).hexdigest()} + selected_env["AGENTPLUGINS_PROJECT_NATIVE_PYTHON"] = str(python) + if node is not None: + selected_env["AGENTPLUGINS_NODE_BIN"] = str(node) + return tests, selected_env, native_output + + +def run(client, output): + shared.native.require_hosted("linux-amd64") + if client not in CASES or not any(row["client"] == client for row in selected_cases()["include"]): + raise ValueError("no reviewed exact native test selected") + if not output.is_absolute() or output.resolve() != output or output.exists() or SOURCE == output or SOURCE in output.parents: + raise ValueError("fresh canonical output outside checkout required") + output.mkdir(mode=0o700, parents=True) + case = CASES[client] + summary = {"client": client, "phase": case["phase"], "commands": [], "cases": [], + "qualification": {name: False for name in shared.QUALIFICATION_CASES}, + "build_complete": False, "test_selected": False, "test_passed": False, + "runtime_artifact_admitted": False, "collection_complete": False} + ctx, native_output, admitted = None, None, None + try: + with shared.Budget(480) as budget: + ctx = shared.Context(output, summary, budget) + tests, env, native_output = prepare(client, ctx) + stdout, stderr = capture(ctx, "build-source-clean", ["/usr/bin/git", "status", "--porcelain", "--untracked-files=normal"], SOURCE, ctx.env, 10) + if stdout or stderr: + raise ValueError("build changed the source checkout") + shared.verify_files(ctx.identities, budget) + # This deadline is created once before the selected test; no stage, + # retry or acquisition can reset the client's own original deadline. + with shared.Budget(case["seconds"] + 15) as budget: + ctx.budget = budget + record, stdout, _ = ctx.command("runtime-test", [tests, "-test.run=^" + case["test"] + "$", "-test.count=1", "-test.v", "-test.timeout=" + str(case["seconds"]) + "s"], output, env, case["seconds"] + 5) + summary["test_selected"] = ("=== RUN " + case["test"] + "\n").encode() in stdout + summary["test_passed"] = bool(summary["test_selected"] and record["exit"] == 0 + and ("--- PASS: " + case["test"] + " ").encode() in stdout + and ("--- SKIP: " + case["test"]).encode() not in stdout) + shared.verify_files(ctx.identities, budget) + if not summary["test_passed"]: + raise ValueError("selected native test failed or did not execute; inspect bounded evidence") + except Exception as error: + summary["error"] = type(error).__name__ + ": " + str(error)[:512] + finally: + with shared.Budget(60): + if native_output is not None: + try: + admitted, result = admit_runtime(native_output / "research.zip", client, summary) + summary["runtime_artifact_admitted"] = True + summary["runtime_test_failed"] = result["test_failed"] + if result["test_failed"]: + summary["test_passed"] = False + if summary["test_passed"]: + summary["qualification"] = result["qualification"] + except Exception as error: + summary["runtime_admission_error"] = type(error).__name__ + summary["test_passed"] = False + summary["collection_complete"] = bool(summary["test_passed"] and summary["runtime_artifact_admitted"]) + shared.publish(summary, ctx, output) + (output / "upload/research.zip").rename(output / "upload/frontend.zip") + if admitted is not None: + shared.artifacts.write_zip(output / "upload/runtime.zip", admitted) + print(json.dumps({key: summary.get(key) for key in ("client", "phase", "source_commit", "build_complete", "test_selected", "test_passed", "runtime_artifact_admitted")})) + return 0 if summary["collection_complete"] else 1 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--matrix", action="store_true") + parser.add_argument("--client", choices=CASES) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + if args.matrix: + print(json.dumps(selected_cases(), separators=(",", ":"))) + return 0 + if args.client is None or args.output is None: + parser.error("--client and --output required") + return run(args.client, args.output) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/test_project_native_kimi.py b/scripts/test_project_native_kimi.py index 05c52dc89..2a53b9e1c 100644 --- a/scripts/test_project_native_kimi.py +++ b/scripts/test_project_native_kimi.py @@ -1,5 +1,7 @@ """Inert Kimi collector contracts; never acquires or executes vendor bytes.""" import importlib.util +import copy +import hashlib from datetime import datetime, timezone import json import os @@ -219,6 +221,82 @@ def service(self, label, args, observe, project="a", timeout=60): class KimiCollectorTests(unittest.TestCase): + def test_installed_catalog_requires_exact_unmodified_receipt_config(self): + expected = {"name": "uap-kimi-native-a", "origin": "/owned/A/.kimi-code/mcp.json", + "config": {"transport": "stdio", "command": "/owned/managed/launcher", + "args": ["owned a"], "env": {"UAP_TEST_NONCE": "private"}}} + row = {"name": expected["name"], "origin": expected["origin"], "source": "global", + "mutable": False, "config": {"transport": "stdio", "command": "/owned/managed/launcher", + "args": ["owned a"], "envKeys": ["UAP_TEST_NONCE"]}} + self.assertEqual(kimi._installed_catalog([row], [expected]), [expected["name"]]) + mutations = [lambda x: x.update(origin="/owned/B/.kimi-code/mcp.json"), + lambda x: x.update(mutable=True), lambda x: x.update(source="caller"), + lambda x: x.update(plugin={"id": "foreign", "name": "foreign"}), + lambda x: x["config"].update(command="/foreign/probe"), + lambda x: x["config"].update(env={"UAP_TEST_NONCE": "private"}), + lambda x: x["config"].update(args=["wrong"])] + for change in mutations: + bad = copy.deepcopy(row) + change(bad) + with self.subTest(row=bad), self.assertRaises(ValueError): + kimi._installed_catalog([bad], [expected]) + for rows in ([], [row, row], [row, {**row, "name": "uap-kimi-native-b"}]): + with self.subTest(rows=rows), self.assertRaises(ValueError): + kimi._installed_catalog(rows, [expected]) + with self.assertRaises(ValueError): + kimi._installed_catalog([row], []) + + def test_installed_skill_names_paths_sources_and_absence_are_exact(self): + expected = {"name": "uap-kimi-native-user", "description": "Kimi installed user-one", + "path": "/owned/home/plugins/managed/user/skills/user/SKILL.md", "source": "extra"} + self.assertEqual(kimi._installed_skills({"skills": [expected]}, [expected]), [expected["name"]]) + for key, value in (("name", "uap-kimi-native-b"), ("description", "other revision"), + ("path", "/owned/B/SKILL.md"), ("source", "project")): + with self.subTest(key=key), self.assertRaises(ValueError): + kimi._installed_skills({"skills": [{**expected, key: value}]}, [expected]) + for rows in ([], [expected, expected], [{**expected, "unknown": True}]): + with self.subTest(rows=rows), self.assertRaises(ValueError): + kimi._installed_skills({"skills": rows}, [expected]) + with self.assertRaises(ValueError): + kimi._installed_skills({"skills": [expected]}, []) + + def test_installed_authority_hash_or_inline_replacement_fails_before_service(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + ctx = FakeContext() + ctx.root = root + ctx.service = lambda *_args, **_kwargs: self.fail("native service requested before authority admission") + skill = root / "SKILL.md" + kimi._write_new(root, skill, b"installed skill bytes") + entry = {"name": "uap-kimi-native-a", "description": "owned", "path": str(skill), + "source": "project", "sha256": "0" * 64} + request = {"stage": "installed", "setup_trust": False, + "skills": {"a": [entry], "b": [], "c": []}, "servers": {k: [] for k in kimi.WORKSPACES}} + with self.assertRaisesRegex(ValueError, "skill bytes"): + kimi.observe_installed(ctx, request) + entry["sha256"] = hashlib.sha256(skill.read_bytes()).hexdigest() + mcp = root / "mcp.json" + kimi._write_new(root, mcp, b'{"mcpServers":{"uap-kimi-native-a":{"transport":"stdio","command":"/actual"}}}') + request["servers"]["a"] = [{"name": entry["name"], "origin": str(mcp), + "config": {"transport": "stdio", "command": "/replacement"}, + "sha256": hashlib.sha256(mcp.read_bytes()).hexdigest(), "event": str(root / "events"), + "nonce": "nonce", "cwd": str(root)}] + with self.assertRaisesRegex(ValueError, "MCP bytes"): + kimi.observe_installed(ctx, request) + + def test_only_empty_installed_baseline_may_author_native_trust(self): + ctx = FakeContext() + for stage in ("installed", "removed", "restart"): + request = {"stage": stage, "setup_trust": True, + "skills": {k: [] for k in kimi.WORKSPACES}, "servers": {k: [] for k in kimi.WORKSPACES}} + with self.subTest(stage=stage), self.assertRaisesRegex(ValueError, "empty baseline"): + kimi.observe_installed(ctx, request) + + def test_installed_entrypoint_obeys_real_host_guard_before_read_or_child(self): + with patch.dict(os.environ, {"GITHUB_ACTIONS": "false"}): + with self.assertRaises(RuntimeError): + kimi._installed_main("/must-not-be-read/fixture.json") + def test_frozen_archive_is_current_official_native_entry(self): self.assertEqual(kimi.CLIENT, "kimi") self.assertEqual(kimi.VERSION, "2.3.2") diff --git a/scripts/test_project_native_runtime.py b/scripts/test_project_native_runtime.py new file mode 100644 index 000000000..f45f625e0 --- /dev/null +++ b/scripts/test_project_native_runtime.py @@ -0,0 +1,115 @@ +"""Inert native envelope and exact selection regressions; no client execution.""" +import io +import json +import os +from pathlib import Path +import stat +import tempfile +import unittest +from unittest.mock import patch +import zipfile + +import project_native_runtime as runtime + + +class RuntimeTests(unittest.TestCase): + source = {"source_commit": "a" * 40, "source_tree": "b" * 40} + + def result(self): + return {"client": "copilot", "provider_kind": "scripted_loopback", "activation": "prepared", + "runtime_attempt_complete": False, "vendor_authentication_qualified": False, + "real_model_qualified": False, "test_failed": True, + "qualification": {key: False for key in runtime.shared.QUALIFICATION_CASES}} + + def envelope(self, path, members, *, mode=stat.S_IFREG | 0o600): + with zipfile.ZipFile(path, "w") as archive: + for name, body in members: + entry = zipfile.ZipInfo(name) + entry.external_attr = mode << 16 + archive.writestr(entry, body) + + def test_failed_attempt_artifact_is_preserved_without_qualification(self): + with tempfile.TemporaryDirectory() as temporary: + path = Path(temporary) / "research.zip" + self.envelope(path, [("runtime-attempt.json", json.dumps(self.result()).encode()), + ("mcp.json.installed", b"{}"), ("probe-events.jsonl", b"")]) + bodies, result = runtime.admit_runtime(path, "copilot", self.source) + self.assertTrue(result["test_failed"]) + self.assertFalse(any(result["qualification"].values())) + self.assertIn("mcp.json.installed", bodies) + + def test_unknown_duplicate_special_non_utf8_and_large_members_are_refused(self): + summary = ("runtime-attempt.json", json.dumps(self.result()).encode()) + cases = [([summary, ("profile.json", b"{}")], stat.S_IFREG | 0o600), + ([summary, summary], stat.S_IFREG | 0o600), + ([summary], stat.S_IFLNK | 0o600), + ([summary, ("probe-events.jsonl", b"\xff")], stat.S_IFREG | 0o600), + ([summary, ("probe-events.jsonl", b"x" * (runtime.MAX_MEMBER + 1))], stat.S_IFREG | 0o600)] + for members, mode in cases: + with self.subTest(mode=mode, names=[row[0] for row in members]), tempfile.TemporaryDirectory() as temporary: + path = Path(temporary) / "research.zip" + self.envelope(path, members, mode=mode) + with self.assertRaises((ValueError, UnicodeError)): + runtime.admit_runtime(path, "copilot", self.source) + + def test_partial_command_and_provider_sequence_gaps_are_refused(self): + summary = {"runtime-attempt.json": json.dumps(self.result()).encode()} + for extra in ({"version.stdout.log": b""}, {"provider-request-2.json": b"{}"}): + with self.assertRaises(ValueError): + runtime.attempt_members({**summary, **extra}, self.source) + + def test_attempt_cannot_promote_qualification_or_claim_another_source(self): + for change in ({"runtime_attempt_complete": True}, {"vendor_authentication_qualified": True}, + {"source_identity": {"installer_base_commit": "c" * 40}}, + {"qualification": {key: True for key in runtime.shared.QUALIFICATION_CASES}}): + with self.assertRaises(ValueError): + runtime.attempt_members({"runtime-attempt.json": json.dumps({**self.result(), **change}).encode()}, self.source) + + def test_selection_requires_actual_declared_test_and_leaf(self): + with tempfile.TemporaryDirectory() as temporary, patch.object(runtime, "SOURCE", Path(temporary)): + self.assertEqual(runtime.selected_cases(), {"include": []}) + leaf = runtime.SOURCE / "scripts/project_native_collectors/copilot.py" + test = runtime.SOURCE / runtime.CASES["copilot"]["file"] + leaf.parent.mkdir(parents=True) + test.parent.mkdir(parents=True) + leaf.write_text("inert") + test.write_text("func Other(t *testing.T) {}") + with self.assertRaises(ValueError): + runtime.selected_cases() + test.write_text("func TestAgentpluginsCopilotNativeProjectRuntimeAttempt(t *testing.T) {\n}") + self.assertEqual(runtime.selected_cases(), {"include": [{"client": "copilot", "phase": "runtime-attempt"}]}) + + def test_host_guard_runs_before_creating_output_or_native_child(self): + with tempfile.TemporaryDirectory() as temporary, patch.dict(os.environ, {"GITHUB_ACTIONS": "false"}): + path = Path(temporary) / "must-not-exist" + with self.assertRaises(RuntimeError): + runtime.run("copilot", path) + self.assertFalse(path.exists()) + + def test_json_rejects_nonfinite_numbers_and_duplicate_keys(self): + for body in (b'{"x":1e999}', b'{"x":-1e999}', b'{"x":NaN}', b'{"x":1,"x":2}'): + with self.assertRaises(ValueError): + runtime.strict_json(body) + + def test_gemini_requires_its_own_exact_snapshot_names(self): + result = {**self.result(), "client": "gemini", "runtime_attempt_complete": False} + bodies = {"runtime-attempt.json": json.dumps(result).encode(), ".gemini-settings.json.installed": b"{}"} + self.assertEqual(runtime.attempt_members(bodies, self.source, "gemini")["client"], "gemini") + with self.assertRaises(ValueError): + runtime.attempt_members(bodies, self.source, "copilot") + result.update({"runtime_attempt_complete": True, "test_failed": False}) + bodies["runtime-attempt.json"] = json.dumps(result).encode() + with self.assertRaisesRegex(ValueError, "missing required"): + runtime.attempt_members(bodies, self.source, "gemini") + + def test_kimi_failed_lifecycle_keeps_no_unobserved_success_or_tool_call(self): + result = {key: value for key, value in self.result().items() if key not in ("provider_kind", "runtime_attempt_complete")} + result["client"] = "kimi" + self.assertTrue(runtime.kimi_members({"lifecycle.json": json.dumps(result).encode()}, self.source)["test_failed"]) + result["qualification"]["mcp-tools-call"] = True + with self.assertRaises(ValueError): + runtime.kimi_members({"lifecycle.json": json.dumps(result).encode()}, self.source) + + +if __name__ == "__main__": + unittest.main() From 71651afd59df87ede305e294e62631d56488a209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Mon, 26 Oct 2026 18:52:10 +0200 Subject: [PATCH 04/42] test: preserve native validation contracts and close errors --- ...gins_kimi_project_native_lifecycle_test.go | 3 +- ...gentplugins_project_native_runtime_test.go | 69 ++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/repotests/agentplugins_kimi_project_native_lifecycle_test.go b/repotests/agentplugins_kimi_project_native_lifecycle_test.go index 086b39716..0f6d02b01 100644 --- a/repotests/agentplugins_kimi_project_native_lifecycle_test.go +++ b/repotests/agentplugins_kimi_project_native_lifecycle_test.go @@ -208,7 +208,7 @@ func newKimiProjectNativeFixture(t *testing.T) *kimiProjectNativeFixture { return f } -func (f *kimiProjectNativeFixture) source(t *testing.T, scope, revision string) string { +func (f *kimiProjectNativeFixture) source(t *testing.T, scope, revision string) { t.Helper() name := "uap-kimi-native-" + scope root := filepath.Join(f.base.Root, "sources", scope) @@ -244,7 +244,6 @@ func (f *kimiProjectNativeFixture) source(t *testing.T, scope, revision string) write(filepath.Join(root, "mcp.json"), mcp, 0600) } f.packages[scope] = &kimiProjectNativePackage{Source: root, Name: name, Revision: revision} - return root } func (f *kimiProjectNativeFixture) uap(t *testing.T, label, scope string, mutated, absent bool, args ...string) { diff --git a/repotests/agentplugins_project_native_runtime_test.go b/repotests/agentplugins_project_native_runtime_test.go index 9e1fcc204..3ab26cf82 100644 --- a/repotests/agentplugins_project_native_runtime_test.go +++ b/repotests/agentplugins_project_native_runtime_test.go @@ -61,7 +61,7 @@ func projectNativeRuntimeStableStat(left, right any) bool { b, okB := right.(*syscall.Stat_t) return okA && okB && a.Dev == b.Dev && a.Ino == b.Ino && a.Mode == b.Mode && a.Nlink == b.Nlink && a.Uid == b.Uid && a.Gid == b.Gid && a.Size == b.Size && a.Mtim == b.Mtim && a.Ctim == b.Ctim } -func projectNativeRuntimeDigest(path string) (string, error) { +func projectNativeRuntimeDigest(path string) (digest string, err error) { if err := projectNativeRuntimeParents(path); err != nil { return "", err } @@ -78,7 +78,11 @@ func projectNativeRuntimeDigest(path string) (string, error) { return "", err } file := os.NewFile(uintptr(fd), path) - defer file.Close() + defer func() { + if closeErr := file.Close(); err == nil && closeErr != nil { + digest, err = "", fmt.Errorf("close immutable file: %w", closeErr) + } + }() same := func(info os.FileInfo) bool { return info != nil && os.SameFile(before, info) && info.Mode() == before.Mode() && info.Size() == before.Size() && info.ModTime().Equal(before.ModTime()) && projectNativeRuntimeStableStat(info.Sys(), before.Sys()) } @@ -267,3 +271,64 @@ func projectNativeRuntimeSchema(v any) error { } return nil } + +func TestProjectNativeRuntimeValidationContracts(t *testing.T) { + t.Run("whole JSON and exact keys", func(t *testing.T) { + value, err := projectNativeRuntimeJSON([]byte(`{"required":1,"optional":"text"}`)) + if err != nil { + t.Fatal(err) + } + object := value.(map[string]any) + if err := projectNativeRuntimeKeys(object, "required", "optional"); err != nil { + t.Fatal(err) + } + if projectNativeRuntimeKeys(object, "required missing", "optional") == nil || projectNativeRuntimeKeys(object, "required", "") == nil { + t.Fatal("missing or unknown JSON field accepted") + } + for _, body := range []string{`{"required":1,"required":2}`, `{"nested":{"x":1,"x":2}}`, `{} {}`, `{"number":1e999}`} { + if _, err := projectNativeRuntimeJSON([]byte(body)); err == nil { + t.Fatalf("unsafe whole JSON accepted: %s", body) + } + } + }) + t.Run("bounded scalar types", func(t *testing.T) { + if !projectNativeRuntimeText("abc", 3) || projectNativeRuntimeText("abcd", 3) || projectNativeRuntimeText("a\x00b", 3) || projectNativeRuntimeText(3, 3) { + t.Fatal("bounded text contract differs") + } + if !projectNativeRuntimeNumber(json.Number("2"), 1, 3, true) || !projectNativeRuntimeNumber(json.Number("2.5"), 1, 3, false) { + t.Fatal("valid exact numeric type rejected") + } + for _, value := range []any{2, "2", json.Number("0"), json.Number("4"), json.Number("2.5"), json.Number("NaN"), json.Number("1e999")} { + if projectNativeRuntimeNumber(value, 1, 3, true) { + t.Fatalf("invalid bounded integer accepted: %v", value) + } + } + }) + t.Run("canonical probe argument schema", func(t *testing.T) { + body := []byte(`{"type":"object","properties":{"nonce":{"type":"string"},"operation":{"type":"string"},"marker":{"type":"string"}},"required":["nonce"],"additionalProperties":false}`) + value, err := projectNativeRuntimeJSON(body) + if err != nil || projectNativeRuntimeSchema(value) != nil { + t.Fatalf("canonical probe schema rejected: %v", err) + } + for _, kind := range []string{"extra", "additional", "required", "property"} { + value, err := projectNativeRuntimeJSON(body) + if err != nil { + t.Fatal(err) + } + object := value.(map[string]any) + switch kind { + case "extra": + object["extra"] = true + case "additional": + object["additionalProperties"] = true + case "required": + object["required"] = []any{"nonce", "marker"} + case "property": + object["properties"].(map[string]any)["nonce"] = map[string]any{"type": "string", "description": "unknown"} + } + if projectNativeRuntimeSchema(object) == nil { + t.Fatalf("noncanonical probe %s schema accepted", kind) + } + } + }) +} From fbc75d201898b8afc5eceaf40460b1c5329929ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 04:41:50 +0200 Subject: [PATCH 05/42] test(kimi): validate current state schema in native project proof --- ...kimi_project_native_proof_contract_test.go | 23 +++++++++++++++---- ...tplugins_kimi_project_native_proof_test.go | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/repotests/agentplugins_kimi_project_native_proof_contract_test.go b/repotests/agentplugins_kimi_project_native_proof_contract_test.go index 96fdd0d61..b3f30c449 100644 --- a/repotests/agentplugins_kimi_project_native_proof_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_proof_contract_test.go @@ -5,6 +5,7 @@ package pluginkitairepo_test import ( "context" "encoding/json" + "fmt" "os" "path/filepath" "reflect" @@ -383,13 +384,27 @@ func TestKimiProjectProofUserProjectionAndCanceledCaptureStayUnadmitted(t *testi } } +func TestKimiProjectProofStateAcceptsCurrentSchema(t *testing.T) { + body := []byte(fmt.Sprintf(`{"schema_version":%d,"installations":[]}`, domain.StateSchemaVersion)) + state, err := kimiProjectProofState(body) + if err != nil { + t.Fatal(err) + } + if state.SchemaVersion != domain.StateSchemaVersion || len(state.Installations) != 0 { + t.Fatal("current empty state did not decode intact") + } +} + func TestKimiProjectProofStateRefusesUnknownAndDuplicateFacts(t *testing.T) { for _, body := range []string{ - `{"schema_version":2,"installations":[],"unknown":true}`, - `{"schema_version":2,"installations":[{"source":{"unknown":"nonempty"}}]}`, - `{"schema_version":2,"installations":[{"clients":{"id":{"native_objects":[{"kind":"managed_package_directory","unknown":true}]}}}]}`, - `{"schema_version":2,"schema_version":2,"installations":[]}`, + fmt.Sprintf(`{"schema_version":%d,"installations":[],"unknown":true}`, domain.StateSchemaVersion), + fmt.Sprintf(`{"schema_version":%d,"installations":[{"source":{"unknown":"nonempty"}}]}`, domain.StateSchemaVersion), + fmt.Sprintf(`{"schema_version":%d,"installations":[{"clients":{"id":{"native_objects":[{"kind":"managed_package_directory","unknown":true}]}}}]}`, domain.StateSchemaVersion), + fmt.Sprintf(`{"schema_version":%d,"schema_version":%d,"installations":[]}`, domain.StateSchemaVersion, domain.StateSchemaVersion), + `{"schema_version":2,"installations":[]}`, `{"schema_version":3,"installations":[]}`, + `{"schema_version":4,"installations":[]}`, + fmt.Sprintf(`{"schema_version":%d,"installations":[]}`, domain.StateSchemaVersion+1), } { if _, err := kimiProjectProofState([]byte(body)); err == nil { t.Fatal("unknown, duplicate or unsupported state facts accepted") diff --git a/repotests/agentplugins_kimi_project_native_proof_test.go b/repotests/agentplugins_kimi_project_native_proof_test.go index b408f7575..ef3c84a92 100644 --- a/repotests/agentplugins_kimi_project_native_proof_test.go +++ b/repotests/agentplugins_kimi_project_native_proof_test.go @@ -65,7 +65,7 @@ func kimiProjectProofState(body []byte) (domain.StateFileV2, error) { if err := decoder.Decode(&state); err != nil { return state, err } - if state.SchemaVersion != 2 { + if state.SchemaVersion != domain.StateSchemaVersion { return state, fmt.Errorf("Kimi state schema differs") } return state, nil From 389206f8a8b0ec864ee209880bf0e7deb7c06222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 04:43:09 +0200 Subject: [PATCH 06/42] test(project): use native absolute fixture paths on Windows --- .../agentpluginscli/project_version_preflight_test.go | 10 +++++++++- .../agentpluginscli/project_version_windsurf_test.go | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cli/internal/agentpluginscli/project_version_preflight_test.go b/cli/internal/agentpluginscli/project_version_preflight_test.go index 26665e94e..6b7691edf 100644 --- a/cli/internal/agentpluginscli/project_version_preflight_test.go +++ b/cli/internal/agentpluginscli/project_version_preflight_test.go @@ -3,6 +3,8 @@ package agentpluginscli import ( "context" "errors" + "os" + "path/filepath" "reflect" "testing" @@ -70,10 +72,16 @@ func (detector *projectVersionDetector) DetectTargetsWithVersionProbe(_ context. return append([]domain.DetectedClient(nil), detector.probed...), detector.err } +// Native paths must be absolute on Windows as well as Unix. No fixture +// executable is created or launched by these inert version-probe tests. +func projectVersionFixturePath(elements ...string) string { + return filepath.Join(append([]string{os.TempDir(), "uap-project-version-fixture"}, elements...)...) +} + func projectVersionClient() domain.DetectedClient { return domain.DetectedClient{ ClientID: domain.ClientKimi, DisplayName: "original Kimi", Status: domain.DetectionDetected, - ExecutablePath: "/fixture/bin/kimi", ConfigRoot: "/fixture/user/.kimi", ProfileNamespace: "original-user", + ExecutablePath: projectVersionFixturePath("bin", "kimi"), ConfigRoot: projectVersionFixturePath("user", ".kimi"), ProfileNamespace: "original-user", Surfaces: []domain.ClientSurface{{ID: "cli", Detected: true, Evidence: "original discovery"}}, } } diff --git a/cli/internal/agentpluginscli/project_version_windsurf_test.go b/cli/internal/agentpluginscli/project_version_windsurf_test.go index 3774bda5b..01c39815d 100644 --- a/cli/internal/agentpluginscli/project_version_windsurf_test.go +++ b/cli/internal/agentpluginscli/project_version_windsurf_test.go @@ -11,7 +11,7 @@ import ( ) func projectVersionDevin() domain.DetectedClient { - return domain.DetectedClient{ClientID: domain.ClientWindsurf, DisplayName: "original Devin", Status: domain.DetectionDetected, ExecutablePath: "/fixture/bin/devin", Surfaces: []domain.ClientSurface{{ID: "devin_cli", Detected: true, Evidence: "original discovery"}}} + return domain.DetectedClient{ClientID: domain.ClientWindsurf, DisplayName: "original Devin", Status: domain.DetectionDetected, ExecutablePath: projectVersionFixturePath("bin", "devin"), Surfaces: []domain.ClientSurface{{ID: "devin_cli", Detected: true, Evidence: "original discovery"}}} } func TestProjectVersionUnionIsIDBoundAtomicAndPreservesEmptyCascadeRoot(t *testing.T) { @@ -88,8 +88,8 @@ func TestProjectDevinVersionProbeKeepsLegacyPrimaryAndMutationBoundaries(t *test opts, scope, target := &options{}, domain.ScopeProject, domain.ClientWindsurf switch kind { case "legacy-primary", "secondary-surface": - client.ExecutablePath = "/fixture/bin/windsurf" - client.ConfigRoot = "/fixture/user/.codeium/windsurf" + client.ExecutablePath = projectVersionFixturePath("bin", "windsurf") + client.ConfigRoot = projectVersionFixturePath("user", ".codeium", "windsurf") if kind == "legacy-primary" { client.Surfaces = nil } From a4a211d60595abbb8592e76e27846ba6c349a6d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 06:57:39 +0200 Subject: [PATCH 07/42] test(kimi): use context-aware HTTP request and listener in native contracts --- ...entplugins_kimi_project_native_attempt_contract_test.go | 7 ++++++- .../agentplugins_kimi_project_native_provider_test.go | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/repotests/agentplugins_kimi_project_native_attempt_contract_test.go b/repotests/agentplugins_kimi_project_native_attempt_contract_test.go index 75b322eba..7ba1ab0ad 100644 --- a/repotests/agentplugins_kimi_project_native_attempt_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_attempt_contract_test.go @@ -4,8 +4,10 @@ package pluginkitairepo_test import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" "net/http/httptest" "testing" ) @@ -58,7 +60,10 @@ func TestKimiSingleToolProviderOwnsOnlyOneTwoTurnExchange(t *testing.T) { } send := func(value map[string]any, wrongKey, wrongHost bool) *httptest.ResponseRecorder { body, _ := json.Marshal(value) - r := httptest.NewRequest("POST", p.server.URL+"/v1/chat/completions", bytes.NewReader(body)) + r, err := http.NewRequestWithContext(context.Background(), http.MethodPost, p.server.URL+"/v1/chat/completions", bytes.NewReader(body)) + if err != nil { + t.Fatalf("construct in-process request: %v", err) + } r.Header.Set("Authorization", "Bearer "+p.key) r.Header.Set("Content-Type", "application/json") if wrongKey { diff --git a/repotests/agentplugins_kimi_project_native_provider_test.go b/repotests/agentplugins_kimi_project_native_provider_test.go index d6283e46d..53fae97ab 100644 --- a/repotests/agentplugins_kimi_project_native_provider_test.go +++ b/repotests/agentplugins_kimi_project_native_provider_test.go @@ -4,6 +4,7 @@ package pluginkitairepo_test import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -134,7 +135,7 @@ type kimiAttemptProvider struct { } func (p *kimiAttemptProvider) start() error { - listener, err := net.Listen("tcp4", "127.0.0.1:0") + listener, err := (&net.ListenConfig{}).Listen(context.Background(), "tcp4", "127.0.0.1:0") if err != nil { return err } From b8593f5f8fc2664a409b591251e7dcca6a09f0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 07:39:14 +0200 Subject: [PATCH 08/42] docs(kimi): plan finite private collector failure diagnostics --- .../KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md diff --git a/docs/KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md b/docs/KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md new file mode 100644 index 000000000..27f9e5543 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md @@ -0,0 +1,140 @@ +# Kimi installed collectors: finite failure-stage diagnostics + +## Fresh actual failure and controlled source + +PR524 current published source a4a211d60595abbb8592e76e27846ba6c349a6d0, +tree975a0faff2689474fe9b6d07c663af0fec78933a, has runtime40452608070. +Both jobs now pass public installation and the corrected receipt/schema proof. +Component122018618870 completes user/A/B/C adds, then observe-installed exits1 +with zero stdout and only "Kimi installed API observation failed; private capture +quarantined". Single-tool122018618873 completes A add, then native-attempt exits1 +with zero stdout and only "Kimi single-tool observation failed; private native +streams quarantined". Neither capture reveals the failing callback operation, +exception category or nested service cleanup result. No HTTP shape or specific +root cause can be inferred from those generic diagnostics. + +Native component12711270709 outer SHA256 +b898629adf03caef42df54afaa4a394b4ccaf63bcbe59bb7300c3f4e44579e10; +native call12711270602 outer SHA256 +12fbc3c6083f530393aee3196c7af3e493d29b7fcd5717414fe0e77938b25abc. +Matching frontends12711445369/12711380421 were independently checked with the +native archives for exact outer hashes, bounded canonical unique ordinary ZIP +inventories, CRCs, exact committed source/tree and empty patch list. All five +native qualification flags and vendor/model flags remain false. Core40452608159 +and Terminal40452608141 now pass; they are not native-runtime qualification. + +Local work/kimi-a4-diagnostics baseline7e10e7da7a883c8c371d837e37a99b331fbc3b54 +has exactly the current published tree975a0fa. It was reconstructed from the +earlier exact tree31a95 plus all six immutable official current-file blobs; +each full git-blob hash and the complete resulting tree were asserted. Preserve +all six externally published schema/Windows/noctx files byte-for-byte. The old +local schema/noctx plan/code sequence through5ac10bf remains on its original +branch and is not transplanted onto this baseline. + +## Existing capability inventory: preserve, do not republish blindly + +The earlier local4406458a finite ResponseSchemaRefusal capability is already +byte-identical in this current published tree: kimi_attempt.py SHA256 +326d4daf8682ad3a85f5251b551cc354a21d330ae4ac092d36c9905558d8e0ef, +test_project_native_kimi_attempt.py SHA256 +8cac1d82093c87e73ba0439325888b3014a251c1d174b8f64236af5e99f69649. +It catches only registered later session/prompt/approval/snapshot/history shape +refusals, stops later tool actions, obtains the same graceful shutdown/reaping +and emits only the existing admitted redacted closure with failure status. +Authority/permission/readiness/transport/cleanup errors remain quarantined. +Preserve that implementation and its meaningful inert supervisor tests. Do not +broaden its catch, expose quarantined HTTP or treat this new generic failure as +a known ResponseSchemaRefusal. + +## Diagnostic-only implementation after two independent plan GOs + +Scope is the two existing collectors scripts/project_native_collectors/kimi.py +and kimi_attempt.py, their two existing test files scripts/test_project_native_kimi.py +and scripts/test_project_native_kimi_attempt.py, and this plan/validation record. +No Go, shared service/frontend, provider, native grammar or authority file changes. + +Add one small Kimi-only failure tracker in kimi.py, reused by the existing +kimi_attempt import. It stores only a registered stage, registered phase, a +finite safe exception category and boolean reached status. Its report must be +constructed from an explicit closed schema, never an arbitrary dict/object. +Do not include exception str/repr/traceback/class name, native output, request +or response bytes/keys, URL/path, bearer/key, nonce, session/call/workspace ID, +stdout/stderr, environment, filenames or user-supplied request.stage. + +The finite stage inventory covers main host-admission, request-read, +request-validate, authority, immutable-closure, context, service-entry, ready, +meta, health, unauthorized, workspace-a/b/c, trust-before/set/after-a/b/c, +trust-untrusted-c, catalog-a/b/c/user, mcp-outside, mcp-a/b/c, skills-a/b/c, +test-a/b, test-refused-c, probe-events-a/b/c, distinct-workspaces, pre-call, +create-session, prompt, approvals, approve-once, snapshot, history, +preserved-authority, shutdown, service-cleanup, serialization and emission. +Only the stages actually used by each collector are instrumented. The repeated +approval/snapshot indexes remain private and map to their single registered +stage. Phase is one of enter, request, envelope, validate, complete. Unknown +stages/phases cannot be rendered; registration errors fail closed. + +Before an existing API operation mark its finite stage/request; after the same +bounded service.http returns mark envelope, and before returning its validated +envelope mark validate so the existing caller's strict object/value checks are +attributed to that operation. Mark readiness/authority and the existing +non-HTTP checks at their actual boundaries. A wrapper around each existing +observer callback records only the safe category of an uncaught exception +before the unchanged Service swallows it; it rethrows that same exception. +If the callback returns normally, mark service-cleanup before the unchanged +Context.service completes supervision and immutable rechecks. Do not catch a +failure and continue to another HTTP, approval, prompt or tool action. + +Safe exception category comes from a finite type mapping (validation, type, +key, OS, timeout, other), with no dynamic type/module names or message. The +original failure/exception and return-code behavior remains unchanged. +Callback-entered is true only if that wrapper was actually entered. A recorded +service result is reported only from the actual existing Context summary +command, not inferred from a generic failure. + +On the existing generic entrypoint exception branch, emit one bounded line of +static-prefix JSON to stderr (maximum2KiB), followed by the unchanged generic +quarantine failure. Report the finite stage/phase/category, observer-entered, +service-recorded and these five cleanup values copied only when present as +exact bools in the actual Service result: shutdown_acknowledged, sigterm_sent, +forced_kill, group_reaped, port_closed. Missing/unreported values are null, +with cleanup_reported=false; never turn unknown into success. Do not serialize +any other service or Context field. No new stdout envelope or artifact member +is added for generic failure. Existing stdout quarantine and the original +closed evidence/secret checks remain unchanged. + +Do not emit a new failure report on success or alter the already supported +ResponseSchemaRefusal path. Preserve its failure return1 and redacted graceful +capture, existing API budget/reserved shutdown slot, original hosted guard, +150/60/65-second budgets, process signals/reaping, endpoint and credential +authority, request inventory, trust/permission/manual approval, source/pins, +cleanup/admission and all qualification flags. This slice identifies a failure; +it does not accept or repair it. + +## Meaningful verification and publication + +Extend the existing inert tests with failures containing planted bearer/key, +path, nonce, session and arbitrary exception text. Exercise the actual tracked +collector callbacks and their existing inert service supervisor where possible, +then render the failure line and require the exact closed fields/enums, finite +size/no raw linebreak, no private value/exception text/HTTP body and failure +status. Cover pre-service failure (no cleanup claim), readiness, HTTP transport, +strict envelope/validator, permission/authority and post-callback cleanup +failure. Compare cleanup booleans with the actual retained supervisor record, +including false and unavailable values. Verify the first failed operation is +the last attempted action; no extra API, grant, prompt or tool is introduced. +Unknown stage/category inputs and malformed cleanup dictionaries cannot cause +arbitrary data to enter stderr. + +Keep and run all existing Kimi collector/attempt and shared service inert +contracts, including actual inert Python child cleanup/quarantine tests and the +previous finite-schema-refusal graceful capture tests. These are not native +Kimi/model/provider observations and require no Go slot. Run Python syntax and +whitespace checks and independent code review before root-owned publication to +the existing PR524. No vendor binary executes locally, no host spoofing, new PR, +release or VS Code work is included. + +Fresh genuine hosted source-bound component and call captures must identify +the actual failed stage/category and truthful cleanup status before any later +behavioral or grammar amendment is planned. Do not relabel the existing failed +runs, assume a response schema mismatch or promote any native qualification. + From 55f5d848fb61899f2c84bd7cccc7592549718b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 07:39:17 +0200 Subject: [PATCH 09/42] test(kimi): expose only finite private failure stages --- .../KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md | 16 +++ scripts/project_native_collectors/kimi.py | 132 ++++++++++++++++-- .../project_native_collectors/kimi_attempt.py | 48 +++++-- scripts/test_project_native_kimi.py | 91 ++++++++++++ scripts/test_project_native_kimi_attempt.py | 75 +++++++++- 5 files changed, 338 insertions(+), 24 deletions(-) diff --git a/docs/KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md b/docs/KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md index 27f9e5543..b8c48ca07 100644 --- a/docs/KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md +++ b/docs/KIMI_PROJECT_NATIVE_FAILURE_STAGE_PLAN.md @@ -138,3 +138,19 @@ the actual failed stage/category and truthful cleanup status before any later behavioral or grammar amendment is planned. Do not relabel the existing failed runs, assume a response schema mismatch or promote any native qualification. + +## Actual implementation validation + +The exact four-file candidate passes all 47 existing/new Kimi collector, +attempt and shared service inert tests in 16.404 seconds, including the +existing graceful finite-schema refusal and actual inert Python child +quarantine/reaping tests. All four Python files parse; whitespace checks are +clean. The six externally published Go files remain byte-for-byte unchanged. +No vendor binary or Go process ran. + +The first run's precise transport-stage assertion exposed an existing inert +test fixture issue: a null selected response stage also matched unregistered +health operations and inserted an unknown field too early. Requiring a +non-null selected stage restores the intended transport/permission/cleanup +negative controls; it changes no collector or native response grammar. The +complete final rerun above passes with that meaningful fixture correction. diff --git a/scripts/project_native_collectors/kimi.py b/scripts/project_native_collectors/kimi.py index d12f92be9..f902b9c00 100644 --- a/scripts/project_native_collectors/kimi.py +++ b/scripts/project_native_collectors/kimi.py @@ -619,6 +619,94 @@ def collect(ctx): return result +_FAILURE_STAGES = frozenset({ + "host-admission", "request-read", "request-validate", "authority", "immutable-closure", "context", + "service-entry", "ready", "meta", "health", "unauthorized", "distinct-workspaces", "pre-call", + "mcp-outside", "create-session", "prompt", "approvals", "approve-once", "snapshot", "history", + "preserved-authority", "shutdown", "service-cleanup", "serialization", "emission", "trust-untrusted-c", + *[prefix + "-" + scope for prefix in ("workspace", "trust-before", "trust-set", "trust-after", + "catalog", "mcp", "skills", "probe-events") for scope in WORKSPACES], + "catalog-user", "test-a", "test-b", "test-refused-c", +}) +_FAILURE_PHASES = frozenset({"enter", "request", "envelope", "validate", "complete"}) +_FAILURE_KINDS = frozenset({"validation", "type", "key", "os", "timeout", "other"}) +_FAILURE_CLEANUP = ("shutdown_acknowledged", "sigterm_sent", "forced_kill", "group_reaped", "port_closed") + + +class _KimiFailureDiagnostic: + """Only finite caller-owned progress and held supervisor booleans may leave quarantine.""" + + def __init__(self, collector): + if collector not in ("installed", "single-tool"): + raise ValueError("unregistered Kimi diagnostic collector") + self.collector, self.stage, self.phase = collector, "host-admission", "enter" + self.kind, self.ctx, self.observer_entered = None, None, False + + def step(self, stage, phase="enter"): + if stage not in _FAILURE_STAGES or phase not in _FAILURE_PHASES: + raise ValueError("unregistered Kimi diagnostic stage or phase") + self.stage, self.phase = stage, phase + + def api(self, label, phase): + if label in {"approvals-" + str(index) for index in range(20)}: + label = "approvals" + elif label in {"snapshot-" + str(index) for index in range(10)}: + label = "snapshot" + self.step(label, phase) + + @staticmethod + def exception_kind(error): + for kind, classes in (("timeout", (TimeoutError,)), ("validation", (ValueError,)), + ("type", (TypeError,)), ("key", (KeyError,)), ("os", (OSError,))): + if isinstance(error, classes): + return kind + return "other" + + def observer(self, callback): + def tracked(service): + self.observer_entered = True + try: + result = callback(service) + except Exception as error: + self.kind = self.exception_kind(error) + raise + self.step("service-cleanup", "complete") + return result + return tracked + + def line(self, error): + self.step(self.stage, self.phase) + kind = self.kind if self.kind is not None else self.exception_kind(error) + if (self.collector not in ("installed", "single-tool") or kind not in _FAILURE_KINDS + or type(self.observer_entered) is not bool): + raise ValueError("unregistered Kimi diagnostic facts") + cleanup = dict.fromkeys(_FAILURE_CLEANUP) + summary = getattr(self.ctx, "summary", None) + commands = summary.get("commands") if type(summary) is dict else None + recorded = type(commands) is list and len(commands) == 1 and type(commands[0]) is dict + held = commands[0].get("cleanup") if recorded else None + if type(held) is dict: + cleanup.update({key: held[key] for key in _FAILURE_CLEANUP if type(held.get(key)) is bool}) + value = {"schema_version": 1, "collector": self.collector, "failure": True, + "stage": self.stage, "phase": self.phase, "exception_kind": kind, + "observer_entered": self.observer_entered, "service_recorded": recorded, + "cleanup_reported": all(type(item) is bool for item in cleanup.values()), "cleanup": cleanup} + line = "Kimi collector failure: " + json.dumps(value, allow_nan=False, sort_keys=True, separators=(",", ":")) + "\n" + if len(line.encode()) > 2048: + raise ValueError("Kimi diagnostic bound exceeded") + return line + + +def _emit_failure_diagnostic(diagnostic, error, stream): + try: + line = diagnostic.line(error) + except Exception: + # An invalid internal diagnostic must leave the original quarantine + # failure intact, rather than render arbitrary state or exception text. + return + stream.write(line) + + def _installed_skills(data, expected): """Compare native rows to receipt-selected files, never replacement fixtures.""" _object(data, ("skills",)) @@ -679,13 +767,16 @@ def _installed_catalog(data, expected): return sorted(found) -def observe_installed(ctx, request): +def observe_installed(ctx, request, diagnostic=None): """One fresh web process over already-installed public UAP authority. The Go lifecycle owns the installer and builds this exact expectation from committed receipts/native bytes. This observer never authors components. Only the empty initial stage may author native A/B trust through its API. """ + diagnostic = diagnostic or _KimiFailureDiagnostic("installed") + diagnostic.ctx = ctx + diagnostic.step("request-validate") _object(request, ("stage", "setup_trust", "skills", "servers")) if (not re.fullmatch(r"[a-z][a-z0-9-]{0,40}", request["stage"]) or type(request["setup_trust"]) is not bool @@ -697,6 +788,7 @@ def observe_installed(ctx, request): or (request["setup_trust"] and (request["stage"] != "empty" or any(request[key][scope] for key in ("skills", "servers") for scope in WORKSPACES)))): raise ValueError("Kimi installed trust setup requires the empty baseline") + diagnostic.step("authority") protected = {} for scope in WORKSPACES: for skill in request["skills"][scope]: @@ -717,21 +809,29 @@ def observe_installed(ctx, request): protected[path] = body def preserved(): + diagnostic.step("preserved-authority") for path, body in protected.items(): if _read(ctx.root, path) != body: raise ValueError("Kimi installed authority changed during observation") # Every fresh process verifies the same native workspace identities/trust. # No trust mutation occurs in any installed or removed stage. def observe(service): + diagnostic.step("ready") token, count = _ready(ctx, service), 0 def api(label, method, path, body=None, code=0): nonlocal count + diagnostic.api(label, "enter") count += 1 if count > 60: raise ValueError("Kimi installed API request budget exhausted") - return _envelope(*service.http(label, method, path, token=token, - json_body=body, timeout=min(5, service.remaining())), code=code) + diagnostic.api(label, "request") + response = service.http(label, method, path, token=token, + json_body=body, timeout=min(5, service.remaining())) + diagnostic.api(label, "envelope") + result = _envelope(*response, code=code) + diagnostic.api(label, "validate") + return result meta = api("meta", "GET", "/api/v1/meta") _meta(meta) @@ -748,6 +848,7 @@ def api(label, method, path, body=None, code=0): if request["setup_trust"] and scope in ("a", "b"): _trust(api("trust-set-" + scope, "POST", trust_path, {}), True) _trust(api("trust-after-" + scope, "GET", trust_path), True) + diagnostic.step("distinct-workspaces") if len(set(ids.values())) != 3: raise ValueError("Kimi installed workspace identities are not distinct") skill_rows, server_rows, probe_rows = {}, {}, {} @@ -761,6 +862,7 @@ def api(label, method, path, body=None, code=0): server_rows[scope] = _installed_catalog(api("mcp-" + scope, "GET", "/api/v2/mcp/servers?" + urlencode({"cwd": str(ctx.projects[scope])})), entries) for item in request["servers"][scope]: + diagnostic.step("probe-events-" + scope) event_fixture = {"event": Path(item["event"]), "nonce": item["nonce"]} before, _ = _events(ctx, event_fixture) if scope == "c": @@ -779,6 +881,7 @@ def api(label, method, path, body=None, code=0): '- inspect_runtime: Inspect only disposable fixture runtime facts.') if result != {"success": True, "output": expected_output}: raise ValueError("Kimi installed named MCP test failed") + diagnostic.step("probe-events-" + scope) after, rows = _events(ctx, event_fixture) fresh = rows[len(before.splitlines()):] pairs = {(row["pid"], row["session"]) for row in fresh} @@ -798,25 +901,31 @@ def api(label, method, path, body=None, code=0): "skill_names": skill_rows, "server_names": server_rows, "probe_events": probe_rows, "tools_call_count": 0, "provider_configured": False, "request_count": count} + diagnostic.step("service-entry") result = ctx.service("kimi-" + request["stage"], - ["web", "--no-open", "--host", "127.0.0.1", "--port", "0"], observe, project="a", timeout=60) + ["web", "--no-open", "--host", "127.0.0.1", "--port", "0"], diagnostic.observer(observe), project="a", timeout=60) preserved() return result -def _installed_main(request_path): +def _installed_main(request_path, diagnostic=None): """Client-specific adapter to the existing private service/capture owner.""" import sys sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import project_native_research as shared # This guard precedes parsing paths, creating files, or any child launch. + diagnostic = diagnostic or _KimiFailureDiagnostic("installed") + diagnostic.step("host-admission") shared.native.require_hosted("linux-amd64") + diagnostic.step("request-read") path = Path(request_path) value = _json(_read(path.parent, path, 1 << 20, mode=0o600)) + diagnostic.step("request-validate") _object(value, ("schema_version", "root", "home", "projects", "client", "identities", "observation")) if value["schema_version"] != 1 or type(value["schema_version"]) is not int: raise ValueError("Kimi installed request version refused") + diagnostic.step("authority") root, home, client = (Path(value[key]) for key in ("root", "home", "client")) projects = {key: Path(item) for key, item in value["projects"].items()} if (set(projects) != set((*WORKSPACES, "user")) or root.resolve() != root @@ -827,6 +936,7 @@ def _installed_main(request_path): or any(left.is_relative_to(right) for left in (home, *projects.values()) for right in (home, *projects.values()) if left != right)): raise ValueError("Kimi installed profile/project authority refused") + diagnostic.step("immutable-closure") identities = {Path(key): digest for key, digest in value["identities"].items()} if (client not in identities or len(identities) < 3 or len(identities) > 128 or any(not isinstance(digest, str) or not re.fullmatch(r"[0-9a-f]{64}", digest) @@ -834,16 +944,19 @@ def _installed_main(request_path): raise ValueError("Kimi installed immutable closure refused") # Reuse the existing Context's service, environment, capture, redaction and # cleanup implementation without acquisition or fixture creation. + diagnostic.step("context") ctx = shared.Context.__new__(shared.Context) ctx.root, ctx.home, ctx.projects, ctx.identities = root, home, projects, identities ctx.argv, ctx.secrets, ctx.captures = [str(client)], [], {} ctx.summary = {"schema_version": 1, "client": "kimi", "phase": "installed-api", "commands": [], "qualification": {"mcp-tools-call": False}} + diagnostic.ctx = ctx ctx.env = shared.legacy.runtime_environment(home, root / "tmp") ctx.env.update({"NO_COLOR": "1", "TERM": "dumb", "KIMI_CODE_NO_AUTO_UPDATE": "1"}) with shared.Budget(65) as budget: ctx.budget = budget - ctx.summary["observations"] = observe_installed(ctx, value["observation"]) + ctx.summary["observations"] = observe_installed(ctx, value["observation"], diagnostic) + diagnostic.step("serialization") bodies = shared.evidence(ctx.summary, ctx.captures, ctx.secrets) # Shared evidence() admits only closed redacted members. Recheck the whole # serialization too; a token cannot leak through an added summary field. @@ -852,6 +965,7 @@ def _installed_main(request_path): allow_nan=False, sort_keys=True).encode() if len(output) > 1 << 20 or any(secret in output for secret in ctx.secrets): raise ValueError("Kimi installed closed evidence refused") + diagnostic.step("emission") sys.stdout.buffer.write(output + b"\n") @@ -859,8 +973,10 @@ def _installed_main(request_path): import sys if len(sys.argv) != 3 or sys.argv[1] != "--installed-request": raise SystemExit("Kimi installed observer requires its exact private request") + diagnostic = _KimiFailureDiagnostic("installed") try: - _installed_main(sys.argv[2]) - except Exception: + _installed_main(sys.argv[2], diagnostic) + except Exception as error: # Never render exception text from bearer-bearing startup/API activity. + _emit_failure_diagnostic(diagnostic, error, sys.stderr) raise SystemExit("Kimi installed API observation failed; private capture quarantined") from None diff --git a/scripts/project_native_collectors/kimi_attempt.py b/scripts/project_native_collectors/kimi_attempt.py index 7ddfe9f6c..3eb5b9c33 100644 --- a/scripts/project_native_collectors/kimi_attempt.py +++ b/scripts/project_native_collectors/kimi_attempt.py @@ -114,11 +114,15 @@ def no_call(ctx, event_path, marker_path): raise ValueError("Kimi marker exists before native manual approval") -def observe(ctx, request): +def observe(ctx, request, diagnostic=None): + diagnostic = diagnostic or kimi._KimiFailureDiagnostic("single-tool") + diagnostic.ctx = ctx + diagnostic.step("request-validate") kimi._object(request, ("server", "skill", "call_id", "provider_key", "nonce", "marker", "data")) server, skill = request["server"], request["skill"] kimi._object(skill, ("name", "description", "path", "source", "sha256")) kimi._object(server, ("name", "origin", "config", "sha256", "event", "nonce", "cwd")) + diagnostic.step("authority") if server["name"] != "uap-kimi-native-a" or server["nonce"] != request["nonce"]: raise ValueError("Kimi selected installed A identity differs") event_path, marker_path = Path(server["event"]), Path(request["data"]) / "native-marker.txt" @@ -130,21 +134,28 @@ def observe(ctx, request): skill_body = kimi._read(ctx.root, Path(skill["path"]), 1 << 20) if hashlib.sha256(skill_body).hexdigest() != skill["sha256"]: raise ValueError("Kimi receipt-selected A skill bytes differ") + diagnostic.step("pre-call") no_call(ctx, event_path, marker_path) args = {"nonce": request["nonce"], "operation": "write", "marker": request["marker"]} def run(service): + diagnostic.step("ready") token, count = kimi._ready(ctx, service), 0 def api(label, method, path, body=None, *, auth=True, status=200, code=0): nonlocal count + diagnostic.api(label, "enter") count += 1 if count > 60 or (label != "shutdown" and count > 59): raise ValueError("Kimi single original HTTP inventory exhausted") + diagnostic.api(label, "request") result_status, result_body = service.http(label, method, path, token=token if auth else None, json_body=body, timeout=min(5, service.remaining())) + diagnostic.api(label, "envelope") strict_json(result_body) - return kimi._envelope(result_status, result_body, expected_status=status, code=code) + result = kimi._envelope(result_status, result_body, expected_status=status, code=code) + diagnostic.api(label, "validate") + return result if kimi._object(api("health", "GET", "/api/v1/healthz", auth=False), ("ok",))["ok"] is not True: raise ValueError("Kimi native health differs") @@ -158,6 +169,7 @@ def api(label, method, path, body=None, *, auth=True, status=200, code=0): {"root": str(fixture["root"]), "name": fixture["name"]}) workspaces[scope] = kimi._workspace(row, fixture) kimi._trust(api("trust-before-" + scope, "GET", "/api/v1/workspaces/" + workspaces[scope] + "/trust"), False) + diagnostic.step("distinct-workspaces") if len(set(workspaces.values())) != 3: raise ValueError("Kimi workspace IDs are not distinct") for scope in ("a", "b"): @@ -189,6 +201,7 @@ def session_capture(): kimi._instant(prompt["created_at"]) matched = None for index in range(20): + diagnostic.step("pre-call") no_call(ctx, event_path, marker_path) pending = api("approvals-" + str(index), "GET", base + "/approvals?status=pending") candidate = None @@ -210,6 +223,7 @@ def session_capture(): time.sleep(min(0.25, service.remaining())) if matched is None: raise ValueError("Kimi exact native manual approval was not observed") + diagnostic.step("pre-call") no_call(ctx, event_path, marker_path) validated["native_turn_id"], validated["approval_id"] = matched[1], matched[0] # Deliberately omit scope: no remembered or blanket tool approval. @@ -253,6 +267,7 @@ def session_capture(): result = {**validated, "response_schema_refused": True, "failed_stage": refusal.stage, "native_capture_complete": False, "native_history_grammar_frozen": False} + diagnostic.step("preserved-authority") if kimi._read(ctx.root, config_path, 1 << 20) != config_body: raise ValueError("Kimi installed config changed during native tool attempt") shutdown = api("shutdown", "POST", "/api/v1/shutdown", {}) @@ -262,16 +277,23 @@ def session_capture(): result["api_request_count"] = count return result - return ctx.service("kimi-single-tool", ["web", "--no-open", "--host", "127.0.0.1", "--port", "0"], run, project="a", timeout=60) + diagnostic.step("service-entry") + return ctx.service("kimi-single-tool", ["web", "--no-open", "--host", "127.0.0.1", "--port", "0"], + diagnostic.observer(run), project="a", timeout=60) -def main(request_path): +def main(request_path, diagnostic=None): + diagnostic = diagnostic or kimi._KimiFailureDiagnostic("single-tool") + diagnostic.step("host-admission") shared.native.require_hosted("linux-amd64") + diagnostic.step("request-read") path = Path(request_path) value = kimi._object(strict_json(kimi._read(path.parent, path, 1 << 20, mode=0o600)), ("schema_version", "root", "home", "projects", "client", "identities", "observation")) + diagnostic.step("request-validate") if type(value["schema_version"]) is not int or value["schema_version"] != 1: raise ValueError("Kimi attempt request version differs") + diagnostic.step("authority") root, home, client = (Path(value[key]) for key in ("root", "home", "client")) projects = {key: Path(item) for key, item in value["projects"].items()} if (set(projects) != set((*kimi.WORKSPACES, "user")) or root.resolve() != root or not client.is_absolute() @@ -280,6 +302,7 @@ def main(request_path): or any(left.is_relative_to(right) for left in (home, *projects.values()) for right in (home, *projects.values()) if left != right)): raise ValueError("Kimi original profile/project authority differs") + diagnostic.step("immutable-closure") identities = {Path(key): digest for key, digest in value["identities"].items()} if (client not in identities or not 3 <= len(identities) <= 128 or any(not isinstance(digest, str) or not re.fullmatch(r"[0-9a-f]{64}", digest) for digest in identities.values())): @@ -287,21 +310,24 @@ def main(request_path): key = value["observation"]["provider_key"] if not isinstance(key, str) or not re.fullmatch(r"uap-kimi-key-[0-9a-f]{32}", key): raise ValueError("Kimi private fixture key differs") + diagnostic.step("context") ctx = shared.Context.__new__(shared.Context) ctx.root, ctx.home, ctx.projects, ctx.identities = root, home, projects, identities ctx.argv, ctx.secrets, ctx.captures = [str(client)], [], {} ctx.register_secret(key) # Before any native startup or capture is possible. ctx.summary = {"schema_version": 1, "client": "kimi", "phase": "installed-single-tool-attempt", "commands": [], "qualification": {name: False for name in shared.QUALIFICATION_CASES}} + diagnostic.ctx = ctx ctx.env = shared.legacy.runtime_environment(home, root / "tmp") ctx.env.update({"NO_COLOR": "1", "TERM": "dumb", "KIMI_CODE_NO_AUTO_UPDATE": "1"}) with shared.Budget(65) as budget: ctx.budget = budget - ctx.summary["observations"] = observe(ctx, value["observation"]) - return emit_capture(ctx) + ctx.summary["observations"] = observe(ctx, value["observation"], diagnostic) + diagnostic.step("serialization") + return emit_capture(ctx, diagnostic) -def emit_capture(ctx): +def emit_capture(ctx, diagnostic=None): # Context.service has already enforced unchanged graceful cleanup before # admitting any HTTP receipt. This is the same closed, redacted envelope # for success or the dedicated failed response-shape observation. @@ -310,6 +336,8 @@ def emit_capture(ctx): allow_nan=False, sort_keys=True).encode() if len(output) > 1 << 20 or any(secret in output for secret in ctx.secrets): raise ValueError("Kimi private closed API evidence differs") + if diagnostic is not None: + diagnostic.step("emission") sys.stdout.buffer.write(output + b"\n") return 1 if ctx.summary["observations"].get("response_schema_refused") is True else 0 @@ -317,8 +345,10 @@ def emit_capture(ctx): if __name__ == "__main__": if len(sys.argv) != 3 or sys.argv[1] != "--attempt-request": raise SystemExit("Kimi runtime attempt requires its exact private request") + diagnostic = kimi._KimiFailureDiagnostic("single-tool") try: - status = main(sys.argv[2]) - except Exception: + status = main(sys.argv[2], diagnostic) + except Exception as error: + kimi._emit_failure_diagnostic(diagnostic, error, sys.stderr) raise SystemExit("Kimi single-tool observation failed; private native streams quarantined") from None raise SystemExit(status) diff --git a/scripts/test_project_native_kimi.py b/scripts/test_project_native_kimi.py index 8627b29e5..f1c442b99 100644 --- a/scripts/test_project_native_kimi.py +++ b/scripts/test_project_native_kimi.py @@ -3,9 +3,11 @@ import copy import hashlib from datetime import datetime, timezone +import io import json import os from pathlib import Path +from types import SimpleNamespace import tempfile import time import unittest @@ -224,6 +226,95 @@ def service(self, label, args, observe, project="a", timeout=60): class KimiCollectorTests(unittest.TestCase): + def diagnostic(self, diagnostic, error): + output = io.StringIO() + kimi._emit_failure_diagnostic(diagnostic, error, output) + line = output.getvalue() + self.assertTrue(line.startswith("Kimi collector failure: ")) + self.assertEqual(line.count("\n"), 1) + self.assertLessEqual(len(line.encode()), 2048) + value = json.loads(line.removeprefix("Kimi collector failure: ")) + self.assertEqual(set(value), {"schema_version", "collector", "failure", "stage", "phase", + "exception_kind", "observer_entered", "service_recorded", "cleanup_reported", "cleanup"}) + self.assertEqual(set(value["cleanup"]), set(kimi._FAILURE_CLEANUP)) + return line, value + + def test_failure_diagnostic_before_service_never_invents_cleanup_or_prints_exception(self): + diagnostic = kimi._KimiFailureDiagnostic("installed") + private = "/private/path bearer-secret nonce-session-id" + line, value = self.diagnostic(diagnostic, RuntimeError(private)) + self.assertNotIn(private, line) + self.assertEqual(value["stage"], "host-admission") + self.assertEqual(value["exception_kind"], "other") + self.assertFalse(value["observer_entered"]) + self.assertFalse(value["service_recorded"]) + self.assertFalse(value["cleanup_reported"]) + self.assertTrue(all(item is None for item in value["cleanup"].values())) + + def test_failure_diagnostic_copies_only_exact_booleans_and_refuses_arbitrary_labels(self): + diagnostic = kimi._KimiFailureDiagnostic("installed") + private = "private-bearer-and-HTTP-body" + diagnostic.ctx = SimpleNamespace(summary={"commands": [{"cleanup": { + "shutdown_acknowledged": True, "forced_kill": False, "group_reaped": 1, + "sigterm_sent": private, "port_closed": None, "unknown": private}, "stdout": private}]}) + line, value = self.diagnostic(diagnostic, ValueError(private)) + self.assertNotIn(private, line) + self.assertTrue(value["service_recorded"]) + self.assertFalse(value["cleanup_reported"]) + self.assertEqual(value["cleanup"], {"shutdown_acknowledged": True, "sigterm_sent": None, + "forced_kill": False, "group_reaped": None, "port_closed": None}) + for stage, phase in ((private, "enter"), ("meta", private), ("approvals-20", "request")): + with self.subTest(stage=stage), self.assertRaises(ValueError): + diagnostic.step(stage, phase) + for field in ("collector", "stage", "phase", "kind"): + clean = kimi._KimiFailureDiagnostic("installed") + setattr(clean, field, private) + output = io.StringIO() + kimi._emit_failure_diagnostic(clean, ValueError(private), output) + self.assertEqual(output.getvalue(), "") + + def test_exception_categories_never_render_private_messages_or_dynamic_type_names(self): + private = "private_bearer_nonce_session" + private_type = type(private, (Exception,), {}) + for error, kind in ((ValueError(private), "validation"), (TypeError(private), "type"), + (KeyError(private), "key"), (OSError(private), "os"), + (TimeoutError(private), "timeout"), (private_type(private), "other")): + line, value = self.diagnostic(kimi._KimiFailureDiagnostic("installed"), error) + self.assertNotIn(private, line) + self.assertEqual(value["exception_kind"], kind) + + def test_installed_callback_reports_transport_envelope_and_validator_without_continuing(self): + private = "planted-bearer /private/path nonce-call-session" + for fault, phase in (("transport", "request"), ("envelope", "envelope"), ("validator", "validate")): + with self.subTest(fault=fault), tempfile.TemporaryDirectory() as root: + calls = [] + diagnostic = kimi._KimiFailureDiagnostic("installed") + ctx = SimpleNamespace(root=Path(root), projects={k: Path(root) / k for k in (*kimi.WORKSPACES, "user")}, + summary={"commands": []}) + def http(label, *args, **kwargs): + calls.append(label) + if fault == "transport": + raise ValueError(private) + body = b"{" if fault == "envelope" else json.dumps({"code": 0, "msg": "success", + "data": {"unmeasured": private}, "request_id": private}).encode() + return 200, body + def service(label, argv, observe, **kwargs): + self.assertEqual(label, "kimi-installed") + return observe(SimpleNamespace(http=http, remaining=lambda: 50)) + ctx.service = service + request = {"stage": "installed", "setup_trust": False, + "skills": dict.fromkeys(kimi.WORKSPACES, []), "servers": dict.fromkeys(kimi.WORKSPACES, [])} + with patch.object(kimi, "_ready", return_value="private-bearer"): + with self.assertRaises(ValueError) as caught: + kimi.observe_installed(ctx, request, diagnostic) + line, value = self.diagnostic(diagnostic, caught.exception) + self.assertNotIn(private, line) + self.assertEqual(calls, ["meta"]) + self.assertEqual((value["stage"], value["phase"]), ("meta", phase)) + self.assertTrue(value["observer_entered"]) + self.assertFalse(value["service_recorded"]) + self.assertTrue(all(item is None for item in value["cleanup"].values())) + def test_literal_update_opt_out_precedes_first_child_without_ambient_auth(self): ctx = FakeContext() ctx.env = {"HOME": "/controlled/original-home"} diff --git a/scripts/test_project_native_kimi_attempt.py b/scripts/test_project_native_kimi_attempt.py index dd3de7bd3..de12f5a53 100644 --- a/scripts/test_project_native_kimi_attempt.py +++ b/scripts/test_project_native_kimi_attempt.py @@ -1,9 +1,12 @@ """Inert admission regressions; never launches Kimi, a provider or a probe.""" import copy +import contextlib import hashlib import io import json +import os from pathlib import Path +import runpy import sys import tempfile import time @@ -35,8 +38,9 @@ def http(self, label, method, path, token=None, json_body=None, timeout=5): self.calls.append((label, method, path, token, json_body)) if token is not None and token != TOKEN: raise ValueError("inert bearer mismatch") - if self.ctx.fault == "transport" and label == "create-session": - raise ValueError("inert transport failure") + if self.ctx.fault in ("transport", "private-transport") and label == "create-session": + raise ValueError(TOKEN + KEY + " /private/path nonce-session" if self.ctx.fault == "private-transport" + else "inert transport failure") status, code = (401, 40101) if label == "unauthorized" else (200, 0) if label == "health" or label == "shutdown": data = {"ok": not (label == "shutdown" and self.ctx.fault == "shutdown")} @@ -73,7 +77,7 @@ def http(self, label, method, path, token=None, json_body=None, timeout=5): data = None stage = {"create-session": "session", "prompt": "prompt", "approvals-0": "approval", "snapshot-0": "snapshot", "history": "history"}.get(label) - if stage == self.ctx.stage: + if stage is not None and stage == self.ctx.stage: target = data["items"][0] if stage == "approval" else data target["unmeasured_field"] = "bounded response " + TOKEN + " " + KEY if self.ctx.fault == "identity" and label == "create-session": @@ -132,15 +136,16 @@ def ready(self, service): while b"inert-ready\n" not in service.snapshot()[0]: time.sleep(0.01) self.secrets.append(TOKEN.encode()) - if self.fault == "readiness": - raise ValueError("inert failed readiness") + if self.fault in ("readiness", "private-readiness"): + raise ValueError(TOKEN + KEY + " /private/path nonce-session" if self.fault == "private-readiness" + else "inert failed readiness") service.bind_origin("http://127.0.0.1:31415") service.capture_ready() return TOKEN class KimiAttemptContracts(unittest.TestCase): - def observe_capture(self, ctx, closed=True): + def observe_capture(self, ctx, closed=True, diagnostic=None): # Previously qualified workspace/catalog validators are separate inert # contracts. Exercise only the new response stages and real cleanup. with ( @@ -153,7 +158,63 @@ def observe_capture(self, ctx, closed=True): patch.object(attempt.kimi, "_installed_skills"), patch.object(live, "port_closed", return_value=closed), ): - return attempt.observe(ctx, ctx.request()) + return attempt.observe(ctx, ctx.request(), diagnostic) + + def test_generic_failure_diagnostic_retains_actual_stage_and_cleanup_without_private_capture(self): + cases = (("private-readiness", None, "ready", "enter", None), + ("private-transport", None, "create-session", "request", "create-session"), + ("identity", "session", "create-session", "validate", "create-session"), + ("permission", "approval", "approvals", "validate", "approvals-0"), + ("shutdown", "session", "shutdown", "validate", "shutdown"), + ("cleanup", "history", "service-cleanup", "complete", "shutdown")) + for fault, stage, failed_stage, phase, last_call in cases: + with self.subTest(fault=fault), tempfile.TemporaryDirectory() as root: + ctx = CaptureContext(root, stage, fault) + diagnostic = attempt.kimi._KimiFailureDiagnostic("single-tool") + with self.assertRaisesRegex(ValueError, "common service refused") as caught: + self.observe_capture(ctx, closed=fault != "cleanup", diagnostic=diagnostic) + line = diagnostic.line(caught.exception) + value = json.loads(line.removeprefix("Kimi collector failure: ")) + self.assertEqual((value["stage"], value["phase"]), (failed_stage, phase)) + self.assertEqual(value["exception_kind"], "validation") + self.assertTrue(value["observer_entered"]) + self.assertTrue(value["service_recorded"]) + self.assertTrue(value["cleanup_reported"]) + self.assertEqual(value["cleanup"], ctx.summary["commands"][0]["cleanup"]) + calls = [row[0] for row in ctx.services[0].calls] + self.assertEqual(calls[-1] if calls else None, last_call) + if failed_stage not in ("shutdown", "service-cleanup"): + self.assertNotIn("shutdown", calls) + for private in (TOKEN, KEY, "/private/path", "nonce-session", root, "provider_owned"): + self.assertNotIn(private, line) + self.assertLessEqual(len(line.encode()), 2048) + self.assertEqual(line.count("\n"), 1) + self.assertEqual(ctx.summary["http_requests"], []) + self.assertFalse(any(name.endswith(".http.json") for name in ctx.captures)) + self.assertTrue(all(TOKEN.encode() not in body and KEY.encode() not in body + for body in ctx.captures.values())) + + def test_both_real_entrypoint_failure_branches_emit_only_finite_diagnostic_then_fail(self): + for module, argument, collector, reason in ( + (attempt.kimi, "--installed-request", "installed", "Kimi installed API observation failed; private capture quarantined"), + (attempt, "--attempt-request", "single-tool", "Kimi single-tool observation failed; private native streams quarantined")): + stdout, stderr = io.StringIO(), io.StringIO() + private = "/private/request-bearer-nonce-session" + with self.subTest(collector=collector), patch.dict(os.environ, {"GITHUB_ACTIONS": "false"}), \ + patch.object(sys, "argv", [module.__file__, argument, private]), \ + contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): + with self.assertRaises(SystemExit) as caught: + runpy.run_path(module.__file__, run_name="__main__") + self.assertEqual(caught.exception.code, reason) + self.assertEqual(stdout.getvalue(), "") + line = stderr.getvalue() + self.assertEqual(line.count("\n"), 1) + self.assertNotIn(private, line) + value = json.loads(line.removeprefix("Kimi collector failure: ")) + self.assertEqual((value["collector"], value["stage"]), (collector, "host-admission")) + self.assertFalse(value["observer_entered"]) + self.assertFalse(value["service_recorded"]) + self.assertTrue(all(item is None for item in value["cleanup"].values())) def test_finite_schema_refusal_stops_actions_then_retains_only_admitted_redacted_failure(self): for stage, refused in (("session", "create-session"), ("prompt", "prompt"), From 5a3ab8702aab04a889af9b17934b521e6447ee4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 08:10:31 +0200 Subject: [PATCH 10/42] docs: plan genuine Kimi observer host marker handoff --- ...PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md diff --git a/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md b/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md new file mode 100644 index 000000000..abd424eaf --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md @@ -0,0 +1,95 @@ +# Kimi single-tool observer: forward only genuine held host markers + +## Actual next failure and source diagnosis + +Current published PR524 head55f5d848fb61899f2c84bd7cccc7592549718b5f, +tree2f20a8e83fe52710eda7ead5494e7821e873ddce, is identical to local +c989277bca17d6205635187fae7a52b349ec3879. Runtime40456546065 call job +122031436268 fails after public installation/receipt proof. Closed native +12712668290 and frontend12712892996 have exact committed source/tree, +bounded canonical unique ordinary ZIP/CRC closure and all five qualification +flags false. Native outer SHA256 +38ceaded9a2b4b9bb28dcfbf6b77bb637012921eef8ea2b7761f6d9e572ec406; +frontend outer SHA256 +3fdc6fc1e63494f32ae666aa7fb933fd094c816f58facee84bbe0e33bba9bd67. + +The actual failure diagnostic (stderr SHA256 +c6a8c370bf0a80ae17a38bcb2e45774944bd4cf9547d8efabb843b688643fbcf) +reports single-tool/host-admission/enter/other, observer_entered=false, +service_recorded=false, cleanup_reported=false and all cleanup values null. +The Python command exits1, stdout0. No nested native API/service was reached. + +Python's unchanged require_hosted in scripts/run-native-client-matrix.py +requires exactly GITHUB_ACTIONS=true, RUNNER_ENVIRONMENT=github-hosted and +AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED=1, then independently checks actual +platform.system()/platform.machine() against linux-amd64. The Go fixture's +constructor independently checks those same observed markers, actual GOOS/ +GOARCH and ordinary UID/GID before construction. Its closed f.env() intentionally +inherits no ambient host/profile/credential environment, and the single-tool +Python invocation currently uses only f.env(), omitting all three required +host markers. This is a source-proven admission handoff mismatch, not evidence +that the hosted runtime was unavailable or that a native API grammar failed. + +The component observer already forwards the exact three real os.Getenv values +explicitly at its existing Python invocation. Preserve that implementation +unchanged as the established finite reference; its pending native installed +result remains a separate observed gate. Do not speculate about its API failure. + +## Finite handoff after two independent plan GOs + +Scope is repotests/agentplugins_kimi_project_native_attempt_test.go, +repotests/agentplugins_kimi_project_native_attempt_contract_test.go and this +plan. Preserve every previously published external Go/Python change. + +Add a small private single-tool observer environment helper which accepts the +already closed base environment plus exactly three held observed strings: +actions, runner-environment and explicit disposable opt-in. Require their exact +eligible values as validation only; output each key using the held argument, +never a synthesized eligibility literal. Reject the entire request on a missing, +changed, whitespace/control-bearing or otherwise invalid marker. Reject any +duplicate occurrence of these three keys already present in the closed base. +Return a copied environment without mutating the original slice. + +At only the existing private Python single-tool invocation, read exactly the +three named os.Getenv values into this helper, validate those held values and +pass its returned environment to the unchanged command. This revalidates the +same values used in the handoff, so an environment change since construction +cannot silently manufacture eligibility. Keep original HOME/XDG/PATH, profile, +all other closed environment entries and original native working directory. +Do not use os.Environ, bulk maps, credentials, tokens, proxy/provider/loader +settings, guessed runner labels or any broader inherited variables. + +Do not forward a platform override: require_hosted determines the real platform +itself, and the existing outer actual GOOS/GOARCH/UID/GID guards remain unchanged. +Do not alter or bypass either independent hosted guard, set host markers for +local native execution, weaken require_hosted or add fallback credentials. +The Python Context's existing closed vendor-child environment remains unchanged; +this finite metadata handoff applies only to the real Python observer process. + +## Meaningful inert controls and remaining hosted proof + +Add a positive pure helper contract using inert argument values, without setting +the local process's hosted markers or invoking either native guard/client. Assert +the exact closed output contains the original base entries and exactly three +held values, with source slice/capacity unchanged and no aliasing. Negative +controls cover missing/wrong/self-hosted/whitespace/control values for each +marker and duplicate/conflicting base keys; every refusal returns no partial +environment. Plant unrelated ambient credential/proxy/private canaries and +verify none enter the output. Other version/public/native commands must retain +their existing base environment and command tables. + +Run the affected existing Kimi single-tool and new environment contracts, Go1.22 +compatible formatting/whitespace checks and both actual affected all-enabled +and whole-file size/architecture lint only after the shared exclusive Go/lint +slot is granted. No vendor, model, installer, credential session or native guard +is executed/spoofed locally. The existing host-guard negative tests remain. + +Independent code review precedes root-owned publication to existing PR524. +No source pin, runtime request, trust/manual approval, permission, native API/ +provider grammar, identity/receipt proof, budget, supervision/cleanup, generic +failure diagnostic or qualification flag changes. VS Code remains excluded; +no new PR or release. Fresh genuine source-bound hosted output must show the +observer passes its unchanged host admission and reveal the next actual native +boundary. The old failed capture is not relabelled and missing cleanup is not +turned into success. + From 705fa429eaad9403113f4fcddb57d3a4f99c96ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 08:10:33 +0200 Subject: [PATCH 11/42] test: forward genuine hosted markers to Kimi observer --- ...PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md | 13 ++++++ ...mi_project_native_attempt_contract_test.go | 42 +++++++++++++++++++ ...lugins_kimi_project_native_attempt_test.go | 23 +++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md b/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md index abd424eaf..2d4bc3588 100644 --- a/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md +++ b/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md @@ -93,3 +93,16 @@ observer passes its unchanged host admission and reveal the next actual native boundary. The old failed capture is not relabelled and missing cleanup is not turned into success. +## Completed local validation + +Both independent plan and code reviews approved the exact two-file handoff. +On the reviewed bytes, the focused existing single-tool contracts, new pure +environment controls and original closed-environment contract passed (0.012s). +Actual affected all-enabled lint and unfiltered whole-file size/complexity/ +architecture lint each reported zero issues. Formatting and diff whitespace +checks passed. Checks used the granted exclusive slot, pinned Go1.26.10 with +Go1.22 source compatibility, private shared-memory temporary/lint directories, +and the existing bounded module/build caches. No native client, local hosted +guard, installer or model was executed. The genuine hosted follow-up remains +required; the distinct installed component context-stage failure is unchanged. + diff --git a/repotests/agentplugins_kimi_project_native_attempt_contract_test.go b/repotests/agentplugins_kimi_project_native_attempt_contract_test.go index 7ba1ab0ad..1ffab6513 100644 --- a/repotests/agentplugins_kimi_project_native_attempt_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_attempt_contract_test.go @@ -9,9 +9,51 @@ import ( "fmt" "net/http" "net/http/httptest" + "reflect" "testing" ) +func TestKimiSingleToolObserverEnvironmentForwardsOnlyHeldMarkers(t *testing.T) { + for _, key := range []string{"AWS_SECRET_ACCESS_KEY", "HTTPS_PROXY", "KIMI_API_KEY", "LD_PRELOAD"} { + t.Setenv(key, "inert-private-canary") + } + base := make([]string, 3, 8) + copy(base, []string{"HOME=/inert/home", "PATH=/inert/bin", "NO_COLOR=1"}) + backing := base[:cap(base)] + backing[3] = "inert-capacity-canary" + before := append([]string(nil), backing...) + env, err := kimiAttemptObserverEnvironment(base, "true", "github-hosted", "1") + want := []string{"HOME=/inert/home", "PATH=/inert/bin", "NO_COLOR=1", "GITHUB_ACTIONS=true", "RUNNER_ENVIRONMENT=github-hosted", "AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED=1"} + if err != nil || !reflect.DeepEqual(env, want) || !reflect.DeepEqual(backing, before) { + t.Fatal("observer handoff did not preserve exact closed base and held markers") + } + env[0] = "changed-result" + if !reflect.DeepEqual(backing, before) || len(base) != 3 || cap(base) != 8 { + t.Fatal("observer environment aliases or mutates original base") + } +} + +func TestKimiSingleToolObserverEnvironmentRefusesUnheldAuthority(t *testing.T) { + for index := range 3 { + for _, wrong := range []string{"", "false", "self-hosted", " true", "true\n", "1\x00", "github-hosted "} { + values := [3]string{"true", "github-hosted", "1"} + values[index] = wrong + if env, err := kimiAttemptObserverEnvironment([]string{"HOME=/inert/home"}, values[0], values[1], values[2]); err == nil || env != nil { + t.Fatalf("invalid held marker %d admitted or partially forwarded", index) + } + } + } + for _, key := range []string{"GITHUB_ACTIONS", "RUNNER_ENVIRONMENT", "AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED"} { + for _, existing := range []string{key, key + "=foreign", key + "=true"} { + base := []string{"HOME=/inert/home", existing} + before := append([]string(nil), base...) + if env, err := kimiAttemptObserverEnvironment(base, "true", "github-hosted", "1"); err == nil || env != nil || !reflect.DeepEqual(base, before) { + t.Fatal("preexisting base host marker admitted or base mutated") + } + } + } +} + func kimiAttemptContractBody() map[string]any { return map[string]any{"model": kimiAttemptModel, "stream": true, "stream_options": map[string]any{"include_usage": true}, "messages": []any{map[string]any{"role": "user", "content": "inert owned single tool request"}}, diff --git a/repotests/agentplugins_kimi_project_native_attempt_test.go b/repotests/agentplugins_kimi_project_native_attempt_test.go index b27bb71f6..b9bac166a 100644 --- a/repotests/agentplugins_kimi_project_native_attempt_test.go +++ b/repotests/agentplugins_kimi_project_native_attempt_test.go @@ -294,7 +294,11 @@ func (a *kimiSingleToolAttempt) run(t *testing.T) { projectNativeRuntimeWrite(t, path, encoded, 0600) f.protected[path] = nativeSHA(t, path) dataBefore := kimiProjectTree(t, p.Data) - r := f.command(t, "native-attempt", f.projects["user"], f.env(), []string{f.python, filepath.Join(RepoRoot(t), "scripts", "project_native_collectors", "kimi_attempt.py"), "--attempt-request", path}, 70*time.Second, false) + observerEnv, err := kimiAttemptObserverEnvironment(f.env(), os.Getenv("GITHUB_ACTIONS"), os.Getenv("RUNNER_ENVIRONMENT"), os.Getenv("AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED")) + if err != nil { + t.Fatal(err) + } + r := f.command(t, "native-attempt", f.projects["user"], observerEnv, []string{f.python, filepath.Join(RepoRoot(t), "scripts", "project_native_collectors", "kimi_attempt.py"), "--attempt-request", path}, 70*time.Second, false) if len(r.Stderr) != 0 { t.Fatal("Kimi private observer emitted diagnostics") } @@ -342,6 +346,23 @@ func (a *kimiSingleToolAttempt) run(t *testing.T) { t.Error("Kimi initial native history capture requires independent finite-schema review; no qualification") } +// Only the private observer receives these genuinely observed host markers. +// The independent Python platform/host guard and closed vendor env remain intact. +func kimiAttemptObserverEnvironment(base []string, actions, runner, optin string) ([]string, error) { + if actions != "true" || runner != "github-hosted" || optin != "1" { + return nil, fmt.Errorf("Kimi observer genuine hosted markers required") + } + for _, entry := range base { + key, _, _ := strings.Cut(entry, "=") + switch key { + case "GITHUB_ACTIONS", "RUNNER_ENVIRONMENT", "AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED": + return nil, fmt.Errorf("Kimi observer base contains host marker") + } + } + env := append([]string(nil), base...) + return append(env, "GITHUB_ACTIONS="+actions, "RUNNER_ENVIRONMENT="+runner, "AGENTPLUGINS_NATIVE_DISPOSABLE_HOSTED="+optin), nil +} + func (a *kimiSingleToolAttempt) finish(t *testing.T) { t.Helper() f := a.f From cef30c1c4f58535d4e642cba85b361863a4ff947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 08:27:06 +0200 Subject: [PATCH 12/42] docs: plan nonmutating Kimi installed profile context --- ...CT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md diff --git a/docs/KIMI_PROJECT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md b/docs/KIMI_PROJECT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md new file mode 100644 index 000000000..8e8732360 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md @@ -0,0 +1,128 @@ +# Kimi installed observers: reuse the actual existing profile without provisioning + +## Current source and measured boundary + +Published PR524 head705fa429eaad9403113f4fcddb57d3a4f99c96ba has +treee8d9fa5d0a6adc90aee91f7032a1cddec1b3af3e, identical to reviewed local +62ea70f4b2e3c7589cab2fbae774345fcf2d6ca3. Preserve its genuine held-marker +handoff and every prior external schema/Windows/context and Python diagnostic +change. This follow-up addresses the distinct installed-component context +failure; it does not claim the single-tool run has passed its later gates. + +At previous source55f5d848fb61899f2c84bd7cccc7592549718b5f/tree2f20a8e8, +runtime40456546065/job122031436251 completes observe-empty and all four public +add operations, then observe-installed exits1/error=false/deadline=false with +stdout0. Its exact401-byte stderr SHA256 is +6ef2c6c28e6dce941d029cc2dd3d6a233b17594c75a78dd9b3e15d6174107741. +The finite diagnostic says installed/context/enter/os, observer_entered=false, +service_recorded=false and all cleanup values null. No later API/service ran. + +Closed native12713940918 outer SHA256 +5a59f0a45ca0139e9cd3ad26e8840adbc9aee4b2e160f3bf863e9c0510ffbef4 +and frontend12714100073 outer SHA256 +2d7907fd2d633c5fbf8207f90b378a1329da9ac548205e52c558f91ef64af859 +have31/25 bounded canonical unique ordinary members, verified CRCs and exact +committed source/tree; all qualification flags remain false. Separate read-only +research40456546030 succeeded with complete source-bound capture, not lifecycle +or MCP qualification. + +## Source-derived cause and original profile authority + +At exactly the context stage, both installed Python entrypoints call +shared.legacy.runtime_environment(home, root/tmp). That is a fresh-profile +factory: native.profile_environment unconditionally mkdir's home/config, +home/cache, home/state, home/data and home/npm-cache, then writes fresh empty +npm/git configuration files. Repeating it after observe-empty necessarily +encounters its existing first directory. A source-only inert temp-directory +reproduction of two calls produces FileExistsError before any service. The raw +capture establishes only context/OSError, not the private exception text or +filesystem body; the narrower FileExistsError diagnosis comes from this actual +source path and deterministic inert reproduction. + +The Go-owned original fixture already creates ordinary home, tmp and +root/xdg-config, root/xdg-cache, root/xdg-state and root/xdg-data directories. +Its closed environment supplies those exact original XDG paths, while the +fresh-profile factory chooses different home/config-style paths. Reusing that +factory with exist_ok, deleting directories or truncating existing profile +files would neither preserve the original environment nor prove ownership. +Installed observation must consume the actual already-created Go-owned profile +without creating a replacement or adopting a native rewrite. + +## Finite nonmutating environment handoff after two plan GOs + +Scope is scripts/project_native_collectors/kimi.py and kimi_attempt.py, their +two existing inert test modules and this plan. No shared research/profile +factory, supervisor, production adapter or Go fixture change. + +Add one Kimi-only private helper used by both installed entrypoints. It accepts +the already request-validated root/home and a closed mapping of exactly12 held +environment values. Each caller reads only these named os.environ entries; +never copy os.environ or accept arbitrary extra keys. Validate exact string +types, keys and equality against the existing Go fixture authority: + +| Held key | Required value from the existing fixture | +| --- | --- | +| HOME, USERPROFILE | exact request home, which is root/home | +| TMPDIR, TEMP, TMP | root/tmp | +| XDG_CONFIG_HOME | root/xdg-config | +| XDG_CACHE_HOME | root/xdg-cache | +| XDG_STATE_HOME | root/xdg-state | +| XDG_DATA_HOME | root/xdg-data | +| GIT_CONFIG_NOSYSTEM | exact held value1 | +| GIT_CONFIG_GLOBAL | exact original os.devnull path | +| GIT_TERMINAL_PROMPT | exact held value0 | + +Return those validated held strings, rather than synthesizing a different +profile. Retain the existing observer's fixed PATH=/usr/bin:/bin, LANG=C.UTF-8, +NO_COLOR=1, TERM=dumb and KIMI_CODE_NO_AUTO_UPDATE=1 controls. No ambient PATH, +credentials/tokens/provider/proxy/loader settings, host eligibility markers, +unrelated application state or npm configuration enters the vendor child. +The original Go command environment remains unchanged; the private provider's +already-authored HOME/.kimi-code config and recorded original HOME stay exact. + +Before returning an environment, check root/home/tmp and each four existing XDG +directories through held descriptor-relative O_DIRECTORY/O_NOFOLLOW traversal. +Require canonical exact paths, current original UID/GID, ordinary private +directories and stable held/linked identities with checked closes. Refuse a +missing directory, alias, wrong owner/type/mode, replaced ancestor or any +inconsistent held value. No mkdir, writes, unlink, chmod, repair, config-body +read, alias resolution or fallback is allowed. Validate only the finite existing +directory set; do not traverse arbitrary profile contents or user credentials. + +Replace only the two calls to the fresh-profile factory at the existing +context stage with this helper and the explicit held mapping. The general +read-only research lane still uses its unchanged fresh-profile preparation. +Preserve both independent real hosted/platform guards, every existing API/ +provider/probe/receipt check, original budgets, diagnostic/quarantine behavior, +permission/trust policy and actual cleanup requirements. No later failure is +accepted or relabelled by this correction. + +## Meaningful inert contracts and genuine follow-up + +Use real owned temporary Go-layout directories and the actual new helper, +without setting any hosted marker or invoking a native guard/client. Compare +the complete finite output to independently expected held values; inject +unrelated ambient private/proxy/loader canaries and require their absence. +Call the helper repeatedly with existing original profile directories and +private sentinel/config files; verify exact full held metadata and bytes stay +unchanged and no home/config-style replacement directories or npm/git files +are created. Preserve an existing nonempty original git/npm/config sentinel; +the helper must not open, truncate, repair or adopt it. + +Negatives cover each wrong/missing/nonstring/extra held key, foreign home/tmp/ +XDG paths, absent/regular-file/symlink directory, aliased ancestor, changed mode, +and mocked finite wrong UID/GID plus deterministic held/path replacement at a +validation boundary. Every failure returns no partial environment, performs no +write. Invoke the actual helper with both installed entrypoints' finite input +shapes and review both sole context-stage call sites to ensure neither calls +the old factory or launches a service before validation. Do not bypass/mock a +positive native hosted guard in order to exercise a local entrypoint. The +actual negative hosted-guard tests remain unmodified and active. + +Run the existing complete Kimi collector/attempt/shared-service Python suite, +new ownership/nonmutation controls, AST and diff checks. No Go or vendor/model +execution is needed. Independent code reviews precede root-only publication to +existing PR524. No pin, public mutation, auth workaround, release/new PR or VS +Code work. Require fresh genuine source-bound runs to pass unchanged context +admission and expose the next actual boundary; full lifecycle, restart/removal +and MCP qualifications still require their complete native evidence. From b7235fb3a7084a6f1e293676c201dc4fd86f2515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 08:27:09 +0200 Subject: [PATCH 13/42] test: reuse held existing profile for Kimi observers --- ...CT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md | 19 +++ scripts/project_native_collectors/kimi.py | 80 +++++++++- .../project_native_collectors/kimi_attempt.py | 4 +- scripts/test_project_native_kimi.py | 138 ++++++++++++++++++ scripts/test_project_native_kimi_attempt.py | 23 +++ 5 files changed, 260 insertions(+), 4 deletions(-) diff --git a/docs/KIMI_PROJECT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md b/docs/KIMI_PROJECT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md index 8e8732360..e0621a844 100644 --- a/docs/KIMI_PROJECT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md +++ b/docs/KIMI_PROJECT_NATIVE_EXISTING_PROFILE_CONTEXT_PLAN.md @@ -126,3 +126,22 @@ existing PR524. No pin, public mutation, auth workaround, release/new PR or VS Code work. Require fresh genuine source-bound runs to pass unchanged context admission and expose the next actual boundary; full lifecycle, restart/removal and MCP qualifications still require their complete native evidence. + +## Completed local validation + +Both independent plan reviews approved this finite existing-profile handoff. +Code review required removal of an added Path.resolve call from the new helper; +the final helper uses lexical canonical checks and held no-follow traversal. +The original earlier request guard remains unchanged. Tests explicitly forbid +Path.resolve while exercising both an unchanged original profile and a matching +alias-shaped environment, so an alias cannot be silently resolved into authority. + +All 54 collector, single-tool and shared-supervisor tests passed on the final +bytes (16.459s), including real owned temporary directory nonmutation, exact +held environment controls, missing/wrong keys, type/mode/owner/group refusal, +symlink/ancestor rejection, deterministic held-directory replacement, complete +descriptor closure and failed-close refusal. All four Python ASTs and diff +whitespace checks passed. These checks ran inert source contracts and the +existing private supervisor child; they did not set eligible hosted markers or +execute a native client, installer or model. No Go was needed. Genuine +source-bound hosted context/API/cleanup and full lifecycle proof remain required. diff --git a/scripts/project_native_collectors/kimi.py b/scripts/project_native_collectors/kimi.py index f902b9c00..db6678538 100644 --- a/scripts/project_native_collectors/kimi.py +++ b/scripts/project_native_collectors/kimi.py @@ -101,6 +101,83 @@ def _identity(info): info.st_size, info.st_mtime_ns, info.st_ctime_ns) +_INSTALLED_ENVIRONMENT_KEYS = ( + "HOME", "USERPROFILE", "TMPDIR", "TEMP", "TMP", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", + "XDG_STATE_HOME", "XDG_DATA_HOME", "GIT_CONFIG_NOSYSTEM", "GIT_CONFIG_GLOBAL", "GIT_TERMINAL_PROMPT", +) + + +def _installed_directory_identity(info, private): + identity = (info.st_dev, info.st_ino, info.st_mode, info.st_uid, info.st_gid) + return identity + (info.st_nlink, info.st_size, info.st_mtime_ns, info.st_ctime_ns) if private else identity + + +def _verify_installed_profile_edges(edges, held, flags): + for parent, name, descriptor, before, private in edges: + expected = _installed_directory_identity(before, private) + if _installed_directory_identity(os.fstat(descriptor), private) != expected: + raise ValueError("Kimi existing profile held directory changed") + linked = os.open(name, flags, dir_fd=parent) + held.append(linked) + if _installed_directory_identity(os.fstat(linked), private) != expected: + raise ValueError("Kimi existing profile linked directory changed") + + +def _installed_profile_directories(root): + flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC + held, edges = [], [] + + def open_directory(parent, name, private): + descriptor = os.open(name, flags, dir_fd=parent) + held.append(descriptor) + before = os.fstat(descriptor) + if (not stat.S_ISDIR(before.st_mode) or (private and + (before.st_uid != os.getuid() or before.st_gid != os.getgid() + or stat.S_IMODE(before.st_mode) != 0o700))): + raise ValueError("Kimi existing profile directory authority differs") + edges.append((parent, name, descriptor, before, private)) + return descriptor + + try: + parent = os.open("/", flags) + held.append(parent) + for index, part in enumerate(root.parts[1:], 1): + parent = open_directory(parent, part, index == len(root.parts) - 1) + for name in ("home", "tmp", "xdg-config", "xdg-cache", "xdg-state", "xdg-data"): + open_directory(parent, name, True) + _verify_installed_profile_edges(edges, held, flags) + finally: + failed = False + for descriptor in reversed(held): + try: + os.close(descriptor) + except OSError: + failed = True + if failed: + raise ValueError("Kimi existing profile descriptor close failed") from None + + +def _installed_environment(root, home, observed): + """Validate the original Go-owned profile; never provision or repair it.""" + if (not root.is_absolute() or len(root.parts) > 64 or len(str(root)) > 4096 + or root.anchor != "/" or root == Path("/") or os.path.normpath(str(root)) != str(root) + or home != root / "home"): + raise ValueError("Kimi existing profile root/home differs") + expected = {"HOME": str(home), "USERPROFILE": str(home), + "TMPDIR": str(root / "tmp"), "TEMP": str(root / "tmp"), "TMP": str(root / "tmp"), + "XDG_CONFIG_HOME": str(root / "xdg-config"), "XDG_CACHE_HOME": str(root / "xdg-cache"), + "XDG_STATE_HOME": str(root / "xdg-state"), "XDG_DATA_HOME": str(root / "xdg-data"), + "GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull, "GIT_TERMINAL_PROMPT": "0"} + if (type(observed) is not dict or set(observed) != set(_INSTALLED_ENVIRONMENT_KEYS) + or any(type(observed[key]) is not str or observed[key] != value for key, value in expected.items())): + raise ValueError("Kimi existing profile held environment differs") + _installed_profile_directories(root) + env = dict(observed) + env.update({"PATH": "/usr/bin:/bin", "LANG": "C.UTF-8", "NO_COLOR": "1", "TERM": "dumb", + "KIMI_CODE_NO_AUTO_UPDATE": "1"}) + return env + + def _parent(root, path, create=False): """Open owned ancestors with directory FDs; aliases cannot redirect I/O.""" root, path = Path(root), Path(path) @@ -951,8 +1028,7 @@ def _installed_main(request_path, diagnostic=None): ctx.summary = {"schema_version": 1, "client": "kimi", "phase": "installed-api", "commands": [], "qualification": {"mcp-tools-call": False}} diagnostic.ctx = ctx - ctx.env = shared.legacy.runtime_environment(home, root / "tmp") - ctx.env.update({"NO_COLOR": "1", "TERM": "dumb", "KIMI_CODE_NO_AUTO_UPDATE": "1"}) + ctx.env = _installed_environment(root, home, {key: os.environ.get(key) for key in _INSTALLED_ENVIRONMENT_KEYS}) with shared.Budget(65) as budget: ctx.budget = budget ctx.summary["observations"] = observe_installed(ctx, value["observation"], diagnostic) diff --git a/scripts/project_native_collectors/kimi_attempt.py b/scripts/project_native_collectors/kimi_attempt.py index 3eb5b9c33..8055face9 100644 --- a/scripts/project_native_collectors/kimi_attempt.py +++ b/scripts/project_native_collectors/kimi_attempt.py @@ -318,8 +318,8 @@ def main(request_path, diagnostic=None): ctx.summary = {"schema_version": 1, "client": "kimi", "phase": "installed-single-tool-attempt", "commands": [], "qualification": {name: False for name in shared.QUALIFICATION_CASES}} diagnostic.ctx = ctx - ctx.env = shared.legacy.runtime_environment(home, root / "tmp") - ctx.env.update({"NO_COLOR": "1", "TERM": "dumb", "KIMI_CODE_NO_AUTO_UPDATE": "1"}) + ctx.env = kimi._installed_environment(root, home, + {key: os.environ.get(key) for key in kimi._INSTALLED_ENVIRONMENT_KEYS}) with shared.Budget(65) as budget: ctx.budget = budget ctx.summary["observations"] = observe(ctx, value["observation"], diagnostic) diff --git a/scripts/test_project_native_kimi.py b/scripts/test_project_native_kimi.py index f1c442b99..9347d29b2 100644 --- a/scripts/test_project_native_kimi.py +++ b/scripts/test_project_native_kimi.py @@ -7,6 +7,7 @@ import json import os from pathlib import Path +import stat from types import SimpleNamespace import tempfile import time @@ -20,6 +21,143 @@ SPEC.loader.exec_module(kimi) +class KimiInstalledProfileEnvironmentTests(unittest.TestCase): + def fixture(self, root): + for name in ("home", "tmp", "xdg-config", "xdg-cache", "xdg-state", "xdg-data"): + (root / name).mkdir(mode=0o700) + return {"HOME": str(root / "home"), "USERPROFILE": str(root / "home"), + "TMPDIR": str(root / "tmp"), "TEMP": str(root / "tmp"), "TMP": str(root / "tmp"), + "XDG_CONFIG_HOME": str(root / "xdg-config"), "XDG_CACHE_HOME": str(root / "xdg-cache"), + "XDG_STATE_HOME": str(root / "xdg-state"), "XDG_DATA_HOME": str(root / "xdg-data"), + "GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull, "GIT_TERMINAL_PROMPT": "0"} + + def snapshot(self, root): + result = {} + for path in [root, *sorted(root.rglob("*"))]: + info = path.lstat() + body = path.read_bytes() if stat.S_ISREG(info.st_mode) else None + result[str(path.relative_to(root))] = (kimi._identity(info), info.st_gid, body) + return result + + def test_existing_profile_is_nonmutating_and_reusable_without_ambient_secrets(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + observed = self.fixture(root) + for relative in ("home/git-config", "home/npm-user-config", "xdg-config/foreign-config"): + (root / relative).write_bytes(b"private existing configuration canary\n") + before, held = self.snapshot(root), dict(observed) + expected = {**held, "PATH": "/usr/bin:/bin", "LANG": "C.UTF-8", "NO_COLOR": "1", + "TERM": "dumb", "KIMI_CODE_NO_AUTO_UPDATE": "1"} + with patch.dict(os.environ, {"KIMI_API_KEY": "private-canary", "HTTPS_PROXY": "private-proxy", + "LD_PRELOAD": "private-loader", "NPM_CONFIG_USERCONFIG": "foreign-profile"}), \ + patch.object(Path, "resolve", side_effect=AssertionError("profile helper must not resolve aliases")): + for _ in range(2): + env = kimi._installed_environment(root, root / "home", observed) + self.assertEqual(env, expected) + env["HOME"] = "changed-returned-mapping" + self.assertEqual(observed, held) + self.assertEqual(self.snapshot(root), before) + self.assertFalse((root / "home/config").exists()) + self.assertFalse((root / "home/npm-cache").exists()) + self.assertFalse((root / "home/npm-global-config").exists()) + + def test_each_missing_changed_nonstring_or_extra_held_value_refuses_without_writes(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + observed = self.fixture(root) + before = self.snapshot(root) + for key in observed: + for kind in ("missing", "foreign", "nonstring"): + value = dict(observed) + if kind == "missing": + del value[key] + else: + value[key] = "foreign-profile" if kind == "foreign" else None + with self.subTest(key=key, kind=kind), self.assertRaises(ValueError): + kimi._installed_environment(root, root / "home", value) + with self.assertRaises(ValueError): + kimi._installed_environment(root, root / "home", {**observed, "KIMI_API_KEY": "private"}) + self.assertEqual(self.snapshot(root), before) + + def test_each_selected_directory_must_be_existing_owned_private_and_not_an_alias(self): + for name in ("home", "tmp", "xdg-config", "xdg-cache", "xdg-state", "xdg-data"): + for kind in ("missing", "regular", "alias", "mode"): + with self.subTest(name=name, kind=kind), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + observed = self.fixture(root) + path = root / name + if kind == "mode": + path.chmod(0o755) + else: + path.rmdir() + if kind == "regular": + path.write_bytes(b"not a directory") + elif kind == "alias": + path.symlink_to(root / "tmp" if name != "tmp" else root / "home", target_is_directory=True) + before = self.snapshot(root) + with self.assertRaises((ValueError, OSError)): + kimi._installed_environment(root, root / "home", observed) + self.assertEqual(self.snapshot(root), before) + + def test_owner_group_and_ancestor_alias_refuse(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + observed = self.fixture(root) + before = self.snapshot(root) + for key, value in (("getuid", os.getuid()), ("getgid", os.getgid())): + with patch.object(kimi.os, key, return_value=value + 1), self.assertRaises(ValueError): + kimi._installed_environment(root, root / "home", observed) + alias = root / "alias" + alias.symlink_to(root, target_is_directory=True) + aliased = {key: value.replace(str(root), str(alias)) for key, value in observed.items()} + with patch.object(Path, "resolve", side_effect=AssertionError("profile helper must not resolve aliases")): + with self.assertRaises(OSError): + kimi._installed_environment(alias, alias / "home", aliased) + alias.unlink() + after = self.snapshot(root) + self.assertEqual({k: v for k, v in after.items() if k != "."}, + {k: v for k, v in before.items() if k != "."}) + + def test_held_directory_replacement_refuses_and_closes_every_descriptor(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + observed = self.fixture(root) + verify, close, open_file = kimi._verify_installed_profile_edges, os.close, os.open + opened = [] + def track_open(*args, **kwargs): + descriptor = open_file(*args, **kwargs) + opened.append(descriptor) + return descriptor + def replace(edges, held, flags): + (root / "xdg-config").rename(root / "held-original") + (root / "xdg-config").mkdir(mode=0o700) + verify(edges, held, flags) + with patch.object(kimi, "_verify_installed_profile_edges", side_effect=replace), \ + patch.object(kimi.os, "open", side_effect=track_open), \ + patch.object(kimi.os, "close", wraps=close) as closes: + with self.assertRaises(ValueError): + kimi._installed_environment(root, root / "home", observed) + self.assertCountEqual([call.args[0] for call in closes.call_args_list], opened) + self.assertGreater(len(opened), 6) + + def test_close_failure_refuses_after_closing_the_remaining_descriptors(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + observed = self.fixture(root) + before, close = self.snapshot(root), os.close + calls = [] + def fail_once(descriptor): + close(descriptor) + calls.append(descriptor) + if len(calls) == 1: + raise OSError("private close failure canary") + with patch.object(kimi.os, "close", side_effect=fail_once): + with self.assertRaisesRegex(ValueError, "descriptor close failed"): + kimi._installed_environment(root, root / "home", observed) + self.assertGreater(len(calls), 6) + self.assertEqual(self.snapshot(root), before) + + def help_body(command): name = "kimi" + (" " + command if command else "") return ("Usage: " + name + " [options]\nOptions:\n --help\n" diff --git a/scripts/test_project_native_kimi_attempt.py b/scripts/test_project_native_kimi_attempt.py index de12f5a53..9a7235742 100644 --- a/scripts/test_project_native_kimi_attempt.py +++ b/scripts/test_project_native_kimi_attempt.py @@ -145,6 +145,29 @@ def ready(self, service): class KimiAttemptContracts(unittest.TestCase): + def test_single_tool_context_uses_original_existing_profile_without_initialization(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + for name in ("home", "tmp", "xdg-config", "xdg-cache", "xdg-state", "xdg-data"): + (root / name).mkdir(mode=0o700) + config = root / "home" / "original-provider-config" + config.write_bytes(b"private existing provider canary\n") + before = config.lstat() + held = {"HOME": str(root / "home"), "USERPROFILE": str(root / "home"), + "TMPDIR": str(root / "tmp"), "TEMP": str(root / "tmp"), "TMP": str(root / "tmp"), + "XDG_CONFIG_HOME": str(root / "xdg-config"), "XDG_CACHE_HOME": str(root / "xdg-cache"), + "XDG_STATE_HOME": str(root / "xdg-state"), "XDG_DATA_HOME": str(root / "xdg-data"), + "GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull, "GIT_TERMINAL_PROMPT": "0"} + for _ in range(2): + env = attempt.kimi._installed_environment(root, root / "home", held) + self.assertEqual({key: env[key] for key in held}, held) + self.assertEqual(len(env), 17) + self.assertEqual(env["KIMI_CODE_NO_AUTO_UPDATE"], "1") + self.assertNotIn("GITHUB_ACTIONS", env) + self.assertEqual(config.read_bytes(), b"private existing provider canary\n") + self.assertEqual(attempt.kimi._identity(config.lstat()), attempt.kimi._identity(before)) + self.assertFalse((root / "home" / "config").exists()) + def observe_capture(self, ctx, closed=True, diagnostic=None): # Previously qualified workspace/catalog validators are separate inert # contracts. Exercise only the new response stages and real cleanup. From 6d419a2e6efdd0239d4091122a0bbe1606d407b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 08:56:31 +0200 Subject: [PATCH 14/42] docs: plan exact Kimi observer identity role parity --- ...CT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md diff --git a/docs/KIMI_PROJECT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md b/docs/KIMI_PROJECT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md new file mode 100644 index 000000000..e9e2b4405 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md @@ -0,0 +1,99 @@ +# Kimi single-tool observer: preserve supervisory and native identity authority + +## Current source and actual next captured failure + +Published PR524 head b7235fb3a7084a6f1e293676c201dc4fd86f2515 has tree +143c1e9689801aa40e1472fdd6cd1acc68717f9a, identical to local reviewed +b1e8a0a67061dd2390827e1289e5beff9c01c922. Preserve the published genuine +host-marker and nonmutating existing-profile context handoffs, external Go +schema/Windows/context fixes, all Python API/refusal/diagnostic controls and +their complete histories. Fresh b723 results remain a separate pending proof. + +The preceding source705fa429eaad9403113f4fcddb57d3a4f99c96ba, treee8d9fa5d, +genuinely passed the previously failed single-tool hosted admission. Runtime +40459545103/job122042431469 instead failed at service-entry/enter/validation, +observer_entered=false, service_recorded=false and all cleanup values null. +The command exits1/error=false/deadline=false, stdout0. Its exact422-byte +stderr SHA256 is +bec14455aab8b82d644734e45bb62b1b4fe3dd3d48ffd95d81a54d6bd2f1cd5c. +No native API callback or model/tool action was reached. + +Native12715040487 outer SHA256 +d4820f4b3dff5609f3eb8521aa7ef529558d19e3c2e2ca9c8e7d9a50a424a5c5 +and frontend12715030553 outer SHA256 +4b43309441ff4bd894691b2e5e9b5dc65ad299291a8a31fae85fd3bf3db39e24 +have21/25 bounded canonical unique ordinary members and verified CRCs. Exact +source/tree/empty patch list, clean source streams and all24 frontend command +stream byte/hash joins were independently verified; all five qualifications, +vendor-authentication and real-model flags remain false. + +## Source-defined identity roles; no claim about uncaptured owner metadata + +The Go fixture retains and checks all immutable inputs in f.immutable, including +the independently selected Python interpreter and Git executable. Go's held +identity reader admits original-user or root-owned immutable runtime tools. +The private Python service's unchanged shared verify_files instead requires +every supplied identity to be an ordinary file owned by its current runner UID. +Its verification precedes the service/callback, consistent with the observed +boundary. Actual Python/Git owner metadata was not serialized, so the capture +does not identify which exact file or condition caused that refusal. Do not +describe the likely root-managed-tool mismatch as directly captured UID proof. + +The existing component observer already defines the correct role separation: +it copies all immutable entries except the exact f.python and f.git paths into +observerIDs, then adds the selected receipt/native installed identities. Both +tools stay frozen in the original Go fixture before and after the Python +command. Python/Git are supervisory acquisition/invocation inputs; neither is +the native Kimi argv or an installed MCP executable authorized by this private +service. The single-tool observer currently sends its entire f.immutable map, +unlike that component handoff. Align only this role boundary, rather than +weakening Python's owner policy or dropping immutable source/native proof. + +## Finite parity correction after two independent plan GOs + +Scope is repotests/agentplugins_kimi_project_native_attempt_test.go, its existing +attempt_contract_test.go, and this plan. No Python/shared verifier/production/ +component fixture, native argv, environment, runtime request grammar or pin change. + +Add a private copied-map projection taking the held immutable map and the exact +held Python/Git paths. Require both distinct selected paths to exist with held +nonempty digests before returning a result; a missing/ambiguous supervisory +role returns no partial map. Exclude exactly those two complete paths from the +observer-only copy. Do not filter by basename, prefix, owner, mode, executable +kind or arbitrary caller input, and do not exclude any other root-owned file. +Retain each other original key/value unchanged. Keep the helper in the existing +contract/helper file if needed for the unchanged Go file-size gate. + +Use that projection only where the single-tool request currently copies all +f.immutable entries. Then retain the existing protected-file/tree digest merge, +receipt proof and request construction unchanged. Do not remove or modify the +two entries in f.immutable, immutable Go baselines, constructor validation or +f.check before/after the public/native command. The component observer remains +byte-identical as the established reference. Python's current-UID verifier and +all source/native/installed identity checks remain strict. No blanket root-owner +allowance, ownership adoption, extra read or acceptance of a failed service. + +## Meaningful inert controls and remaining genuine proof + +Actual helper contracts use a distinct held map with two selected supervisory +paths, the pinned-client role, source/helper/compiler/scanner roles, and another +ordinary entry sharing a supervisory basename. Require exact output: only the +two exact selected keys are absent and every other held digest is unchanged. +Verify the original map, both selected digests and any separately merged +protected identities remain exact; mutate the returned map to prove no aliasing. +Negatives cover either missing/empty selected role and the same path used for +both roles, with no partial output or input mutation. No local native guard, +vendor binary, ownership spoof or qualification flag is involved. + +Run focused existing single-tool and new identity-projection contracts plus +the existing closed-environment controls under the exclusive Go/lint slot, then +actual affected all-enabled and unfiltered whole-file size/architecture lint, +format/diff checks. Preserve the source-bound failed705 evidence unchanged. +Independent code reviews precede root-only publication to existing PR524 using +a fresh real ref/lease. No release, new PR or deferred VS Code work. + +Fresh genuine source-bound output must show whether the unchanged service +verification and callback now proceed; a later failure still requires its own +measured interpretation. Full lifecycle, restart/removal, MCP protocol/process +and actual cleanup evidence remain required. This exact role projection is +neither a native success claim nor permission to bypass unknown identity errors. From 499a694729e03d56cab843e7b94ea840a71295d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 08:57:09 +0200 Subject: [PATCH 15/42] test: preserve Kimi observer identity role parity --- ...CT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md | 15 +++++ ...mi_project_native_attempt_contract_test.go | 66 +++++++++++++++++++ ...lugins_kimi_project_native_attempt_test.go | 11 ++-- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/docs/KIMI_PROJECT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md b/docs/KIMI_PROJECT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md index e9e2b4405..4d4d407e7 100644 --- a/docs/KIMI_PROJECT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md +++ b/docs/KIMI_PROJECT_NATIVE_OBSERVER_IDENTITY_PARITY_PLAN.md @@ -97,3 +97,18 @@ verification and callback now proceed; a later failure still requires its own measured interpretation. Full lifecycle, restart/removal, MCP protocol/process and actual cleanup evidence remain required. This exact role projection is neither a native success claim nor permission to bypass unknown identity errors. + +## Completed local validation + +Both independent plan/code reviews approved the exact two-role copied-map +projection. The native705 inner-member count was independently corrected to21; +its actual failed capture and source/UID inference distinction remain intact. +Focused existing single-tool and closed-environment contracts plus the new +identity role/no-alias/refusal controls passed (0.013s). Actual affected +all-enabled lint and unfiltered whole-file size/complexity/architecture lint +each reported zero issues. Formatting and diff whitespace checks passed. +Validation used the granted exclusive slot, pinned Go1.26.10 with Go1.22 source +compatibility, private shared-memory temporary/lint directories and existing +bounded caches. No vendor, model or positive hosted guard was run locally. +All prior Python/component bytes and full Go immutable guards remain unchanged; +fresh genuine service/callback and full lifecycle evidence are still required. diff --git a/repotests/agentplugins_kimi_project_native_attempt_contract_test.go b/repotests/agentplugins_kimi_project_native_attempt_contract_test.go index 1ffab6513..1370b68ca 100644 --- a/repotests/agentplugins_kimi_project_native_attempt_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_attempt_contract_test.go @@ -13,6 +13,72 @@ import ( "testing" ) +// Supervisory Python/Git stay frozen in the full Go immutable closure. +// The native observer receives the same role projection as the component lane. +func kimiAttemptObserverIdentities(immutable map[string]string, python, git string) (map[string]string, error) { + if python == "" || git == "" || python == git || immutable[python] == "" || immutable[git] == "" { + return nil, fmt.Errorf("Kimi observer supervisory identity roles differ") + } + result := make(map[string]string, len(immutable)-2) + for path, digest := range immutable { + if path != python && path != git { + result[path] = digest + } + } + return result, nil +} + +func TestKimiSingleToolObserverIdentitiesPreserveEveryNativeAndSourceRole(t *testing.T) { + immutable := map[string]string{"/inert/supervisor/python": "held-python", "/inert/supervisor/git": "held-git", + "/inert/native/kimi": "held-client", "/inert/source/kimi.py": "held-source", "/inert/tools/compiler": "held-compiler", + "/inert/tools/scanner": "held-scanner", "/inert/native/python": "held-same-basename", "/inert/native/probe": "held-probe"} + before := make(map[string]string, len(immutable)) + for path, digest := range immutable { + before[path] = digest + } + got, err := kimiAttemptObserverIdentities(immutable, "/inert/supervisor/python", "/inert/supervisor/git") + want := map[string]string{"/inert/native/kimi": "held-client", "/inert/source/kimi.py": "held-source", "/inert/tools/compiler": "held-compiler", + "/inert/tools/scanner": "held-scanner", "/inert/native/python": "held-same-basename", "/inert/native/probe": "held-probe"} + if err != nil || !reflect.DeepEqual(got, want) || !reflect.DeepEqual(immutable, before) { + t.Fatal("observer projection changed original pins or omitted a native/source role") + } + got["/inert/native/kimi"] = "changed-result" + got["/inert/supervisor/python"] = "changed-result" + if !reflect.DeepEqual(immutable, before) { + t.Fatal("observer projection aliases the complete immutable authority") + } +} + +func TestKimiSingleToolObserverIdentitiesRefuseMissingOrAmbiguousSupervisors(t *testing.T) { + for _, kind := range []string{"python-path", "git-path", "same-path", "missing-python", "missing-git", "empty-python", "empty-git"} { + immutable := map[string]string{"/inert/python": "held-python", "/inert/git": "held-git", "/inert/kimi": "held-client"} + python, git := "/inert/python", "/inert/git" + switch kind { + case "python-path": + python = "" + case "git-path": + git = "" + case "same-path": + git = python + case "missing-python": + delete(immutable, python) + case "missing-git": + delete(immutable, git) + case "empty-python": + immutable[python] = "" + case "empty-git": + immutable[git] = "" + } + before := make(map[string]string, len(immutable)) + for path, digest := range immutable { + before[path] = digest + } + if got, err := kimiAttemptObserverIdentities(immutable, python, git); err == nil || got != nil || !reflect.DeepEqual(immutable, before) { + t.Fatal("unheld supervisory role admitted or original authority mutated", kind) + } + } +} + func TestKimiSingleToolObserverEnvironmentForwardsOnlyHeldMarkers(t *testing.T) { for _, key := range []string{"AWS_SECRET_ACCESS_KEY", "HTTPS_PROXY", "KIMI_API_KEY", "LD_PRELOAD"} { t.Setenv(key, "inert-private-canary") diff --git a/repotests/agentplugins_kimi_project_native_attempt_test.go b/repotests/agentplugins_kimi_project_native_attempt_test.go index b9bac166a..8728abafc 100644 --- a/repotests/agentplugins_kimi_project_native_attempt_test.go +++ b/repotests/agentplugins_kimi_project_native_attempt_test.go @@ -272,11 +272,12 @@ func (a *kimiSingleToolAttempt) run(t *testing.T) { } object, _ := kimiProjectObject(body) server := object["mcpServers"].(map[string]any)[p.Name] - identities := map[string]string{} - for _, inventory := range []map[string]string{f.immutable, f.protected} { - for path, digest := range inventory { - identities[path] = digest - } + identities, err := kimiAttemptObserverIdentities(f.immutable, f.python, f.git) + if err != nil { + t.Fatal(err) + } + for path, digest := range f.protected { + identities[path] = digest } for root, inventory := range f.protectedTrees { for relative, value := range inventory { From 02e645b7982e4585eea5e878432f908f9d310048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 09:23:51 +0200 Subject: [PATCH 16/42] docs: plan actual Kimi snapshot seam and finite refusal --- ...IMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md diff --git a/docs/KIMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md b/docs/KIMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md new file mode 100644 index 000000000..ec54b4b6d --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md @@ -0,0 +1,108 @@ +# Kimi: exercise the actual profile snapshot adapter and classify its refusal + +## Measured current failures and source certainty + +Published identity-parity head499a694729e03d56cab843e7b94ea840a71295d8 +has tree41554ec446b33cb8997e26ace18b3902c828ba71, identical to localb6fe441b. +Preserve that separate role projection and all prior host/profile/schema/pin/ +supervision/ownership changes. This new slice does not revise or combine them. + +Genuine preceding b7235fb3a7084a6f1e293676c201dc4fd86f2515/tree143c1e96 +run40461197005 passed admission, then both native jobs failed before a service: + +| Job / artifact pair | Exact current captured boundary | +| --- | --- | +| components122047731757, native12715247415/frontend12715366882 | observe-empty exit1/error=false/deadline=false/stdout0; installed/context/enter/validation, observer_entered=false/service_recorded=false/cleanup all null | +| single-tool122047731730, native12714519282/frontend12714469374 | native-attempt exit1/error=false/deadline=false/stdout0; single-tool/context/enter/other, same unreached service/cleanup | + +Native/frontend outer SHA256 values are respectively +0f1dc9fe1cef13cf4cbb0935e94e3d108f11c5ffe0899e67797734a0fa149044 / +792d786b7dc260ff13365ef3129b3f5a17ea729d2055f74b9c6fec2b07b0a13b, +and56b68ce6733b06ea5ee7eeb53999102fcba67c15b206ad54e6faedacc6582c82 / +d5465d4daad3c781e93dc54939175225c6674b6cb33da4546a8824f410e08583. +Closure is16/25 and21/25 canonical unique ordinary members with CRCs; both +frontends have24 verified stream byte/hash joins and exact committed source/ +tree/clean streams. All qualification/auth/model flags remain false. Detailed +audit.json and raw files are under /tmp/uap-shell-fresh-oct27//. + +The source-proven single-tool defect is concrete: its new context-stage +comprehension refers to os.environ, but kimi_attempt.py never imports os. +Thus that adapter has an unresolved NameError before calling the helper. The +earlier inert positive directly called attempt.kimi._installed_environment, +so it did not exercise this actual process-environment snapshot expression. +Own and correct that coverage gap; AST parsing alone cannot detect this name. + +The component's captured validation refusal does not identify whether it came +from held-key comparison, private directory owner/group/mode or a stability +check. Do not infer an actual directory mode/owner or weaken any predicate. +This slice measures only that finite refusal before choosing a later fix. + +## Finite correction and diagnostics after two independent plan GOs + +Scope: the two existing Kimi collector Python modules, their existing two test +modules and this plan. No Go, production adapter, shared service/profile factory, +native API grammar, source pin, account/trust policy or qualification change. + +Centralize the existing explicit12-key os.environ snapshot in one tiny private +adapter in kimi.py, which already imports os. Both installed entrypoints call +that actual adapter, which delegates to the unchanged strict existing-profile +environment validator. Remove the undefined os reference from kimi_attempt.py; +do not introduce a bulk inherited environment or a second profile recipe. +The exact12 selected values, fixed controls and original no-write/nofollow/ +UID/GID/0700/stability/checked-close refusal remain identical. + +Make those existing private profile refusal sites raise a small internal typed +ValueError carrying only a closed role/reason and, when actually held, bounded +directory metadata. Roles are the12 static environment key names, root/home/ +tmp/four XDG roles, common-ancestor or descriptors. Reasons are finite key-set, +missing/type/value, canonical, open/type/uid/gid/mode, held/linked identity or +close categories. Unknown roles/reasons/metadata refuse diagnostic rendering. +Never capture a raw key/value, path/component/link target, credential, file body, +error string/repr or guessed errno/root cause. For an open failure, report only +the known role and fixed open category; do not read an unsafe node for metadata. +The typed wrapper preserves the safe original exception category: caught +OSError/open/close failures remain exception_kind=os; explicit existing +ValueError predicates remain validation. Validate this finite category in the +constructor/classifier; attaching metadata must not relabel an OS failure. + +Only when a held directory stat already exists may metadata include its +permission-mode integer (bounded0..07777) and exact booleans for ordinary +directory, current UID equality and current GID equality. Otherwise omit those +fields. No new filesystem reads, fallback, writes, mode repair or policy waiver. +The ordinary common ancestors retain type/no-follow/stable binding checks; +private0700/UID/GID predicates remain limited to the selected fixture dirs. + +The existing bounded private failure line may include one optional exact +profile_refusal object only for this typed internal exception. All strings are +finite literals and metadata has strict types/bounds; the existing2KiB limit, +generic quarantine failure, stdout0, first failed action/last action behavior, +truthful service/cleanup nulls and all other exception categories remain. +Other failures have no such object, and malformed diagnostic state must leave +the original failure/quarantine intact. No diagnostic can accept a failed run. + +## Meaningful source-only verification and remaining hosted proof + +Invoke the new actual snapshot adapter through both modules' production call +paths with real owned Go-layout directories and only the12 real test-profile +environment values. Do not set hosted eligibility or bypass a positive guard. +Assert exact output/no mutation/no unrelated ambient private/proxy/loader +inheritance; this exercises the missing-name seam that the prior helper test +missed. Both mains must call that exact extracted snapshot seam under test; +directly testing _installed_environment again is insufficient. Keep the real +entrypoint negative hosted guards unchanged. + +Use actual held wrong-mode/symlink/missing/replaced directory cases and finite +mocked UID/GID comparison controls to verify exact role/reason and only held +metadata. A private exception/value/path/target canary must never occur in the +serialized optional object. Malformed role/reason/type/bound controls suppress +diagnostics while keeping failure. Existing key-set, held identity, complete +descriptor closure/close-error and unchanged private configuration tests remain. + +Run the complete existing collector/single-tool/shared-supervisor Python suite, +new actual-adapter and finite refusal controls, AST and diff checks. No Go or +native client/model/positive hosted guard is needed. Obtain two independent +code reviews, then root alone publishes to existing PR524 on a fresh lease. +Require a genuine source-bound follow-up to identify the component's actual +context condition and advance the single-tool adapter. Later source/ownership +or API amendments still need their own measured plan; full lifecycle, MCP and +cleanup qualification remains mandatory. No release/new PR/VS work. From 1c0dcd1e9fbd097a97a63c0a8c2e88a17b6dd928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 09:23:54 +0200 Subject: [PATCH 17/42] test: exercise Kimi snapshot adapter and finite context refusal --- ...IMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md | 15 +++ scripts/project_native_collectors/kimi.py | 112 ++++++++++++++---- .../project_native_collectors/kimi_attempt.py | 3 +- scripts/test_project_native_kimi.py | 67 ++++++++++- scripts/test_project_native_kimi_attempt.py | 3 +- 5 files changed, 170 insertions(+), 30 deletions(-) diff --git a/docs/KIMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md b/docs/KIMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md index ec54b4b6d..0f2e56e81 100644 --- a/docs/KIMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md +++ b/docs/KIMI_PROJECT_NATIVE_CONTEXT_REFUSAL_PLAN.md @@ -106,3 +106,18 @@ Require a genuine source-bound follow-up to identify the component's actual context condition and advance the single-tool adapter. Later source/ownership or API amendments still need their own measured plan; full lifecycle, MCP and cleanup qualification remains mandatory. No release/new PR/VS work. + +## Completed local validation + +Both independent plan and complete code reviews approved the exact four-file +correction. The actual twelve-key process snapshot adapter is now exercised +from both installed modules, including repeated no-mutation/private-canary +controls. Nine focused real profile/adapter/refusal contracts passed (0.053s). +The complete collector, single-tool and shared-supervisor suite passed all56 +tests (16.650s); root independently reran all44 Kimi module tests (3.962s). +All four Python ASTs and diff whitespace checks passed. Finite role/reason and +held metadata controls verify mode/owner/group/open/close failures, original +os category, null unreached cleanup, bounded output and malformed-state refusal. +No Go, vendor/native client, model or positive hosted guard was executed. +The component's actual context predicate is still unclaimed until a new genuine +source-bound capture reports it; none of this promotes the old failed evidence. diff --git a/scripts/project_native_collectors/kimi.py b/scripts/project_native_collectors/kimi.py index db6678538..d7872136a 100644 --- a/scripts/project_native_collectors/kimi.py +++ b/scripts/project_native_collectors/kimi.py @@ -105,6 +105,47 @@ def _identity(info): "HOME", "USERPROFILE", "TMPDIR", "TEMP", "TMP", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_STATE_HOME", "XDG_DATA_HOME", "GIT_CONFIG_NOSYSTEM", "GIT_CONFIG_GLOBAL", "GIT_TERMINAL_PROMPT", ) +_PROFILE_ROLES = frozenset((*_INSTALLED_ENVIRONMENT_KEYS, "root", "ancestor", "descriptors")) +_PROFILE_REASONS = frozenset(("key-set", "missing", "type", "value", "canonical", "open", + "uid", "gid", "mode", "held-identity", "linked-identity", "close")) + + +class _InstalledProfileRefusal(ValueError): + def __init__(self, role, reason, category="validation", metadata=None): + super().__init__("Kimi existing profile refused") + if metadata is not None and type(metadata) is not dict: + raise ValueError("Kimi profile refusal metadata container differs") + self.role, self.reason, self.category = role, reason, category + self.metadata = dict(metadata) if metadata is not None else None + self.public() + + def public(self): + if (any(type(value) is not str for value in (self.role, self.reason, self.category)) + or self.role not in _PROFILE_ROLES or self.reason not in _PROFILE_REASONS + or self.category not in ("os", "validation") + or (self.reason in ("open", "close")) != (self.category == "os")): + raise ValueError("Kimi profile refusal classification differs") + value = {"role": self.role, "reason": self.reason} + if self.metadata is not None: + if (type(self.metadata) is not dict or set(self.metadata) != {"mode", "directory", "uid_matches", "gid_matches"} + or type(self.metadata["mode"]) is not int or not 0 <= self.metadata["mode"] <= 0o7777 + or any(type(self.metadata[key]) is not bool for key in ("directory", "uid_matches", "gid_matches"))): + raise ValueError("Kimi profile refusal held metadata differs") + value["metadata"] = dict(self.metadata) + return value + + +def _profile_refuse(role, reason, info): + metadata = {"mode": stat.S_IMODE(info.st_mode), "directory": stat.S_ISDIR(info.st_mode), + "uid_matches": info.st_uid == os.getuid(), "gid_matches": info.st_gid == os.getgid()} + raise _InstalledProfileRefusal(role, reason, metadata=metadata) + + +def _profile_open(name, flags, parent, role): + try: + return os.open(name, flags, dir_fd=parent) + except OSError: + raise _InstalledProfileRefusal(role, "open", "os") from None def _installed_directory_identity(info, private): @@ -113,38 +154,45 @@ def _installed_directory_identity(info, private): def _verify_installed_profile_edges(edges, held, flags): - for parent, name, descriptor, before, private in edges: + for parent, name, descriptor, before, private, role in edges: expected = _installed_directory_identity(before, private) - if _installed_directory_identity(os.fstat(descriptor), private) != expected: - raise ValueError("Kimi existing profile held directory changed") - linked = os.open(name, flags, dir_fd=parent) + current = os.fstat(descriptor) + if _installed_directory_identity(current, private) != expected: + _profile_refuse(role, "held-identity", current) + linked = _profile_open(name, flags, parent, role) held.append(linked) - if _installed_directory_identity(os.fstat(linked), private) != expected: - raise ValueError("Kimi existing profile linked directory changed") + current = os.fstat(linked) + if _installed_directory_identity(current, private) != expected: + _profile_refuse(role, "linked-identity", current) def _installed_profile_directories(root): flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC held, edges = [], [] - def open_directory(parent, name, private): - descriptor = os.open(name, flags, dir_fd=parent) + def open_directory(parent, name, private, role): + descriptor = _profile_open(name, flags, parent, role) held.append(descriptor) before = os.fstat(descriptor) - if (not stat.S_ISDIR(before.st_mode) or (private and - (before.st_uid != os.getuid() or before.st_gid != os.getgid() - or stat.S_IMODE(before.st_mode) != 0o700))): - raise ValueError("Kimi existing profile directory authority differs") - edges.append((parent, name, descriptor, before, private)) + if not stat.S_ISDIR(before.st_mode): + _profile_refuse(role, "type", before) + if private: + for valid, reason in ((before.st_uid == os.getuid(), "uid"), (before.st_gid == os.getgid(), "gid"), + (stat.S_IMODE(before.st_mode) == 0o700, "mode")): + if not valid: + _profile_refuse(role, reason, before) + edges.append((parent, name, descriptor, before, private, role)) return descriptor try: - parent = os.open("/", flags) + parent = _profile_open("/", flags, None, "ancestor") held.append(parent) for index, part in enumerate(root.parts[1:], 1): - parent = open_directory(parent, part, index == len(root.parts) - 1) - for name in ("home", "tmp", "xdg-config", "xdg-cache", "xdg-state", "xdg-data"): - open_directory(parent, name, True) + private = index == len(root.parts) - 1 + parent = open_directory(parent, part, private, "root" if private else "ancestor") + for name, role in (("home", "HOME"), ("tmp", "TMPDIR"), ("xdg-config", "XDG_CONFIG_HOME"), + ("xdg-cache", "XDG_CACHE_HOME"), ("xdg-state", "XDG_STATE_HOME"), ("xdg-data", "XDG_DATA_HOME")): + open_directory(parent, name, True, role) _verify_installed_profile_edges(edges, held, flags) finally: failed = False @@ -154,7 +202,12 @@ def open_directory(parent, name, private): except OSError: failed = True if failed: - raise ValueError("Kimi existing profile descriptor close failed") from None + raise _InstalledProfileRefusal("descriptors", "close", "os") from None + + +def _installed_environment_from_process(root, home): + """The actual entrypoint snapshot seam reads only the twelve held keys.""" + return _installed_environment(root, home, {key: os.environ.get(key) for key in _INSTALLED_ENVIRONMENT_KEYS}) def _installed_environment(root, home, observed): @@ -162,15 +215,23 @@ def _installed_environment(root, home, observed): if (not root.is_absolute() or len(root.parts) > 64 or len(str(root)) > 4096 or root.anchor != "/" or root == Path("/") or os.path.normpath(str(root)) != str(root) or home != root / "home"): - raise ValueError("Kimi existing profile root/home differs") + raise _InstalledProfileRefusal("root", "canonical") expected = {"HOME": str(home), "USERPROFILE": str(home), "TMPDIR": str(root / "tmp"), "TEMP": str(root / "tmp"), "TMP": str(root / "tmp"), "XDG_CONFIG_HOME": str(root / "xdg-config"), "XDG_CACHE_HOME": str(root / "xdg-cache"), "XDG_STATE_HOME": str(root / "xdg-state"), "XDG_DATA_HOME": str(root / "xdg-data"), "GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull, "GIT_TERMINAL_PROMPT": "0"} - if (type(observed) is not dict or set(observed) != set(_INSTALLED_ENVIRONMENT_KEYS) - or any(type(observed[key]) is not str or observed[key] != value for key, value in expected.items())): - raise ValueError("Kimi existing profile held environment differs") + if type(observed) is not dict: + raise _InstalledProfileRefusal("root", "key-set") + for key, value in expected.items(): + if key not in observed or observed[key] is None: + raise _InstalledProfileRefusal(key, "missing") + if type(observed[key]) is not str: + raise _InstalledProfileRefusal(key, "type") + if observed[key] != value: + raise _InstalledProfileRefusal(key, "value") + if set(observed) != set(_INSTALLED_ENVIRONMENT_KEYS): + raise _InstalledProfileRefusal("root", "key-set") _installed_profile_directories(root) env = dict(observed) env.update({"PATH": "/usr/bin:/bin", "LANG": "C.UTF-8", "NO_COLOR": "1", "TERM": "dumb", @@ -733,6 +794,9 @@ def api(self, label, phase): @staticmethod def exception_kind(error): + if isinstance(error, _InstalledProfileRefusal): + error.public() + return error.category for kind, classes in (("timeout", (TimeoutError,)), ("validation", (ValueError,)), ("type", (TypeError,)), ("key", (KeyError,)), ("os", (OSError,))): if isinstance(error, classes): @@ -768,6 +832,8 @@ def line(self, error): "stage": self.stage, "phase": self.phase, "exception_kind": kind, "observer_entered": self.observer_entered, "service_recorded": recorded, "cleanup_reported": all(type(item) is bool for item in cleanup.values()), "cleanup": cleanup} + if isinstance(error, _InstalledProfileRefusal): + value["profile_refusal"] = error.public() line = "Kimi collector failure: " + json.dumps(value, allow_nan=False, sort_keys=True, separators=(",", ":")) + "\n" if len(line.encode()) > 2048: raise ValueError("Kimi diagnostic bound exceeded") @@ -1028,7 +1094,7 @@ def _installed_main(request_path, diagnostic=None): ctx.summary = {"schema_version": 1, "client": "kimi", "phase": "installed-api", "commands": [], "qualification": {"mcp-tools-call": False}} diagnostic.ctx = ctx - ctx.env = _installed_environment(root, home, {key: os.environ.get(key) for key in _INSTALLED_ENVIRONMENT_KEYS}) + ctx.env = _installed_environment_from_process(root, home) with shared.Budget(65) as budget: ctx.budget = budget ctx.summary["observations"] = observe_installed(ctx, value["observation"], diagnostic) diff --git a/scripts/project_native_collectors/kimi_attempt.py b/scripts/project_native_collectors/kimi_attempt.py index 8055face9..dd5d767a6 100644 --- a/scripts/project_native_collectors/kimi_attempt.py +++ b/scripts/project_native_collectors/kimi_attempt.py @@ -318,8 +318,7 @@ def main(request_path, diagnostic=None): ctx.summary = {"schema_version": 1, "client": "kimi", "phase": "installed-single-tool-attempt", "commands": [], "qualification": {name: False for name in shared.QUALIFICATION_CASES}} diagnostic.ctx = ctx - ctx.env = kimi._installed_environment(root, home, - {key: os.environ.get(key) for key in kimi._INSTALLED_ENVIRONMENT_KEYS}) + ctx.env = kimi._installed_environment_from_process(root, home) with shared.Budget(65) as budget: ctx.budget = budget ctx.summary["observations"] = observe(ctx, value["observation"], diagnostic) diff --git a/scripts/test_project_native_kimi.py b/scripts/test_project_native_kimi.py index 9347d29b2..453b3267f 100644 --- a/scripts/test_project_native_kimi.py +++ b/scripts/test_project_native_kimi.py @@ -1,6 +1,7 @@ """Inert Kimi collector contracts; never acquires or executes vendor bytes.""" import importlib.util import copy +import contextlib import hashlib from datetime import datetime, timezone import io @@ -48,11 +49,11 @@ def test_existing_profile_is_nonmutating_and_reusable_without_ambient_secrets(se before, held = self.snapshot(root), dict(observed) expected = {**held, "PATH": "/usr/bin:/bin", "LANG": "C.UTF-8", "NO_COLOR": "1", "TERM": "dumb", "KIMI_CODE_NO_AUTO_UPDATE": "1"} - with patch.dict(os.environ, {"KIMI_API_KEY": "private-canary", "HTTPS_PROXY": "private-proxy", + with patch.dict(os.environ, {**observed, "KIMI_API_KEY": "private-canary", "HTTPS_PROXY": "private-proxy", "LD_PRELOAD": "private-loader", "NPM_CONFIG_USERCONFIG": "foreign-profile"}), \ patch.object(Path, "resolve", side_effect=AssertionError("profile helper must not resolve aliases")): for _ in range(2): - env = kimi._installed_environment(root, root / "home", observed) + env = kimi._installed_environment_from_process(root, root / "home") self.assertEqual(env, expected) env["HOME"] = "changed-returned-mapping" self.assertEqual(observed, held) @@ -111,8 +112,9 @@ def test_owner_group_and_ancestor_alias_refuse(self): alias.symlink_to(root, target_is_directory=True) aliased = {key: value.replace(str(root), str(alias)) for key, value in observed.items()} with patch.object(Path, "resolve", side_effect=AssertionError("profile helper must not resolve aliases")): - with self.assertRaises(OSError): + with self.assertRaises(kimi._InstalledProfileRefusal) as caught: kimi._installed_environment(alias, alias / "home", aliased) + self.assertEqual(caught.exception.category, "os") alias.unlink() after = self.snapshot(root) self.assertEqual({k: v for k, v in after.items() if k != "."}, @@ -152,11 +154,68 @@ def fail_once(descriptor): if len(calls) == 1: raise OSError("private close failure canary") with patch.object(kimi.os, "close", side_effect=fail_once): - with self.assertRaisesRegex(ValueError, "descriptor close failed"): + with self.assertRaises(kimi._InstalledProfileRefusal) as caught: kimi._installed_environment(root, root / "home", observed) + self.assertEqual(caught.exception.public(), {"role": "descriptors", "reason": "close"}) + self.assertEqual(caught.exception.category, "os") self.assertGreater(len(calls), 6) self.assertEqual(self.snapshot(root), before) + def test_context_refusal_exposes_only_finite_role_reason_and_actually_held_metadata(self): + for fault, role, reason, category in (("value", "HOME", "value", "validation"), + ("mode", "HOME", "mode", "validation"), ("missing", "XDG_CACHE_HOME", "open", "os"), + ("alias", "TMPDIR", "open", "os"), ("uid", "root", "uid", "validation"), + ("gid", "root", "gid", "validation")): + with self.subTest(fault=fault), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + observed = self.fixture(root) + if fault == "value": + observed["HOME"] = "/private-context-canary" + elif fault == "mode": + (root / "home").chmod(0o755) + elif fault == "missing": + (root / "xdg-cache").rmdir() + elif fault == "alias": + (root / "tmp").rmdir() + (root / "tmp").symlink_to(root / "home", target_is_directory=True) + with contextlib.ExitStack() as stack: + if fault in ("uid", "gid"): + name = "get" + fault + stack.enter_context(patch.object(kimi.os, name, return_value=getattr(os, name)() + 1)) + with self.assertRaises(kimi._InstalledProfileRefusal) as caught: + kimi._installed_environment(root, root / "home", observed) + diagnostic = kimi._KimiFailureDiagnostic("installed") + diagnostic.step("context") + line = diagnostic.line(caught.exception) + value = json.loads(line.split(": ", 1)[1]) + self.assertEqual(value["exception_kind"], category) + self.assertEqual(value["profile_refusal"]["role"], role) + self.assertEqual(value["profile_refusal"]["reason"], reason) + self.assertEqual("metadata" in value["profile_refusal"], fault in ("mode", "uid", "gid")) + if fault == "mode": + self.assertEqual(value["profile_refusal"]["metadata"], + {"mode": 0o755, "directory": True, "uid_matches": True, "gid_matches": True}) + self.assertFalse(value["observer_entered"] or value["service_recorded"] or value["cleanup_reported"]) + self.assertTrue(all(item is None for item in value["cleanup"].values())) + self.assertLess(len(line.encode()), 2048) + self.assertNotIn(str(root), line) + self.assertNotIn("private-context-canary", line) + + def test_context_refusal_rejects_unknown_or_malformed_diagnostic_state(self): + for role, reason, category, metadata in (("private-role", "mode", "validation", None), + ("HOME", "private-reason", "validation", None), ("HOME", "open", "validation", None), + ("HOME", "value", "os", None), ("HOME", "mode", "private-category", None), + ("HOME", "mode", "validation", {"mode": True, "directory": True, "uid_matches": True, "gid_matches": True}), + ("HOME", "mode", "validation", {"mode": 0o10000, "directory": True, "uid_matches": True, "gid_matches": True}), + ("HOME", "mode", "validation", {"mode": 0o700, "directory": "private", "uid_matches": True, "gid_matches": True})): + with self.subTest(role=role, reason=reason), self.assertRaises(ValueError): + kimi._InstalledProfileRefusal(role, reason, category, metadata) + error = kimi._InstalledProfileRefusal("HOME", "mode") + error.role = "private-error-canary" + stream = io.StringIO() + kimi._emit_failure_diagnostic(kimi._KimiFailureDiagnostic("installed"), error, stream) + self.assertEqual(stream.getvalue(), "") + def help_body(command): name = "kimi" + (" " + command if command else "") diff --git a/scripts/test_project_native_kimi_attempt.py b/scripts/test_project_native_kimi_attempt.py index 9a7235742..7c7c6f07b 100644 --- a/scripts/test_project_native_kimi_attempt.py +++ b/scripts/test_project_native_kimi_attempt.py @@ -159,7 +159,8 @@ def test_single_tool_context_uses_original_existing_profile_without_initializati "XDG_STATE_HOME": str(root / "xdg-state"), "XDG_DATA_HOME": str(root / "xdg-data"), "GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull, "GIT_TERMINAL_PROMPT": "0"} for _ in range(2): - env = attempt.kimi._installed_environment(root, root / "home", held) + with patch.dict(os.environ, held): + env = attempt.kimi._installed_environment_from_process(root, root / "home") self.assertEqual({key: env[key] for key in held}, held) self.assertEqual(len(env), 17) self.assertEqual(env["KIMI_CODE_NO_AUTO_UPDATE"], "1") From ccb529d5dc876796133151198e4000b5f22508d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 13:06:39 +0200 Subject: [PATCH 18/42] docs: restore reviewed private Kimi fixture root plan --- ...ROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md diff --git a/docs/KIMI_PROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md b/docs/KIMI_PROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md new file mode 100644 index 000000000..83c93d35a --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md @@ -0,0 +1,97 @@ +# Kimi: author the fresh fixture root with its required private mode + +## Fresh source-bound measured refusal + +PR524 head1c0dcd1e9fbd097a97a63c0a8c2e88a17b6dd928/tree +33332edea3af8dd14e67af80c489e22d5a812fe7 preserves the reviewed snapshot +adapter and finite held-directory diagnostic. Genuine runtime40466885261 +passes admission but components job122064401112 and single-tool +job122064401257 both fail at context/enter before Service or any native API +observer enters. Both independently report profile_refusal role=root, +reason=mode, directory=true, mode493 (0755), uid_matches=true and +gid_matches=true; exception_kind=validation. All cleanup fields are null +because no service record exists; all five qualification flags remain false. + +| Capture | Outer SHA256 | Inner SHA256 | Members | +| --- | --- | --- | --- | +| component native12717011752 | 4db6bd87d3988c67f600a2dad4051cc09f40022e10cfa4206016e71bb26181db | 7aa13b6c48f6dec509413db17191996f380092c8fae693eae63bcde3550659c1 | 16 | +| component frontend12717096561 | 72d5dd865572a0c17e3d0c0f3f885f23bee05026bb51924c2676fd45474a7d76 | a6cd5006a321669f5ca7dbdfd2500da1eed2f94e6b425e3f84d38a34508147b7 | 25 | +| single native12716769483 | ad0422ad04b135c08e4a56bfec479d49e1546aa2cac22a023786a9fac8cb0864 | 08143b5ac5862ae06c9c48e1aa63b7b5e13d92b67c7accb3c48f1db60b0ee0bf | 21 | +| single frontend12716908474 | 74cb6eab19bdb59837173fb0f1e072a58d1cd51c9bfff4ea21d9d80a8e5927bb | 35d940b2d441dbeb3dbf66bab51b98390f89d53d219b2d81d65aa12a25e65ec3 | 25 | + +All four archives are exact API-digest matched, bounded canonical unique +ordinary ZIP/CRC checked. Both frontends prove exact1c0/source tree, empty +clean-source streams and all24 command-stream byte/hash joins. Component +stderr538 bytes has SHA256 +ec1f46f3c5b6addb3c828278d711a7bed8abd99bd14af17a132ac42a7cbb81e6; +single stderr545 bytes has SHA256 +13b0a8fad069256f956cd918ab53f31204031fb6eda2d339a56c7fcb603cc379. +Closed raw artifacts and finite audit are retained under +/tmp/uap-shell-fresh-oct27, including kimi-1c-context-audit.json. + +## Existing authoring recipe and exact mismatch + +Both actual Kimi constructors call newNativeFixture(t,false), then configure +their client/project maps and scanner before executing any external Git, +version, public CLI or observer command. The shared constructor calls +t.TempDir(), canonicalizes that freshly owned path and creates HOME, tmp and +four XDG children with0700. It does not set the root leaf's mode. The checked +Go1.26.10 testing source makeTempDir creates its numbered leaf with0777, +subject to process umask, inside a private MkdirTemp parent. Thus the actual +0755 root is consistent with this authoring recipe; it is not evidence of +foreign ownership or a vendor mutation. The Python installed profile helper +requires this root and six named profile directories to be0700, current +UID/GID, ordinary and stable through held no-follow descriptors. Keep that +strict contract. No API grammar or authentication conclusion follows yet. + +## Finite correction after two independent plan reviews + +Scope only the two Kimi Go constructor files, the existing Kimi Go contract +file (private helper and actual filesystem controls), one root-mode refusal +case in the existing Kimi Python contract module, and this plan/validation +record. Do not modify the shared newNativeFixture, another +client, Python validation, source pins, public CLI or production engine. + +Immediately after each Kimi constructor receives its fresh newNativeFixture +result, before assigning client metadata or running any external baseline, +call one private Kimi helper on exactly f.base.Root. This root is newly +created by that constructor inside testing's private parent; no ambient or +user-supplied path is eligible. Existing HOME/tmp/XDG child creation inside +newNativeFixture precedes the call, but no vendor/public/observer action has +run and the private enclosing temporary parent already bounds access. + +The helper rejects a noncanonical/nonabsolute root, any existing ancestor +alias, a leaf alias or non-directory, wrong actual UID/GID, or an initial mode +other than0700/0755. Open the leaf with O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC, +compare its held identity with its initial ordinary Lstat and verify actual +owner/group before mutation. Change only that held fresh root descriptor to +0700. Verify the held descriptor and final ordinary path still name the same +inode and retain expected owner/group/type with exact0700, recheck parent +alias refusal, and check Close. Any error is fatal before external commands. +Never recursively chmod, follow an alias, change a parent/child/foreign path, +adopt an existing user profile or repair modes later during observation. + +All original Python0700/UID/GID/no-follow/stability predicates remain byte +identical, including refusal of a later root0755 mutation. Original genuine +host guards, original HOME/XDG authority, immutable identities, deadlines, +service supervision, private quarantine, source receipts and flags stay +unchanged. No host values are invented and no credentials are inherited. + +## Meaningful verification and publication + +Use actual temporary filesystem paths through this exact helper. An owned +0755 root becomes0700 with identical inode and exact child bytes/modes; a +second call on0700 is idempotent. An ordinary file, leaf alias and ancestor +alias must refuse without altering their modes/targets. Unknown writable +modes must refuse without a change. Existing Python actual-adapter positive and private-directory negatives stay +unchanged. Add an actual root0755 case to the existing Python contract module +that expects the finite root/mode refusal and no filesystem mutation; +no mock hosted guard or vendor executable runs locally. + +Run focused existing/new Kimi inert Go contracts, complete Kimi Python tests, +actual affected all-enabled and unfiltered six-linter gates in the exclusive +slot, gofmt/AST/diff checks, and two independent code reviews. Root alone +publishes reviewed incremental commits to existingPR524 on a fresh ref. +Fresh genuine source-bound evidence must pass the original context/privacy +gate and reveal or complete subsequent actual stages before any runtime or +full lifecycle qualification. No release/newPR or VS Code work. From 91a3c54ac74002c0bf67977d3107230f9bbbca30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Tue, 27 Oct 2026 13:06:41 +0200 Subject: [PATCH 19/42] test: author exact private Kimi fixture root before baselines --- ...ROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md | 25 ++++ ...lugins_kimi_project_native_attempt_test.go | 3 + ...ugins_kimi_project_native_contract_test.go | 129 ++++++++++++++++++ ...gins_kimi_project_native_lifecycle_test.go | 3 + scripts/test_project_native_kimi.py | 13 ++ 5 files changed, 173 insertions(+) diff --git a/docs/KIMI_PROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md b/docs/KIMI_PROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md index 83c93d35a..9d3ab073c 100644 --- a/docs/KIMI_PROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md +++ b/docs/KIMI_PROJECT_NATIVE_PRIVATE_FIXTURE_ROOT_PLAN.md @@ -95,3 +95,28 @@ publishes reviewed incremental commits to existingPR524 on a fresh ref. Fresh genuine source-bound evidence must pass the original context/privacy gate and reveal or complete subsequent actual stages before any runtime or full lifecycle qualification. No release/newPR or VS Code work. + +## Implemented and recovered validation + +The exact plan received independent root and desktop GOs before code. Complete +implementation review received desktop and CLI GOs on these exact SHA256s: + +- lifecycle: 0b758ace0cac43c9a8b7c3917cb3ddf1a5444f3c5e6dba651bb82985757065cc +- single-tool: 3e885dd5b22afab3f4a69ab305d067eec1bbc53b0fa35764cd72158fa0a8aa9f +- helper/contracts: dd8d61405e489e6dc3050e1172aac4445d5bfc68bbce4464d892cc9046b6e4cd +- Python contract: d14be5f4e940abdc11f6dee1c12d2209b4eb171c47921728415d314d4be62b69 + +After the execution workspace was replaced, all four source files and the +original plan were reconstructed byte-for-byte from the published1c0 source +and reviewed patches; every recorded SHA matched. The raw archives and exact +source/tree/clean command-stream joins were independently restored and checked. +No original unpublished local commit ID is claimed for the recovered history. + +Fresh focused ^TestKimiProject|^TestKimiSingleTool contracts passed in0.176s, +including actual filesystem root-authoring and strict proof controls. The full +57-test Kimi/attempt/service Python suite passed in16.484s. Actual all-enabled +affected lint against recovered plan96ebef5b and the unfiltered whole-file +revive/funlen/gocyclo/gocognit/dupl/depguard gate each reported zero issues. +Gofmt-list, Python AST and whitespace checks pass. No vendor/installer process +or positive hosted guard ran locally. The source-bound1c0 failure remains +failed/all-false; a fresh genuine run of this correction is still mandatory. diff --git a/repotests/agentplugins_kimi_project_native_attempt_test.go b/repotests/agentplugins_kimi_project_native_attempt_test.go index 8728abafc..1d54e0fb6 100644 --- a/repotests/agentplugins_kimi_project_native_attempt_test.go +++ b/repotests/agentplugins_kimi_project_native_attempt_test.go @@ -96,6 +96,9 @@ func newKimiSingleToolFixture(t *testing.T) *kimiProjectNativeFixture { f.evidence["source_identity"], f.evidence["acquisition"] = identitySource, identity f.check(t) f.base = newNativeFixture(t, false) + if err := kimiProjectPrivateFixtureRoot(f.base.Root); err != nil { + t.Fatal(err) + } f.base.ClientBinDir = filepath.Dir(f.client) f.projects = map[string]string{"a": f.base.Project, "b": filepath.Join(f.base.Root, "project-b"), "c": filepath.Join(f.base.Root, "project-c"), "user": filepath.Join(f.base.Root, "outside")} for key, path := range f.projects { diff --git a/repotests/agentplugins_kimi_project_native_contract_test.go b/repotests/agentplugins_kimi_project_native_contract_test.go index b530c7299..8e519c35e 100644 --- a/repotests/agentplugins_kimi_project_native_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_contract_test.go @@ -5,15 +5,144 @@ package pluginkitairepo_test import ( "bytes" "encoding/json" + "fmt" "os" "path/filepath" "reflect" "strings" + "syscall" "testing" "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" ) +// Only the two Kimi constructors call this on their newly created fixture root, +// before any external baseline. Python's later private-profile check stays strict. +func kimiProjectPrivateFixtureRoot(root string) (result error) { + if !filepath.IsAbs(root) || filepath.Clean(root) != root || root == "/" { + return fmt.Errorf("Kimi fresh fixture root is not canonical") + } + if err := projectNativeRuntimeParents(filepath.Join(root, "leaf")); err != nil { + return fmt.Errorf("Kimi fresh fixture root has an alias") + } + before, err := os.Lstat(root) + if err != nil || !kimiProjectFreshRootOwner(before) || (before.Mode() != os.ModeDir|0700 && before.Mode() != os.ModeDir|0755) { + return fmt.Errorf("Kimi fresh fixture root type, owner or mode differs") + } + file, err := os.OpenFile(root, os.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0) + if err != nil { + return fmt.Errorf("Kimi fresh fixture root could not be held") + } + defer func() { + if err := file.Close(); err != nil { + result = fmt.Errorf("Kimi fresh fixture root close failed") + } + }() + held, err := file.Stat() + if err != nil || !kimiProjectFreshRootMatches(before, held, before.Mode()) { + return fmt.Errorf("Kimi fresh fixture root held identity differs") + } + if held.Mode() != os.ModeDir|0700 { + if err := file.Chmod(0700); err != nil { + return fmt.Errorf("Kimi fresh fixture root private mode could not be authored") + } + } + after, heldErr := file.Stat() + current, pathErr := os.Lstat(root) + if heldErr != nil || pathErr != nil || !kimiProjectFreshRootMatches(before, after, os.ModeDir|0700) || !kimiProjectFreshRootMatches(after, current, os.ModeDir|0700) { + return fmt.Errorf("Kimi fresh fixture root final identity differs") + } + if err := projectNativeRuntimeParents(filepath.Join(root, "leaf")); err != nil { + return fmt.Errorf("Kimi fresh fixture root final ancestor differs") + } + return nil +} + +func kimiProjectFreshRootMatches(expected, current os.FileInfo, mode os.FileMode) bool { + return kimiProjectFreshRootOwner(current) && os.SameFile(expected, current) && current.Mode() == mode +} + +func kimiProjectFreshRootOwner(info os.FileInfo) bool { + if info == nil || !info.IsDir() { + return false + } + identity, ok := info.Sys().(*syscall.Stat_t) + return ok && identity.Uid == uint32(os.Getuid()) && identity.Gid == uint32(os.Getgid()) +} + +func TestKimiProjectNativePrivateRootAuthorsOnlyFreshOwnedDirectory(t *testing.T) { + root := filepath.Join(t.TempDir(), "fresh-root") + if err := os.Mkdir(root, 0755); err != nil { + t.Fatal(err) + } + if err := os.Chmod(root, 0755); err != nil { + t.Fatal(err) + } + child := filepath.Join(root, "home", "foreign-config") + projectNativeRuntimeWrite(t, child, []byte("held original child bytes\n"), 0600) + before, err := os.Lstat(root) + if err != nil { + t.Fatal(err) + } + wantTree := kimiProjectTree(t, filepath.Dir(child)) + var private os.FileInfo + for range 2 { + if err := kimiProjectPrivateFixtureRoot(root); err != nil { + t.Fatal(err) + } + after, err := os.Lstat(root) + if err != nil || !os.SameFile(before, after) || after.Mode() != os.ModeDir|0700 || !reflect.DeepEqual(wantTree, kimiProjectTree(t, filepath.Dir(child))) { + t.Fatal("fresh Kimi root authoring changed identity or child authority") + } + if private != nil && !reflect.DeepEqual(private.Sys(), after.Sys()) { + t.Fatal("private Kimi root was mutated on a repeated authoring check") + } + private = after + } +} + +func TestKimiProjectNativePrivateRootRefusesAliasesFilesAndUnknownModes(t *testing.T) { + parent := t.TempDir() + target := filepath.Join(parent, "target") + if err := os.Mkdir(target, 0755); err != nil { + t.Fatal(err) + } + leafAlias, ancestorAlias := filepath.Join(parent, "leaf-alias"), filepath.Join(parent, "ancestor-alias") + if err := os.Symlink(target, leafAlias); err != nil { + t.Fatal(err) + } + if err := os.Symlink(parent, ancestorAlias); err != nil { + t.Fatal(err) + } + ordinary := filepath.Join(parent, "ordinary") + projectNativeRuntimeWrite(t, ordinary, []byte("unchanged ordinary bytes\n"), 0600) + for _, root := range []string{leafAlias, filepath.Join(ancestorAlias, "target"), ordinary, "relative", "/"} { + if err := kimiProjectPrivateFixtureRoot(root); err == nil { + t.Fatalf("unsafe Kimi fixture root admitted: %s", root) + } + } + info, err := os.Lstat(target) + if err != nil || info.Mode() != os.ModeDir|0755 { + t.Fatal("Kimi alias refusal changed its target mode") + } + for _, mode := range []os.FileMode{0777, 0750, 0700 | os.ModeSticky, 0755 | os.ModeSetgid, 0700 | os.ModeSetuid} { + if err := os.Chmod(target, mode); err != nil { + t.Fatal(err) + } + if err := kimiProjectPrivateFixtureRoot(target); err == nil { + t.Fatalf("unknown Kimi root mode admitted: %v", mode) + } + info, err := os.Lstat(target) + if err != nil || info.Mode() != os.ModeDir|mode { + t.Fatal("Kimi refusal rewrote an unknown mode") + } + } + body, err := os.ReadFile(ordinary) + if err != nil || string(body) != "unchanged ordinary bytes\n" { + t.Fatal("Kimi ordinary-file refusal changed bytes") + } +} + func TestKimiProjectNativeWholeJSONRemainsBoundedAndExact(t *testing.T) { for _, body := range [][]byte{ []byte(`{"stage":"a","stage":"b"}`), []byte(`{"a":{"x":1,"x":2}}`), diff --git a/repotests/agentplugins_kimi_project_native_lifecycle_test.go b/repotests/agentplugins_kimi_project_native_lifecycle_test.go index 49b0e8d98..a75399d56 100644 --- a/repotests/agentplugins_kimi_project_native_lifecycle_test.go +++ b/repotests/agentplugins_kimi_project_native_lifecycle_test.go @@ -187,6 +187,9 @@ func newKimiProjectNativeFixture(t *testing.T) *kimiProjectNativeFixture { f.evidence["source_identity"], f.evidence["acquisition"] = identitySource, identity f.check(t) f.base = newNativeFixture(t, false) + if err := kimiProjectPrivateFixtureRoot(f.base.Root); err != nil { + t.Fatal(err) + } f.base.ClientBinDir = filepath.Dir(f.client) f.projects = map[string]string{"a": f.base.Project, "b": filepath.Join(f.base.Root, "project-b"), "c": filepath.Join(f.base.Root, "project-c"), "user": filepath.Join(f.base.Root, "outside")} for key, path := range f.projects { diff --git a/scripts/test_project_native_kimi.py b/scripts/test_project_native_kimi.py index 453b3267f..81f832902 100644 --- a/scripts/test_project_native_kimi.py +++ b/scripts/test_project_native_kimi.py @@ -80,6 +80,19 @@ def test_each_missing_changed_nonstring_or_extra_held_value_refuses_without_writ kimi._installed_environment(root, root / "home", {**observed, "KIMI_API_KEY": "private"}) self.assertEqual(self.snapshot(root), before) + def test_later_root_mode_drift_still_refuses_without_repair(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + observed = self.fixture(root) + root.chmod(0o755) + before = self.snapshot(root) + with self.assertRaises(kimi._InstalledProfileRefusal) as caught: + kimi._installed_environment(root, root / "home", observed) + self.assertEqual(caught.exception.public(), + {"role": "root", "reason": "mode", "metadata": + {"mode": 0o755, "directory": True, "uid_matches": True, "gid_matches": True}}) + self.assertEqual(self.snapshot(root), before) + def test_each_selected_directory_must_be_existing_owned_private_and_not_an_alias(self): for name in ("home", "tmp", "xdg-config", "xdg-cache", "xdg-state", "xdg-data"): for kind in ("missing", "regular", "alias", "mode"): From cb0fd256e231c662a596e555e9fea703c1a2b98f Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 13:16:32 +0100 Subject: [PATCH 20/42] docs: plan exact Kimi native skill and turn expectations --- ...ATIVE_SOURCE_IDENTITY_EXPECTATIONS_PLAN.md | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_PLAN.md diff --git a/docs/KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_PLAN.md b/docs/KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_PLAN.md new file mode 100644 index 000000000..337fcc3de --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_PLAN.md @@ -0,0 +1,124 @@ +# Kimi: two exact source-bound observer expectation corrections + +## Baseline and actual observation limits + +Use published PR524 source91a3c54ac74002c0bf67977d3107230f9bbbca30, +tree17957bb4494ad7139a1e5e6fe8a26bbd27f418ce. Preserve its exact private-root +fix and all prior reviewed/external source. New run40493669986 progressed +through context: component job122158328387 completed empty observation and +four public installs, then refused skills-a/validate; single-tool +job122158328503 reached its first provider request and initialize/list events, +then refused approvals/validate before any approved tool call. Both retain +quarantined response bodies, failed/incomplete/all-five-false qualification. + +The four closed archives, API-correlated hashes, exact source/tree/clean +records and48 frontend stream joins are recorded in +`/tmp/uap-shell-fresh-oct27/kimi91a-source-stream-audit.json`, SHA256 +1b68024b3b0a872c9e396bd5c9d1ad1cae34127a39b702215649294a5cfeb970. +Component native12730973797 has31 members, frontend12731208487 has25; +single-tool native12732047373 has23, frontend12732022524 has25. +Do not describe the two source contradictions below as the captured response +values or the conclusively measured causes. They are independently provable +observer defects at the two actual failure boundaries; fresh native evidence +must decide whether another mismatch remains afterward. + +The separate finite-refusal diagnostic plan a8604e9c is available but is not +part of this correction. Do not combine its instrumentation into this slice +or publish duplicate patches while an earlier exact-source run is active. + +## Exact source proof + +All sources below belong to pinned MoonshotAI/kimi-code +b8dd3a134060c6e80ba4a1659335cd63b68483f2. Full files are independently +retrieved and Git-blob/SHA256-verified under +`/tmp/uap-shell-kimi91a-pinned-source`. + +| Source | Git blob | SHA256 | +| --- | --- | --- | +| features/skill/catalog/registry.ts |9d07a1b16b125c4e4b4f07358d34d42437434fc2|265fa39bde3a7a0a58d7772b2769122b58da00316bb4e87bbd39bd91c74762d2| +| app/plugin/manager.ts |f917017e1524964566dc0404f4aafa26fa6b137d|214440d3e0bc69b83f37110878923ff427cff3f898ad5c14ca6999af5e266316| +| agent/loop/turnOps.ts |ec3921a37b4333e8034ccfa0d69380026b819e98|eaa5dcfa8b21784a0c77da2d4e818d34a051a9cfc769e5aa02e4111522847746| +| agent/toolApproval/toolApprovalService.ts |96f36f15ec9fcaf00f4ec373e935004ed14857e2|f1f79066f2184a112774fb1f025cb25580f991f4a9c14aa73104a0cfc439036d| + +Paths in this table are relative to packages/agent-core-v2/src. +`PluginManager.pluginSkillRoots()` uses source extra and a held plugin.id. +`InMemorySkillCatalog.registerPluginSkill()` stores the bare plugin skill +separately and stores the public listSkills row with exact name +`${plugin.id}:${bareName}`. The workspace route builds that catalog and calls +listSkills/toProtocolSkill; its blobde5525e6/SHA +9f820a2cb91eef9a1a24a0fb439fece7da9da9c6bdd956045b16b57c32ba31ec +was also verified. Current UAP user activation registers the installed plugin +at original HOME/.kimi-code/plugins/installed.json; it does not copy that +plugin skill into project/.kimi-code/skills. The independently authored user +plugin ID and bare skill name are both uap-kimi-native-user. Therefore the +expected native user row name is exactly +uap-kimi-native-user:uap-kimi-native-user, source extra and the same owned +SKILL.md path/digest/description. Current observer expects its bare package +name. Project direct skill names remain exactly bare a/b/c names. + +`turnOps` initializes nextTurnId=0 and assigns the first TurnPrompt its +current nextTurnId, then advances it. The single-tool observer creates a +fresh session, verifies message_count/last_seq, then submits one prompt. +Approval service carries context.turnId into interaction tags; pinned +kap-server approval route b90f1023 maps that numeric tag to turn_id. Current +observer requires turn_id>=1, contradicting this fresh first-turn identity. +Require exactly integer0 for this selected first-turn approval, rather than +accepting an arbitrary nonnegative or later turn. Bool is not an integer. + +## Finite source delta + +1. In the component Go observer request authoring only, construct the expected + native skill name from the already held fixture scope/package identity: + exact user role maps to pluginID:bareName; a/b/c remain unchanged. Keep + package/manifest/registry/NativeObject/file-frontmatter names, source extra, + description/path/digest and all public/receipt authority unchanged. Do not + rename installed files or normalize an incoming native name. A small pure + private helper must reject unknown/mismatched fixture role/name inputs. +2. In the existing Python single-approval validator only, require the exact + fresh first turn integer0. Preserve the same current session/main agent, + provider-owned call ID/tool/arguments/action/display/approval-ID/timestamp + checks and ordering. Missing, bool, string, negative, positive/later turn, + duplicate/unknown field and all current permission negatives still fail. + Preserve ResponseSchemaRefusal separately and the graceful initial-capture + branch; no additional approval, retry, request or remembered scope. + +Scope: one existing Go component lifecycle request-authoring file, one bounded +new private Go expectation helper/contracts file if needed, existing Python +attempt validator/tests and component collector tests, and this plan/validation +record. No production adapter, installed source, shared process/host guard, +context/environment, native pin, provider behavior, diagnostics, cleanup, +deadline, archive admission or qualification change. Keep source history of +the two corrections separable in local commits even if root publishes them +together after final review to avoid unnecessary duplicate native runs. + +## Required meaningful verification + +Go contracts must use the actual authoring seam with real held user/project +proof fixtures to show that only expected native user name differs; selected +path/digest/description/source remain the existing exact receipt authority, +all four scopes reject malformed roles/names, and no passed receipt/name is +adopted as a new package or native path. Python installed-skills contracts +must accept exact source-shaped qualified user+A/B/C rows and reject bare +user, foreign plugin prefix, wrong scope, duplicated/missing/project sibling, +wrong path/description/source and malformed fields. Keep the generic catalog +grammar strict; only the expected held request name changes. + +Single-tool contracts must use actual session/prompt/approval observer seam: +source-shaped first turn0 reaches the existing one-call approval and ordinary +shutdown path; every wrong turn class/value remains a refusal before that +action. Correct the old inert service's fabricated turn1 to source-derived0 +so existing session/permission/schema/privacy cases remain meaningful, not +masked by the stronger first-turn equality. Explicitly retain wrong tool/ +session/agent/provider call/action/display/args and unknown-key controls, +ResponseSchemaRefusal behavior, no-call-before-approval events and no blanket +scope in the outgoing approved request. No native/vendor runs locally. + +Obtain two independent plan GOs before code and two exact-code reviews after. +Run affected existing/new Kimi Go contracts plus the complete Kimi/supervisor +Python suite, formatting/AST/diff and affected/whole-file lint in the granted +exclusive Go slot. Root alone publishes the frozen plan/code on fresh PR524 +head. Both installed lanes then need new genuine source-bound raw proof; +single-tool native-history capture remains deliberately unqualified even if +the provider/probe exchange advances. Any next grammar/permission discrepancy +needs a separate evidence-backed plan; neither source correction qualifies +authentication, model service or a previously failed artifact. From 33b5d73ed94ce7e6be8516a3a5cb49222483c4b1 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 13:26:35 +0100 Subject: [PATCH 21/42] test: bind Kimi native user skills to exact plugin namespace --- ...gins_kimi_project_native_lifecycle_test.go | 8 +- ...ins_kimi_project_native_skill_name_test.go | 100 ++++++++++++++++++ scripts/test_project_native_kimi.py | 18 +++- 3 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 repotests/agentplugins_kimi_project_native_skill_name_test.go diff --git a/repotests/agentplugins_kimi_project_native_lifecycle_test.go b/repotests/agentplugins_kimi_project_native_lifecycle_test.go index a75399d56..babd7b669 100644 --- a/repotests/agentplugins_kimi_project_native_lifecycle_test.go +++ b/repotests/agentplugins_kimi_project_native_lifecycle_test.go @@ -382,11 +382,11 @@ func (f *kimiProjectNativeFixture) observe(t *testing.T, stage string, trust boo continue } p := f.packages[key] - source := "project" - if key == "user" { - source = "extra" + skill, err := kimiProjectExpectedSkill(key, p) + if err != nil { + t.Fatal(err) } - skills[scope] = append(skills[scope], map[string]any{"name": p.Name, "description": "Kimi installed " + p.Revision, "path": p.Skill, "source": source, "sha256": kimiProjectProofSHA(p.Proof.authored["skills/"+p.Name+"/SKILL.md"].Body)}) + skills[scope] = append(skills[scope], skill) } if selected[scope] { p := f.packages[scope] diff --git a/repotests/agentplugins_kimi_project_native_skill_name_test.go b/repotests/agentplugins_kimi_project_native_skill_name_test.go new file mode 100644 index 000000000..6370b79a9 --- /dev/null +++ b/repotests/agentplugins_kimi_project_native_skill_name_test.go @@ -0,0 +1,100 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "fmt" + "path/filepath" + "reflect" + "testing" +) + +// The pinned native catalog qualifies plugin skills, while direct project +// skills retain their authored name. This constructs an expectation only; +// package/receipt/file names and incoming native values are never rewritten. +func kimiProjectExpectedSkill(scope string, p *kimiProjectNativePackage) (map[string]any, error) { + switch scope { + case "user", "a", "b", "c": + default: + return nil, fmt.Errorf("unregistered Kimi skill expectation scope") + } + if p == nil || p.Proof == nil || p.Name != "uap-kimi-native-"+scope || p.Proof.scope != scope || p.Proof.name != p.Name || !filepath.IsAbs(p.Skill) || filepath.Clean(p.Skill) != p.Skill { + return nil, fmt.Errorf("Kimi skill expectation differs from selected recipe") + } + file, exists := p.Proof.authored["skills/"+p.Name+"/SKILL.md"] + if !exists || len(file.Body) == 0 { + return nil, fmt.Errorf("Kimi skill expectation requires the frozen authored leaf") + } + name, source := p.Name, "project" + if scope == "user" { + name, source = p.Name+":"+p.Name, "extra" + } + return map[string]any{"name": name, "description": "Kimi installed " + p.Revision, "path": p.Skill, "source": source, "sha256": kimiProjectProofSHA(file.Body)}, nil +} + +func TestKimiProjectExpectedSkillBindsActualStagerWithoutRenamingAuthority(t *testing.T) { + for _, scope := range []string{"user", "a", "b", "c"} { + t.Run(scope, func(t *testing.T) { + f := newKimiProjectProofContract(t, scope) + validated, err := f.proof.validate(f.ctx, f.installation) + if err != nil { + t.Fatal(err) + } + p := f.fixture.packages[scope] + p.Skill = validated.skill + before, err := kimiProjectProofCapture(f.ctx, validated.authority.active) + if err != nil { + t.Fatal(err) + } + row, err := kimiProjectExpectedSkill(scope, p) + wantName, wantSource := "uap-kimi-native-"+scope, "project" + if scope == "user" { + wantName, wantSource = "uap-kimi-native-user:uap-kimi-native-user", "extra" + } + body := f.proof.authored["skills/uap-kimi-native-"+scope+"/SKILL.md"].Body + want := map[string]any{"name": wantName, "description": "Kimi installed " + scope + "-one", "path": validated.skill, "source": wantSource, "sha256": kimiProjectProofSHA(body)} + if err != nil || !reflect.DeepEqual(row, want) || p.Name != "uap-kimi-native-"+scope || f.installation.DeclaredName != p.Name { + t.Fatal("native skill name did not bind the selected unchanged recipe", err) + } + after, captureErr := kimiProjectProofCapture(f.ctx, validated.authority.active) + again, proofErr := f.proof.validate(f.ctx, f.installation) + if captureErr != nil || proofErr != nil || !reflect.DeepEqual(before, after) || !reflect.DeepEqual(validated, again) { + t.Fatal("native name expectation changed installed payload or receipt authority", captureErr, proofErr) + } + }) + } +} + +func TestKimiProjectExpectedSkillRejectsUnregisteredOrMismatchedRecipe(t *testing.T) { + f := newKimiProjectProofContract(t, "user") + validated, err := f.proof.validate(f.ctx, f.installation) + if err != nil { + t.Fatal(err) + } + for _, kind := range []string{"nil", "unknown", "name", "proof", "proof-scope", "path", "leaf"} { + p := *f.fixture.packages["user"] + proof := *p.Proof + p.Proof, p.Skill = &proof, validated.skill + scope := "user" + candidate := &p + switch kind { + case "nil": + candidate = nil + case "unknown": + scope = "outside" + case "name": + p.Name = "uap-kimi-native-a" + case "proof": + p.Proof = nil + case "proof-scope": + proof.scope = "a" + case "path": + p.Skill = "relative/SKILL.md" + case "leaf": + proof.authored = map[string]kimiProjectProofFile{} + } + if row, err := kimiProjectExpectedSkill(scope, candidate); err == nil || row != nil { + t.Fatal("invalid expectation acquired native authority", kind) + } + } +} diff --git a/scripts/test_project_native_kimi.py b/scripts/test_project_native_kimi.py index 81f832902..f00fe7409 100644 --- a/scripts/test_project_native_kimi.py +++ b/scripts/test_project_native_kimi.py @@ -562,7 +562,7 @@ def test_installed_catalog_requires_exact_unmodified_receipt_config(self): kimi._installed_catalog([row], []) def test_installed_skill_names_paths_sources_and_absence_are_exact(self): - expected = {"name": "uap-kimi-native-user", "description": "Kimi installed user-one", + expected = {"name": "uap-kimi-native-user:uap-kimi-native-user", "description": "Kimi installed user-one", "path": "/owned/home/plugins/managed/user/skills/user/SKILL.md", "source": "extra"} self.assertEqual(kimi._installed_skills({"skills": [expected]}, [expected]), [expected["name"]]) for key, value in (("name", "uap-kimi-native-b"), ("description", "other revision"), @@ -575,6 +575,22 @@ def test_installed_skill_names_paths_sources_and_absence_are_exact(self): with self.assertRaises(ValueError): kimi._installed_skills({"skills": [expected]}, []) + def test_installed_plugin_qualified_user_and_direct_project_names_remain_distinct(self): + user = {"name": "uap-kimi-native-user:uap-kimi-native-user", "description": "Kimi installed user-one", + "path": "/owned/home/plugins/managed/user/skills/uap-kimi-native-user/SKILL.md", "source": "extra"} + for scope in ("a", "b", "c"): + project = {"name": "uap-kimi-native-" + scope, "description": "Kimi installed " + scope + "-one", + "path": "/owned/" + scope + "/.kimi-code/skills/uap-kimi-native-" + scope + "/SKILL.md", "source": "project"} + expected = [user, project] + self.assertEqual(kimi._installed_skills({"skills": expected}, expected), sorted(row["name"] for row in expected)) + for wrong in ("uap-kimi-native-user", "foreign:uap-kimi-native-user", "uap-kimi-native-user:foreign"): + with self.subTest(scope=scope, wrong=wrong), self.assertRaises(ValueError): + kimi._installed_skills({"skills": [{**user, "name": wrong}, project]}, expected) + for changed in ([user, user, project], [project], [user, {**project, "name": "uap-kimi-native-foreign"}], + [{**user, "path": "/foreign/SKILL.md"}, project], [{**user, "source": "user"}, project]): + with self.subTest(scope=scope, changed=changed), self.assertRaises(ValueError): + kimi._installed_skills({"skills": changed}, expected) + def test_installed_authority_hash_or_inline_replacement_fails_before_service(self): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary).resolve() From 659278e69af14230a9ed517aa0650f9009e229cd Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 13:26:36 +0100 Subject: [PATCH 22/42] test: require the Kimi fresh-session first turn identity --- .../project_native_collectors/kimi_attempt.py | 4 +- scripts/test_project_native_kimi_attempt.py | 39 +++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/scripts/project_native_collectors/kimi_attempt.py b/scripts/project_native_collectors/kimi_attempt.py index dd5d767a6..a5cb67096 100644 --- a/scripts/project_native_collectors/kimi_attempt.py +++ b/scripts/project_native_collectors/kimi_attempt.py @@ -90,7 +90,9 @@ def approval(value, session_id, call_id, args): action = "Approve " + TOOL if isinstance(value, dict): kimi._string(value.get("approval_id"), 256) - _integer(value.get("turn_id"), 1) + _integer(value.get("turn_id")) + if value["turn_id"] != 0: + raise ValueError("Kimi approval is not the selected fresh first turn") if (value.get("session_id") != session_id or value.get("agent_id") != "main" or value.get("tool_call_id") != call_id or value.get("tool_name") != TOOL or value.get("action") != action or value.get("tool_input_display") != { diff --git a/scripts/test_project_native_kimi_attempt.py b/scripts/test_project_native_kimi_attempt.py index 7c7c6f07b..f7f8c694f 100644 --- a/scripts/test_project_native_kimi_attempt.py +++ b/scripts/test_project_native_kimi_attempt.py @@ -56,7 +56,7 @@ def http(self, label, method, path, token=None, json_body=None, timeout=5): action = "Approve " + attempt.TOOL detail = {"nonce": "owned-nonce", "operation": "write", "marker": "owned-marker"} data = {"items": [{"approval_id": "approval_owned", "session_id": "session_owned", - "agent_id": "main", "turn_id": 1, "tool_call_id": "provider_owned", + "agent_id": "main", "turn_id": 0, "tool_call_id": "provider_owned", "tool_name": attempt.TOOL, "action": action, "tool_input_display": {"kind": "generic", "summary": action, "detail": detail}, "created_at": STAMP, "expires_at": "2026-10-27T12:00:00Z"}]} @@ -64,6 +64,8 @@ def http(self, label, method, path, token=None, json_body=None, timeout=5): data["items"][0]["tool_name"] = "Bash" if self.ctx.fault == "permission-outer": data["unmeasured_field"] = TOKEN + KEY + if self.ctx.fault == "later-turn": + data["items"][0]["turn_id"] = 1 elif label == "approve-once": data = {"resolved": True, "resolved_at": STAMP} elif label.startswith("snapshot-"): @@ -218,6 +220,30 @@ def test_generic_failure_diagnostic_retains_actual_stage_and_cleanup_without_pri self.assertTrue(all(TOKEN.encode() not in body and KEY.encode() not in body for body in ctx.captures.values())) + def test_fresh_turn_zero_reaches_only_exact_one_call_approval_and_later_turn_stops(self): + for fault in (None, "later-turn"): + with self.subTest(fault=fault), tempfile.TemporaryDirectory() as root: + ctx = CaptureContext(root, fault=fault) + if fault is None: + result = self.observe_capture(ctx) + self.assertEqual(result["native_turn_id"], 0) + self.assertIs(result["native_capture_complete"], False) + self.assertIs(result["native_history_grammar_frozen"], False) + approved = [row for row in ctx.services[0].calls if row[0] == "approve-once"] + self.assertEqual(len(approved), 1) + self.assertEqual(approved[0][1:3], ("POST", "/api/v1/sessions/session_owned/approvals/approval_owned")) + self.assertEqual(approved[0][4], {"decision": "approved"}) + self.assertTrue(ctx.summary["commands"][0]["cleanup"]["shutdown_acknowledged"]) + self.assertFalse((ctx.data / "native-marker.txt").exists()) + else: + with self.assertRaisesRegex(ValueError, "common service refused"): + self.observe_capture(ctx) + labels = [row[0] for row in ctx.services[0].calls] + self.assertEqual(labels[-1], "approvals-0") + self.assertNotIn("approve-once", labels) + self.assertNotIn("shutdown", labels) + self.assertEqual(ctx.summary["http_requests"], []) + def test_both_real_entrypoint_failure_branches_emit_only_finite_diagnostic_then_fail(self): for module, argument, collector, reason in ( (attempt.kimi, "--installed-request", "installed", "Kimi installed API observation failed; private capture quarantined"), @@ -295,7 +321,7 @@ def test_identity_permission_readiness_transport_shutdown_or_cleanup_failure_rem def approval(self): action = "Approve " + attempt.TOOL args = {"nonce": "owned-nonce", "operation": "write", "marker": "owned-marker"} - value = {"approval_id": "approval_owned", "session_id": "session_owned", "agent_id": "main", "turn_id": 1, + value = {"approval_id": "approval_owned", "session_id": "session_owned", "agent_id": "main", "turn_id": 0, "tool_call_id": "provider_owned", "tool_name": attempt.TOOL, "action": action, "tool_input_display": {"kind": "generic", "summary": action, "detail": args}, "created_at": "2026-10-26T12:00:00Z", "expires_at": "2026-10-27T12:00:00Z"} @@ -303,9 +329,10 @@ def approval(self): def test_approval_requires_exact_session_turn_provider_call_and_arguments(self): value, args = self.approval() - self.assertEqual(attempt.approval(value, "session_owned", "provider_owned", args), ("approval_owned", 1)) + self.assertEqual(attempt.approval(value, "session_owned", "provider_owned", args), ("approval_owned", 0)) for key, wrong in (("session_id", "session_foreign"), ("agent_id", "foreign"), ("turn_id", True), - ("turn_id", 0), ("tool_call_id", "foreign-call"), ("tool_name", "Bash"), + ("turn_id", -1), ("turn_id", 1), ("turn_id", 100), ("turn_id", "0"), ("turn_id", None), + ("tool_call_id", "foreign-call"), ("tool_name", "Bash"), ("action", "Approve everything"), ("approval_id", 7), ("scope", "session")): with self.subTest(key=key, wrong=wrong): changed = copy.deepcopy(value) @@ -313,6 +340,10 @@ def test_approval_requires_exact_session_turn_provider_call_and_arguments(self): with self.assertRaises(ValueError): attempt.approval(changed, "session_owned", "provider_owned", args) changed = copy.deepcopy(value) + del changed["turn_id"] + with self.assertRaises(ValueError): + attempt.approval(changed, "session_owned", "provider_owned", args) + changed = copy.deepcopy(value) changed["tool_input_display"]["detail"]["operation"] = "read" with self.assertRaises(ValueError): attempt.approval(changed, "session_owned", "provider_owned", args) From 03d7c243129c98ecdc4cbb99bfe1a47536a556bf Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 13:27:22 +0100 Subject: [PATCH 23/42] docs: record exact Kimi expectation source and check closure --- ...SOURCE_IDENTITY_EXPECTATIONS_VALIDATION.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_VALIDATION.md diff --git a/docs/KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_VALIDATION.md b/docs/KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_VALIDATION.md new file mode 100644 index 000000000..591566887 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_VALIDATION.md @@ -0,0 +1,52 @@ +# Kimi source identity expectation checks + +The approved plan is `KIMI_PROJECT_NATIVE_SOURCE_IDENTITY_EXPECTATIONS_PLAN.md`, +SHA256 `0248a4f6a26077468b1b2c7f6ece24898c932d784d0c0909ed485d00709d95be`. +CLI and desktop independently reviewed the complete plan and full pinned native +sources before code. Both also gave independent exact-code GOs on the same +five source hashes below. Their conditional actual-check requirements are now +closed. Root remains the only external publisher. + +Local plan commit a4aa9f5c is followed by two separate corrections: +93758271 authors only the exact expected plugin-qualified user skill name; +c73a696e requires the exact fresh first-turn integer0. Their code tree is +`4d9e6e76d1bdc6f25c9f7ba2cc8ee3babd13044d`. + +| Exact source | SHA256 | +| --- | --- | +| repotests/agentplugins_kimi_project_native_lifecycle_test.go |74540783f24afc434f7851bc147e4122f6e841f08133530610c5ffdbefafa9b1| +| repotests/agentplugins_kimi_project_native_skill_name_test.go |5469b6b4d7d7c0aead27924d347eb0dfe668016e8331ad96472097c6fb5a9bfa| +| scripts/test_project_native_kimi.py |976c85ac1817c2bd0d869c4eaf7f55443c0d6324d6d5c44fb48eccf63093ebb4| +| scripts/project_native_collectors/kimi_attempt.py |bd49f1777c44bf39aca68c1f391e422ddbe4bcf3a21f6c6ed77dc8cb0e6c365a| +| scripts/test_project_native_kimi_attempt.py |eaae849851de8415ea1bc5db0f348df3d63d22761ef3a0ed65f55440765f2f9a| + +Actual offline checks on those final bytes, 2026-10-27: + +- Focused Go `^TestKimiProject|^TestKimiSingleTool`: PASS0.255s, including all + four actual Loader/Planner/Stager/adapter/proof user/A/B/C request-expectation + contracts. These execute inert owned files, not a Kimi/native installer. +- All configured enabled linters on affected lines from plan a4aa9f5c: + zero issues. +- Unfiltered whole-file revive/funlen/gocyclo/gocognit/dupl/depguard on the + complete repotests package: zero total issues, not merely zero selected-file + issues. Report `/tmp/uap-shell-kimi-expectations-whole.json`. +- Complete Kimi collector, single-tool and shared supervisor Python suite: + 59 tests PASS17.884s. The actual observer seam accepts firstturn0, sends + exactly one selected decision with no remembered scope, and keeps native + capture/history unqualified; laterturn1 stops before that action and raw + HTTP remains quarantined. Existing schema/identity/privacy controls pass. +- Python AST, gofmt and git diff checks are clean. + +Go1.26.10 and golangci-lint2.13.2 used the recovered official exact assets, +GOPROXY/GOSUMDB off, approved private shared-memory caches, GOMAXPROCS2, +GOFLAGS -p=1 and GOMEMLIMIT500MiB, in the explicitly coordinated exclusive +slot. The slot was released after checks; no process remains. + +Current hosted source91a remains failed in both installed lanes at the +observed skills-a/approval validation boundaries. Its response values were +quarantined, so these corrections remain source-proven observer fixes, not +assertions about uncaptured response bodies. Fresh genuine hosted component +and single-tool runs are mandatory after publication. The single-tool native +history review gate and all account/model qualification limits remain intact. +The alternative finite-refusal diagnostic plan is retained outside the code +checkout and is not implemented or included in this candidate. From 4b420105115d66b6b9dfabb54b3d777611d4ddc3 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 20:20:57 +0100 Subject: [PATCH 24/42] test(kimi): validate the exact repeated-add direct result --- ...I_PROJECT_NATIVE_REPEAT_ADD_RESULT_PLAN.md | 108 ++++++++++++++ ...gins_kimi_project_native_lifecycle_test.go | 8 +- ...ins_kimi_project_native_repeat_add_test.go | 141 ++++++++++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 docs/KIMI_PROJECT_NATIVE_REPEAT_ADD_RESULT_PLAN.md create mode 100644 repotests/agentplugins_kimi_project_native_repeat_add_test.go diff --git a/docs/KIMI_PROJECT_NATIVE_REPEAT_ADD_RESULT_PLAN.md b/docs/KIMI_PROJECT_NATIVE_REPEAT_ADD_RESULT_PLAN.md new file mode 100644 index 000000000..eddd82042 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_REPEAT_ADD_RESULT_PLAN.md @@ -0,0 +1,108 @@ +# Kimi: exact repeated-add result contract + +## Current genuine failure and source + +Use published PR524 source `03d7c243129c98ecdc4cbb99bfe1a47536a556bf`, +tree `de7a5565545f67e26e7e3e101b843f728e3876b1`. Runtime40553154543, +component job122367952819, now passes the installed native observation. +It then fails at repeat-add-a because the fixture's shared array-only +`commandsRequireMutated` cannot read the actual successful direct result. + +Native12759050559 outer SHA256 +`01d59c89fe8124ec17d2ab08f5239e746e23d2c0db7876e34ba482885cef4ffb` +has34 closed ordinary members. Frontend12759450014 outer +`b8163211655fe5215607758975694968dd05bc67c24c3b26a014b599a7ec9aae` +has25. Both archives and CRCs are verified against current API receipts. +The actual repeat-add command exits0, reports success and returns data.result +with mutated=false, no_change=true and the held installation/binding identity. +This failed16.26s capture remains unqualified; later lifecycle stages were not +executed. The separate single-tool approval refusal is a separate slice. + +Production add_multi.go145-154 sends an already installed single target to +runAddLoaded. add.go205 renders the direct newAddResultData on NoChange. +The fixture's first multi-target-style add and later repeated single-target +result are therefore distinct supported public shapes, not fallback aliases. + +## Narrow change and authority + +Change only Kimi's f.uap handling of the exact repeat-add-a/add/project/A stage, +plus one small private helper/contracts Go file and this accepted record. +Keep strict whole JSON parsing and nativeValidateInstallerJSON before the +stage-specific result check. Every other stage keeps the existing shared +commandsRequireMutated array contract. Do not change shared legacy helpers, +production output, command argv/cwd, progress text or installed state. + +The private repeated-add helper requires the already held fixture recipe, +exact stage/add/A/project/want-mutated-false parameters, a direct data.result +object, no data.targets member, data.plugin equal to the held package name, +and exact boolean dry_run=false. Require mutated=false, no_change=true and +requires_confirmation=false with exact bool types. Bind installation_id to +the first independently validated receipt and plan.physical_artifact_id to +that same held native binding. Require the observed plan's client_id=kimi, +scope=project, status=ready, package_mode=native, activation=prepared, +authentication=not_checked, policy=allowed and verification=package_validated. +Require outcome.activation's prepared/not_checked/allowed/installation_verified +facts. No direct/group ambiguity or generic either-shape fallback is accepted. +The existing successful versioned-envelope check and strict JSON duplicate/ +trailing-value checks remain before this branch; require command=add here. + +The existing source, state, native-file, package, data, foreign-tree and +unchanged-operation checks still follow. Neither result metadata nor a new +installation ID may renew the original proof authority. Preserve the four +component qualification gates, the separate initial single-tool false flags, +native admission, original deadlines and the original900s Kimi suite. + +## Meaningful checks and publication + +Use independently stated direct-result fixtures based on the actual public +shape and ordinary inert held identities. Reject array/group and ambiguous +dual shapes, missing fields, wrong types, target/scope/package/installation/ +physical identity, mutated=true, no_change=false, confirmation or dry-run. +Exercise the actual stage-selection seam so every other stage retains the +array-only contract and cannot enter the new repeated-add branch. + +Replay the current held repeat-add stdout against its separately held first +add receipt in a temporary inert Go control, removed before final lint. Do +not commit raw captures or call a vendor/client/installer. Preserve all existing +proof, receipt, mutation, privacy and hostile-schema contracts. + +Obtain root R1 and independent R2 on this exact record before implementation; +the peer clarification requiring physical binding and confirmation checks is +included above. Obtain independent exact-code review. Use the coordinated +Go/lint slot for focused Kimi/shared contracts, affected all-enabled lint, +unchanged whole-file architecture/size lint, gofmt and diff checks. +Keep the code commit separate from any approval diagnostic. Root alone +publishes on a fresh exact PR524 head. A new genuine full component run must +establish later lifecycle outcomes; this correction does not reclassify the +failed archive or qualify MCP calls, authentication, models or a release. + +## Accepted implementation boundary + +Root R1 and independent desktop R2 approved proposal SHA256 +`c709b9d3390263eb602ac1781090b1c0b8f48811c10c0c6696ee8aeff2d38e62`; +the native CLI reviewer independently approved the same plan. Implementation +uses the proof's first held stable installation/physical identities. The new +private selector handles only label repeat-add-a and refuses mismatched scope, +argv or mutation intent; every other label reaches the unchanged shared array +check. No native command has been run locally. Focused inert validation, +actual-stdout replay, lint and exact-code reviews precede root publication. + +Actual inert validation on 2026-10-27: focused +`^(TestKimiProject|TestKimiSingleTool|TestProjectNativeCLI)` passed in 0.392s. +A temporary inert Go control replayed the actual repeat-add stdout against the +separately captured first-add installation/physical identities in 0.009s; +the shared array-only parser still rejects that same direct result. The replay +file was removed before final lint and no raw capture is committed. All +configured linters on affected root lines versus 03d7c243 reported zero issues, +as did the unfiltered six-linter architecture/size gate on the complete root module. Reports +are `/tmp/uap-kimi-repeat-add-affected-lint.json` and +`/tmp/uap-kimi-repeat-add-whole-root-six-lint.json`. The first lint invocation lacked +Go in PATH; a later whole-file invocation accidentally retained all configured +linters and reported unrelated legacy findings. Neither invocation is counted +as the required gates; the successful runs use the exact stated scope. + +Desktop and native CLI reviewers gave independent code GOs. The only review +finding corrected a misleading comment from whole-schema validation to the +actual successful versioned-envelope check. Code semantics are unchanged from +the reviewed tree `ce7d293580faf433844c9b64f6b52456a04b3199`; this validation +record adds no native observations or qualification. Root alone publishes. diff --git a/repotests/agentplugins_kimi_project_native_lifecycle_test.go b/repotests/agentplugins_kimi_project_native_lifecycle_test.go index babd7b669..04e1aeecd 100644 --- a/repotests/agentplugins_kimi_project_native_lifecycle_test.go +++ b/repotests/agentplugins_kimi_project_native_lifecycle_test.go @@ -299,7 +299,13 @@ func (f *kimiProjectNativeFixture) uap(t *testing.T, label, scope string, mutate if err := nativeValidateInstallerJSON(r.Stdout); err != nil { t.Fatal(err) } - commandsRequireMutated(t, label, value, mutated) + handled, err := kimiProjectRepeatAddOutcome(label, scope, mutated, args, value, f.packages[scope]) + if err != nil { + t.Fatal(err) + } + if !handled { + commandsRequireMutated(t, label, value, mutated) + } var progress string switch args[0] { case "add": diff --git a/repotests/agentplugins_kimi_project_native_repeat_add_test.go b/repotests/agentplugins_kimi_project_native_repeat_add_test.go new file mode 100644 index 000000000..115c6eb38 --- /dev/null +++ b/repotests/agentplugins_kimi_project_native_repeat_add_test.go @@ -0,0 +1,141 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" +) + +// Only the already-installed single-target add has the public direct result. +// Its authority remains the first independently validated receipt, never the +// installation or physical identity returned by this later no-change command. +func kimiProjectRepeatAddOutcome(label, scope string, mutated bool, args []string, value map[string]any, p *kimiProjectNativePackage) (bool, error) { + if label != "repeat-add-a" { + return false, nil + } + refuse := func() (bool, error) { return true, fmt.Errorf("Kimi repeated add differs from held no-change result") } + if scope != "a" || mutated || value["command"] != "add" || p == nil || p.Proof == nil || p.Proof.stable == nil || + p.Name == "" || p.Source == "" || p.Proof.stable.installation == "" || p.Proof.stable.artifact == "" || + !reflect.DeepEqual(args, []string{"add", p.Source, "--no-save"}) { + return refuse() + } + data, ok := value["data"].(map[string]any) + if !ok || data["plugin"] != p.Name || data["dry_run"] != false { + return refuse() + } + if _, exists := data["targets"]; exists { + return refuse() + } + outcome, ok := data["result"].(map[string]any) + if !ok || outcome["installation_id"] != p.Proof.stable.installation || outcome["mutated"] != false || + outcome["no_change"] != true || outcome["requires_confirmation"] != false { + return refuse() + } + plan, ok := outcome["plan"].(map[string]any) + if !ok || plan["physical_artifact_id"] != p.Proof.stable.artifact { + return refuse() + } + for key, expected := range map[string]string{"client_id": "kimi", "scope": "project", "status": "ready", "package_mode": "native", "activation": "prepared", "authentication": "not_checked", "policy": "allowed", "verification": "package_validated"} { + if plan[key] != expected { + return refuse() + } + } + activation, ok := outcome["activation"].(map[string]any) + if !ok { + return refuse() + } + for key, expected := range map[string]string{"activation": "prepared", "authentication": "not_checked", "policy": "allowed", "verification": "installation_verified"} { + if activation[key] != expected { + return refuse() + } + } + return true, nil +} + +func kimiProjectRepeatAddContract(t *testing.T) (map[string]any, *kimiProjectNativePackage) { + t.Helper() + // Independently stated public no-change shape; neither identity is learned + // from this result. Strict whole JSON parsing and successful versioned- + // envelope validation remain in f.uap. + const body = `{"schema_version":1,"command":"add","result":"success","data":{"plugin":"held-package","dry_run":false,"result":{"installation_id":"held-installation","mutated":false,"no_change":true,"requires_confirmation":false,"plan":{"client_id":"kimi","scope":"project","status":"ready","package_mode":"native","activation":"prepared","authentication":"not_checked","policy":"allowed","verification":"package_validated","physical_artifact_id":"held-physical"},"activation":{"activation":"prepared","authentication":"not_checked","policy":"allowed","verification":"installation_verified"}}}}` + var value map[string]any + if err := json.Unmarshal([]byte(body), &value); err != nil { + t.Fatal(err) + } + p := &kimiProjectNativePackage{Name: "held-package", Source: "/held/source", Proof: &kimiProjectProof{stable: &kimiProjectProofAuthority{installation: "held-installation", artifact: "held-physical"}}} + return value, p +} + +func TestKimiProjectRepeatAddRequiresHeldDirectNoChange(t *testing.T) { + value, p := kimiProjectRepeatAddContract(t) + if handled, err := kimiProjectRepeatAddOutcome("repeat-add-a", "a", false, []string{"add", p.Source, "--no-save"}, value, p); !handled || err != nil { + t.Fatalf("held direct repeated add refused: %v", err) + } + if _, err := commandsTargetOutcome(value); err == nil { + t.Fatal("shared array-only contract unexpectedly accepts direct result") + } + for _, label := range []string{"add-a", "add-b", "add-user", "unchanged-update-a", "intact-repair-a", "update-a", "missing-repair-a", "remove-a"} { + if handled, err := kimiProjectRepeatAddOutcome(label, "a", false, []string{"add", p.Source, "--no-save"}, value, p); handled || err != nil { + t.Fatalf("unrelated stage %s selected direct-result branch", label) + } + } +} + +func TestKimiProjectRepeatAddRefusesIdentityShapeAndMutationChanges(t *testing.T) { + for _, path := range [][]string{{"command"}, {"data"}, {"data", "plugin"}, {"data", "dry_run"}, {"data", "result"}, {"data", "result", "installation_id"}, {"data", "result", "mutated"}, {"data", "result", "no_change"}, {"data", "result", "requires_confirmation"}, {"data", "result", "plan"}, {"data", "result", "plan", "physical_artifact_id"}, {"data", "result", "plan", "client_id"}, {"data", "result", "plan", "scope"}, {"data", "result", "plan", "status"}, {"data", "result", "plan", "package_mode"}, {"data", "result", "plan", "activation"}, {"data", "result", "plan", "authentication"}, {"data", "result", "plan", "policy"}, {"data", "result", "plan", "verification"}, {"data", "result", "activation"}, {"data", "result", "activation", "activation"}, {"data", "result", "activation", "authentication"}, {"data", "result", "activation", "policy"}, {"data", "result", "activation", "verification"}} { + for _, replacement := range []any{nil, "wrong", true, false, float64(0), []any{}, map[string]any{}} { + value, p := kimiProjectRepeatAddContract(t) + parent := value + for _, key := range path[:len(path)-1] { + parent = parent[key].(map[string]any) + } + key := path[len(path)-1] + if reflect.DeepEqual(parent[key], replacement) { + continue + } + if replacement == nil { + delete(parent, key) + } else { + parent[key] = replacement + } + if handled, err := kimiProjectRepeatAddOutcome("repeat-add-a", "a", false, []string{"add", p.Source, "--no-save"}, value, p); !handled || err == nil { + t.Fatalf("changed repeated-add field %v accepted", path) + } + } + } +} + +func TestKimiProjectRepeatAddRefusesAmbiguityAndAuthority(t *testing.T) { + for _, targets := range []any{nil, []any{}, []any{map[string]any{"target": "kimi"}}} { + value, p := kimiProjectRepeatAddContract(t) + value["data"].(map[string]any)["targets"] = targets + if _, err := kimiProjectRepeatAddOutcome("repeat-add-a", "a", false, []string{"add", p.Source, "--no-save"}, value, p); err == nil { + t.Fatal("ambiguous direct/group result accepted") + } + } + for _, mutate := range []func(*kimiProjectNativePackage){func(p *kimiProjectNativePackage) { p.Proof = nil }, func(p *kimiProjectNativePackage) { p.Proof.stable = nil }, func(p *kimiProjectNativePackage) { p.Proof.stable.installation = "replacement" }, func(p *kimiProjectNativePackage) { p.Proof.stable.artifact = "replacement" }} { + value, p := kimiProjectRepeatAddContract(t) + mutate(p) + if _, err := kimiProjectRepeatAddOutcome("repeat-add-a", "a", false, []string{"add", p.Source, "--no-save"}, value, p); err == nil { + t.Fatal("missing or rebound first receipt accepted") + } + } + for _, args := range [][]string{nil, {"update", "/held/source", "--no-save"}, {"add", "/other/source", "--no-save"}, {"add", "/held/source"}, {"add", "/held/source", "--no-save", "--dry-run"}} { + value, p := kimiProjectRepeatAddContract(t) + if _, err := kimiProjectRepeatAddOutcome("repeat-add-a", "a", false, args, value, p); err == nil { + t.Fatal("different repeated-add command accepted") + } + } + value, p := kimiProjectRepeatAddContract(t) + for _, scope := range []string{"user", "b", "c", ""} { + if _, err := kimiProjectRepeatAddOutcome("repeat-add-a", scope, false, []string{"add", p.Source, "--no-save"}, value, p); err == nil { + t.Fatal("different repeated-add scope accepted") + } + } + if _, err := kimiProjectRepeatAddOutcome("repeat-add-a", "a", true, []string{"add", p.Source, "--no-save"}, value, p); err == nil { + t.Fatal("mutating repeated-add stage accepted") + } +} From a58aba727c613a2f2b5f129f0acfecfb20c234c7 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 20:25:50 +0100 Subject: [PATCH 25/42] test(kimi): retain finite facts at approval refusal --- ...JECT_NATIVE_APPROVAL_REFUSAL_FACTS_PLAN.md | 123 +++++++++++++ scripts/project_native_collectors/kimi.py | 16 ++ .../kimi_approval_facts.py | 122 ++++++++++++ .../project_native_collectors/kimi_attempt.py | 18 +- scripts/test_project_native_kimi_attempt.py | 173 ++++++++++++++++++ 5 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 docs/KIMI_PROJECT_NATIVE_APPROVAL_REFUSAL_FACTS_PLAN.md create mode 100644 scripts/project_native_collectors/kimi_approval_facts.py diff --git a/docs/KIMI_PROJECT_NATIVE_APPROVAL_REFUSAL_FACTS_PLAN.md b/docs/KIMI_PROJECT_NATIVE_APPROVAL_REFUSAL_FACTS_PLAN.md new file mode 100644 index 000000000..bb97d1af6 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_APPROVAL_REFUSAL_FACTS_PLAN.md @@ -0,0 +1,123 @@ +# Kimi: finite facts at the current approval refusal + +## Current evidence and exact limitation + +Use PR524 source `03d7c243129c98ecdc4cbb99bfe1a47536a556bf`, tree +`de7a5565545f67e26e7e3e101b843f728e3876b1`. Genuine runtime40553154543, +single-tool job122367952876, still refuses at approvals/validate/validation, +before a permitted MCP call. It does not reach the deliberate history-review +gate. Native12756894604 outer SHA256 +`80fdf584518a9f2d879c61ce0eb12eb8b4b6adcbaa1fc1028c188399406422ac` +contains23 closed ordinary members; frontend12758701279 outer +`443be84f49ffa69c4e789d0cd33b7bd27c71d45ae3f6571af887486c69c3cf80` +contains25. API hashes, bounds and CRCs are verified. The finite diagnostic +records observer/service entry, group_reaped and port_closed, SIGTERM without +shutdown acknowledgement, and no forced kill. Raw approval values remain +quarantined, so no particular remaining field mismatch is claimed observed. + +The earlier unused two-topic diagnostic draft was based on91a and positive +turn acceptance. This new plan is approval-only, on the current exact-turn0 +validator. It does not change that acceptance rule or installed-skills code. +Pinned MCP tool source ba7412305673619ab2010e98e3bc622902f96c1f +defines no execution description/display override; this supports the existing +fallback expectation, but still does not reveal the actual refused response. + +## Finite failure-only observation + +Keep the complete current approval predicate order and return behavior in a +private strict function. A thin existing-callsite wrapper catches only its +existing ValueError refusal, optionally attaches a closed finite observation, +then rethrows a private typed ValueError. ResponseSchemaRefusal passes through +unchanged, with exact class identity and its existing graceful capture path. +All other exception classes retain their existing boundary. Success returns +exactly as before and produces no observation. + +Use one private bounded helper module only if needed for size/architecture, +the existing kimi_attempt.py, existing kimi.py failure diagnostic and their +inert contracts. The existing diagnostic observer copies only prevalidated +facts when it catches the original callback refusal, before the supervisor +can replace that error. It rethrows the same refusal. Final emission validates +the optional facts again under the existing complete2KiB diagnostic-line cap. +Malformed/projection failures suppress optional facts while preserving the +original failure, zero stdout and private-stream quarantine. + +No HTTP call, response parse, profile/event read, retry, prompt, decision, +approval, tool action or continuation is added. Use only the already decoded +single candidate and held session/provider/tool/argument expectations. +No raw request/response, dynamic field name, native path, action/display/args, +timestamp, identifier, actual turn number, hash, exception text or Python +class name may enter evidence. No value becomes permission or authority. + +The optional topic is exactly single-approval, status captured or +projection-refused. Its closed schema contains row JSON type, exact-key-set +boolean, and presence/type facts for the nine fixed required fields plus +turn_id. Type labels are exactly absent/null/boolean/integer/number/string/ +array/object/other. Add only fixed equality booleans for held session, main +agent, provider call, tool, action and complete expected display; approval-ID +pattern and created/expiry timestamp-validity booleans; turn category +negative/zero/positive/noninteger; and display object/key-set booleans. +Never export values or traverse arbitrary display leaf paths into output. +An absent candidate retains the existing generic stage failure only. + +Projection must first reject malformed/excessive input and nonbuiltin JSON +types, use exact builtin type checks and bounded comparisons, and never call +arbitrary rendering/equality on inert hostile objects. All output keys and +enum values are static. Bound decoded inspection to the already enforced +response limits and at most32 depth/16384 nodes; no budget renewal. A refused +projection emits only its fixed topic/status, with no partial field metadata. + +## Controls and unchanged native gates + +Exercise the actual wrapper with a valid current turn0 row: identical tuple, +no diagnostic and only the existing exact one-call decision. Independently +mutate each identity, field/key/type, display, turn category, ID pattern and +timestamp: every old negative remains refused, with only expected finite facts. +Retain later-turn, wrong-tool/session/agent/call/args/action/display, duplicate/ +unknown-schema and ResponseSchemaRefusal controls. Unknown schema must never +be caught as a generic identity refusal or change its graceful branch. + +Plant credential/path/control/identifier canaries and hostile objects in all +candidate fields. Verify complete serialized stdout/stderr contains none. +Test projection exceptions, wrong topic/key/type, oversize and repeated +failures, unknown facts suppression, and the real inert callback/supervisor +replacement seam. Cleanup facts still come only from the actual held command +record. The first refusal stays the last native/HTTP action. + +Obtain two independent plan reviews, then two exact-code reviews and run +the complete Kimi collector/single-tool/supervisor Python suite, AST and diff +checks. No Go or vendor execution is required for this diagnostic slice. +Preserve host/profile/ownership/pin/provider/permission/deadline/archive gates, +the original900s suite and every qualification/auth/model flag. Keep this code +commit separate from the repeated-add Go correction. Root alone publishes +on the current exact PR524 head. Only the next genuine finite diagnostic can +identify the remaining field; any later acceptance change needs its own +evidence-backed plan, reviews and fresh native proof. No release or VS work. + +## Accepted implementation and inert checks + +Root R1, desktop R2 and a separate native CLI review approved proposal SHA256 +`e841ca2be7330be91b787f2a4a015c6a0a329b529cf16a55404921d40c7bbcf1` +before implementation. The strict approval function's AST is identical to the +published function except for its private name; predicate order and return are +unchanged. The wrapper preserves ResponseSchemaRefusal's exact object/class +before the ValueError branch and leaves other exception classes untouched. +A private facts module first bounds exact builtin JSON and then emits only +fixed keys, booleans and categories. The observer holds a validated copy before +the actual supervisor replaces the callback error, and final emission checks +the closed schema and original complete two-KiB cap again. Installed direct +script startup remains independent of this single-tool-only optional import. + +Inert controls exercise the real callback/supervisor replacement with wrong +tool, combined unknown-schema/wrong-tool and later-turn failures, preserving +quarantine, actual cleanup and the last approvals request. Separate controls +cover exact successful return without projection, schema exception identity, +each refused field/type, unknown-schema precedence, bounded hostile objects, +private canaries, malformed facts, projection exceptions and held-copy drift. +The final full Kimi collector/single-tool/shared-supervisor suite passed 65 +tests in 17.727s, after all six new focused cases passed in 0.611s. Python AST +and diff checks are clean. Root and the native CLI reviewer independently +gave exact-code GOs on tree `bcee7cdf3c5b509676118b6346ee40f7999617e4`; +this final validation note changes no executable bytes. These local children +are inert Python processes; no Kimi, vendor binary, public installer or hosted +environment surrogate ran locally. The current genuine 03d7c243 approval +failure remains unqualified; only a new hosted run can supply its finite facts. diff --git a/scripts/project_native_collectors/kimi.py b/scripts/project_native_collectors/kimi.py index d7872136a..76119270c 100644 --- a/scripts/project_native_collectors/kimi.py +++ b/scripts/project_native_collectors/kimi.py @@ -779,6 +779,7 @@ def __init__(self, collector): raise ValueError("unregistered Kimi diagnostic collector") self.collector, self.stage, self.phase = collector, "host-admission", "enter" self.kind, self.ctx, self.observer_entered = None, None, False + self.approval_refusal = None def step(self, stage, phase="enter"): if stage not in _FAILURE_STAGES or phase not in _FAILURE_PHASES: @@ -810,6 +811,14 @@ def tracked(service): result = callback(service) except Exception as error: self.kind = self.exception_kind(error) + if (self.approval_refusal is None and self.collector == "single-tool" + and self.stage == "approvals" and self.phase == "validate"): + try: + from project_native_collectors import kimi_approval_facts + if isinstance(error, kimi_approval_facts.Refusal): + self.approval_refusal = error.public() + except Exception: + pass # Optional facts must never replace the original refusal. raise self.step("service-cleanup", "complete") return result @@ -834,6 +843,13 @@ def line(self, error): "cleanup_reported": all(type(item) is bool for item in cleanup.values()), "cleanup": cleanup} if isinstance(error, _InstalledProfileRefusal): value["profile_refusal"] = error.public() + if (self.approval_refusal is not None and self.collector == "single-tool" + and self.stage == "approvals" and self.phase == "validate"): + try: + from project_native_collectors import kimi_approval_facts + value["approval_refusal"] = kimi_approval_facts.validate(self.approval_refusal) + except Exception: + pass # Keep the generic finite diagnostic if optional facts are invalid. line = "Kimi collector failure: " + json.dumps(value, allow_nan=False, sort_keys=True, separators=(",", ":")) + "\n" if len(line.encode()) > 2048: raise ValueError("Kimi diagnostic bound exceeded") diff --git a/scripts/project_native_collectors/kimi_approval_facts.py b/scripts/project_native_collectors/kimi_approval_facts.py new file mode 100644 index 000000000..98b7e1067 --- /dev/null +++ b/scripts/project_native_collectors/kimi_approval_facts.py @@ -0,0 +1,122 @@ +"""Closed failure-only facts; no native value leaves approval quarantine.""" +import copy +import math +import re + + +FIELDS = ("approval_id", "session_id", "agent_id", "tool_call_id", "tool_name", "action", + "tool_input_display", "created_at", "expires_at", "turn_id") +TYPES = frozenset(("absent", "null", "boolean", "integer", "number", "string", "array", "object", "other")) +EQUALS = ("session", "agent", "call", "tool", "action", "display") +REFUSED = {"topic": "single-approval", "status": "projection-refused"} +KEYS = frozenset((*REFUSED, "row_type", "exact_keys", "fields", "equals", "approval_id_valid", + "created_at_valid", "expires_at_valid", "turn", "display_object", "display_keys")) + + +def _bounded(value): + pending, nodes, characters = [(value, 0)], 0, 0 + while pending: + item, depth = pending.pop() + nodes += 1 + if nodes > 16384 or depth > 32: + return False + kind = type(item) + if kind is str: + characters += len(item) + elif kind is dict: + if nodes + len(pending) + 2 * len(item) > 16384: + return False + for key, child in item.items(): + if type(key) is not str: + return False + pending.extend(((key, depth + 1), (child, depth + 1))) + elif kind is list: + if nodes + len(pending) + len(item) > 16384: + return False + pending.extend((child, depth + 1) for child in item) + elif kind is float: + if not math.isfinite(item): + return False + elif kind not in (type(None), bool, int): + return False + if characters > 1 << 20: + return False + return True + + +def _type(value): + return {type(None): "null", bool: "boolean", int: "integer", float: "number", + str: "string", list: "array", dict: "object"}.get(type(value), "other") + + +def project(value, session_id, call_id, args, tool, instant): + # Validate builtin types and bounds before equality, regex or iteration + # beyond the bounded walk. Never invoke a hostile object's repr/eq method. + if (not _bounded(value) or not _bounded([session_id, call_id, args, tool]) + or any(type(item) is not str for item in (session_id, call_id, tool)) or type(args) is not dict): + return dict(REFUSED) + row = value if type(value) is dict else {} + action = "Approve " + tool + display = row.get("tool_input_display") + turn = row.get("turn_id") + fields = {key: {"present": key in row, "type": _type(row[key]) if key in row else "absent"} for key in FIELDS} + + def valid_stamp(key): + try: + instant(row.get(key)) + except (ValueError, TypeError): + return False + return True + + result = {"topic": "single-approval", "status": "captured", "row_type": _type(value), + "exact_keys": type(value) is dict and set(row) == set(FIELDS), "fields": fields, + "equals": {key: row.get(field) == expected for key, field, expected in ( + ("session", "session_id", session_id), ("agent", "agent_id", "main"), + ("call", "tool_call_id", call_id), ("tool", "tool_name", tool), + ("action", "action", action), ("display", "tool_input_display", { + "kind": "generic", "summary": action, "detail": args}))}, + "approval_id_valid": type(row.get("approval_id")) is str and bool(re.fullmatch( + r"approval_[A-Za-z0-9_-]{1,128}", row["approval_id"])), + "created_at_valid": valid_stamp("created_at"), "expires_at_valid": valid_stamp("expires_at"), + "turn": ("negative" if turn < 0 else "positive" if turn > 0 else "zero") if type(turn) is int else "noninteger", + "display_object": type(display) is dict, + "display_keys": type(display) is dict and set(display) == {"kind", "summary", "detail"}} + return validate(result) + + +def validate(value): + if not _bounded(value) or type(value) is not dict: + raise ValueError("Kimi finite approval facts refused") + if value == REFUSED: + return dict(REFUSED) + if set(value) != KEYS or value["topic"] != "single-approval" or value["status"] != "captured": + raise ValueError("Kimi finite approval facts refused") + if (type(value["row_type"]) is not str or value["row_type"] not in TYPES or type(value["turn"]) is not str + or value["turn"] not in ("negative", "zero", "positive", "noninteger")): + raise ValueError("Kimi finite approval facts refused") + flags = ("exact_keys", "approval_id_valid", "created_at_valid", "expires_at_valid", "display_object", "display_keys") + if any(type(value[key]) is not bool for key in flags): + raise ValueError("Kimi finite approval facts refused") + fields, equal = value["fields"], value["equals"] + if type(fields) is not dict or set(fields) != set(FIELDS) or type(equal) is not dict or set(equal) != set(EQUALS): + raise ValueError("Kimi finite approval facts refused") + for field in FIELDS: + fact = fields[field] + if (type(fact) is not dict or set(fact) != {"present", "type"} or type(fact["present"]) is not bool + or type(fact["type"]) is not str or fact["type"] not in TYPES + or fact["present"] != (fact["type"] != "absent")): + raise ValueError("Kimi finite approval facts refused") + if any(type(equal[key]) is not bool for key in EQUALS): + raise ValueError("Kimi finite approval facts refused") + return copy.deepcopy(value) + + +class Refusal(ValueError): + """The original strict refusal, with separately validated optional facts.""" + + def __init__(self, facts): + self.facts = validate(facts) + super().__init__("Kimi selected approval refused") + + def public(self): + return validate(self.facts) diff --git a/scripts/project_native_collectors/kimi_attempt.py b/scripts/project_native_collectors/kimi_attempt.py index a5cb67096..a4a4f7e65 100644 --- a/scripts/project_native_collectors/kimi_attempt.py +++ b/scripts/project_native_collectors/kimi_attempt.py @@ -16,6 +16,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import project_native_research as shared from project_native_collectors import kimi +from project_native_collectors import kimi_approval_facts TOOL = "mcp__uap-kimi-native-a__inspect_runtime" MODEL = "uap-kimi-native-model" @@ -85,7 +86,7 @@ def session(value, workspace, cwd): return value["id"] -def approval(value, session_id, call_id, args): +def _strict_approval(value, session_id, call_id, args): # Exact source fallback: toolApprovalService blob96f36f15ec9fcaf00f4ec373e935004ed14857e2. action = "Approve " + TOOL if isinstance(value, dict): @@ -106,6 +107,21 @@ def approval(value, session_id, call_id, args): return value["approval_id"], value["turn_id"] +def approval(value, session_id, call_id, args): + try: + return _strict_approval(value, session_id, call_id, args) + except ResponseSchemaRefusal: + # Preserve the exact existing class and graceful failed-capture branch. + raise + except ValueError: + try: + facts = kimi_approval_facts.project(value, session_id, call_id, args, TOOL, kimi._instant) + refusal = kimi_approval_facts.Refusal(facts) + except Exception: + refusal = kimi_approval_facts.Refusal(kimi_approval_facts.REFUSED) + raise refusal from None + + def no_call(ctx, event_path, marker_path): body = kimi._read(ctx.root, event_path, 1 << 20, missing=True) for line in body.splitlines(): diff --git a/scripts/test_project_native_kimi_attempt.py b/scripts/test_project_native_kimi_attempt.py index f7f8c694f..5ca74b94d 100644 --- a/scripts/test_project_native_kimi_attempt.py +++ b/scripts/test_project_native_kimi_attempt.py @@ -244,6 +244,29 @@ def test_fresh_turn_zero_reaches_only_exact_one_call_approval_and_later_turn_sto self.assertNotIn("shutdown", labels) self.assertEqual(ctx.summary["http_requests"], []) + def test_approval_facts_survive_the_real_callback_and_supervisor_error_replacement(self): + for fault in ("permission", "permission-outer", "later-turn"): + with self.subTest(fault=fault), tempfile.TemporaryDirectory() as root: + ctx = CaptureContext(root, fault=fault) + diagnostic = attempt.kimi._KimiFailureDiagnostic("single-tool") + with self.assertRaisesRegex(ValueError, "common service refused") as caught: + self.observe_capture(ctx, diagnostic=diagnostic) + self.assertNotIsInstance(caught.exception, attempt.kimi_approval_facts.Refusal) + line = diagnostic.line(caught.exception) + value = json.loads(line.removeprefix("Kimi collector failure: ")) + facts = value["approval_refusal"] + self.assertEqual((facts["topic"], facts["status"]), ("single-approval", "captured")) + self.assertEqual(facts["turn"], "positive" if fault == "later-turn" else "zero") + self.assertEqual(facts["equals"]["tool"], fault == "later-turn") + self.assertEqual(value["cleanup"], ctx.summary["commands"][0]["cleanup"]) + self.assertEqual(ctx.services[0].calls[-1][0], "approvals-0") + self.assertNotIn("approve-once", [row[0] for row in ctx.services[0].calls]) + self.assertEqual(ctx.summary["http_requests"], []) + self.assertFalse(any(name.endswith(".http.json") for name in ctx.captures)) + self.assertLessEqual(len(line.encode()), 2048) + for private in (TOKEN, KEY, root, "session_owned", "provider_owned", "approval_owned", "owned-nonce"): + self.assertNotIn(private, line) + def test_both_real_entrypoint_failure_branches_emit_only_finite_diagnostic_then_fail(self): for module, argument, collector, reason in ( (attempt.kimi, "--installed-request", "installed", "Kimi installed API observation failed; private capture quarantined"), @@ -374,5 +397,155 @@ def test_probe_call_or_marker_before_approval_is_refused(self): attempt.no_call(ctx, event, marker) +class KimiApprovalFactsContracts(unittest.TestCase): + def fixture(self): + args = {"nonce": "PRIVATE-NONCE", "operation": "write", "marker": "PRIVATE-MARKER"} + action = "Approve " + attempt.TOOL + return {"approval_id": "approval_private", "session_id": "session_private", "agent_id": "main", "turn_id": 0, + "tool_call_id": "call_private", "tool_name": attempt.TOOL, "action": action, + "tool_input_display": {"kind": "generic", "summary": action, "detail": dict(args)}, + "created_at": STAMP, "expires_at": "2026-10-27T12:00:00Z"}, args + + def project(self, value, args): + return attempt.kimi_approval_facts.project(value, "session_private", "call_private", args, + attempt.TOOL, attempt.kimi._instant) + + def test_success_and_schema_refusal_keep_their_exact_original_boundary(self): + value, args = self.fixture() + with patch.object(attempt.kimi_approval_facts, "project", side_effect=AssertionError("must not project")): + self.assertEqual(attempt.approval(value, "session_private", "call_private", args), ("approval_private", 0)) + value["unknown"] = "PRIVATE-UNKNOWN" + with self.assertRaises(attempt.ResponseSchemaRefusal) as caught: + attempt.approval(value, "session_private", "call_private", args) + self.assertIs(type(caught.exception), attempt.ResponseSchemaRefusal) + self.assertEqual(caught.exception.stage, "approval") + sentinel = attempt.ResponseSchemaRefusal("approval") + with patch.object(attempt, "_strict_approval", side_effect=sentinel): + with self.assertRaises(attempt.ResponseSchemaRefusal) as exact: + attempt.approval(value, "session_private", "call_private", args) + self.assertIs(exact.exception, sentinel) + for error in (TypeError("PRIVATE-TYPE"), KeyError("PRIVATE-KEY"), OSError("PRIVATE-OS")): + with patch.object(attempt, "_strict_approval", side_effect=error): + with self.assertRaises(type(error)) as exact: + attempt.approval(value, "session_private", "call_private", args) + self.assertIs(exact.exception, error) + + def test_each_old_identity_and_type_refusal_stays_refused_with_finite_facts(self): + mutations = [(key, wrong) for key, wrong in ( + ("session_id", "session_foreign"), ("agent_id", "other"), ("tool_call_id", "other"), + ("tool_name", "Bash"), ("action", "PRIVATE-ACTION"), ("approval_id", "wrong"), + ("tool_input_display", {}), ("created_at", "PRIVATE-TIME"), ("expires_at", False))] + mutations.extend(("turn_id", turn) for turn in (-1, 1, 100, True, False, None, "0", [], {})) + mutations.extend((key, None) for key in attempt.kimi_approval_facts.FIELDS) + for key, wrong in mutations: + with self.subTest(key=key, wrong=wrong): + value, args = self.fixture() + value[key] = wrong + with self.assertRaises(ValueError) as original: + attempt._strict_approval(value, "session_private", "call_private", args) + with self.assertRaises(ValueError) as wrapped: + attempt.approval(value, "session_private", "call_private", args) + if type(original.exception) is attempt.ResponseSchemaRefusal: + self.assertIs(type(wrapped.exception), attempt.ResponseSchemaRefusal) + else: + self.assertIs(type(wrapped.exception), attempt.kimi_approval_facts.Refusal) + facts = wrapped.exception.public() + self.assertEqual(facts["fields"][key]["type"], attempt.kimi_approval_facts._type(wrong)) + self.assertTrue(facts["fields"][key]["present"]) + self.assertEqual(attempt.kimi_approval_facts.validate(facts), facts) + self.assertNotIn("PRIVATE", json.dumps(facts)) + value, args = self.fixture() + value.update(unknown="PRIVATE-UNKNOWN", tool_name="Bash") + with self.assertRaises(attempt.kimi_approval_facts.Refusal) as caught: + attempt.approval(value, "session_private", "call_private", args) + self.assertFalse(caught.exception.public()["exact_keys"]) + self.assertFalse(caught.exception.public()["equals"]["tool"]) + del value["turn_id"] + with self.assertRaises(attempt.kimi_approval_facts.Refusal) as caught: + attempt.approval(value, "session_private", "call_private", args) + self.assertEqual(caught.exception.public()["fields"]["turn_id"], {"present": False, "type": "absent"}) + + def test_projection_only_exports_fixed_booleans_and_categories_not_candidate_values(self): + value, args = self.fixture() + for key in attempt.kimi_approval_facts.FIELDS: + value[key] = "/PRIVATE-PATH bearer-secret\nPRIVATE-CONTROL PRIVATE-ID" + value["PRIVATE-DYNAMIC-KEY"] = "PRIVATE-VALUE" + facts = self.project(value, args) + serialized = json.dumps(facts) + for canary in ("PRIVATE", "bearer-secret", "session_private", "call_private", attempt.TOOL, STAMP): + self.assertNotIn(canary, serialized) + self.assertFalse(facts["exact_keys"]) + self.assertEqual(facts["turn"], "noninteger") + self.assertFalse(facts["display_object"]) + self.assertTrue(all(result is False for result in facts["equals"].values())) + for row, kind in ((None, "null"), (True, "boolean"), (0, "integer"), (0.5, "number"), + ("PRIVATE", "string"), ([], "array")): + projected = self.project(row, args) + self.assertEqual(projected["row_type"], kind) + self.assertTrue(all(field == {"present": False, "type": "absent"} for field in projected["fields"].values())) + + def test_projection_rejects_excessive_nonbuiltin_and_hostile_objects_before_comparison(self): + class Hostile: + def __eq__(self, _): + raise AssertionError("hostile equality called") + + def __repr__(self): + raise AssertionError("hostile rendering called") + + class HostileDict(dict): + def items(self): + raise AssertionError("hostile iteration called") + + _, args = self.fixture() + deep = None + for _ in range(34): + deep = [deep] + cycle = [] + cycle.append(cycle) + for value in (Hostile(), HostileDict(), {"tool_input_display": Hostile()}, + [0] * 16385, deep, cycle, "PRIVATE" * (1 << 18), float("nan"), {1: "PRIVATE"}): + self.assertEqual(self.project(value, args), {"topic": "single-approval", "status": "projection-refused"}) + + def test_projection_failure_and_malformed_facts_do_not_change_the_original_failure(self): + value, args = self.fixture() + value["turn_id"] = 1 + for failure in (ValueError("PRIVATE-EXCEPTION"), {"topic": "PRIVATE-TOPIC"}, ["PRIVATE-FIELD"]): + patching = {"side_effect": failure} if isinstance(failure, Exception) else {"return_value": failure} + with patch.object(attempt.kimi_approval_facts, "project", **patching): + with self.assertRaises(attempt.kimi_approval_facts.Refusal) as caught: + attempt.approval(value, "session_private", "call_private", args) + self.assertEqual(caught.exception.public(), {"topic": "single-approval", "status": "projection-refused"}) + valid = self.project(value, args) + for path, wrong in ((["topic"], "PRIVATE"), (["row_type"], []), (["turn"], "PRIVATE"), + (["exact_keys"], 1), (["fields", "turn_id", "type"], "PRIVATE"), + (["fields", "turn_id", "present"], False), (["equals", "call"], "PRIVATE")): + changed = copy.deepcopy(valid) + parent = changed + for key in path[:-1]: + parent = parent[key] + parent[path[-1]] = wrong + with self.assertRaises(ValueError): + attempt.kimi_approval_facts.validate(changed) + diagnostic = attempt.kimi._KimiFailureDiagnostic("single-tool") + diagnostic.approval_refusal = changed + diagnostic.step("approvals", "validate") + line = diagnostic.line(ValueError("PRIVATE-ORIGINAL")) + self.assertNotIn("PRIVATE", line) + self.assertNotIn("approval_refusal", json.loads(line.removeprefix("Kimi collector failure: "))) + diagnostic = attempt.kimi._KimiFailureDiagnostic("single-tool") + diagnostic.step("approvals", "validate") + first = attempt.kimi_approval_facts.Refusal(valid) + + def refuse(_): + raise first + + with self.assertRaises(attempt.kimi_approval_facts.Refusal) as caught: + diagnostic.observer(refuse)(None) + self.assertIs(caught.exception, first) + first.facts["unknown"] = "PRIVATE-MUTATION" + self.assertNotIn("unknown", diagnostic.approval_refusal) + self.assertLessEqual(len(diagnostic.line(ValueError("PRIVATE-REPLACEMENT")).encode()), 2048) + + if __name__ == "__main__": unittest.main() From b2bb2b7f85b5461420f932478b197905e6fc5ad9 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 20:29:48 +0100 Subject: [PATCH 26/42] docs(kimi): record current-base inert validation --- ...JECT_NATIVE_CURRENT_REFUSALS_VALIDATION.md | 109 ++++++++++++++++++ ...PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md | 1 - 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 docs/KIMI_PROJECT_NATIVE_CURRENT_REFUSALS_VALIDATION.md diff --git a/docs/KIMI_PROJECT_NATIVE_CURRENT_REFUSALS_VALIDATION.md b/docs/KIMI_PROJECT_NATIVE_CURRENT_REFUSALS_VALIDATION.md new file mode 100644 index 000000000..c704a06b7 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_CURRENT_REFUSALS_VALIDATION.md @@ -0,0 +1,109 @@ +# Kimi current repeat-add and approval refusal validation + +## Reviewed source and authority + +The source for the current genuine failures is PR524 commit +`03d7c243129c98ecdc4cbb99bfe1a47536a556bf`, tree +`de7a5565545f67e26e7e3e101b843f728e3876b1`, runtime +[40553154543](https://github.com/777genius/universal-agent-plugins/actions/runs/40553154543). +The component and single-tool jobs both failed; all five qualification flags +remain false in both native and frontend captures. The complete current API +check set was 44 successful, two native failures and one skipped check. + +Both exact proposals received root R1, independent desktop R2 and a separate +native CLI plan review before implementation: + +| Proposal | Approved SHA256 | +| --- | --- | +| Repeated-add direct result | `c709b9d3390263eb602ac1781090b1c0b8f48811c10c0c6696ee8aeff2d38e62` | +| Approval refusal finite facts | `e841ca2be7330be91b787f2a4a015c6a0a329b529cf16a55404921d40c7bbcf1` | + +The changes remain separate commits: +`4b420105115d66b6b9dfabb54b3d777611d4ddc3` implements the exact repeated-add +result contract; `a58aba727c613a2f2b5f129f0acfecfb20c234c7` implements only +failure diagnostic facts. Native CLI and desktop reviewers independently +approved the first code at tree `ce7d293580faf433844c9b64f6b52456a04b3199`; +the desktop code GO covered the same executable bytes before a comment-only +truthfulness correction. Root, native CLI and desktop reviewers independently +approved the second at tree `bcee7cdf3c5b509676118b6346ee40f7999617e4`. +Final validation notes changed no executable bytes. + +The strict approval function's AST is identical to the published function +except for its private name. Its exact turn0, identity, permission and schema +predicate order remains intact. ResponseSchemaRefusal retains its exact +object/class and existing graceful branch. The diagnostic grants no approval +or tool authority and exports no native values, identifiers or hashes. + +## Genuine failed captures retained unchanged + +All four current artifacts were freshly downloaded again after the interrupted +publication session. Size and SHA256 matched the current GitHub API receipts; +every ZIP byte equaled the previously reviewed copy. Nested CRCs, closed member +inventories, extracted bytes and frontend stream joins remain valid. + +| Artifact | Members | Outer ZIP SHA256 | +| --- | ---: | --- | +| Component native 12759050559 | 34 | `01d59c89fe8124ec17d2ab08f5239e746e23d2c0db7876e34ba482885cef4ffb` | +| Component frontend 12759450014 | 25 | `b8163211655fe5215607758975694968dd05bc67c24c3b26a014b599a7ec9aae` | +| Single-tool native 12756894604 | 23 | `80fdf584518a9f2d879c61ce0eb12eb8b4b6adcbaa1fc1028c188399406422ac` | +| Single-tool frontend 12758701279 | 25 | `443be84f49ffa69c4e789d0cd33b7bd27c71d45ae3f6571af887486c69c3cf80` | + +The component capture passes its installed observation, then refuses the +successful direct repeat-add result in the fixture's old array-only parser. +The single-tool capture refuses at approvals/validate/validation before any +approved tools/call. Its raw approval values remain quarantined and unobserved; +this correction does not claim which remaining predicate differs. + +## Current-base alignment and final inert gates + +Commit `29f93abe9739d65336a39a81fc082ce238b5dfa1`, tree +`bbf6199df0628636c81bb719a80005a07c90ab3f`, is a true merge of the reviewed +candidate and current PR520 base `a412ba5e137fdcb7e004393229b323211ed746e1`. +All six Kimi code/test files below remain byte-identical to the reviewed leaf. +The only manual conflict retained both append-only consolidated-plan blocks +verbatim. Independent native CLI review verified the full nonconflicting tree +union, every nonblank parent plan line in order and the automatic detector-test union. +The shared runtime Python source/tests are byte-identical to the Cline-qualified +base. A final documentation-only cleanup removes one inherited extra blank EOF +from KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md. + +| Check | Actual result | +| --- | --- | +| Focused Kimi/proof/single-tool/shared native CLI Go, before base merge | PASS 0.392s | +| Actual current repeat-add stdout against separately held first-add identities | PASS 0.009s | +| Complete Kimi collector/single-tool/supervisor Python | 65 tests PASS 17.727s | +| Focused Go after true base merge | PASS 0.620s | +| Kimi/supervisor/shared-runtime Python after true base merge | 92 tests PASS 23.859s | +| All configured root linters on affected lines versus actual a412ba5e | Zero issues | +| Unfiltered root revive/funlen/gocyclo/gocognit/dupl/depguard | Zero issues | +| Strict approval AST comparison, changed Python AST, gofmt and final diff checks | Clean | + +The raw replay was a temporary inert Go control; it was removed before final +lint. It also confirms that the unchanged shared array parser still refuses +the actual direct result. No raw capture is committed. Local child processes +were inert test processes; no vendor/client or public installer ran locally. +Go1.26.10 and golangci-lint2.13.2 ran in a coordinated exclusive slot with +GOPROXY/GOSUMDB off, the existing module/cache paths and the unchanged linter +configuration. Python used the existing complete inert suites. Final lint +receipts are `/tmp/uap-kimi-postbase-affected-lint.json` and +`/tmp/uap-kimi-postbase-whole-six-lint.json`. + +| Reviewed code/test file | SHA256 | +| --- | --- | +| repotests/agentplugins_kimi_project_native_lifecycle_test.go | `b6cc4cf42b1e425f0ce9b801d5475fdc9810b485daed983466cf24cc63e844ba` | +| repotests/agentplugins_kimi_project_native_repeat_add_test.go | `bb0c1d30455efbccb32fd19290909dc64d275d995ccbfe4258b75547bee5e130` | +| scripts/project_native_collectors/kimi.py | `e772cb0d2c2b7a3bdc290f061d1dab8310a1fe7f9a3e35ef02751e8aa91448e9` | +| scripts/project_native_collectors/kimi_attempt.py | `420509c0101c708ac26fa836f23753441381cbf926e368fa18d8e2c61f442000` | +| scripts/project_native_collectors/kimi_approval_facts.py | `fe829a905c18ee33ee08f6266d89b00b5c852b8a3efe7727860de2d638988ee7` | +| scripts/test_project_native_kimi_attempt.py | `c3b6c38108516f3d4d037c2c8a18e260236c2a953a5644884c190cd4c51e93c6` | + +## Remaining native gates + +Root alone publishes after a fresh expected-head check. A fresh genuine hosted +component run must establish the later update, repair, restart, removal and +preservation stages. The separate single-tool lane still requires its native +approval/history evidence and a separately reviewed finite history grammar; +its deliberate final failed-capture boundary is unchanged. No account/model, +release or VS Code qualification is added. The original 900s Kimi lifecycle +suite, admission, pins, ownership, provider and cleanup limits are preserved. +Historical failed artifacts are never relabeled as results of this candidate. diff --git a/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md b/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md index 2d4bc3588..507b5a715 100644 --- a/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md +++ b/docs/KIMI_PROJECT_NATIVE_HOST_MARKER_HANDOFF_PLAN.md @@ -105,4 +105,3 @@ Go1.22 source compatibility, private shared-memory temporary/lint directories, and the existing bounded module/build caches. No native client, local hosted guard, installer or model was executed. The genuine hosted follow-up remains required; the distinct installed component context-stage failure is unchanged. - From 970b8d6532990517440588263606f21a20854627 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 21:37:56 +0100 Subject: [PATCH 27/42] docs(kimi): approve explicit scoped repair selection plan --- .../kimi-scoped-selection-plan-review.md | 24 ++ .../kimi-scoped-selection-plan.md | 224 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 docs/plans/project-install-e2e/kimi-scoped-selection-plan-review.md create mode 100644 docs/plans/project-install-e2e/kimi-scoped-selection-plan.md diff --git a/docs/plans/project-install-e2e/kimi-scoped-selection-plan-review.md b/docs/plans/project-install-e2e/kimi-scoped-selection-plan-review.md new file mode 100644 index 000000000..8bdc50563 --- /dev/null +++ b/docs/plans/project-install-e2e/kimi-scoped-selection-plan-review.md @@ -0,0 +1,24 @@ +# Kimi scoped-selection plan approval + +The exact archived draft in kimi-scoped-selection-plan.md is approved before implementation. +The historical DRAFT header is retained to preserve the reviewed bytes. Plan SHA256: +`a9068b73fd29a8cad1bc6e0e6a79fc51f8d4b42e3841fb1cd618a346342299ef`. +Baseline: `b2bb2b7f85b5461420f932478b197905e6fc5ad9`, tree +`b12354b2565a05d14037556f8493b149d7d36ea2`. + +Two independent GPT-6.1 xhigh critics returned PRE-CODE GO on 2026-10-27: + +- R1 /root/scope_and_route_critic: review record SHA256 + `94230a717d9651f22bc51845c4005eeb0ce4233f119a3ae96a3be64be5080438`. +- R2 /root/native_current_review: review record SHA256 + `5b52e5e39c185863f3243b34908021773680d7e557248e4a63cf7e099276cc4b`. + +Both independently checked actual source/interface compatibility, wrong-layout +cause, explicit prior-binding scope and locator/digest authority, profile wrapper +forwarding, no fallback after scoped refusal, preserved old user API and damaged +artifact gates. Implementation must include actual intact composed public CLI and +Engine RED/GREEN regressions and the plan's refusal/compatibility controls. +Neither critic ran Go, lint, vendor processes or remote mutations. This approval +grants only the planned implementation; exact code reviews, executed gates, fresh +genuine hosted evidence and independent audit remain required. No native +qualification, merge, release or VS Code authorization follows from this record. diff --git a/docs/plans/project-install-e2e/kimi-scoped-selection-plan.md b/docs/plans/project-install-e2e/kimi-scoped-selection-plan.md new file mode 100644 index 000000000..f51866e49 --- /dev/null +++ b/docs/plans/project-install-e2e/kimi-scoped-selection-plan.md @@ -0,0 +1,224 @@ +# Kimi: preserve project MCP selection during intact repair + +Status: PRE-CODE DRAFT. This author has made no repository changes and run no Go, +lint, installer executable or vendor process. Obtain two independent plan reviews +and fix their findings before implementation. Root coordinates an isolated worktree +and the Go/lint slot; original checkouts and publication manifests remain untouched. +This draft is not a code GO, a native qualification or release authorization. + +## 1. Exact evidence and current source + +Read AGENTS.md, docs/PROJECT_INSTALL_OWNER_SCOPE.md, +docs/AUTHORING_CAPABILITY_PRESERVATION.md and the project completion plan. +Preserve useful user/legacy behavior. Active PR521–529 remain the scope; VS Code +is deferred and its dedicated source/tests must not change. No release. + +Starting Gemini-independent Kimi leaf: b2bb2b7f85b5461420f932478b197905e6fc5ad9, +tree b12354b2565a05d14037556f8493b149d7d36ea2. The original Kimi continuation +checkout was clean at that head during the source review. A narrow search found +no newer Kimi repair-selection plan, worktree or implementation. Recheck exact +head/clean status and coordinate with the current author before implementation; +never overwrite concurrent work. + +Genuine Kimi component run40569825577/job122438517506 failed after21.40s. +Native12767504412:14298 bytes, SHA256 +bb61975315455a478c3fc54083f23426c2c75b90be7f641132f98dbeac0e3af4, +40 closed ordinary members. Frontend12767812666:3519 bytes, SHA256 +06f000c1fc78c4546632cbb72d3b9d7704c06e4275538df2216e9d233082b075, +25 members. Independent audit: +/tmp/uap-kimi-b2bb-independent-20261027-uyq51c5q/audit.json, SHA256 +13138425fd8b44e166b3297882b04f023605b69dd00437b2cf6dde97800ea932. +Fresh API metadata, nested closed ZIP/CRC/ordinary membership, exact source/tree, +source/build cleanliness,24 frontend stream joins,4 nested service-stream joins, +37 HTTP joins and source-bound Python Kimi intake all pass. + +The repeated-add direct-result correction now advances: repeat-add-a and unchanged +update both complete without state/native-byte changes. Intact repair exits0 and +reports mutated=true, then the existing fixture correctly refuses with +“Kimi intact repair changed native bytes”. Its combined assertion checks the +selected skill OR project MCP file; after-repair bytes are not exported, so do +not claim to have directly read either changed file. + +Actual public result facts provide a separate concrete lead: add-a and unchanged +update classify the held MCP server as native. Intact repair keeps the same +installation/source tree/manifest/physical identity but changes that MCP component +to unsupported, reason not_in_recorded_delivery, and emits +managed_component_removal_planned. The old03d repeated-add parser defect is not +this failure. All five qualification, account/model flags remain false, and the +stable observation, later update/missing repair/removal/restart were not reached. +The separate Kimi single-tool lane supplies no qualification to this component lane. + +## 2. Source-proven scope/layout mismatch + +At exact b2bb: + +- clients/kimi/adapter.go ManagedMCPSelection always returns the user layout: + .kimi-plugin/plugin.json, nested mcpServers, with that member optional. +- clients/kimi/project_mcp.go projectKimiComponents writes the project delivery's + selected servers into the managed artifact's root mcp.json. It does not author + the user plugin manifest. Native .kimi-code/mcp.json is a separate projection; + read selection only from the verified managed artifact, never native mutable + configuration, a source package, or an unverified receipt claim. +- providers.Stager.ManagedMCPNames verifies the entire recorded artifact against + its held digest, dispatches the adapter's unscoped SelectionReader, reads that + one layout, then verifies the artifact again. Missing selection file yields + the existing nonnil empty selection. Therefore a valid project artifact is + read through the wrong user manifest layout and produces an empty selection. +- usecase.preflightTargetComponents uses that verified empty selection during + repair. restrictRepairMCPSelection then marks the project MCP unsupported with + the observed not_in_recorded_delivery reason; readiness/removal handling emits + the observed planned-removal diagnostic. + +This trace explains the observed removal plan. It is not a substitute for the +unexported post-repair byte observation. Preserve the current fixture refusal. +Do not add a formatting exception, semantic-only byte comparison or native-file +baseline adoption. + +## 3. Narrow additive scope-aware selection contract + +### Adapter policy + +Add one optional pure adapter interface next to SelectionReader, provisionally +ScopedSelectionReader with ManagedMCPSelectionForScope(domain.InstallScope) +(clients.SelectionLayout,error). It chooses a managed-artifact layout only; +it observes no filesystem, runs no process and adds no client capability. + +Kimi implements it with an explicit closed scope switch: + +- user: return the existing .kimi-plugin/plugin.json layout exactly, including + ServersMemberOptional=true; +- project: return root mcp.json, nested mcpServers, without optional member; +- unknown/empty scope: return an error, without guessing. + +Retain the existing ManagedMCPSelection method and its user API behavior exactly. +No choice based on file presence, directory spelling, detected ConfigRoot, +manifest contents, project-like paths or fallback to another candidate file. +A project skills-only artifact may still have no mcp.json and yield the existing +verified empty selection; a present dedicated document without mcpServers keeps +its existing refusal. User manifest omission semantics remain unchanged. + +### Provider and usecase propagation + +Add an explicit scoped Stager method, provisionally ManagedMCPNamesForScope(ctx, +clientID,scope,root,digest), while retaining ManagedMCPNames's existing signature +and behavior for existing direct/user callers. Both share the existing strict +artifact read implementation; preserve both complete digest verifications, error +behavior, fixed selection document parser and deterministic sorted names. +The new method validates scope, asks an adapter ScopedSelectionReader when present, +and otherwise uses the adapter's existing SelectionReader or generic default. +This fallback is for unchanged adapter policy; no generic client-ID table or +Kimi condition belongs in providers/usecase. + +In usecase, add a separate optional scoped managed-selection reader port. +recordedManagedMCPNames receives the already planned scope explicitly. Before +reading, require a known scope equal to the selected prior binding's scope; +retain exact target resolution/path verification and the prior managed digest. +Prefer the scoped port when present. The production providers.Stager implements +it, and profileStager forwards it after its existing original-profile check. + +Keep the legacy unscoped reader fallback for unchanged host stagers that implement +only the old optional port; do not remove their existing API or silently treat +that old port as absent. A wrapper must forward the scoped inner port when it +exists; it may not accidentally erase it and fall back. If a scoped reader returns +an error, propagate it: never retry unscoped or interpret that refusal as empty. +The production CLI/Engine/profile-wrapped Kimi paths must demonstrably reach the +scoped method. This correction makes no new scope-correctness claim for an +external host's custom legacy-only stager implementation. + +No staging, activation, repair selection restriction, native configuration +mutation, confirmation, version admission, data authority, scope authority or +state format is relaxed. Preserve the damaged/missing artifact route: names +from damaged bytes remain untrusted; the existing exact rebuilt-artifact digest +and ownership gates remain mandatory. This is not permission to resurrect +unrecorded MCP selection. + +### Compatibility alternatives and decision + +Changing the old exported ManagedMCPNames signature would unnecessarily break +existing direct consumers. Replacing Kimi's unscoped user layout globally would +break existing user registry semantics. Selecting whichever layout file exists +would infer scope from bytes and could confuse foreign/contradictory candidates. +A project-only registry adapter instance would leave Engine/SDK hosts that inject +the ordinary context-aware Kimi adapter inconsistent. The additive scoped port +carries the existing explicit authority through both composition paths while +retaining old contracts; its small forwarding surface is covered directly. + +## 4. Meaningful inert regression before native rerun + +Use existing fixture/composition helpers where their routes match. No native +vendor, scanner/installer binary, managed launcher or hosted-guard surrogate runs +locally. In-process Go filesystem/App/Engine execution is inert product testing, +not a native qualification. Do not replace the public repair path with a fake +result or hand-written native repair. + +1. Reproduce intact project repair through actual composed CLI App entrypoints: + independently author a local standard package containing skill plus MCP, + original user/A/B foreign settings and retained data, with inert detected + pinned Kimi and explicit failing-on-use native runner/launcher seams. Run + public project add, then public repair of the installed ID WITHOUT corrupting + managed/native/source bytes first. Before repair hold exact selected skill + and project MCP bytes and original user/B trees. Require the unchanged source, + installation, physical binding/data identities, project Prepared/Installed + state, selected MCP native support and ownership retained, no + not_in_recorded_delivery/removal diagnostic, and exact skill/MCP bytes after. + State/receipt evolution may reflect the real mutated repair; do not assert a + fictitious no-op classification. This regression must fail against b2bb's + original implementation and pass only with the scoped correction. +2. Exercise the same intact repair using the actual public Engine/SDK composition + with a directly injected ordinary Kimi adapter. Preserve the existing corrupt + or missing old-pin repair test separately: that damaged-byte fallback did not + cover this intact verified-artifact case. Prove original user/B/data isolation + and run no native process. +3. Through the actual Stager, stage/verify project and user artifacts and read + their recorded selections using explicit scopes. Project selection must use + root mcp.json; user and the old unscoped API must use the original manifest. + A bounded independently authored digest-verified artifact with conflicting + candidate files proves the selected layout is authoritative and no fallback + occurs. Skills-only user/project cases retain their intended empty selection. + A malformed selected dedicated document refuses even if another candidate is + well formed. Unsupported scope errors must occur before interpretation. +4. Verify both artifact-digest checks and no damaged-byte selection. Altered + managed bytes or mismatched held digest refuse selection; wrong scope/prior + binding/target locator refuses at the usecase authority boundary. Foreign + native namespace collisions or changed managed native objects retain existing + refusal and no target mutation. The ordinary safe repair of damaged owned + bytes still requires its existing exact reconstruction/ownership conditions. +5. Add focused forwarding contracts for profileStager and scoped-versus-legacy + stagers: exact held scope reaches the new port, original profile failure + prevents the read, scoped errors never fall back, legacy user behavior is + retained, and adapters without scoped policy retain their previous layout. + Do not add generic client-ID cases or grow architecture allowlists/baselines. + +The existing native byte-stability assertion at intact repair remains unchanged. +Run the regression RED on the original source via inert overlay or isolated +checkout, then GREEN on the final exact code. Do not edit original native evidence +or execute the full opt-in native fixture locally. + +## 5. Files, review and gates + +Expected product files: clients/identity.go; Kimi adapter-owned selection method; +providers/managed_selection.go; usecase/managed_selection.go and native_binding.go. +Use small new focused provider/usecase/Kimi contracts and a real composed CLI +regression plus a public Engine regression, extending existing fixtures rather +than introducing a second installer. Preserve all existing tests and useful +legacy source. No dedicated VS Code file, global client registry policy, shared +native parser, native collector, qualification flag, Python evidence schema, +provider credential, runtime pin, timeout, artifact member or production state +format needs to change. + +After two independent exact plan GOs, root supplies an isolated branch at the +fresh agreed source. Commit the accepted plan before implementation. Obtain two +independent exact-code reviews, fix findings and retain incremental commits. +Root coordinates focused existing/new Kimi public lifecycle/provider/usecase/ +selection tests, relevant default-adapter compatibility contracts, all-enabled +affected lint, the unfiltered whole-file architecture/size lint gate, gofmt and +whitespace. Attribute actual test commands/results to their executor. Do not run +concurrent Go/lint or repeat optional gates without a remaining concrete risk. + +Root alone performs fresh expected-head publication to the existing Kimi PR, +without force or overwriting concurrent source. If another author has moved the +leaf, preserve/reconcile/review their exact combined bytes first. A new genuine +hosted component run and independent raw audit must establish native consequences. +The separate single-tool approval/history boundaries remain independent. No old +failed capture becomes successful, no qualification is inherited, and no merge, +release or VS Code authorization follows from this plan. From e113f00eeb1c843e46f34cf219fb76a35971f8a7 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 21:43:24 +0100 Subject: [PATCH 28/42] test(kimi): reproduce intact project repair selection loss --- .../project_kimi_intact_repair_test.go | 53 ++++++++++++++++ .../project_kimi_intact_repair_test.go | 63 +++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 cli/cmd/agentplugins/project_kimi_intact_repair_test.go create mode 100644 install/integrationctl/agentplugins/installer/project_kimi_intact_repair_test.go diff --git a/cli/cmd/agentplugins/project_kimi_intact_repair_test.go b/cli/cmd/agentplugins/project_kimi_intact_repair_test.go new file mode 100644 index 000000000..bf630efef --- /dev/null +++ b/cli/cmd/agentplugins/project_kimi_intact_repair_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "os" + "path/filepath" + "reflect" + "strings" + "testing" +) + +// Exercise the public App with an intact recorded artifact. The existing +// damaged-pin repair regression deliberately takes a different selection path. +func TestProjectKimiPublicIntactRepairPreservesMCPSelection(t *testing.T) { + transport := newProjectJourneyTransport(t, "zip") + journey := newProjectJourney(t, transport) + user := filepath.Join(journey.home, ".kimi-code") + journey.app = configureProjectKimiJourney(journey.app, user, filepath.Join(journey.home, "kimi")) + for _, project := range []string{journey.a, journey.b} { + if err := os.Mkdir(filepath.Join(project, ".git"), 0700); err != nil { + t.Fatal(err) + } + projectJourneyWrite(t, filepath.Join(project, ".kimi-code", "mcp.json"), kimiIntactForeign) + journey.run(t, project, "add", transport.repo, "--target", "kimi", "--no-save") + } + projectJourneyWrite(t, filepath.Join(user, "mcp.json"), kimiIntactForeign) + installation, binding := projectKimiVersionOwned(t, journey, projectJourneySkill, false) + projectJourneyDataMarker(t, journey, journey.a, installation) + beforeUser, beforeB := securityFixtureTree(t, user), securityFixtureTree(t, journey.b) + mcp := filepath.Join(journey.a, ".kimi-code", "mcp.json") + skill := filepath.Join(journey.a, ".kimi-code", "skills", "docs", "SKILL.md") + beforeMCP, beforeSkill := projectJourneyRead(t, mcp), projectJourneyRead(t, skill) + output := journey.run(t, journey.a, "repair", installation.InstallationID, "--target", "kimi") + if projectJourneyRead(t, mcp) != beforeMCP || projectJourneyRead(t, skill) != beforeSkill { + t.Fatal("intact public repair changed native skill or MCP bytes") + } + if strings.Contains(output, "not_in_recorded_delivery") || strings.Contains(output, "managed_component_removal_planned") { + t.Fatal("intact public repair discarded recorded project MCP selection") + } + after, repaired := projectKimiVersionOwned(t, journey, projectJourneySkill, false) + if after.InstallationID != installation.InstallationID || !reflect.DeepEqual(after.Source, installation.Source) || repaired.ClientBindingID != binding.ClientBindingID || repaired.DataReceiptID != binding.DataReceiptID || !reflect.DeepEqual(repaired.NativeObjects, binding.NativeObjects) { + t.Fatal("intact repair changed held installation, binding, data or native ownership") + } + if !reflect.DeepEqual(beforeUser, securityFixtureTree(t, user)) || !reflect.DeepEqual(beforeB, securityFixtureTree(t, journey.b)) { + t.Fatal("intact repair changed original user or B") + } + for _, receipt := range installation.DataReceipts { + if projectJourneyRead(t, filepath.Join(receipt.Locator, "keep.txt")) != "retained environment data\n" { + t.Fatal("intact repair changed retained data") + } + } +} + +const kimiIntactForeign = `{"mcpServers":{"foreign":{"transport":"http","url":"https://foreign.test/mcp","enabled":false}},"policy":"keep"}` diff --git a/install/integrationctl/agentplugins/installer/project_kimi_intact_repair_test.go b/install/integrationctl/agentplugins/installer/project_kimi_intact_repair_test.go new file mode 100644 index 000000000..8ad67b24e --- /dev/null +++ b/install/integrationctl/agentplugins/installer/project_kimi_intact_repair_test.go @@ -0,0 +1,63 @@ +package installer_test + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/installer" +) + +func kimiSDKIntactPair(t *testing.T) (*kimiSDKFixture, *kimiSDKFixture) { + t.Helper() + base, err := filepath.EvalSymlinks(t.TempDir()) + if err != nil { + t.Fatal(err) + } + user, pkg, helper := filepath.Join(base, "user"), filepath.Join(base, "package"), filepath.Join(base, "inert-helper") + kimiSDKWrite(t, helper, "inert helper bytes; never executed") + if err := os.Chmod(helper, 0700); err != nil { + t.Fatal(err) + } + kimiSDKWrite(t, filepath.Join(pkg, "plugin.json"), `{"$schema":"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json","name":"sdk-kimi","version":"1.0.0"}`) + kimiSDKWrite(t, filepath.Join(pkg, "skills", "docs", "SKILL.md"), "---\nname: docs\ndescription: Intact repair\n---\nHeld skill bytes.\n") + kimiSDKWrite(t, filepath.Join(pkg, "mcp.json"), `{"$schema":"https://agent-plugins.org/schemas/1.0.0/mcp.schema.json","mcpServers":{"docs":{"type":"stdio","command":"node","args":["${PLUGIN_ROOT}/server.js"],"cwd":"${PLUGIN_DATA}"}}}`) + kimiSDKWrite(t, filepath.Join(pkg, "server.js"), "inert source; never executed\n") + a, b := newKimiSDKFixture(t, filepath.Join(base, "A"), user, pkg, helper), newKimiSDKFixture(t, filepath.Join(base, "B"), user, pkg, helper) + for _, f := range []*kimiSDKFixture{a, b} { + kimiSDKWrite(t, filepath.Join(f.project, ".kimi-code", "mcp.json"), `{"mcpServers":{"foreign":{"transport":"http","url":"https://foreign.test/mcp"}}}`) + f.apply(t, installer.OpInstall, "install-intact") + kimiSDKWrite(t, filepath.Join(f.data, "keep.txt"), "retained data") + } + kimiSDKWrite(t, filepath.Join(user, "mcp.json"), kimiSDKIntactForeign) + return a, b +} + +func TestProjectKimiSDKIntactRepairPreservesMCPSelection(t *testing.T) { + a, b := kimiSDKIntactPair(t) + binding := a.binding(t) + mcp := filepath.Join(a.project, ".kimi-code", "mcp.json") + skill := filepath.Join(a.project, ".kimi-code", "skills", "docs", "SKILL.md") + beforeMCP, beforeSkill := kimiSDKRead(t, mcp), kimiSDKRead(t, skill) + beforeB := kimiSDKRead(t, filepath.Join(b.project, ".uap", "state-v2.json")) + result := a.apply(t, installer.OpRepair, "repair-intact") + if !result.Mutated || result.Client.Activation != string(domain.ActivationPrepared) { + t.Fatal("intact repair lost its actual prepared mutation outcome") + } + if kimiSDKRead(t, mcp) != beforeMCP || kimiSDKRead(t, skill) != beforeSkill { + t.Fatal("intact SDK repair changed native skill or MCP bytes") + } + repaired := a.binding(t) + if repaired.ClientBindingID != binding.ClientBindingID || repaired.PhysicalArtifact != binding.PhysicalArtifact || repaired.DataReceiptID != binding.DataReceiptID || !reflect.DeepEqual(repaired.NativeObjects, binding.NativeObjects) { + t.Fatal("intact SDK repair changed held binding, data or native ownership") + } + if kimiSDKRead(t, filepath.Join(b.project, ".uap", "state-v2.json")) != beforeB || kimiSDKRead(t, filepath.Join(a.user, "mcp.json")) != kimiSDKIntactForeign || kimiSDKRead(t, filepath.Join(a.data, "keep.txt")) != "retained data" { + t.Fatal("intact SDK repair changed user, B or retained data") + } + kimiSDKCheckStdio(t, a) + kimiSDKCheckStdio(t, b) +} + +const kimiSDKIntactForeign = `{"mcpServers":{"original-user":{"transport":"http","url":"https://original.test/mcp"}},"preference":"keep"}` From f4aa9ad63dd6726d936b808d59dba0ea39cbe31a Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 21:47:53 +0100 Subject: [PATCH 29/42] docs(kimi): approve finite approval polling refusal diagnostic --- .../kimi-poll-refusal-plan-review.md | 22 +++ .../kimi-poll-refusal-plan.md | 133 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 docs/plans/project-install-e2e/kimi-poll-refusal-plan-review.md create mode 100644 docs/plans/project-install-e2e/kimi-poll-refusal-plan.md diff --git a/docs/plans/project-install-e2e/kimi-poll-refusal-plan-review.md b/docs/plans/project-install-e2e/kimi-poll-refusal-plan-review.md new file mode 100644 index 000000000..f16290304 --- /dev/null +++ b/docs/plans/project-install-e2e/kimi-poll-refusal-plan-review.md @@ -0,0 +1,22 @@ +# Kimi approval-poll diagnostic pre-code approval + +The exact archived draft kimi-poll-refusal-plan.md is approved before code. Its +historical DRAFT header preserves the reviewed bytes, SHA256 +`d46cac19a760a8cf359a2424a1b649fc0a88d4cd23de5d67aa6658f303ab8771`. +Baseline b2bb2b7f85b5461420f932478b197905e6fc5ad9, tree +b12354b2565a05d14037556f8493b149d7d36ea2. + +Two independent GPT-6.1 xhigh critics returned PRE-CODE GO on2026-10-27: + +- R1 scope_and_route_critic, record SHA256 `09fef3556d2bc854e0a1fd87e202371c0a8db6f8767f261d9f9446e6ae50155e`. +- R2 native_current_review, record SHA256 `17127432aa723572f2dd6ad1f5ff1decbbab0a8371f4e74c6f03c29a119df9db`. + +Both require unchanged early multiple-item guard ordering, separate typed +ValueError outside graceful schema capture, exact20completed-empty exhaustion, +privacy/type/ordinal/2048byte limits and no additional action or qualification. +First-cause retention is mutual across existing row and new poll facts. Inert +sleep controls must replace only the observer module's time binding; global +supervisor timing remains real. Both reviewers performed source-only reviews, +no tests or native execution. Exact code review, executed inert gates, unchanged +raw replay and fresh genuine observation remain required. No merge/release or +VS Code authorization follows from this approval. diff --git a/docs/plans/project-install-e2e/kimi-poll-refusal-plan.md b/docs/plans/project-install-e2e/kimi-poll-refusal-plan.md new file mode 100644 index 000000000..f3eae877f --- /dev/null +++ b/docs/plans/project-install-e2e/kimi-poll-refusal-plan.md @@ -0,0 +1,133 @@ +# Kimi single-tool: diagnose only the two unobserved approval-poll refusals + +PRE-CODE proposal. No implementation until two independent GPT-6.1 high/xhigh +plan reviews. This is independent of the approved production intact-repair fix +in /tmp/uap-additional-kimi-scoped-selection-20261027; preserve that work and its +native byte-stability assertion. No VS Code work or release. Read AGENTS.md, +docs/PROJECT_INSTALL_OWNER_SCOPE.md, docs/AUTHORING_CAPABILITY_PRESERVATION.md +and docs/plans/project-install-e2e/completion-plan.md. + +## Exact observed boundary + +Published source b2bb2b7f85b5461420f932478b197905e6fc5ad9, +tree b12354b2565a05d14037556f8493b149d7d36ea2. Genuine run40569825577, +single-tool job122438517585, failed after12.939s. Native artifact12768461663: +7895B, outer0500d6d9cafb34246f71cfd557db0fca848125d0a4d120cffbefdebe764c4ab3, +inner171da150010ee68b6aa8af632863447ad411c3bcca09f48d9d1d22a6e7ddcd29, +23 closed members. Frontend12768386913:3518B,25members, +outer66746161fca39fda9cdf71bfbe07060e2a1b85b98be4296500bd3fb6143d44da. +Exact-source admission, clean source/tree/build and24frontend stream joins +pass. Root audit /tmp/uap-additional-qoder-kimicall-fresh-ka537i9_/audit.json +SHA2569d48d60ff30934eda04e370893e74e130ed9d1843289b3d660c1e14cbbec02cf. + +Public add succeeds Prepared. Actual probe events contain initialize and +tools/list, no tools/call, and one provider request is retained. The collector +emits only its closed generic failure: stage=approvals,phase=validate, +exception_kind=validation,observer_entered=true,service_recorded=true. +Cleanup reports group_reaped and port_closed, no forced kill, SIGTERM sent, +no shutdown acknowledgment. Private service/HTTP streams remain quarantined. +No approval_refusal optional facts or python-api.json were exported. All five, +runtime-complete, authentication and real-model flags remain false. + +The source already projects finite facts for strict single-row ValueError via +kimi_approval_facts.Refusal. It deliberately leaves ResponseSchemaRefusal and +its separately reviewed graceful branch unchanged. The current approval-poll +loop can also raise a generic ValueError when more than one pending item is +returned, or when all20polls finish without a selected approval. These two +refusals carry no finite explanation today. The raw result does not establish +which occurred. Elapsed time is not proof of polling exhaustion. Do not infer +an actual approval shape, request count, native turn ID, missing permission, +tool execution, timeout or binary/source defect from this failed envelope. + +## Narrow implementation + +Add a separate typed ValueError subclass for exactly these existing two +polling refusal sites (including the duplicate post-shape multiple-items +check, if retained). Construct it using only a fixed reason and caller-owned +poll ordinal; pass no pending row, value, exception text, session, ID, path, +body, credential or arbitrary native text. + +Finite public object, provisionally: +- topic: single-approval-poll +- status: refused +- reason: multiple-pending OR no-pending-after-polls +- polls: exact integer1..20; no-pending-after-polls requires exactly20. + +A dedicated strict validator rejects unknown/missing fields, nonexact builtin +types (including bool-as-int), unknown reasons, impossible counts and arbitrary +strings. It returns an independent copied object. Keep the existing single-row +Refusal/project/validate API and its facts unchanged; use a separate method/type +in the existing small diagnostic module or a focused small sibling if lint/size +requires it. No acceptance branch or native permission is introduced. + +_KimiFailureDiagnostic may retain this separately validated optional object only +for collector=single-tool,stage=approvals,phase=validate when the original observer +actually raises the new typed refusal. Preserve first retained cause; do not let +cleanup errors or a later unrelated phase acquire/replace it. Emit it only under +those same closed conditions, as an optional approval_poll_refusal field in the +existing <=2048byte failure line. Invalid internal optional facts fall back to +the existing generic failure; never replace or swallow the original exception. + +The observer still fails at exactly the same place. It does not catch the new +refusal as a graceful ResponseSchemaRefusal, call approve-once, fetch snapshot/ +history, call shutdown, start another process or expose quarantined raw data. +The existing ordinary supervisor determines actual failure cleanup and the +collector exit remains nonzero. This slice authorizes no new approval response, +remembered/blanket permission, native request, route, schema acceptance, polling +iteration, sleep, work/service/total budget, token, source/executable pin, hosted +guard, profile write, cache allowance, provider response or qualification. + +The exact native runtime failed-only ZIP schema/readers should need no change: +the diagnostic remains inside the existing stderr member. Verify that existing +closed admission still accepts original bytes unchanged. If an additional +schema consumer actually requires a change, identify its exact source contract +and return for review before expanding the plan. + +## Meaningful inert controls + +Use existing KimiAttemptContracts/CaptureContext/CaptureService and the actual +observer with the real ordinary supervisor around its existing inert Python +child. No Kimi, installer, provider model, scanner or managed launcher runs +locally; never fabricate hosted eligibility. + +1. Return two pending approvals on the first poll, including private canaries. + Require the same failed observer/quarantine behavior and nonzero failure, + finite multiple-pending/polls1 facts, no approve-once/snapshot/history, no + marker/tool call and no new native permission. Unknown row bodies stay absent + from the serialized diagnostic. Its whole line remains<=2048bytes. +2. Return an empty pending items list for all20polls. Exercise the exact existing + loop and its real bounds, require no-pending-after-polls/polls20, unchanged + failure/cleanup and no follow-on action. Do not increase sleeps or deadlines; + an inert sleep seam may only remove wall-clock delay while asserting20existing + sleep/poll operations, never alter production timing. +3. A single mismatched row still uses the existing single-row refusal facts; + response-shape failures retain their original dedicated graceful path. No + poll facts appear for successful single-row selection, unrelated stages, + component collector, malformed internal objects or another exception class. + Preserve all existing identity/permission/privacy/supervisor tests. +4. Strict object boundary tests reject bool counts,0/21/negative/float counts, + wrong/missing/extra fields, arbitrary/private reasons, wrong topic/status, + impossible exhausted count, nested/hostile objects and corrupted optional + facts. Verify returned maps do not alias held facts and original refusal + survives diagnostic failure. Check byte limit with existing optional fields. +5. Re-admit the unchanged genuine b2bb failed artifact through the exact new + source reader and compare all bodies/summary. It remains failed/incomplete/ + allfalse and does not acquire a fabricated reason retroactively. + +## Integration and publication + +Before coding, obtain two exact plan GOs and commit the accepted plan. Use an +isolated branch from agreed current Kimi source; no edits to the original +continuation checkouts. Prefer merging this reviewed disjoint Python slice with +the independently reviewed production scoped-selection fix before publication, +so one genuine hosted run can observe both independent boundaries. Reconcile +concurrent remote movement; never force, overwrite or substitute commits. + +Root coordinates actual serial focused and retained Python contracts/AST and +whitespace checks, and exact code review by two independent critics. The other +Kimi author owns the current Go/lint slot; no concurrent test-process suite +while that slot is active. Root alone publishes and updates PR524 after fresh +expected-head checks. A fresh genuine hosted capture and independent raw audit +must establish which refusal occurred, if either. It cannot turn the old failed +run into a pass or establish a full lifecycle, successful tool call, auth/model +qualification, main merge or release. From 8311c8e9f571e9fa272abc2daed1a2f367ad160e Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 21:49:04 +0100 Subject: [PATCH 30/42] test(kimi): retain finite approval polling refusal cause --- scripts/project_native_collectors/kimi.py | 18 ++- .../kimi_approval_poll.py | 31 ++++ .../project_native_collectors/kimi_attempt.py | 7 +- .../test_project_native_kimi_approval_poll.py | 143 ++++++++++++++++++ scripts/test_project_native_kimi_attempt.py | 4 + 5 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 scripts/project_native_collectors/kimi_approval_poll.py create mode 100644 scripts/test_project_native_kimi_approval_poll.py diff --git a/scripts/project_native_collectors/kimi.py b/scripts/project_native_collectors/kimi.py index 76119270c..1229382f1 100644 --- a/scripts/project_native_collectors/kimi.py +++ b/scripts/project_native_collectors/kimi.py @@ -780,6 +780,7 @@ def __init__(self, collector): self.collector, self.stage, self.phase = collector, "host-admission", "enter" self.kind, self.ctx, self.observer_entered = None, None, False self.approval_refusal = None + self.approval_poll_refusal = None def step(self, stage, phase="enter"): if stage not in _FAILURE_STAGES or phase not in _FAILURE_PHASES: @@ -811,12 +812,15 @@ def tracked(service): result = callback(service) except Exception as error: self.kind = self.exception_kind(error) - if (self.approval_refusal is None and self.collector == "single-tool" + if (self.approval_refusal is None and self.approval_poll_refusal is None + and self.collector == "single-tool" and self.stage == "approvals" and self.phase == "validate"): try: - from project_native_collectors import kimi_approval_facts + from project_native_collectors import kimi_approval_facts, kimi_approval_poll if isinstance(error, kimi_approval_facts.Refusal): self.approval_refusal = error.public() + elif isinstance(error, kimi_approval_poll.Refusal): + self.approval_poll_refusal = error.public() except Exception: pass # Optional facts must never replace the original refusal. raise @@ -843,13 +847,21 @@ def line(self, error): "cleanup_reported": all(type(item) is bool for item in cleanup.values()), "cleanup": cleanup} if isinstance(error, _InstalledProfileRefusal): value["profile_refusal"] = error.public() - if (self.approval_refusal is not None and self.collector == "single-tool" + if (self.approval_refusal is not None and self.approval_poll_refusal is None + and self.collector == "single-tool" and self.stage == "approvals" and self.phase == "validate"): try: from project_native_collectors import kimi_approval_facts value["approval_refusal"] = kimi_approval_facts.validate(self.approval_refusal) except Exception: pass # Keep the generic finite diagnostic if optional facts are invalid. + if (self.approval_poll_refusal is not None and self.approval_refusal is None + and self.collector == "single-tool" and self.stage == "approvals" and self.phase == "validate"): + try: + from project_native_collectors import kimi_approval_poll + value["approval_poll_refusal"] = kimi_approval_poll.validate(self.approval_poll_refusal) + except Exception: + pass # The original quarantine failure remains authoritative. line = "Kimi collector failure: " + json.dumps(value, allow_nan=False, sort_keys=True, separators=(",", ":")) + "\n" if len(line.encode()) > 2048: raise ValueError("Kimi diagnostic bound exceeded") diff --git a/scripts/project_native_collectors/kimi_approval_poll.py b/scripts/project_native_collectors/kimi_approval_poll.py new file mode 100644 index 000000000..7720ce560 --- /dev/null +++ b/scripts/project_native_collectors/kimi_approval_poll.py @@ -0,0 +1,31 @@ +"""Finite failed-poll facts; no pending approval value leaves quarantine.""" + + +FIELDS = frozenset(("topic", "status", "reason", "polls")) +REASONS = frozenset(("multiple-pending", "no-pending-after-polls")) + + +def validate(value): + if (type(value) is not dict or any(type(key) is not str for key in value) + or set(value) != FIELDS + or any(type(value[key]) is not str for key in ("topic", "status", "reason")) + or value["topic"] != "single-approval-poll" or value["status"] != "refused" + or value["reason"] not in REASONS or type(value["polls"]) is not int + or not 1 <= value["polls"] <= 20 + or value["reason"] == "no-pending-after-polls" and value["polls"] != 20): + raise ValueError("Kimi finite approval poll facts refused") + return {key: value[key] for key in ("topic", "status", "reason", "polls")} + + +class Refusal(ValueError): + """The original polling refusal with only a fixed reason and held ordinal.""" + + def __init__(self, reason, polls): + self.facts = validate({"topic": "single-approval-poll", "status": "refused", + "reason": reason, "polls": polls}) + message = ("Kimi has foreign or multiple pending approvals" if reason == "multiple-pending" + else "Kimi exact native manual approval was not observed") + super().__init__(message) + + def public(self): + return validate(self.facts) diff --git a/scripts/project_native_collectors/kimi_attempt.py b/scripts/project_native_collectors/kimi_attempt.py index a4a4f7e65..b4108abc5 100644 --- a/scripts/project_native_collectors/kimi_attempt.py +++ b/scripts/project_native_collectors/kimi_attempt.py @@ -17,6 +17,7 @@ import project_native_research as shared from project_native_collectors import kimi from project_native_collectors import kimi_approval_facts +from project_native_collectors import kimi_approval_poll TOOL = "mcp__uap-kimi-native-a__inspect_runtime" MODEL = "uap-kimi-native-model" @@ -225,7 +226,7 @@ def session_capture(): candidate = None if isinstance(pending, dict) and isinstance(pending.get("items"), list): if len(pending["items"]) > 1: - raise ValueError("Kimi has foreign or multiple pending approvals") + raise kimi_approval_poll.Refusal("multiple-pending", index + 1) if pending["items"]: # An extra container field must not mask a mismatched # session/tool/permission in its already known entry. @@ -234,13 +235,13 @@ def session_capture(): if not isinstance(pending["items"], list): raise ResponseSchemaRefusal("approval") if len(pending["items"]) > 1: - raise ValueError("Kimi has foreign or multiple pending approvals") + raise kimi_approval_poll.Refusal("multiple-pending", index + 1) if pending["items"]: matched = candidate break time.sleep(min(0.25, service.remaining())) if matched is None: - raise ValueError("Kimi exact native manual approval was not observed") + raise kimi_approval_poll.Refusal("no-pending-after-polls", 20) diagnostic.step("pre-call") no_call(ctx, event_path, marker_path) validated["native_turn_id"], validated["approval_id"] = matched[1], matched[0] diff --git a/scripts/test_project_native_kimi_approval_poll.py b/scripts/test_project_native_kimi_approval_poll.py new file mode 100644 index 000000000..b15b3cbfb --- /dev/null +++ b/scripts/test_project_native_kimi_approval_poll.py @@ -0,0 +1,143 @@ +"""Inert polling-refusal controls; no Kimi, installer or provider execution.""" +import copy +import io +import json +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +from project_native_collectors import kimi_approval_facts as row_facts +from project_native_collectors import kimi_approval_poll as poll +from project_native_collectors import kimi_attempt as attempt +import test_project_native_kimi_attempt as inert + + +class Hostile: + def __eq__(self, other): + raise AssertionError("untrusted equality must not execute") + + def __repr__(self): + raise AssertionError("untrusted representation must not execute") + + __hash__ = object.__hash__ + + +class KimiApprovalPollContracts(unittest.TestCase): + observe_capture = inert.KimiAttemptContracts.observe_capture + + def test_actual_observer_preserves_both_refusals_and_quarantine(self): + for fault, reason, count in (("multiple-pending", "multiple-pending", 1), + ("empty-pending", "no-pending-after-polls", 20)): + with self.subTest(fault=fault), tempfile.TemporaryDirectory() as temporary: + ctx = inert.CaptureContext(temporary, fault=fault) + diagnostic = attempt.kimi._KimiFailureDiagnostic("single-tool") + diagnostic.ctx = ctx + sleeps, stdout = [], io.BytesIO() + # Only the observer binding changes. Supervisor/readiness time + # and its inert child remain real and independently supervised. + with patch.object(attempt, "time", SimpleNamespace(sleep=sleeps.append)), \ + patch.object(attempt.sys, "stdout", SimpleNamespace(buffer=stdout)): + with self.assertRaisesRegex(ValueError, "inert common service refused") as caught: + self.observe_capture(ctx, diagnostic=diagnostic) + self.assertEqual(stdout.getvalue(), b"") + calls = [row[0] for row in ctx.services[0].calls] + self.assertEqual([label for label in calls if label.startswith("approvals-")], + ["approvals-" + str(index) for index in range(count)]) + self.assertEqual(len(sleeps), 20 if fault == "empty-pending" else 0) + self.assertTrue(all(0 < value <= 0.25 for value in sleeps)) + self.assertFalse(any(label in ("approve-once", "history", "shutdown") + or label.startswith("snapshot-") for label in calls)) + self.assertFalse((ctx.root / "events.jsonl").exists()) + self.assertEqual(list(ctx.data.iterdir()), []) + line = diagnostic.line(caught.exception) + self.assertLessEqual(len(line.encode()), 2048) + for private in (inert.TOKEN, inert.KEY, "/private/path", "private-pending-row"): + self.assertNotIn(private, line) + value = json.loads(line.removeprefix("Kimi collector failure: ")) + self.assertEqual(value["approval_poll_refusal"], poll.Refusal(reason, count).public()) + self.assertNotIn("approval_refusal", value) + self.assertEqual((value["stage"], value["phase"], value["exception_kind"]), + ("approvals", "validate", "validation")) + self.assertTrue(value["failure"]) + self.assertTrue(value["cleanup"]["group_reaped"]) + self.assertTrue(value["cleanup"]["port_closed"]) + self.assertFalse(value["cleanup"]["forced_kill"]) + self.assertTrue(value["cleanup"]["sigterm_sent"]) + self.assertFalse(value["cleanup"]["shutdown_acknowledged"]) + self.assertFalse(any(ctx.summary["qualification"].values())) + + def test_closed_facts_reject_unknown_and_hostile_values_before_operations(self): + good = poll.Refusal("multiple-pending", 1).public() + bad = [None, [], Hostile(), {Hostile(): "private"}, {**good, "extra": "private"}] + bad.extend({key: value for key, value in good.items() if key != missing} for missing in good) + for key in ("topic", "status", "reason"): + bad.extend({**good, key: value} for value in (None, True, 1, [], {}, Hostile(), "private")) + bad.extend({**good, "polls": value} for value in (True, False, 0, -1, 21, 1.0, "1", [], {}, Hostile())) + bad.extend({**good, "reason": "no-pending-after-polls", "polls": value} for value in (1, 19)) + for value in bad: + with self.assertRaises(ValueError): + poll.validate(value) + for count in (1, 20): + self.assertEqual(poll.Refusal("multiple-pending", count).public()["polls"], count) + self.assertEqual(poll.Refusal("no-pending-after-polls", 20).public()["polls"], 20) + error = poll.Refusal("multiple-pending", 1) + projected = error.public() + projected["reason"] = "private" + self.assertEqual(error.public(), good) + + @staticmethod + def raise_error(error): + raise error + + def test_first_cause_is_shared_by_row_and_poll_facts(self): + row = row_facts.Refusal(row_facts.project({}, "session_owned", "provider_owned", {}, + attempt.TOOL, attempt.kimi._instant)) + pending = poll.Refusal("multiple-pending", 1) + for first, second, field in ((row, pending, "approval_refusal"), + (pending, row, "approval_poll_refusal")): + diagnostic = attempt.kimi._KimiFailureDiagnostic("single-tool") + diagnostic.step("approvals", "validate") + for error in (first, second): + with self.assertRaises(ValueError): + diagnostic.observer(lambda _service, held=error: self.raise_error(held))(None) + value = json.loads(diagnostic.line(second).removeprefix("Kimi collector failure: ")) + self.assertEqual(value[field], first.public()) + self.assertEqual(set(value) & {"approval_refusal", "approval_poll_refusal"}, {field}) + diagnostic.step("service-cleanup") + cleanup = json.loads(diagnostic.line(OSError("private-cleanup")).removeprefix("Kimi collector failure: ")) + self.assertFalse(set(cleanup) & {"approval_refusal", "approval_poll_refusal"}) + diagnostic.step("approvals", "validate") + self.assertEqual(json.loads(diagnostic.line(second).removeprefix("Kimi collector failure: "))[field], + first.public()) + + def test_unrelated_or_corrupt_optional_facts_keep_generic_failure(self): + for collector, stage, phase in (("installed", "approvals", "validate"), + ("single-tool", "ready", "enter"), + ("single-tool", "approvals", "request")): + diagnostic = attempt.kimi._KimiFailureDiagnostic(collector) + diagnostic.step(stage, phase) + with self.assertRaises(ValueError): + diagnostic.observer(lambda _service: self.raise_error(poll.Refusal("multiple-pending", 1)))(None) + self.assertIsNone(diagnostic.approval_poll_refusal) + diagnostic = attempt.kimi._KimiFailureDiagnostic("single-tool") + diagnostic.step("approvals", "validate") + with self.assertRaises(ValueError): + diagnostic.observer(lambda _service: self.raise_error(ValueError("private-error")))(None) + self.assertIsNone(diagnostic.approval_poll_refusal) + for corrupt in ({"private": "secret"}, {**poll.Refusal("multiple-pending", 1).public(), "polls": True}): + diagnostic.approval_poll_refusal = copy.deepcopy(corrupt) + stream = io.StringIO() + attempt.kimi._emit_failure_diagnostic(diagnostic, ValueError("private-error"), stream) + value = json.loads(stream.getvalue().removeprefix("Kimi collector failure: ")) + self.assertTrue(value["failure"]) + self.assertNotIn("approval_poll_refusal", value) + self.assertNotIn("private", stream.getvalue()) + diagnostic.approval_poll_refusal = poll.Refusal("multiple-pending", 1).public() + diagnostic.approval_refusal = dict(row_facts.REFUSED) + self.assertNotIn("approval_poll_refusal", diagnostic.line(ValueError())) + self.assertNotIn("approval_refusal", diagnostic.line(ValueError())) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test_project_native_kimi_attempt.py b/scripts/test_project_native_kimi_attempt.py index 5ca74b94d..721e72a6c 100644 --- a/scripts/test_project_native_kimi_attempt.py +++ b/scripts/test_project_native_kimi_attempt.py @@ -66,6 +66,10 @@ def http(self, label, method, path, token=None, json_body=None, timeout=5): data["unmeasured_field"] = TOKEN + KEY if self.ctx.fault == "later-turn": data["items"][0]["turn_id"] = 1 + if self.ctx.fault == "multiple-pending": + data["items"].append({"private-pending-row": TOKEN + KEY + " /private/path"}) + elif self.ctx.fault == "empty-pending": + data["items"] = [] elif label == "approve-once": data = {"resolved": True, "resolved_at": STAMP} elif label.startswith("snapshot-"): From 25673dc043cc29c1eadd2e31f26ed516b264f048 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 22:08:01 +0100 Subject: [PATCH 31/42] docs(kimi): record reviewed poll refusal diagnostic validation --- ...PROJECT_NATIVE_APPROVAL_POLL_VALIDATION.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_APPROVAL_POLL_VALIDATION.md diff --git a/docs/KIMI_PROJECT_NATIVE_APPROVAL_POLL_VALIDATION.md b/docs/KIMI_PROJECT_NATIVE_APPROVAL_POLL_VALIDATION.md new file mode 100644 index 000000000..9292f62be --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_APPROVAL_POLL_VALIDATION.md @@ -0,0 +1,42 @@ +# Kimi approval poll refusal diagnostic: validation + +Code reviewed: `8311c8e9f571e9fa272abc2daed1a2f367ad160e`. +Tree: `86e78b33b391186b85641fd1838e5e608c85c20b`. +Base: `b2bb2b7f85b5461420f932478b197905e6fc5ad9`. + +The approved plan was committed before code as +`f4aa9ad63dd6726d936b808d59dba0ea39cbe31a`. Two independent GPT-6.1 +Sol xhigh pre-code reviews accepted the same plan; two independent exact-code +reviews then accepted the implementation without blocking findings. + +## Actual validation + +- Inert Python Kimi suite: 57 tests passed in5.650 seconds. +- Inert runtime admission suite: 27 tests passed in6.417 seconds. +- Original b2bb single-tool artifact: all23 raw member bodies admitted unchanged; + its failed result and allfalse qualification claims remained unchanged. +- AST parsing, source hashes and git whitespace checks passed. + +The tests exercise the actual polling observer and its ordinary supervisor around +an existing harmless Python child. They check both refusal causes, the unchanged +20poll boundary, no approval/history/snapshot/shutdown action after refusal, +quarantine, cleanup facts, finite scalar projection, privacy, hostile types, +malformed ambiguity, copied facts, mixed first causes and later cleanup errors. +This gate did not run a native vendor binary, installer or Go process. + +The optional diagnostic does not turn the existing failure into success. It adds +only a bounded reason for the two original multiple-pending guards and the +original exhaustion guard. Existing manual approval validation, timings, +quarantine, supervision and qualification conditions remain unchanged. + +## Exact review and executor records + +- `/tmp/uap-additional-kimi-poll-refusal-code-R1.md`: SHA256 `51ee412c539cdb59e0087ca3a2b6fe0759c94b59364cbb9ef1704131e4ca51d3`. +- `/tmp/uap-kimi-poll-refusal-code-r2-review.json`: SHA256 `b07cca2fcb15eaeb7ee8d2339ce878384542a8c7f0a3096d7d96f12b9e3ce992`. +- `/tmp/uap-additional-kimi-poll-validation-442uupq9/report.json`: SHA256 `33f5eedbbe75926aee8c5b6aeb9dd7b026dab29f0a4298021a33de4dd0cc3ad6`. + +Local record paths identify this execution's review receipts; the committed +source and this validation description remain the durable repository record. +Native follow-through still requires a genuine disposable hosted run after +publication of the composed Kimi fixes. This validation makes no authentication, +model, complete lifecycle, release or main-merge claim. From a87dd947572c78c2787db56827087121bedda1db Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 22:11:10 +0100 Subject: [PATCH 32/42] docs(kimi): record reviewed session cache metadata plan --- docs/KIMI_PROJECT_NATIVE_CACHE_KEY_PLAN.md | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_CACHE_KEY_PLAN.md diff --git a/docs/KIMI_PROJECT_NATIVE_CACHE_KEY_PLAN.md b/docs/KIMI_PROJECT_NATIVE_CACHE_KEY_PLAN.md new file mode 100644 index 000000000..b2fc8d4d7 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_CACHE_KEY_PLAN.md @@ -0,0 +1,50 @@ +# Kimi single-tool provider: observed session cache metadata + +Status: PRE-CODE draft; no provider implementation changes authorized until two independent GOs on this exact plan. Base source is PR524 b2bb2b7f85b5461420f932478b197905e6fc5ad9; implementation will be composed on the reviewed disjoint polling-diagnostic branch (code8311c8e9f571e9fa272abc2daed1a2f367ad160e) and then the independently reviewed scoped-selection fix. Provider source is byte-identical on these bases. + +## Concrete observed failure + +Genuine disposable GitHub-hosted run40569825577, job122438517585, source b2bb2b7f85b5461420f932478b197905e6fc5ad9/tree b12354b2565a05d14037556f8493b149d7d36ea2 produced native artifact12768461663 (23 members). Its original provider-request-1.json has SHA256 ddd8a402037716fe582f854dcf5d5dae1ebb902dc6b3a962466e4d34874c4de7. It offers exactly the installed fixture tool/model and includes prompt_cache_key, a44-byte string of the form session_. Raw request remains in /tmp/uap-additional-qoder-kimicall-fresh-ka537i9_/12768461663/members/provider-request-1.json. No session identifier needs to be copied into repository documentation. + +The current kimiAttemptRequest top-level allowlist excludes prompt_cache_key. The provider appends the raw request, validates that envelope, and returns400 on error before setting issued or producing its owned tool call. This source path plus the observed body identifies the first provider rejection. HTTP response bytes and the native empty approval response are not independently exported. The observed failed result, ordinary quarantine, SIGTERM cleanup and allfalse qualifications remain truthful. + +## Pinned primary-source basis + +Repository MoonshotAI/kimi-code, commit b8dd3a134060c6e80ba4a1659335cd63b68483f2. Source archive and exact Git blobs were read only; no vendor code was executed locally. Every cited file was verified against the complete pinned Git tree and its Git blob hash. + +- packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycleService.ts, blob b3eab53a221dca58aab5876a68d04da64da0e6f5: createSessionId returns session_ followed by randomUUID(). +- packages/agent-core-v2/src/agent/profile/profileService.ts, blob c73828a3be887c221ad16155898ecd52f9218ffe: model request params use this.sessionContext.sessionId as cacheKey. +- packages/agent-core-v2/src/llm-adapter/model/model-requester-impl.ts, blob83cc1cbde9c44724864b4f19a7ce73c96366673a: forwards params.cacheKey to the requester. +- packages/agent-core-v2/src/human/llm/requester/bases/openai/requester.ts, blob3726b5b38dace425ea3da588e53971991a6b1307: encodes an optional input.cacheKey. +- packages/agent-core-v2/src/human/llm/requester/bases/openai/format.ts, blob d074e9c1fb310d78f3dd1f497f28bb4481e2ed75: maps the value to prompt_cache_key. + +Source receipts, hashes and excerpts: /tmp/uap-additional-kimi-cache-key-source-thrtbnmr/cache-key-source-trace.json and session-id-source-trace.json. The field is request metadata, not a credential or tool ownership proof. + +## Exact intended change + +1. Extend only the Kimi single-tool test provider's known optional envelope fields with prompt_cache_key. If present, require a built-in Go string of exactly44 ASCII bytes matching session_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}. Use anchored bounded validation; reject null, booleans, numeric/array/object values, malformed/oversized/noncanonical strings. Absence remains valid for preserved inert requests and the source-supported no-cache case. Do not add other request fields or widen message/tool grammars. +2. Hold the first admitted request's cache metadata presence/value in provider state (empty string represents absence, because empty values are invalid). Require exact consistency on the second admitted completion request; added, removed or changed cache metadata fails before validating tool result or marking returned. This is an additional same-session invariant supported by the source's sessionContext binding. It does not establish equality to the native API session, which is not available to the Go provider at this stage; no such join will be claimed. Do not include raw values in errors or new diagnostics. +3. Preserve original bearer key, host/route/MIME/body/count limits, installed model/exact-one-tool declaration and schema, first-call no preexisting result, owned call ID/nonce/operation/marker, two-turn result validation, SSE framing, first-error latch and counters. Cache metadata cannot change any ownership or qualification decision. +4. Preserve client pin/config, default manual permission mode, actual approval validation/action, native service supervision, deadlines/poll count, artifact format/privacy/admission, original HOME/profile and whole-profile guard. No auth/trust/credential/pin/route/timeout/baseline workaround, fake hosted marker or local vendor execution. + +## Meaningful validation + +Before parser code changes, add a permanent inert regression with a fresh synthetic canonical session key and capture its failure against the old parser. Separately replay the unchanged genuine raw request through the old exact parser and the real in-process provider using a temporary test file; retain the assertion failure and exact raw hash. Do not commit the private/session-bearing raw request. Environmental setup failure does not count as RED. + +After implementation: +- Exact unchanged genuine raw request passes its envelope and reaches the existing owned first SSE response under a separately held inert provider credential. This is an inert request replay, not native execution or new end-to-end proof. Verify issued=true/returned=false, one captured request, unchanged raw bytes, one request count, no validation callback, expected owned call and no terminal completion. +- Permanent positive tests cover absent metadata compatibility and exact same canonical metadata through both real in-process provider turns, using the existing installed tool/nonce/marker facts. Verify original second-turn facts and terminal sentinel, request counts and no error. +- Permanent table-driven refusals cover wrong type, empty, prefix/length/hyphen/hex/case/control/oversized values and duplicate JSON key. Unknown fields/foreign tools/schema/duplicate JSON remain refused. Exercise the real provider path for representative invalid metadata, not only the helper. +- Second-turn changed/added/removed metadata refuses before result validation/returned. A later valid request cannot recover a refused provider. Verify source arguments, original model/tool/credential/route and counts remain required. Existing wrong-key/host/call/arguments/extra-turn tests stay intact; include metadata-bearing wrong-key, wrong route or foreign tool controls so metadata cannot bypass those gates. +- Run focused inert Go Kimi/provider contracts and existing relevant project-native contracts with no vendor opt-in. Serialized Go/lint slot; compile/test failures are recorded separately. gofmt and git diff --check. Affected all-enabled lint plus current six-linter gate. Read/replay the original failed artifact through the exact composed-source Python intake to confirm it stays failed/allfalse and keeps identical member bodies. No redundant broad test fan-out. + +## Review, publication and evidence boundary + +Record both exact pre-code GOs, RED/GREEN and final source manifest. Obtain two independent exact implementation reviews, fix actual findings, then compose with the separately reviewed scoped-selection and poll-diagnostic slices. Root alone performs normal expected-head fast-forward publication to existing PR524; no forced update, replacement PR, release or main merge. Actual hosted E2E after publication determines the next client state. Retain failures/unknowns without promoting artifact admission or source guidance to native qualification. VSCode remains deferred and unchanged. + +## Accepted before implementation + +Two independent GPT-6.1 Sol xhigh reviewers accepted corrected draft SHA256 `383c3df38324513560aca8c0d1cc878eb312c59668cfbb9429e9a11e9f633978`. The earlier draft mistakenly labeled the cache-value hash as the whole request hash; both reviewers verified the corrected original7839B body before granting GO. + +- `/tmp/uap-additional-kimi-cache-key-plan-R1.md`: SHA256 `8728c97e602b2177dfe3a9a3b50dc1220311917ef4fcfcd6f6d2649ab1aa6c6d`. +- `/tmp/uap-kimi-cache-key-plan-r2-review.json`: SHA256 `37651c4f69c89fd8def9101dee346eee709fe5cb20f657ea5df1bf6eb2d28c03`. From f09fbb1e52960c56e6b731aaf2aafc1c2aefd01b Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 22:13:17 +0100 Subject: [PATCH 33/42] fix(kimi): retain explicitly scoped MCP selection during repair --- .../project_kimi_intact_repair_test.go | 38 +++- .../agentplugins/clients/identity.go | 7 + .../agentplugins/clients/kimi/adapter.go | 12 ++ .../clients/kimi/selection_scope_test.go | 29 ++++ .../project_kimi_intact_repair_test.go | 43 ++++- .../providers/managed_selection.go | 31 +++- .../managed_selection_scoped_test.go | 163 ++++++++++++++++++ .../providers/project_kimi_selection_test.go | 42 +++++ .../agentplugins/usecase/managed_selection.go | 30 +++- .../usecase/managed_selection_scoped_test.go | 125 ++++++++++++++ .../agentplugins/usecase/native_binding.go | 10 ++ 11 files changed, 513 insertions(+), 17 deletions(-) create mode 100644 install/integrationctl/agentplugins/clients/kimi/selection_scope_test.go create mode 100644 install/integrationctl/agentplugins/providers/managed_selection_scoped_test.go create mode 100644 install/integrationctl/agentplugins/providers/project_kimi_selection_test.go create mode 100644 install/integrationctl/agentplugins/usecase/managed_selection_scoped_test.go diff --git a/cli/cmd/agentplugins/project_kimi_intact_repair_test.go b/cli/cmd/agentplugins/project_kimi_intact_repair_test.go index bf630efef..77c9a9a1d 100644 --- a/cli/cmd/agentplugins/project_kimi_intact_repair_test.go +++ b/cli/cmd/agentplugins/project_kimi_intact_repair_test.go @@ -1,11 +1,14 @@ package main import ( + "encoding/json" "os" "path/filepath" "reflect" "strings" "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" ) // Exercise the public App with an intact recorded artifact. The existing @@ -30,6 +33,7 @@ func TestProjectKimiPublicIntactRepairPreservesMCPSelection(t *testing.T) { skill := filepath.Join(journey.a, ".kimi-code", "skills", "docs", "SKILL.md") beforeMCP, beforeSkill := projectJourneyRead(t, mcp), projectJourneyRead(t, skill) output := journey.run(t, journey.a, "repair", installation.InstallationID, "--target", "kimi") + projectKimiIntactRepairSelection(t, output, installation.InstallationID) if projectJourneyRead(t, mcp) != beforeMCP || projectJourneyRead(t, skill) != beforeSkill { t.Fatal("intact public repair changed native skill or MCP bytes") } @@ -37,7 +41,7 @@ func TestProjectKimiPublicIntactRepairPreservesMCPSelection(t *testing.T) { t.Fatal("intact public repair discarded recorded project MCP selection") } after, repaired := projectKimiVersionOwned(t, journey, projectJourneySkill, false) - if after.InstallationID != installation.InstallationID || !reflect.DeepEqual(after.Source, installation.Source) || repaired.ClientBindingID != binding.ClientBindingID || repaired.DataReceiptID != binding.DataReceiptID || !reflect.DeepEqual(repaired.NativeObjects, binding.NativeObjects) { + if after.InstallationID != installation.InstallationID || !reflect.DeepEqual(after.Source, installation.Source) || !reflect.DeepEqual(after.Package, installation.Package) || repaired.ClientBindingID != binding.ClientBindingID || repaired.PhysicalArtifact != binding.PhysicalArtifact || repaired.DataReceiptID != binding.DataReceiptID || !reflect.DeepEqual(repaired.NativeObjects, binding.NativeObjects) { t.Fatal("intact repair changed held installation, binding, data or native ownership") } if !reflect.DeepEqual(beforeUser, securityFixtureTree(t, user)) || !reflect.DeepEqual(beforeB, securityFixtureTree(t, journey.b)) { @@ -50,4 +54,36 @@ func TestProjectKimiPublicIntactRepairPreservesMCPSelection(t *testing.T) { } } +func projectKimiIntactRepairSelection(t *testing.T, output, installationID string) { + t.Helper() + var envelope struct { + Data struct { + Targets []struct { + Target string `json:"target"` + Output struct { + Result struct { + InstallationID string `json:"installation_id"` + Plan domain.DeliveryPlan `json:"plan"` + } `json:"result"` + } `json:"output"` + } `json:"targets"` + } `json:"data"` + } + // The journey helper appends diagnostic stderr after the JSON stdout. + if err := json.NewDecoder(strings.NewReader(output)).Decode(&envelope); err != nil || len(envelope.Data.Targets) != 1 { + t.Fatal("public repair did not return one target result", err) + } + target := envelope.Data.Targets[0] + plan := target.Output.Result.Plan + if target.Target != "kimi" || target.Output.Result.InstallationID != installationID || plan.Scope != domain.ScopeProject || plan.ClientID != domain.ClientKimi { + t.Fatal("public repair changed explicit selected identity") + } + for _, component := range plan.Components { + if component.Kind == domain.ComponentMCPServer && component.Name == "docs" && component.Support == domain.SupportNative && component.Reason == "" { + return + } + } + t.Fatal("public repair did not preserve the recorded native MCP component") +} + const kimiIntactForeign = `{"mcpServers":{"foreign":{"transport":"http","url":"https://foreign.test/mcp","enabled":false}},"policy":"keep"}` diff --git a/install/integrationctl/agentplugins/clients/identity.go b/install/integrationctl/agentplugins/clients/identity.go index 35c35e3b5..bc60889eb 100644 --- a/install/integrationctl/agentplugins/clients/identity.go +++ b/install/integrationctl/agentplugins/clients/identity.go @@ -62,3 +62,10 @@ var DefaultSelectionLayout = SelectionLayout{File: "mcp.json", Nested: true} type SelectionReader interface { ManagedMCPSelection() SelectionLayout } + +// ScopedSelectionReader chooses the managed selection layout for an explicit +// installation scope. It does not infer scope from artifact or native files. +// Adapters without this policy retain their existing SelectionReader layout. +type ScopedSelectionReader interface { + ManagedMCPSelectionForScope(domain.InstallScope) (SelectionLayout, error) +} diff --git a/install/integrationctl/agentplugins/clients/kimi/adapter.go b/install/integrationctl/agentplugins/clients/kimi/adapter.go index 41bb15641..4472fc95f 100644 --- a/install/integrationctl/agentplugins/clients/kimi/adapter.go +++ b/install/integrationctl/agentplugins/clients/kimi/adapter.go @@ -22,6 +22,7 @@ var ( _ clients.TargetLayout = (*Adapter)(nil) _ clients.PlanRefiner = (*Adapter)(nil) _ clients.SelectionReader = (*Adapter)(nil) + _ clients.ScopedSelectionReader = (*Adapter)(nil) _ clients.Projector = (*Adapter)(nil) _ clients.Lifecycle = (*Adapter)(nil) _ clients.ActivationPreflighter = (*Adapter)(nil) @@ -61,4 +62,15 @@ func (*Adapter) ManagedMCPSelection() clients.SelectionLayout { return clients.SelectionLayout{File: filepath.Join(".kimi-plugin", "plugin.json"), Nested: true, ServersMemberOptional: true} } +func (a *Adapter) ManagedMCPSelectionForScope(scope domain.InstallScope) (clients.SelectionLayout, error) { + switch scope { + case domain.ScopeUser: + return a.ManagedMCPSelection(), nil + case domain.ScopeProject: + return clients.SelectionLayout{File: "mcp.json", Nested: true}, nil + default: + return clients.SelectionLayout{}, fmt.Errorf("kimi managed selection requires an explicit user or project scope") + } +} + const reloadAction = "Run /reload or /new in Kimi Code to load the installed plugin; runtime MCP connections and authentication have not been verified." diff --git a/install/integrationctl/agentplugins/clients/kimi/selection_scope_test.go b/install/integrationctl/agentplugins/clients/kimi/selection_scope_test.go new file mode 100644 index 000000000..67abba150 --- /dev/null +++ b/install/integrationctl/agentplugins/clients/kimi/selection_scope_test.go @@ -0,0 +1,29 @@ +package kimi + +import ( + "path/filepath" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" +) + +func TestManagedMCPSelectionHasExplicitScopeAndKeepsUserAPI(t *testing.T) { + adapter := New() + user := clients.SelectionLayout{File: filepath.Join(".kimi-plugin", "plugin.json"), Nested: true, ServersMemberOptional: true} + project := clients.SelectionLayout{File: "mcp.json", Nested: true} + if adapter.ManagedMCPSelection() != user { + t.Fatal("legacy user selection policy changed") + } + for scope, want := range map[domain.InstallScope]clients.SelectionLayout{domain.ScopeUser: user, domain.ScopeProject: project} { + got, err := adapter.ManagedMCPSelectionForScope(scope) + if err != nil || got != want { + t.Fatalf("scope %q layout=%+v err=%v", scope, got, err) + } + } + for _, scope := range []domain.InstallScope{"", "unknown", "PROJECT"} { + if _, err := adapter.ManagedMCPSelectionForScope(scope); err == nil { + t.Fatalf("unrecognized scope %q accepted", scope) + } + } +} diff --git a/install/integrationctl/agentplugins/installer/project_kimi_intact_repair_test.go b/install/integrationctl/agentplugins/installer/project_kimi_intact_repair_test.go index 8ad67b24e..3025d0c4a 100644 --- a/install/integrationctl/agentplugins/installer/project_kimi_intact_repair_test.go +++ b/install/integrationctl/agentplugins/installer/project_kimi_intact_repair_test.go @@ -1,6 +1,7 @@ package installer_test import ( + "encoding/json" "os" "path/filepath" "reflect" @@ -38,26 +39,54 @@ func kimiSDKIntactPair(t *testing.T) (*kimiSDKFixture, *kimiSDKFixture) { func TestProjectKimiSDKIntactRepairPreservesMCPSelection(t *testing.T) { a, b := kimiSDKIntactPair(t) binding := a.binding(t) + installation := kimiSDKIntactInstallation(t, a) mcp := filepath.Join(a.project, ".kimi-code", "mcp.json") skill := filepath.Join(a.project, ".kimi-code", "skills", "docs", "SKILL.md") beforeMCP, beforeSkill := kimiSDKRead(t, mcp), kimiSDKRead(t, skill) - beforeB := kimiSDKRead(t, filepath.Join(b.project, ".uap", "state-v2.json")) + beforeB, beforeUser := qoderSDKTree(t, b.project), qoderSDKTree(t, a.user) result := a.apply(t, installer.OpRepair, "repair-intact") - if !result.Mutated || result.Client.Activation != string(domain.ActivationPrepared) { - t.Fatal("intact repair lost its actual prepared mutation outcome") - } + kimiSDKIntactSelection(t, result.Delivery) if kimiSDKRead(t, mcp) != beforeMCP || kimiSDKRead(t, skill) != beforeSkill { t.Fatal("intact SDK repair changed native skill or MCP bytes") } repaired := a.binding(t) - if repaired.ClientBindingID != binding.ClientBindingID || repaired.PhysicalArtifact != binding.PhysicalArtifact || repaired.DataReceiptID != binding.DataReceiptID || !reflect.DeepEqual(repaired.NativeObjects, binding.NativeObjects) { + after := kimiSDKIntactInstallation(t, a) + if after.InstallationID != installation.InstallationID || !reflect.DeepEqual(after.Source, installation.Source) || !reflect.DeepEqual(after.Package, installation.Package) { + t.Fatal("intact SDK repair changed installation or pinned source identity") + } + if repaired.Activation != domain.ActivationPrepared || repaired.Verification != domain.VerificationInstalled || repaired.ClientBindingID != binding.ClientBindingID || repaired.PhysicalArtifact != binding.PhysicalArtifact || repaired.DataReceiptID != binding.DataReceiptID || !reflect.DeepEqual(repaired.NativeObjects, binding.NativeObjects) { t.Fatal("intact SDK repair changed held binding, data or native ownership") } - if kimiSDKRead(t, filepath.Join(b.project, ".uap", "state-v2.json")) != beforeB || kimiSDKRead(t, filepath.Join(a.user, "mcp.json")) != kimiSDKIntactForeign || kimiSDKRead(t, filepath.Join(a.data, "keep.txt")) != "retained data" { - t.Fatal("intact SDK repair changed user, B or retained data") + if !reflect.DeepEqual(beforeB, qoderSDKTree(t, b.project)) || !reflect.DeepEqual(beforeUser, qoderSDKTree(t, a.user)) { + t.Fatal("intact SDK repair changed original user or B trees") + } + if kimiSDKRead(t, filepath.Join(a.data, "keep.txt")) != "retained data" { + t.Fatal("intact SDK repair changed retained data") } kimiSDKCheckStdio(t, a) kimiSDKCheckStdio(t, b) } +func kimiSDKIntactInstallation(t *testing.T, f *kimiSDKFixture) domain.Installation { + t.Helper() + var state domain.StateFileV2 + if err := json.Unmarshal([]byte(kimiSDKRead(t, filepath.Join(f.project, ".uap", "state-v2.json"))), &state); err != nil || len(state.Installations) != 1 { + t.Fatal("SDK did not persist one selected installation", err) + } + return state.Installations[0] +} + +func kimiSDKIntactSelection(t *testing.T, plan *installer.DeliveryPlan) { + t.Helper() + if plan == nil { + t.Fatal("intact SDK repair omitted its actual delivery plan") + } + for _, component := range plan.Components { + if component.Kind == string(domain.ComponentMCPServer) && component.Name == "docs" && component.Support == string(domain.SupportNative) && component.Reason == "" { + return + } + } + t.Fatal("intact SDK repair did not retain native MCP support") +} + const kimiSDKIntactForeign = `{"mcpServers":{"original-user":{"transport":"http","url":"https://original.test/mcp"}},"preference":"keep"}` diff --git a/install/integrationctl/agentplugins/providers/managed_selection.go b/install/integrationctl/agentplugins/providers/managed_selection.go index 2d016981a..e1dee723f 100644 --- a/install/integrationctl/agentplugins/providers/managed_selection.go +++ b/install/integrationctl/agentplugins/providers/managed_selection.go @@ -16,15 +16,28 @@ import ( // A second verification detects artifact drift during the read. It does not // promise protection from same-principal ABA races. func (stager Stager) ManagedMCPNames(ctx context.Context, client domain.ClientID, root, digest string) ([]string, error) { + return stager.managedMCPNames(ctx, client, "", root, digest) +} + +// ManagedMCPNamesForScope reads the explicitly scoped selection without changing +// the existing unscoped API. Refused scoped policy never retries another layout. +func (stager Stager) ManagedMCPNamesForScope(ctx context.Context, client domain.ClientID, scope domain.InstallScope, root, digest string) ([]string, error) { + if scope != domain.ScopeUser && scope != domain.ScopeProject { + return nil, fmt.Errorf("managed MCP selection requires an explicit user or project scope") + } + return stager.managedMCPNames(ctx, client, scope, root, digest) +} + +func (stager Stager) managedMCPNames(ctx context.Context, client domain.ClientID, scope domain.InstallScope, root, digest string) ([]string, error) { if err := stager.requireDeps(); err != nil { return nil, err } if err := stager.Verify(ctx, root, digest); err != nil { return nil, err } - layout := clients.DefaultSelectionLayout - if reader, ok := clients.As[clients.SelectionReader](stager.Registry, client); ok { - layout = reader.ManagedMCPSelection() + layout, err := stager.managedSelectionLayout(client, scope) + if err != nil { + return nil, err } names, err := readSelectionNames(root, layout) if err != nil { @@ -37,6 +50,18 @@ func (stager Stager) ManagedMCPNames(ctx context.Context, client domain.ClientID return names, nil } +func (stager Stager) managedSelectionLayout(client domain.ClientID, scope domain.InstallScope) (clients.SelectionLayout, error) { + if scope != "" { + if reader, ok := clients.As[clients.ScopedSelectionReader](stager.Registry, client); ok { + return reader.ManagedMCPSelectionForScope(scope) + } + } + if reader, ok := clients.As[clients.SelectionReader](stager.Registry, client); ok { + return reader.ManagedMCPSelection(), nil + } + return clients.DefaultSelectionLayout, nil +} + func readSelectionNames(root string, layout clients.SelectionLayout) ([]string, error) { candidate := filepath.Join(root, layout.File) info, statErr := os.Lstat(candidate) diff --git a/install/integrationctl/agentplugins/providers/managed_selection_scoped_test.go b/install/integrationctl/agentplugins/providers/managed_selection_scoped_test.go new file mode 100644 index 000000000..0d4bc482f --- /dev/null +++ b/install/integrationctl/agentplugins/providers/managed_selection_scoped_test.go @@ -0,0 +1,163 @@ +package providers + +import ( + "errors" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients/kimi" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/ports" +) + +func scopedSelectionArtifact(t *testing.T, files map[string]string) (Stager, string, string) { + t.Helper() + root := t.TempDir() + for name, body := range files { + path := filepath.Join(root, name) + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(body), 0600); err != nil { + t.Fatal(err) + } + } + stager := testStager(Stager{}) + var mismatch *ports.VerificationError + if err := stager.Verify(t.Context(), root, "observe"); !errors.As(err, &mismatch) { + t.Fatal("actual staged-artifact digest was not observed", err) + } + return stager, root, mismatch.ActualDigest +} + +func TestScopedManagedMCPSelectionDoesNotInferLayoutFromFiles(t *testing.T) { + stager, root, digest := scopedSelectionArtifact(t, map[string]string{ + "mcp.json": `{"mcpServers":{"project-only":{}}}`, + ".kimi-plugin/plugin.json": `{"name":"demo","mcpServers":{"user-only":{}}}`, + }) + for scope, name := range map[domain.InstallScope]string{domain.ScopeUser: "user-only", domain.ScopeProject: "project-only"} { + names, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, scope, root, digest) + if err != nil || !reflect.DeepEqual(names, []string{name}) { + t.Fatalf("explicit scope %q names=%v err=%v", scope, names, err) + } + } + names, err := stager.ManagedMCPNames(t.Context(), domain.ClientKimi, root, digest) + if err != nil || !reflect.DeepEqual(names, []string{"user-only"}) { + t.Fatal("legacy API no longer reads the original user manifest", names, err) + } + for _, scope := range []domain.InstallScope{"", "unknown"} { + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, scope, root, digest); err == nil { + t.Fatal("unknown scope selected an artifact layout") + } + } +} + +func TestScopedManagedMCPSelectionRequiresSelectedDocumentAndArtifact(t *testing.T) { + for _, body := range []string{`{"servers":{}}`, `null`, `[]`} { + stager, root, digest := scopedSelectionArtifact(t, map[string]string{ + "mcp.json": body, ".kimi-plugin/plugin.json": `{"mcpServers":{"alternate":{}}}`, + }) + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, root, digest); err == nil { + t.Fatal("malformed selected document fell back to the alternate") + } + } + stager, root, digest := scopedSelectionArtifact(t, map[string]string{"mcp.json": `{"mcpServers":{"held":{}}}`}) + if err := os.WriteFile(filepath.Join(root, "mcp.json"), []byte(`{"mcpServers":{"changed":{}}}`), 0600); err != nil { + t.Fatal(err) + } + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, root, digest); err == nil { + t.Fatal("selection trusted damaged artifact bytes") + } +} + +func TestScopedManagedMCPSelectionKeepsEmptyAndOtherAdapterPolicies(t *testing.T) { + stager, root, digest := scopedSelectionArtifact(t, map[string]string{".kimi-plugin/plugin.json": `{"name":"skills-only"}`}) + for _, scope := range []domain.InstallScope{domain.ScopeUser, domain.ScopeProject} { + names, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, scope, root, digest) + if err != nil || names == nil || len(names) != 0 { + t.Fatal("verified skills-only selection differs", scope, names, err) + } + } + stager, root, digest = scopedSelectionArtifact(t, map[string]string{"mcp.json": `{"mcpServers":{"z":{},"a":{}}}`}) + for _, scope := range []domain.InstallScope{domain.ScopeUser, domain.ScopeProject} { + names, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientCursor, scope, root, digest) + if err != nil || !reflect.DeepEqual(names, []string{"a", "z"}) { + t.Fatal("adapter without scoped policy changed", scope, names, err) + } + } +} + +type selectionPolicyAdapter struct { + *kimi.Adapter + choose func(domain.InstallScope) (clients.SelectionLayout, error) +} + +func (a selectionPolicyAdapter) ManagedMCPSelectionForScope(scope domain.InstallScope) (clients.SelectionLayout, error) { + return a.choose(scope) +} + +func TestScopedManagedMCPSelectionVerifiesBeforePolicyAndAfterRead(t *testing.T) { + stager, root, digest := scopedSelectionArtifact(t, map[string]string{"mcp.json": `{"mcpServers":{"held":{}}}`}) + calls := 0 + adapter := selectionPolicyAdapter{Adapter: kimi.New(), choose: func(scope domain.InstallScope) (clients.SelectionLayout, error) { + calls++ + if err := os.WriteFile(filepath.Join(root, "mcp.json"), []byte(`{"mcpServers":{"changed":{}}}`), 0600); err != nil { + return clients.SelectionLayout{}, err + } + return kimi.New().ManagedMCPSelectionForScope(scope) + }} + var err error + stager.Registry, err = clients.NewRegistry(adapter) + if err != nil { + t.Fatal(err) + } + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, root, "wrong"); err == nil || calls != 0 { + t.Fatal("policy ran before complete original artifact verification") + } + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, root, digest); err == nil || calls != 1 { + t.Fatal("second complete verification missed mutation during selection") + } +} + +func TestScopedManagedMCPSelectionPolicyRefusalNeverRetriesUserLayout(t *testing.T) { + stager, root, digest := scopedSelectionArtifact(t, map[string]string{".kimi-plugin/plugin.json": `{"mcpServers":{"user-only":{}}}`}) + sentinel := errors.New("scoped policy refused") + adapter := selectionPolicyAdapter{Adapter: kimi.New(), choose: func(domain.InstallScope) (clients.SelectionLayout, error) { + return clients.SelectionLayout{}, sentinel + }} + var err error + stager.Registry, err = clients.NewRegistry(adapter) + if err != nil { + t.Fatal(err) + } + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, root, digest); !errors.Is(err, sentinel) { + t.Fatal("scoped policy refusal was hidden by a fallback", err) + } +} + +func TestScopedManagedMCPSelectionRejectsLinkedSelectedFiles(t *testing.T) { + for _, symbolic := range []bool{false, true} { + if symbolic && runtime.GOOS == "windows" { + continue // Windows symlink creation may require elevation. + } + stager, root, digest := scopedSelectionArtifact(t, map[string]string{"mcp.json": `{"mcpServers":{"held":{}}}`}) + selected, outside := filepath.Join(root, "mcp.json"), filepath.Join(t.TempDir(), "held-original") + if err := os.Rename(selected, outside); err != nil { + t.Fatal(err) + } + link := os.Link + if symbolic { + link = os.Symlink + } + if err := link(outside, selected); err != nil { + t.Fatal(err) + } + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, root, digest); err == nil { + t.Fatal("selected file alias bypassed full artifact verification") + } + } +} diff --git a/install/integrationctl/agentplugins/providers/project_kimi_selection_test.go b/install/integrationctl/agentplugins/providers/project_kimi_selection_test.go new file mode 100644 index 000000000..f48c22114 --- /dev/null +++ b/install/integrationctl/agentplugins/providers/project_kimi_selection_test.go @@ -0,0 +1,42 @@ +package providers_test + +import ( + "path/filepath" + "reflect" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/adapters/pathpolicy" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/planner" +) + +func TestProjectKimiActualStagedSelectionKeepsProjectAndUserLayouts(t *testing.T) { + f := newKimiProjectFixture(t, false, false) + delivery := f.request.Delivery + names, err := f.stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, delivery.ActivePath, delivery.ArtifactDigest) + if err != nil || !reflect.DeepEqual(names, []string{"docs"}) { + t.Fatal("actual project delivery lost recorded MCP selection", names, err) + } + p := planner.Planner{Registry: f.registry, Paths: pathpolicy.Policy{}, ManagedRoot: filepath.Join(f.base, "user-managed")} + plan, err := p.Plan(t.Context(), domain.PlanRequest{Envelope: f.envelope, Client: f.request.Client, Scope: domain.ScopeUser, PhysicalArtifactID: "user-held-0123456789ab"}) + if err != nil || plan.Status != domain.PlanReady { + t.Fatal("actual user plan failed", err) + } + delivery, err = f.stager.Stage(t.Context(), f.envelope, plan, "user-selection", domain.CompatibilityHints{}) + if err != nil { + t.Fatal(err) + } + for _, read := range []func() ([]string, error){ + func() ([]string, error) { + return f.stager.ManagedMCPNames(t.Context(), domain.ClientKimi, delivery.StagingPath, delivery.ArtifactDigest) + }, + func() ([]string, error) { + return f.stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeUser, delivery.StagingPath, delivery.ArtifactDigest) + }, + } { + names, err := read() + if err != nil || !reflect.DeepEqual(names, []string{"docs"}) { + t.Fatal("actual user delivery or legacy API changed", names, err) + } + } +} diff --git a/install/integrationctl/agentplugins/usecase/managed_selection.go b/install/integrationctl/agentplugins/usecase/managed_selection.go index ed7fc9014..df5154692 100644 --- a/install/integrationctl/agentplugins/usecase/managed_selection.go +++ b/install/integrationctl/agentplugins/usecase/managed_selection.go @@ -14,6 +14,10 @@ type managedSelectionReader interface { ManagedMCPNames(context.Context, domain.ClientID, string, string) ([]string, error) } +type scopedManagedSelectionReader interface { + ManagedMCPNamesForScope(context.Context, domain.ClientID, domain.InstallScope, string, string) ([]string, error) +} + func installationIfExisting(state domain.StateFileV2, index int, existing bool) *domain.Installation { if !existing { return nil @@ -26,7 +30,7 @@ func (service Service) preflightTargetComponents(ctx context.Context, input AddI if prior == nil { return service.preflightComponents(input.Envelope, plan, false) } - names, unverified, err := service.recordedManagedMCPNames(ctx, input, prior, repair, updating) + names, unverified, err := service.recordedManagedMCPNames(ctx, input, plan.Scope, prior, repair, updating) if err != nil { return err } @@ -65,9 +69,13 @@ func priorManagedBinding(installation *domain.Installation, plan *domain.Deliver return nil } -func (service Service) recordedManagedMCPNames(ctx context.Context, input AddInput, prior *domain.ClientBinding, repair, updating bool) ([]string, bool, error) { - reader, ok := service.Stager.(managedSelectionReader) - if !ok { +func (service Service) recordedManagedMCPNames(ctx context.Context, input AddInput, scope domain.InstallScope, prior *domain.ClientBinding, repair, updating bool) ([]string, bool, error) { + if (scope != domain.ScopeUser && scope != domain.ScopeProject) || prior.Scope != string(scope) || (input.Scope != "" && input.Scope != scope) { + return nil, false, fmt.Errorf("managed MCP selection scope differs from the selected binding") + } + _, scoped := service.Stager.(scopedManagedSelectionReader) + _, legacy := service.Stager.(managedSelectionReader) + if !scoped && !legacy { return nil, false, nil } if service.Targets == nil { @@ -75,14 +83,14 @@ func (service Service) recordedManagedMCPNames(ctx context.Context, input AddInp } client := input.Client client.ClientID = domain.ClientID(prior.ClientID) - target, err := service.Targets.ResolveTarget(ctx, client, input.Scope, prior.PhysicalArtifact) + target, err := service.Targets.ResolveTarget(ctx, client, scope, prior.PhysicalArtifact) if err != nil { return nil, false, err } if err := service.Paths.RequireExactPath(target.ActivePath, prior.TargetLocator); err != nil { return nil, false, fmt.Errorf("untrusted persisted target while reading managed MCP selection: %w", err) } - names, err := reader.ManagedMCPNames(ctx, client.ClientID, prior.TargetLocator, managedDigest(*prior)) + names, err := readManagedSelection(ctx, service.Stager, client.ClientID, scope, prior.TargetLocator, managedDigest(*prior)) if err == nil { return names, false, nil } @@ -99,6 +107,16 @@ func (service Service) recordedManagedMCPNames(ctx context.Context, input AddInp return nil, false, fmt.Errorf("managed package was changed or is missing; refusing silent %s before previous MCP selection can be verified: %w", operation, err) } +func readManagedSelection(ctx context.Context, stager ports.PackageStager, client domain.ClientID, scope domain.InstallScope, root, digest string) ([]string, error) { + if reader, ok := stager.(scopedManagedSelectionReader); ok { + return reader.ManagedMCPNamesForScope(ctx, client, scope, root, digest) + } + if reader, ok := stager.(managedSelectionReader); ok { + return reader.ManagedMCPNames(ctx, client, root, digest) + } + return nil, nil +} + func describeUnsupportedManagedSelection(plan *domain.DeliveryPlan, names []string, repair, updating bool) error { supported := map[string]bool{} for _, c := range plan.Components { diff --git a/install/integrationctl/agentplugins/usecase/managed_selection_scoped_test.go b/install/integrationctl/agentplugins/usecase/managed_selection_scoped_test.go new file mode 100644 index 000000000..87b3a206f --- /dev/null +++ b/install/integrationctl/agentplugins/usecase/managed_selection_scoped_test.go @@ -0,0 +1,125 @@ +package usecase + +import ( + "context" + "errors" + "path/filepath" + "reflect" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/adapters/pathpolicy" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/ports" +) + +type legacySelectionStager struct { + ports.PackageStager + calls int +} + +func (s *legacySelectionStager) ManagedMCPNames(context.Context, domain.ClientID, string, string) ([]string, error) { + s.calls++ + return []string{"legacy"}, nil +} + +type selectionRead struct { + client domain.ClientID + scope domain.InstallScope + root, digest string +} + +type scopedSelectionStager struct { + *legacySelectionStager + read selectionRead + calls int + err error +} + +func (s *scopedSelectionStager) ManagedMCPNamesForScope(_ context.Context, client domain.ClientID, scope domain.InstallScope, root, digest string) ([]string, error) { + s.calls++ + s.read = selectionRead{client, scope, root, digest} + return []string{"scoped"}, s.err +} + +func TestScopedManagedSelectionProfileGuardAndErrorNeverFallBack(t *testing.T) { + sentinel := errors.New("scoped refusal") + inner := &scopedSelectionStager{legacySelectionStager: &legacySelectionStager{}, err: sentinel} + guardErr := errors.New("original profile changed") + stager := profileStager{PackageStager: inner, check: func() error { return guardErr }} + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, "/held", "held-digest"); !errors.Is(err, guardErr) || inner.calls != 0 { + t.Fatal("profile failure did not precede the scoped read") + } + guardErr = nil + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, "/held", "held-digest"); !errors.Is(err, sentinel) || inner.calls != 1 || inner.legacySelectionStager.calls != 0 { + t.Fatal("scoped refusal retried the legacy reader") + } + if inner.read != (selectionRead{domain.ClientKimi, domain.ScopeProject, "/held", "held-digest"}) { + t.Fatal("profile wrapper lost explicit selection authority") + } + if _, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, "", "/held", "held-digest"); err == nil || inner.calls != 1 { + t.Fatal("unknown scope reached an inner reader") + } +} + +func TestScopedManagedSelectionLegacyAndAbsentPortsRemainCompatible(t *testing.T) { + legacy := &legacySelectionStager{} + stager := profileStager{PackageStager: legacy, check: func() error { return nil }} + for _, read := range []func() ([]string, error){ + func() ([]string, error) { + return stager.ManagedMCPNames(t.Context(), domain.ClientKimi, "/held", "digest") + }, + func() ([]string, error) { + return stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeUser, "/held", "digest") + }, + } { + names, err := read() + if err != nil || !reflect.DeepEqual(names, []string{"legacy"}) { + t.Fatal("legacy selection API changed", names, err) + } + } + if legacy.calls != 2 { + t.Fatal("legacy reader was not forwarded exactly once per call") + } + stager.PackageStager = nil + if names, err := stager.ManagedMCPNamesForScope(t.Context(), domain.ClientKimi, domain.ScopeProject, "/held", "digest"); names != nil || err != nil { + t.Fatal("absent optional reader gained a selection") + } +} + +type selectionTarget struct { + path string + scope domain.InstallScope + calls int +} + +func (r *selectionTarget) ResolveTarget(_ context.Context, _ domain.DetectedClient, scope domain.InstallScope, _ string) (domain.DeliveryTarget, error) { + r.calls++ + r.scope = scope + return domain.DeliveryTarget{ActivePath: r.path}, nil +} + +func TestScopedManagedSelectionRequiresHeldScopeTargetAndDigest(t *testing.T) { + root := t.TempDir() + reader := &scopedSelectionStager{legacySelectionStager: &legacySelectionStager{}} + target := &selectionTarget{path: root} + service := Service{Stager: reader, Targets: target, Paths: pathpolicy.Policy{}} + prior := &domain.ClientBinding{ClientID: "kimi", Scope: "project", TargetLocator: root, PhysicalArtifact: "held-physical", NativeObjects: []domain.NativeObjectOwnership{{Kind: "managed_package_directory", ManagedDigest: "held-digest"}}} + input := AddInput{Scope: domain.ScopeProject, Client: domain.DetectedClient{ClientID: domain.ClientKimi}} + names, unverified, err := service.recordedManagedMCPNames(t.Context(), input, domain.ScopeProject, prior, true, false) + if err != nil || unverified || !reflect.DeepEqual(names, []string{"scoped"}) || reader.read != (selectionRead{domain.ClientKimi, domain.ScopeProject, root, "held-digest"}) || target.scope != domain.ScopeProject { + t.Fatal("selected scope/locator/digest did not reach the scoped reader", names, err) + } + for _, scope := range []domain.InstallScope{"", "unknown", domain.ScopeUser} { + if _, _, err := service.recordedManagedMCPNames(t.Context(), input, scope, prior, true, false); err == nil || reader.calls != 1 || target.calls != 1 { + t.Fatal("wrong held scope reached target resolution or artifact read") + } + } + target.path = filepath.Join(root, "different-target") + if _, _, err := service.recordedManagedMCPNames(t.Context(), input, domain.ScopeProject, prior, true, false); err == nil || reader.calls != 1 { + t.Fatal("different target locator was trusted") + } + input.Scope = domain.ScopeUser + if _, _, err := service.recordedManagedMCPNames(t.Context(), input, domain.ScopeProject, prior, true, false); err == nil || reader.calls != 1 { + t.Fatal("contradictory input scope was trusted") + } +} diff --git a/install/integrationctl/agentplugins/usecase/native_binding.go b/install/integrationctl/agentplugins/usecase/native_binding.go index d5e5c84d6..3d8fa90cd 100644 --- a/install/integrationctl/agentplugins/usecase/native_binding.go +++ b/install/integrationctl/agentplugins/usecase/native_binding.go @@ -209,6 +209,16 @@ func (s profileStager) ManagedMCPNames(ctx context.Context, id domain.ClientID, return nil, nil } +func (s profileStager) ManagedMCPNamesForScope(ctx context.Context, id domain.ClientID, scope domain.InstallScope, path, digest string) ([]string, error) { + if err := s.check(); err != nil { + return nil, err + } + if scope != domain.ScopeUser && scope != domain.ScopeProject { + return nil, fmt.Errorf("managed MCP selection requires an explicit user or project scope") + } + return readManagedSelection(ctx, s.PackageStager, id, scope, path, digest) +} + type profileData struct { PluginDataManager check func() error From 20921c06b3212f1243bde77f7d6cbbb35adcdeed Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 22:15:27 +0100 Subject: [PATCH 34/42] test(kimi): admit canonical session cache metadata without widening tool authority --- ..._project_native_cache_key_contract_test.go | 163 ++++++++++++++++++ ...ugins_kimi_project_native_provider_test.go | 19 +- 2 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 repotests/agentplugins_kimi_project_native_cache_key_contract_test.go diff --git a/repotests/agentplugins_kimi_project_native_cache_key_contract_test.go b/repotests/agentplugins_kimi_project_native_cache_key_contract_test.go new file mode 100644 index 000000000..b3555e802 --- /dev/null +++ b/repotests/agentplugins_kimi_project_native_cache_key_contract_test.go @@ -0,0 +1,163 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +const kimiCacheContractKey = "session_11111111-2222-4333-8444-555555555555" + +func kimiCacheContractProvider() (*kimiAttemptProvider, *int) { + validated := new(int) + p := &kimiAttemptProvider{server: &httptest.Server{URL: "http://127.0.0.1:43219"}, + key: "inert-provider-key", id: "owned-call", nonce: "owned-nonce", marker: "owned-marker", sentinel: "inert-terminal"} + p.validate = func(body string) error { + if body != "owned facts" { + return fmt.Errorf("wrong facts") + } + *validated++ + return nil + } + return p, validated +} + +func kimiCacheContractBody(t *testing.T, value map[string]any) []byte { + t.Helper() + body, err := json.Marshal(value) + if err != nil { + t.Fatal(err) + } + return body +} + +func kimiCacheContractSend(p *kimiAttemptProvider, body []byte, mutate func(*http.Request)) *httptest.ResponseRecorder { + r := httptest.NewRequestWithContext(context.Background(), http.MethodPost, p.server.URL+"/v1/chat/completions", bytes.NewReader(body)) + r.Header.Set("Authorization", "Bearer "+p.key) + r.Header.Set("Content-Type", "application/json") + if mutate != nil { + mutate(r) + } + w := httptest.NewRecorder() + p.serve(w, r) + return w +} + +func kimiCacheContractSecond(t *testing.T, p *kimiAttemptProvider, key string) map[string]any { + t.Helper() + value := kimiAttemptContractBody() + if key != "" { + value["prompt_cache_key"] = key + } + args := kimiCacheContractBody(t, map[string]any{"nonce": p.nonce, "operation": "write", "marker": p.marker}) + value["messages"] = append(value["messages"].([]any), + map[string]any{"role": "assistant", "content": nil, "tool_calls": []any{map[string]any{ + "id": p.id, "type": "function", "function": map[string]any{"name": kimiAttemptTool, "arguments": string(args)}}}}, + map[string]any{"role": "tool", "tool_call_id": p.id, "content": "owned facts"}) + return value +} + +func TestKimiSingleToolCanonicalCacheMetadataPreservesOwnedExchange(t *testing.T) { + for _, key := range []string{"", kimiCacheContractKey} { + p, validated := kimiCacheContractProvider() + first := kimiAttemptContractBody() + if key != "" { + first["prompt_cache_key"] = key + } + body := kimiCacheContractBody(t, first) + if _, err := kimiAttemptRequest(body); err != nil { + t.Fatalf("canonical session metadata refused: %v", err) + } + w := kimiCacheContractSend(p, body, nil) + if w.Code != 200 || !p.issued || p.returned || p.err != nil || p.requestCount != 1 || len(p.requests) != 1 || *validated != 0 || !bytes.Equal(p.requests[0], body) || !strings.Contains(w.Body.String(), kimiAttemptTool) || strings.Contains(w.Body.String(), p.sentinel) { + t.Fatal("canonical metadata did not preserve the exact first owned response") + } + w = kimiCacheContractSend(p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, key)), nil) + if w.Code != 200 || !p.returned || p.err != nil || *validated != 1 || p.requestCount != 2 || len(p.requests) != 2 || !strings.Contains(w.Body.String(), p.sentinel) { + t.Fatal("same-session metadata did not preserve the exact owned result") + } + } +} + +func TestKimiSingleToolCacheMetadataRefusesNoncanonicalValues(t *testing.T) { + values := []any{nil, false, 1, []any{}, map[string]any{}, "", "session_owned", "session_" + strings.Repeat("a", 36), + strings.Replace(kimiCacheContractKey, "session_", "Session_", 1), strings.Replace(kimiCacheContractKey, "1111", "GGGG", 1), + strings.Replace(kimiCacheContractKey, "1111", "ABCD", 1), kimiCacheContractKey + "\n", " " + kimiCacheContractKey, + kimiCacheContractKey[:43] + "\n", kimiCacheContractKey[:43] + "\x00", kimiCacheContractKey[:42] + "é", + kimiCacheContractKey[:43], kimiCacheContractKey + "a", "session_" + strings.Repeat("a", 4096)} + for index, value := range values { + t.Run(fmt.Sprintf("shape-%d", index), func(t *testing.T) { + request := kimiAttemptContractBody() + request["prompt_cache_key"] = value + assertKimiCacheContractRefusal(t, kimiCacheContractBody(t, request), nil) + }) + } + request := kimiAttemptContractBody() + request["prompt_cache_key"] = kimiCacheContractKey + body := kimiCacheContractBody(t, request) + body = append([]byte(`{"prompt_cache_key":"`+kimiCacheContractKey+`",`), body[1:]...) + assertKimiCacheContractRefusal(t, body, nil) +} + +func assertKimiCacheContractRefusal(t *testing.T, body []byte, mutate func(*http.Request)) { + t.Helper() + p, validated := kimiCacheContractProvider() + w := kimiCacheContractSend(p, body, mutate) + if w.Code != 400 || p.issued || p.returned || p.err == nil || *validated != 0 || p.requestCount != 1 || strings.Contains(w.Body.String(), kimiCacheContractKey) { + t.Fatal("invalid metadata or authority reached an owned response or leaked metadata") + } +} + +func TestKimiSingleToolCacheMetadataCannotBypassAuthority(t *testing.T) { + for _, kind := range []string{"wrong-key", "wrong-route", "foreign-tool", "unknown-field"} { + t.Run(kind, func(t *testing.T) { + request := kimiAttemptContractBody() + request["prompt_cache_key"] = kimiCacheContractKey + switch kind { + case "foreign-tool": + request["tools"].([]any)[0].(map[string]any)["function"].(map[string]any)["name"] = "foreign-tool" + case "unknown-field": + request["unknown_cache_authority"] = true + } + assertKimiCacheContractRefusal(t, kimiCacheContractBody(t, request), func(r *http.Request) { + switch kind { + case "wrong-key": + r.Header.Set("Authorization", "Bearer foreign-key") + case "wrong-route": + r.URL.Path = "/" + } + }) + }) + } +} + +func TestKimiSingleToolCacheMetadataCannotChangeBetweenTurns(t *testing.T) { + other := "session_aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee" + for _, pair := range [][2]string{{"", kimiCacheContractKey}, {kimiCacheContractKey, ""}, {kimiCacheContractKey, other}} { + p, validated := kimiCacheContractProvider() + first := kimiAttemptContractBody() + if pair[0] != "" { + first["prompt_cache_key"] = pair[0] + } + if w := kimiCacheContractSend(p, kimiCacheContractBody(t, first), nil); w.Code != 200 || !p.issued { + t.Fatal("canonical first request refused") + } + w := kimiCacheContractSend(p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, pair[1])), nil) + if w.Code != 400 || p.returned || p.err == nil || *validated != 0 || p.requestCount != 2 || len(p.requests) != 2 { + t.Fatal("changed cache metadata crossed the owned result boundary") + } + firstError := p.err + w = kimiCacheContractSend(p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, pair[0])), nil) + if w.Code != 400 || !errors.Is(p.err, firstError) || p.returned || *validated != 0 || p.requestCount != 3 || len(p.requests) != 2 { + t.Fatal("later valid metadata cleared the provider refusal") + } + } +} diff --git a/repotests/agentplugins_kimi_project_native_provider_test.go b/repotests/agentplugins_kimi_project_native_provider_test.go index 53fae97ab..01b06949d 100644 --- a/repotests/agentplugins_kimi_project_native_provider_test.go +++ b/repotests/agentplugins_kimi_project_native_provider_test.go @@ -12,6 +12,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "regexp" "strings" "sync" "time" @@ -20,6 +21,9 @@ import ( const kimiAttemptTool = "mcp__uap-kimi-native-a__inspect_runtime" const kimiAttemptModel = "uap-kimi-native-model" +// Pinned profileService passes its generated session ID as cache metadata. +var kimiAttemptCacheKey = regexp.MustCompile(`^session_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) + // Source b8dd3a134060c6e80ba4a1659335cd63b68483f2, OpenAI format // d074e9c1fb310d78f3dd1f497f28bb4481e2ed75. This is a source-guided // capture grammar; unfamiliar requests fail instead of expanding tool authority. @@ -29,9 +33,15 @@ func kimiAttemptRequest(body []byte) (map[string]any, error) { return nil, err } m, ok := v.(map[string]any) - if !ok || projectNativeRuntimeKeys(m, "model messages tools stream stream_options", "max_tokens max_completion_tokens temperature top_p parallel_tool_calls") != nil || m["model"] != kimiAttemptModel || m["stream"] != true || !reflect.DeepEqual(m["stream_options"], map[string]any{"include_usage": true}) { + if !ok || projectNativeRuntimeKeys(m, "model messages tools stream stream_options", "max_tokens max_completion_tokens temperature top_p parallel_tool_calls prompt_cache_key") != nil || m["model"] != kimiAttemptModel || m["stream"] != true || !reflect.DeepEqual(m["stream_options"], map[string]any{"include_usage": true}) { return nil, fmt.Errorf("Kimi completion envelope differs") } + if value, exists := m["prompt_cache_key"]; exists { + key, ok := value.(string) + if !ok || len(key) != 44 || !kimiAttemptCacheKey.MatchString(key) { + return nil, fmt.Errorf("Kimi session cache metadata differs") + } + } for _, key := range []string{"max_tokens", "max_completion_tokens"} { if value, exists := m[key]; exists && !projectNativeRuntimeNumber(value, 1, 1<<20, true) { return nil, fmt.Errorf("Kimi token bound differs") @@ -127,6 +137,7 @@ type kimiAttemptProvider struct { mu sync.Mutex server *httptest.Server key, id, nonce, marker, sentinel string + cacheKey string requests [][]byte requestCount int err error @@ -170,6 +181,7 @@ func (p *kimiAttemptProvider) serve(w http.ResponseWriter, r *http.Request) { return } messages := m["messages"].([]any) + cacheKey, _ := m["prompt_cache_key"].(string) args := map[string]any{"nonce": p.nonce, "operation": "write", "marker": p.marker} if len(p.requests) == 1 { for _, raw := range messages { @@ -180,10 +192,15 @@ func (p *kimiAttemptProvider) serve(w http.ResponseWriter, r *http.Request) { } } encoded, _ := json.Marshal(args) + p.cacheKey = cacheKey p.issued = true // Establish ownership before a byte can reach the native client. p.chunk(w, map[string]any{"role": "assistant", "tool_calls": []any{map[string]any{"index": 0, "id": p.id, "type": "function", "function": map[string]any{"name": kimiAttemptTool, "arguments": string(encoded)}}}}, nil) p.chunk(w, map[string]any{}, "tool_calls") } else if len(p.requests) == 2 { + if cacheKey != p.cacheKey { + fail(fmt.Errorf("Kimi session cache metadata changed between turns")) + return + } calls, results := 0, 0 for _, raw := range messages { message := raw.(map[string]any) From 30f5dfef137604b2a2367be2a125bb724fe6ab57 Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 22:18:05 +0100 Subject: [PATCH 35/42] test(kimi): keep inert cache request helper compatible with Go 1.22 --- ..._project_native_cache_key_contract_test.go | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/repotests/agentplugins_kimi_project_native_cache_key_contract_test.go b/repotests/agentplugins_kimi_project_native_cache_key_contract_test.go index b3555e802..477c2e311 100644 --- a/repotests/agentplugins_kimi_project_native_cache_key_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_cache_key_contract_test.go @@ -39,8 +39,12 @@ func kimiCacheContractBody(t *testing.T, value map[string]any) []byte { return body } -func kimiCacheContractSend(p *kimiAttemptProvider, body []byte, mutate func(*http.Request)) *httptest.ResponseRecorder { - r := httptest.NewRequestWithContext(context.Background(), http.MethodPost, p.server.URL+"/v1/chat/completions", bytes.NewReader(body)) +func kimiCacheContractSend(t *testing.T, p *kimiAttemptProvider, body []byte, mutate func(*http.Request)) *httptest.ResponseRecorder { + t.Helper() + r, err := http.NewRequestWithContext(context.Background(), http.MethodPost, p.server.URL+"/v1/chat/completions", bytes.NewReader(body)) + if err != nil { + t.Fatal(err) + } r.Header.Set("Authorization", "Bearer "+p.key) r.Header.Set("Content-Type", "application/json") if mutate != nil { @@ -76,11 +80,11 @@ func TestKimiSingleToolCanonicalCacheMetadataPreservesOwnedExchange(t *testing.T if _, err := kimiAttemptRequest(body); err != nil { t.Fatalf("canonical session metadata refused: %v", err) } - w := kimiCacheContractSend(p, body, nil) + w := kimiCacheContractSend(t, p, body, nil) if w.Code != 200 || !p.issued || p.returned || p.err != nil || p.requestCount != 1 || len(p.requests) != 1 || *validated != 0 || !bytes.Equal(p.requests[0], body) || !strings.Contains(w.Body.String(), kimiAttemptTool) || strings.Contains(w.Body.String(), p.sentinel) { t.Fatal("canonical metadata did not preserve the exact first owned response") } - w = kimiCacheContractSend(p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, key)), nil) + w = kimiCacheContractSend(t, p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, key)), nil) if w.Code != 200 || !p.returned || p.err != nil || *validated != 1 || p.requestCount != 2 || len(p.requests) != 2 || !strings.Contains(w.Body.String(), p.sentinel) { t.Fatal("same-session metadata did not preserve the exact owned result") } @@ -110,7 +114,7 @@ func TestKimiSingleToolCacheMetadataRefusesNoncanonicalValues(t *testing.T) { func assertKimiCacheContractRefusal(t *testing.T, body []byte, mutate func(*http.Request)) { t.Helper() p, validated := kimiCacheContractProvider() - w := kimiCacheContractSend(p, body, mutate) + w := kimiCacheContractSend(t, p, body, mutate) if w.Code != 400 || p.issued || p.returned || p.err == nil || *validated != 0 || p.requestCount != 1 || strings.Contains(w.Body.String(), kimiCacheContractKey) { t.Fatal("invalid metadata or authority reached an owned response or leaked metadata") } @@ -147,15 +151,15 @@ func TestKimiSingleToolCacheMetadataCannotChangeBetweenTurns(t *testing.T) { if pair[0] != "" { first["prompt_cache_key"] = pair[0] } - if w := kimiCacheContractSend(p, kimiCacheContractBody(t, first), nil); w.Code != 200 || !p.issued { + if w := kimiCacheContractSend(t, p, kimiCacheContractBody(t, first), nil); w.Code != 200 || !p.issued { t.Fatal("canonical first request refused") } - w := kimiCacheContractSend(p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, pair[1])), nil) + w := kimiCacheContractSend(t, p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, pair[1])), nil) if w.Code != 400 || p.returned || p.err == nil || *validated != 0 || p.requestCount != 2 || len(p.requests) != 2 { t.Fatal("changed cache metadata crossed the owned result boundary") } firstError := p.err - w = kimiCacheContractSend(p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, pair[0])), nil) + w = kimiCacheContractSend(t, p, kimiCacheContractBody(t, kimiCacheContractSecond(t, p, pair[0])), nil) if w.Code != 400 || !errors.Is(p.err, firstError) || p.returned || *validated != 0 || p.requestCount != 3 || len(p.requests) != 2 { t.Fatal("later valid metadata cleared the provider refusal") } From 9e9779f2352101e8002070ac64ac30d24e2f9be5 Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 22:23:34 +0100 Subject: [PATCH 36/42] docs(kimi): record reviewed composed corrections and actual validation --- ..._NATIVE_COMPOSED_CORRECTIONS_VALIDATION.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/KIMI_PROJECT_NATIVE_COMPOSED_CORRECTIONS_VALIDATION.md diff --git a/docs/KIMI_PROJECT_NATIVE_COMPOSED_CORRECTIONS_VALIDATION.md b/docs/KIMI_PROJECT_NATIVE_COMPOSED_CORRECTIONS_VALIDATION.md new file mode 100644 index 000000000..a32a32021 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_COMPOSED_CORRECTIONS_VALIDATION.md @@ -0,0 +1,100 @@ +# Kimi scoped selection and native capture corrections: validation + +Composed source commit: `c1a3720f341ae2e746051bdcd2199567995627b0`. +Composed tree: `83ebe639b645e367d5b4707d27c64714d83027e8`. +Existing PR524 base: `b2bb2b7f85b5461420f932478b197905e6fc5ad9`. + +## Reviewed source and reason + +The genuine b2bb component capture completed repeat-add and unchanged update, +then showed intact repair planning removal of selected project MCP. Kimi's old +managed-selection policy always selected the user-format .kimi-plugin/plugin.json, +while its project artifact uses root mcp.json. Reading the wrong layout erased +recorded selection during repair preflight. The native byte-stability assertion +correctly failed and remains unchanged. + +Production fix `f09fbb1e52960c56e6b731aaf2aafc1c2aefd01b` adds an optional scoped +selection port and carries explicit held scope through the guarded usecase and +provider into Kimi's layout policy. It preserves the old user API/layout, optional +legacy ports, exact target/digest authority, both artifact verifications, original +profile checks and existing damaged-artifact reconstruction gates. A refused +scoped policy never retries a user layout. + +The same genuine b2bb single-tool capture preserved one completion request but +no owned tool call. Its original7839-byte request contains prompt_cache_key; +whole-request SHA256 is +`ddd8a402037716fe582f854dcf5d5dae1ebb902dc6b3a962466e4d34874c4de7`. +The old strict provider rejected that known client metadata before issuing the +owned first response. Exact pinned source proves generated session ID -> profile +cache key -> OpenAI request field. Fix `20921c06b3212f1243bde77f7d6cbbb35adcdeed`, +with Go1.22 test compatibility correction `30f5dfef137604b2a2367be2a125bb724fe6ab57`, +admits only the optional observed44-byte canonical string and requires unchanged +presence/value on the second turn. It grants no credential/tool authority and +makes no unobserved native API session join. Authentication, route, body/model/tool +schema, owned call/result facts, counts, SSE and first-error latch remain intact. + +Separately reviewed poll diagnostic `8311c8e9f571e9fa272abc2daed1a2f367ad160e` +retains only finite scalar facts at the original multiple-pending and exhaustion +failures. It preserves manual approval validation/actions, polling/timing, ordinary +supervision/quarantine and all qualification decisions. + +Each slice had two independent GPT-6.1 Sol xhigh plan GOs before implementation +and two independent exact code GOs after meaningful executor verification. The +composition preserves all20 reviewed source/document file hashes without conflict. +No native collector acceptance, native byte-stability gate, VSCode source, lint +configuration, authentication/trust setting or release policy was relaxed. + +## Actual inert validation + +- Old product: actual composed public CLI and public Engine intact-repair tests + failed on native skill/MCP byte changes. Current production source passes both + public paths, retaining explicit native MCP selection and persisted identities. +- Final frozen SDK intact-repair regression: PASS0.097seconds. Its complete user + and B tree comparison covers membership and file bytes, not mode/inode/link + metadata. Final affected whole-file lint and unfiltered six-linter gate:0issues. +- CLI, Kimi adapter, provider and usecase focused gates passed on the identical + reviewed files; both CLI lint gates reported0issues. Exact chronology and + earlier fixture/setup/test-helper failures are retained in executor receipts. +- Old provider: permanent canonical metadata regression plus original unchanged + request replay failed through the actual parser and actual in-process provider + with400 before issued. Corrected provider: exact original request reaches only + the expected owned first SSE response, with identical captured bytes and no + completed result/callback/sentinel claim. +- Final Go1.22-compatible provider contracts, including that unchanged raw replay, + PASS0.017seconds; both final lint gates report0issues. Controls cover absent/same + metadata,19 invalid type/shape values, duplicate JSON, unknown/foreign authority, + changed/added/removed second-turn metadata and inability to clear a refusal. +- Poll diagnostic:57 inert Kimi Python contracts and27 runtime contracts PASS. +- Original23-member failed single-tool artifact re-admits with identical bodies + and summary; all failure/qualification flags remain unchanged. +- Final composed-source Go compile and Kimi/shared CLI contracts PASS0.017seconds. +- gofmt, whitespace and exact reviewed-source hash comparisons pass. Session-bearing + transient replay files are removed from the source checkout; no raw session key + is committed by this correction. + +Retained saved-Git CLI lifecycle cases encountered the executor's missing +/proc/PID/task/TID/children capability before acquisition. That environmental +failure is preserved and is not counted as a pass; no ordinary containment guard +was changed. Genuine hosted CI retains that execution responsibility. + +No vendor/client/scanner process ran locally. Existing native component/single-tool +failures remain failed and unqualified. New actual disposable hosted captures must +establish the effect of the fixes and the remaining user/A/B lifecycle and MCP +boundaries. This validation does not claim authentication, real-model operation, +complete native qualification, main merge, release or deferred VSCode completion. + +## Exact review and executor records + +- `/tmp/uap-kimi-scoped-selection-f09f-source-manifest.json`: SHA256 `9ecb212ca8674580140ca55b7267ad359daf265ea010b576f799be72741a21d7`. +- `/tmp/uap-kimi-scoped-selection-f09f-code-R1.md`: SHA256 `7a0102a2b1dcd7d6a1eae04439a667faeab20df2202769c91760257e14da9acd`. +- `/tmp/uap-kimi-scoped-selection-code-r2-review.json`: SHA256 `bdcc89c716026c5f723812dee235c204f58b04bfbcf994e4dcbdb42b0df9d733`. +- `/tmp/uap-additional-kimi-cache-key-code-R1.md`: SHA256 `6a13e71db5947b89543b52e0b117c2230b6430f46ee991c1e26a7d1163ba91b9`. +- `/tmp/uap-kimi-cache-key-go122-delta-r2-review.json`: SHA256 `b75b3fa369d3e5ace1a11ded6b8b244801932b4353ef08ff56f6f833d9c836b5`. +- `/tmp/uap-additional-kimi-composition-code-R1.md`: SHA256 `d06ea64110f288d74de89f5fd97ee8ca9c39406167d4fba3918dc3a8434a67b9`. +- `/tmp/uap-kimi-composed-source-code-r2-review.json`: SHA256 `c208f636c5f22e1b654aa268b4b8e7682ed3696cf21d3d05b19d8b44f5d1f8ee`. +- `/tmp/uap-additional-final-serial-gates-1d4m_qyl/report.json`: SHA256 `1ca286a35b6c73e4dc32b6ef5525741aa8b323bb726ceda86968c06c017e00de`. +- `/tmp/uap-additional-final-serial-gates-1d4m_qyl/kimi-30f-report.json`: SHA256 `51053bf6d849d31679fe9be7f7cadc46fce654f961cdd6b1a8721f56d38e0a8f`. +- `/tmp/uap-additional-final-serial-gates-1d4m_qyl/kimi-composed-go.json`: SHA256 `9a3c437119aa1c945e2fbfe0fabc280387d63189c520d938825a9c3abc49d55e`. +- `/tmp/uap-additional-final-serial-gates-1d4m_qyl/kimi-call-unchanged-old-replay.json`: SHA256 `04ceb745d9019c529c3e2ed484a4575646fb567ea35c91b3d5a7586433140944`. + +Local paths identify this execution; committed source, plans and this validation document preserve the durable repository record. From 22aa3e465132fa35fb135c30809f18b495ef4a0d Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 23:35:05 +0100 Subject: [PATCH 37/42] docs(kimi): freeze finite native completion amendment --- .../remaining-native-qualification.md | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index 9fee90d34..f1623f0ba 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1715,3 +1715,107 @@ then review and validate the combined source before merging the base PR. Update current-source support guidance only to the actually observed level. Do not publish a release or claim real-model/OAuth qualification from a fixture. + + +# Kimi: exact model tool-result framing and finite native single-call completion + +PRE-CODE plan. No source implementation before root and a second independent reviewer accept this exact plan. Root owns Git/GitHub mutations and publication. All genuine native/client/public-installer/scanner execution remains exclusively on an actual current-source GitHub-hosted runner. Local work consists of inert contracts and pure byte readers. Preserve the unpublished alternative26fdd4279d30cebc74811e84cfe9c9ba6e96ade1. No VS Code, other client, release, profile substitution, plugin authority expansion or native pin change. + +## 1. Actual source-bound evidence and remaining failure + +Current PR524 head is9e9779f2352101e8002070ac64ac30d24e2f9be5, treea77b1de54dfbc16522f603265a74954615be2bc4. Exact hosted run40586437481 is completed: component job122491615607 succeeds; single-call job122491615779 fails; inherited Cline job122491615841 succeeds. No pending run is cancelled, retried or superseded. + +Component native12775170868:34578bytes, outerSHA256 fdd801ded762604e8c4bf6525c8c6d29619f29d4007479fb096e3fbcd341d3e0, inner7a8bd821a88209192b9fb7ca7123c54d3c0cda6a2afcafa1d6b635ee60f2ab06,67members. Component frontend12775275513:3417bytes, outerea310853ef8f10cdab4992c922d58c1baf38ca7f3262740b3c236a890bcec1de, inner339fe3375ce709b0bf54785854e051150518231c3801f53648e38876484d10ff,25members. The independent audit is `/tmp/uap-kimi-9e-current/component-raw-audit.json`, SHA256 ed3dce6833e381d5555e58ae696da7ce60aefd1604818cece4aefef51aa8e6e2. It verifies24frontend stream,14nested service stream and126HTTP response joins;22public commands;7fresh native server IDs;11distinct probe sessions with11initialize+11tools/list and0tools/call; all seven native services acknowledge shutdown, reap groups, close ports, with no SIGTERM/forced kill. Actual runtime36.951seconds. The component legitimately reports project-lifecycle/native-restart-removal/mcp-initialize/mcp-tools-list true; tools-call/auth/model false. Preserve this evidence and unchanged component behavior. + +Single-call native12775780239:21365bytes, outerb1a896e2c0f47d4ff8351544c542f65f6dc02be0a74ae9fa8a58a7ab59a3c0c9, innerb50e9656245696306a6ba8352e7de8ec99ed6c76e7a2b47a05a84f7fde66649d,25members. Frontend12775087398:3546bytes, outer7a93803301bff7ad71b9260d23878dc89f0a61f858fabfe181ffcfc094ec7311, inner8ac7d7ce9932ea76293d57f285360aedf9983b5d5f9c6d797089e55daf1625a3,25members. Independent audit `/tmp/uap-kimi-9e-current/single-raw-audit.json`, SHA256 ee193ed9cdf2983a32508fcca75a9211bfa82ddf0ae6f7e7323992cae9aa01e1, verifies24frontend+2nested service+27HTTP joins and all30closed nested files. Public commands and native collector all exit0, no process/deadline error. Native service closes ordinarily as above. + +Actual approval poll0 contains exactly one matching session/main-agent/turn0/provider call/tool/action/input, then one approval resolves true. The three raw probe events now are initialize, tools/list, tools/call, correlated to one PID/session/nonce and exact selected managed root/data/runtime facts. Provider requests1 and2 retain a stable cache key equal to the actual native session. Request2 has exactly the issued assistant call and the matching tool result. Its tool-message content is exactly one line `Wall time: 0.004 seconds`, LF, then the probe facts JSON. Current `kimiSingleToolAttempt.validateFacts` directly calls whole-document `projectNativeRuntimeJSON` on this content; that fails before facts/returned are recorded. An independent pure JSON replay reproduces the refusal. + +Native history independently contains matching tool_use and raw unframed tool_result JSON, then an empty assistant message. The two snapshots report `last_turn_reason=failed`, and the last has no in-flight turn. This actual capture remains failed and unqualified after every correction in this plan. It does not contain the final success sentinel or an independently reached persistent-marker/unrelated-data check. Current code also intentionally calls t.Error after provider success pending native-history review. Both barriers need a strict reviewed success contract, not removal without replacement. + +## 2. Authoritative pinned source, independently read without execution + +The native2.3.2 archive/executable pins remain4715ccd1a591b64683929a14b26b3d07ac4c484e4da3881b22a2ccca56304042 /1cc5979fca0ea8c01c0a4d53806df8e65573cfbe7daf17ccff86db392807e4fa. GitHub tag `@moonshot-ai/kimi-code@2.3.2` resolves to annotated tag7aa8fa1eab3557ffac521a2139e4dd6583f70f58, which points to source commitb8dd3a134060c6e80ba4a1659335cd63b68483f2. Both API records are preserved under `/tmp/uap-kimi-9e-current/`. The complete nontruncated5346-entry primary Git tree and individually fetched blobs were verified using Git blob SHA1 plus byte length/SHA256. No vendor code or release payload was run locally. + +Primary source files are under `/tmp/uap-kimi-9e-current/native-source/`; full21-file manifest SHA256 b82f252f58f2949485583fb428e8e819a5621ad7771cb751ff735cc8b242e288. Relevant frozen blobs: + +| Primary path under source commitb8dd | Git blob | Specific contract | +| --- | --- | --- | +| packages/agent-core-v2/src/agent/contextMemory/toolResultRender.ts |2bf13ed32a098c16c9b8a4094c49f93e569f58d0 | MCP tools render wall time; header is `Wall time: ${(durationMs / 1000).toFixed(3)} seconds`, then LF and single text output. Notes/errors are separate rendering and are not accepted as successful facts. | +| packages/agent-core-v2/src/agent/contextMemory/loopEventFold.ts |f05f9a438ca699e4a0a43e2f3bc85c11895a67fa | Raw tool output is retained with toolCallId and duration; wall time applies to MCP names. | +| packages/agent-core-v2/src/human/llm/requester/bases/openai/format.ts |d074e9c1fb310d78f3dd1f497f28bb4481e2ed75 | OpenAI tool messages and existing request metadata grammar. | +| packages/kap-server/src/protocol/message.ts |7bd4b36116fd5c2202032171a40f2f99068cb62c | Message identity, session, role, timestamp and discriminated text/tool_use/tool_result content shapes. | +| packages/kap-server/src/protocol/session.ts |f408383451b5a5e4c3df563853ff89e4352cb278 | Session/workspace/config/usage; last_turn_reason enumerates completed/cancelled/failed. | +| packages/kap-server/src/protocol/rest-snapshot.ts |f8d5df7561f0462aabef890f0dd17b3be6042134 | Snapshot watermark/epoch/session/messages/in-flight turn/pending approvals/questions/subagents. | +| packages/kap-server/src/services/messages/messageProjection.ts |86e1f20a027194dd9d652c589373f9dd19625240 | Native tool_result carries raw output, call ID and optional is_error; assistant tool_use carries exact call ID/name/input. | +| packages/kap-server/src/services/messages/messageHistory.ts |0e99d0ffe5f000abfe352f07bd01173243468a22 | REST list is newest-first; bounded pagination has_more. | +| packages/kap-server/src/routes/snapshot.ts |26ce5a746e44e1c762bdd04f38c039b533d2ca91 | Snapshot messages are original oldest-first; in-flight null is a separate terminal condition. | +| packages/kap-server/src/routes/sessions.ts |8126c532406a62354c0fdc4eb278083824dcd3f9 | Session metadata.cwd and live busy/main-turn/pending/reason projection. | +| packages/agent-core-v2/src/features/dateChange/dateChangeService.ts |d52e17741c0ec2d563f995527d4a5481a113ca75 | Only the observed first-session date injection text and finite disclosure metadata. | +| packages/agent-core-v2/src/features/reminder/systemReminder.ts |2272a9962c883e62ec869dd8c63bdee0eb6d1f31 | Exact ``, LF, content, LF, closing tag. | + +Source is supporting evidence for a narrowly predicted success shape. It does not make the unsuccessful current runtime complete. Any future new shape remains refused with truthful failed evidence; do not broaden based on an error alone. + +## 3. Exact correction: model-only wall-time framing + +Introduce a pure Go helper for the model-visible returned tool text. It receives the already request/call-owned second-turn tool content and validates the existing whole1MiB bound before parsing. Require exactly one anchored header `Wall time: D.DDD seconds` followed by LF, where the integer portion is canonical unsigned decimal (no signs/leading zeroes except0), exactly three fractional digits, and numeric seconds is finite in[0,60]. The60second maximum is the unchanged native service's original total bound, not a new timeout. + +Remove exactly that one header and pass the remaining whole body to the existing duplicate-rejecting, finite, UTF8, depth/node/size-bounded JSON reader. Do not search for a brace, trim arbitrary native prefixes, split on a last line, accept a suffix/note/error block, ignore unknown fields or fall back to another protocol. An unframed model tool response is refused by this new model-text helper. Existing raw-facts validation remains unframed for the independent probe/native-history JSON path; this separates two source-defined serializations of one owned result. + +After framing, retain every current exact facts field, nonce/revision/marker/argv/cwd/managed-root/data check and the full fresh probe PID/session/nonce/time/order/count join. Keep the provider's issued-before-write ownership, exact route/host/single credential header, fixed model/tool declaration, request limit, stable canonical cache key, single call/result counts and two-turn sequencing. The actual current second-turn facts payload should pass this pure framing/facts portion, while the overall failed history still refuses completion. + +## 4. Exact correction: independent native-history verification in Go + +Keep `kimi_attempt.py` as the bounded capture-only observer: no additional native HTTP command, model request, approval, tool authority, profile change or polling budget. Its inner observations remain explicitly `native_capture_complete=false` and `native_history_grammar_frozen=false`; they describe capture without qualification. Add a pure Go history verifier over the emitted raw envelope in the source-selected test. Do not trust a new Python boolean or a caller-supplied expected native output. + +The verifier takes expectations from the already independently validated fixture/binding/provider/probe: project A cwd/workspace, actual selected session/prompt/approval IDs, turn0, own provider call/tool/arguments, exact prompt text, expected final sentinel, exact unframed probe facts and original time window. It must: + +1. Strictly decode the whole version1 envelope with exactly schema_version/files. Admit only canonical single ordinary member names. Require research.json, one exact native command stdout/stderr pair and every recorded HTTP response; reject missing/extra names, duplicate labels/filenames, unsafe names, oversized bodies and aggregate/nodes/depth excess. Verify UTF8 byte length and SHA256 of all2service streams and allHTTP bodies against research receipts. Preserve the1MiB whole outer envelope cap and current fixed maximum60HTTP requests. Approvals are a sequential prefix0..19 and snapshots0..9; no gaps or renewed budgets. Other labels/methods/paths/auth/status use the exact fixed source inventory. The successful native command argv/cwd/version and all five ordinary service cleanup booleans must match existing contracts. +2. Bind authenticated session-specific raw create-session/prompt/approval/snapshot/history receipts to the selected actual native session, workspace A cwd, own provider call and turn0. Keep prior workspace/trust/catalog checks unchanged. Require exactly one owned pending approval, one once-resolution, no remembered/blanket permission, no extra native prompt or approval action. Verify endpoint methods/paths/status/auth and exact success/error envelopes; ordinary unauthenticated health and the existing401negative case remain distinct. +3. Validate finite known session/snapshot fields rather than arbitrary maps: bounded IDs/timestamps, selected workspace/cwd/model, empty permission rules, archived=false; typed finite nonnegative usage/message-count/sequence data; known optional fields only. In all snapshots require no pending approvals/questions or subagents. Bound in-flight turn to selected turn0 and the source's finite tool/assistant/thinking shapes. Any nonempty running tool must be the single owned tool/call/args; no additional tool, thinking, free assistant text or delegated activity. Preserve and reject failed/cancelled terminal reasons. The final snapshot must exist within the original10poll bound, have in_flight_turn=null, busy=false, main_turn_active=false, pending_interaction=none and last_turn_reason=completed. Its epoch is nonempty/stable and sequence watermark is nondecreasing across snapshots. +4. Require bounded complete history with has_more=false and exactly four or five messages. Normalize only the documented newest-first history order, then require exact message identity/content equality with the oldest-first terminal snapshot's complete messages, also has_more=false. All IDs unique/bounded, same actual session; timestamps finite/nondecreasing within the original run window. No loose scan for a matching substring or arbitrary extra messages. +5. The chronological grammar is: exactly the accepted user prompt; optionally exactly one first-session date injection; exactly one assistant tool_use; exactly one successful tool_result; exactly one final assistant text sentinel. The optional injected date row has the observed user role, one exact system-reminder text part, origin.kind=injection/variant=date_change and closed disclosure(kind=date,renderGeneration=1,valid localDate, timeZone=UTC). Bind date text to the disclosure and UTC message timestamp; other date templates/metadata/messages are refused. The prompt has only the observed user origin metadata; tool/final assistant rows do not acquire arbitrary optional metadata. Other source protocol optional fields are rejected when not part of this exact fixture grammar. +6. The assistant tool_use must have only the owned call ID/name and exact nonce/write/marker arguments. The tool_result must have that same call ID, no error (is_error absent or false), and a string containing only one whole strict raw facts JSON object. Compare it structurally with the already independently validated unframed provider/probe facts, including PID/session/nonce and runtime authority. No tools/call count beyond one. The final assistant must contain exactly one text part equal to the own fixed final sentinel; empty/extra/partial assistant, wrong sentinel or a tool result alone fails. + +Return a closed typed proof containing selected session/prompt/call/approval/turn, terminal reason, message/snapshot counts, exact final snapshot/history digests and matching probe identity. Preserve raw evidence; do not serialize private bearer/provider secrets. The current9e raw history MUST fail because its reason is failed and final assistant empty, even though framing/probe/result joins now pass. The verifier may admit a clearly labeled synthetic success contract; that is not native qualification. + +## 5. Completion, cleanup and truthful artifact admission + +Run native-history verification only after the native collector returns ordinarily and the provider has issued and validated exactly two owned completion requests with the full probe facts. Require the persistent native marker to equal the expected marker and unchanged remainder of the original data inventory, plus the existing immutable/original/foreign authority checks. Those later current checks remain essential; do not infer marker state from returned facts. + +Replace the unconditional initial-history t.Error only with all of these completed checks. Add bounded ordinary provider shutdown inside the original deadline, with a finite exported cleanup receipt. Use graceful HTTP server shutdown, no forced connection close on the success path; only a failure path may force disposal, and that records failure, clears every qualification and cannot admit success. Reserve cleanup inside the constructor's original150second work/180second total origin; never start another origin or extend any native/HTTP/probe budget. A failed original work context or forced/errored/unfinished provider shutdown prevents completion. Require native acknowledged API shutdown, group reaped and port closed with no forced kill/SIGTERM, as already supplied and checked in raw. + +After final source/data/cleanup/capture checks, and only then, set single-call top-level native_capture_complete/native_history_grammar_frozen/runtime_attempt_complete=true and test_failed=false, with the typed proof and ordinary cleanup receipt. This branch sets only mcp-initialize/mcp-tools-list/mcp-tools-call true. Project-lifecycle/native-restart-removal stay false in this single-call lane; vendor_authentication/real_model remain false and activation remains Prepared. It cannot inherit the component lane's lifecycle flags. Root may combine independently audited current-source component and single-call evidence later. + +Update both Go archive admission and Python pure `kimi_call_members` to recognize an exact closed success pair alongside the preserved failed-only pair. Success requires every selected command triple, two provider requests, installed native projection/skill, probe stream and complete raw Python envelope, correct source/tree, three MCP flags only, own proof and ordinary native/provider cleanup. Reject partial/mixed-success flags, missing proof, failed commands, absent/mismatched source and any forced cleanup. Existing failure paths retain every original allowed prefix/raw diagnostic, false completion and all five false flags. Clear any staged success fields/flags whenever a late original authority/deadline/cleanup/capture check fails before serialization. Frontend actual test success remains required independently; admitting bytes never turns a failed test green. + +The historical9e andb2bb raw captures remain byte-identical and failed-only when replayed with their ORIGINAL source identities. No old raw is retargeted to a new source or promoted to success. Component raw admission and stage flags remain unchanged. + +## 6. Implementation surface and meaningful verification + +Keep changes narrow to Kimi repotest helpers/contracts, the existing runtime pure-reader success branch/contracts and evidence documentation. Expected paths: existing `repotests/agentplugins_kimi_project_native_attempt_test.go`, provider helper if needed, one new finite history helper plus focused contract file, existing Kimi attempt/provider contracts where expectations need precise updates, `scripts/project_native_runtime.py`, `scripts/test_project_native_runtime.py`, and a concise Kimi validation record. Existing Python native collector, shared service/supervisor/acquisition, production client/provider/installer selection, component lifecycle, workflow routing, versions/pins and other clients stay byte-identical. If implementation requires wider behavior, stop for amended plan review. + +Before code, two independent plan GOs. Implement in a new isolated exact-current-source checkout after root confirms the observed remote head; preserve concurrently advanced work and the unpublished alternative. Root alone commits/pushes. Before publishing obtain two exact-tree code critics and execute these meaningful local gates under root-owned serialized Go/lint slots: + +- Pure model framing: actual0.004header+facts succeeds; malformed prefix, sign/leading zero, wrong fractional width/unit/CRLF/multiple headers, >60/nonfinite, error/note/trailing payload, duplicate/unknown facts fields and oversized/deep/invalidUTF8 refuse. Keep existing source-bound facts/probe event mutation tests. +- Pure raw envelope/history: actual9e current terminalfailed+emptyassistant remains rejected; its raw bytes/digests/flags remain unchanged and failed admission remains exact. A synthetic finite completed fixture alone exercises the positive grammar, explicitly labeled non-native. Mutations cover wrong session/workspace/cwd/model/prompt/call/tool/nonce/marker/root/data/PID/probe session, wrong ordering/reverse snapshot mismatch, duplicate ID, count/pagination excess, extra/missing/error tool result, missing/extra/wrong/partial sentinel, unknown fields/content/metadata, nonfinite/deep/oversized/invalidUTF8, stale/out-of-window timestamps, injected-date mismatch, unstable epoch/regressing sequence, absent terminal snapshot, active/failed/cancelled terminal, extra questions/approvals/subagents and receipt/hash/member closure errors. +- Provider/source/data/cleanup: preserve one-call authority and two-turn request checks; ordinary graceful provider shutdown passes inertly, forced/error/expired closure cannot qualify; missing/wrong marker and unrelated data changes still refuse; late failures clear staged completion/flags. No fake native binaries or skipped-host marker impersonation. +- Pure archive readers: failed pair unchanged, exact successful three-MCP-only pair, reject mixed flags/false-green/missing raw or proof/extra files/gapped provider sequence/failed command/forced cleanup/source mismatch. Actual current component67/25 and single25/25 archives replay as their original outcomes. Add no vendored executable fixture. +- Focused Kimi inert Go/contracts plus affected Python contracts, gofmt/whitespace, actual affected all-enabled lint and whole unfiltered six-linter gates. Do not run any native/vendor/public-installer/scanner path locally. Scope broader tests only to concrete shared-reader risks. + +After accepted code and executed gates, root normal expected-head fast-forward publication triggers fresh genuine hosted CI. Do not manually retry unchanged source or declare completion from inert fixtures. Audit fresh exact source/tree/pins/raw source-to-output receipts, approval, provider/probe/native-history, persistent marker/data and whole ordinary cleanup. Component must remain independently green and audited on the same published source. Only an actual fresh native pass with independent raw review can qualify the tools/call slice. Any still-unfamiliar native history remains failed with its original raw capture for a new evidence-based review. + +## Accepted Kimi framing/history amendment before implementation + +Exact draft SHA256 `fb44cab8565ed9023f43ce45a0e2d1c8d5f06ea03cd9c0118b092003c151aaa7` +received independent PLAN GOs from root and GPT-6.1 Sol xhigh `native_e2e_finish` +before any source edit. Receipt SHA256s: +`7850e5f2a948b5074bf300deb1e0c67a860da921003ca65abc9b44d2fb4c4a21` and +`a5306ed37e714e594cfc6b61198dbdf9c871d3482afcf6481e85671023debf5f`. +Both rehashed all21 primary blobs and inspected the actual framing/native/probe +failed boundary. Closed typed proof must join full raw/HTTP/session/history +identities, marker/data and ordinary shutdown; success booleans alone cannot +qualify. Current9e failed native history remains refused. A reader mutation that +places success flags/proof over failed raw must fail; late failures clear staged +flags. Only future same-source actual component and single-call success may be +combined. Root freshly checked PR524 remains9e before this isolated checkout. From 19f64f9c0cace7906333f589227f9d711f3b45b5 Mon Sep 17 00:00:00 2001 From: 777genius Date: Wed, 28 Oct 2026 00:03:30 +0100 Subject: [PATCH 38/42] test(kimi): verify finite native history and completion evidence --- ...ECT_NATIVE_FINITE_COMPLETION_VALIDATION.md | 107 ++ ...lugins_kimi_project_native_attempt_test.go | 70 +- ...mi_project_native_history_contract_test.go | 549 ++++++++++ ...lugins_kimi_project_native_history_test.go | 980 ++++++++++++++++++ ...ugins_kimi_project_native_provider_test.go | 33 + scripts/project_native_runtime.py | 482 ++++++++- scripts/test_project_native_runtime.py | 155 +++ 7 files changed, 2361 insertions(+), 15 deletions(-) create mode 100644 docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md create mode 100644 repotests/agentplugins_kimi_project_native_history_contract_test.go create mode 100644 repotests/agentplugins_kimi_project_native_history_test.go diff --git a/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md b/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md new file mode 100644 index 000000000..e8e317885 --- /dev/null +++ b/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md @@ -0,0 +1,107 @@ +# Kimi finite single-call completion + +Implementation of the accepted pre-code plan in +[remaining native qualification](plans/project-install-e2e/remaining-native-qualification.md) +(plan SHA256 fb44cab8565ed9023f43ce45a0e2d1c8d5f06ea03cd9c0118b092003c151aaa7). Root and an independent second +reviewer accepted the exact plan before source changes. Validation and fresh +native qualification for this implementation are pending. + +## Measured starting point + +Published source9e9779f2352101e8002070ac64ac30d24e2f9be5, tree +a77b1de54dfbc16522f603265a74954615be2bc4, hosted run40586437481: + +- Component job122491615607 passed36.951seconds. Independently audited native67 / + frontend25 members,24frontend stream joins,14service stream joins,126HTTP joins, + seven fresh native servers and eleven initialize/list probe sessions. The four + component flags are true; tools/call, vendor authentication and real model are + false. This component implementation and its reader remain unchanged. +- Single-call job122491615779 failed8.164seconds. Its original25-member native + archive and25-member frontend archive remain external audit inputs; no measured + raw capture or log is added to the repository. The actual collector exited0 + with ordinary shutdown, one matching manual approval, three correlated probe + events and two provider requests. Its second returned tool message includes + the exact native wall-time line before JSON. Native history ends with a failed + turn and empty assistant. All original flags remain false. + +The immutable native2.3.2 tag resolves through annotated tag7aa8fa1eab3557ffac521a2139e4dd6583f70f58 +to primary sourceb8dd3a134060c6e80ba4a1659335cd63b68483f2. The accepted plan records +verified Git blobs for model rendering, native session/message/snapshot schemas, +message projection/history order and the first-session date reminder. No native +or public installer/scanner execution occurs locally. + +## Implemented boundary + +The model-visible facts path requires one exact anchored native +`Wall time: D.DDD seconds` line, finite0..60seconds, then one whole bounded strict +JSON document. The raw native-history/probe JSON path stays unframed. Existing +nonce, revision, argv, managed root/data, marker and fresh PID/session/event joins +remain mandatory. No brace search, arbitrary prefix/suffix removal or alternate +protocol is introduced. + +The Python native observer remains capture-only and byte-identical. An independent +pure Go verifier validates its closed native service/HTTP evidence, byte/digest +joins, fixed endpoint sequence, one owned manual approval and finite native +history. It requires a terminal completed session with no in-flight turn, +pending interactions or subagents; the exact owned tool_use and successful raw +tool_result; the final own sentinel; and exact reverse-order identity between +complete terminal snapshot and history. The optional date reminder has the +source-defined closed metadata and text. Additional or unfamiliar native shapes +remain refused. The original measured failed history is never promoted. + +Success also requires the observed marker and unchanged remaining data inventory, +all original immutable/foreign authority guards, and ordinary bounded provider +shutdown inside the original150second work/180second total origin. Failure clears +staged proof/completion and every qualification flag before serialization. + +The single-call success pair has exactly three true flags: mcp-initialize, +mcp-tools-list and mcp-tools-call. Project-lifecycle/native-restart-removal remain +false in this lane. Activation stays Prepared; vendor authentication and real +model remain false. Component evidence is independently qualified and must not +be copied into this lane. + +## Closed marker/data evidence + +The only new optional failed/successful artifact member is the fixed ordinary +UTF8 JSON file `native-data-proof.json`, within the existing1MiB member/total +capture bounds. It is emitted only after the live fixture has read the exact +expected marker and compared the original owned data inventories. No unknown +file content, user profile content or credential is exported. + +The exact schema has six fields: + +| Field | Shape and check | +| --- | --- | +| schema_version | Integer1. | +| before | Already-held owned data path/mode/digest map; at most4096 entries and32 levels, canonical relative paths, original root and owner file required. | +| after_without_marker | Independently observed same map after removing only the exact owned `native-marker.txt` entry; structurally equal to before. | +| marker_body | Exact observed expected `retained-` text, at most256bytes. | +| marker_bytes | Exact UTF8 byte length. | +| marker_sha256 | Recomputed SHA256 of observed marker text. | + +The typed native-history proof adds `data_proof_sha256`, recomputed over this +entire raw member. Both Go and Python success readers recheck inventory equality, +canonical parent closure, marker text/length/digest and proof digest; no true +boolean substitutes for these joins. Success has exactly26 members. A later +failed capture may retain this member only if the relevant live checks were +already reached; it still has false completion and all five flags false. +Historical25-member failure archives remain accepted unchanged with their +original source identities. + +## Verification and publication gates + +Repository contracts construct independently authored inert records under +`/inert/never-executed/` with synthetic IDs/timestamps. Positive records are +explicitly synthetic and establish no native observation. Negative cases cover +exact framing, finite message/history/session/receipt/member authority, +failed/cancelled/active native outcomes, forged success over failed raw, +provider/probe/result/marker/data mismatches and late/forced cleanup. Original +measured archive replay belongs to a separately executed scratch audit, outside +the repository. + +Required before publication: executed focused inert Go and affected Python +contracts, whitespace/gofmt and affected/whole lint gates, original/new reader +scratch replay with unchanged original raw outcomes, and two independent exact +code reviews. Root alone commits/pushes. A fresh genuine current-source hosted +pass plus independent raw audit is required before any tools/call qualification +claim. No release is authorized by this work. diff --git a/repotests/agentplugins_kimi_project_native_attempt_test.go b/repotests/agentplugins_kimi_project_native_attempt_test.go index 1d54e0fb6..900d86334 100644 --- a/repotests/agentplugins_kimi_project_native_attempt_test.go +++ b/repotests/agentplugins_kimi_project_native_attempt_test.go @@ -200,7 +200,13 @@ func (a *kimiSingleToolAttempt) setup(t *testing.T) { t.Helper() f := a.f a.secret, a.marker = "uap-kimi-key-"+f.base.Nonce, "retained-"+f.base.Nonce - a.provider = &kimiAttemptProvider{key: a.secret, id: "uap_" + f.base.Nonce + "_attempt", nonce: f.base.Nonce + "-a", marker: a.marker, sentinel: "UAP_KIMI_DONE_" + f.base.Nonce, validate: a.validateFacts} + a.provider = &kimiAttemptProvider{key: a.secret, id: "uap_" + f.base.Nonce + "_attempt", nonce: f.base.Nonce + "-a", marker: a.marker, sentinel: "UAP_KIMI_DONE_" + f.base.Nonce, validate: func(body string) error { + raw, err := kimiAttemptModelFacts(body) + if err != nil { + return err + } + return a.validateFacts(string(raw)) + }} if err := a.provider.start(); err != nil { t.Fatal(err) } @@ -329,13 +335,32 @@ func (a *kimiSingleToolAttempt) run(t *testing.T) { f.evidence[key] = observation[key] } a.provider.mu.Lock() - issued, returned, problem, count := a.provider.issued, a.provider.returned, a.provider.err, len(a.provider.requests) + issued, returned, problem, count, cacheKey := a.provider.issued, a.provider.returned, a.provider.err, len(a.provider.requests), a.provider.cacheKey a.provider.mu.Unlock() f.evidence["provider_call_id"], f.evidence["provider_returned"] = a.provider.id, returned if problem != nil || !issued || !returned || count != 2 || a.facts == nil { t.Fatal("Kimi provider/native probe exchange incomplete; bounded initial evidence retained") } f.evidence["probe_facts"] = a.facts + session, sessionOK := observation["native_session_id"].(string) + approval, approvalOK := observation["approval_id"].(string) + if !sessionOK || !approvalOK || cacheKey != session { + t.Fatal("Kimi native session/provider metadata join differs") + } + expected := kimiAttemptHistoryExpectation{Client: f.client, Session: session, Call: a.provider.id, + Approval: approval, Prompt: "Call the one installed fixture tool once with the exact nonce and marker; then report completion. " + f.base.Nonce + "-a", + Sentinel: a.provider.sentinel, Skill: p.Skill, Projects: f.projects, + Server: kimiProjectExpectedServer(p, "a", f.base.Root, f.base.Nonce), Facts: a.facts, + Started: f.started, Ended: time.Now()} + proof, err := kimiAttemptHistory(r.Stdout, expected) + if err != nil { + t.Fatal("Kimi finite native history did not complete; bounded raw evidence retained") + } + providerCleanup := a.provider.closeOriginal(f.started) + f.evidence["provider_cleanup"] = providerCleanup + if !kimiAttemptProviderClosed(providerCleanup) || f.ctx.Err() != nil { + t.Fatal("Kimi original provider closure failed") + } marker, err := projectNativeRuntimeRead(filepath.Join(p.Data, "native-marker.txt"), 256) if err != nil || string(marker) != a.marker { t.Fatal("Kimi native marker differs") @@ -345,9 +370,18 @@ func (a *kimiSingleToolAttempt) run(t *testing.T) { if !reflect.DeepEqual(after, dataBefore) { t.Fatal("Kimi tool changed unrelated persistent data") } + dataProof, err := json.Marshal(map[string]any{"schema_version": 1, "before": dataBefore, "after_without_marker": after, "marker_body": string(marker), "marker_bytes": len(marker), "marker_sha256": kimiProjectProofSHA(marker)}) + if err != nil { + t.Fatal(err) + } + f.keep(t, "native-data-proof.json", dataProof) + proof["data_proof_sha256"] = kimiProjectProofSHA(dataProof) f.check(t) - // Even source-guided success is not measured native-history qualification. - t.Error("Kimi initial native history capture requires independent finite-schema review; no qualification") + f.evidence["native_history"] = proof + f.evidence["native_capture_complete"], f.evidence["native_history_grammar_frozen"], f.evidence["runtime_attempt_complete"] = true, true, true + for _, key := range []string{"mcp-initialize", "mcp-tools-list", "mcp-tools-call"} { + f.evidence["qualification"].(map[string]bool)[key] = true + } } // Only the private observer receives these genuinely observed host markers. @@ -372,8 +406,7 @@ func (a *kimiSingleToolAttempt) finish(t *testing.T) { f := a.f defer f.cancel() if a.provider != nil && a.provider.server != nil { - a.provider.server.CloseClientConnections() - a.provider.server.Close() + f.evidence["provider_cleanup"] = a.provider.closeOriginal(f.started) a.provider.mu.Lock() for index, body := range a.provider.requests { f.keep(t, fmt.Sprintf("provider-request-%d.json", index+1), body) @@ -383,6 +416,12 @@ func (a *kimiSingleToolAttempt) finish(t *testing.T) { if time.Since(f.started) > 180*time.Second { t.Error("Kimi original runtime attempt total deadline exceeded") } + if f.evidence["runtime_attempt_complete"] == true && (!kimiAttemptProviderClosed(f.evidence["provider_cleanup"]) || f.ctx.Err() != nil) { + t.Error("Kimi staged completion lost original work/ordinary closure") + } + if t.Failed() { + kimiAttemptClearCompletion(f.evidence) + } if body, err := projectNativeRuntimeRead(filepath.Join(f.base.Root, "a-events.jsonl"), 1<<20); err == nil { f.keep(t, "probe-events.jsonl", body) } @@ -434,7 +473,7 @@ func (a *kimiSingleToolAttempt) finish(t *testing.T) { } func kimiAttemptAdmit(files map[string][]byte) error { - allowed := map[string]bool{"runtime-attempt.json": true, "probe-events.jsonl": true, "python-api.json": true, + allowed := map[string]bool{"runtime-attempt.json": true, "probe-events.jsonl": true, "python-api.json": true, "native-data-proof.json": true, ".kimi-code-mcp.json.installed": true, "uap-kimi-native-a-SKILL.md.installed": true} for index := 1; index <= 8; index++ { allowed[fmt.Sprintf("provider-request-%d.json", index)] = true @@ -479,18 +518,27 @@ func kimiAttemptAdmit(files map[string][]byte) error { } } result, err := kimiProjectObject(files["runtime-attempt.json"]) - if err != nil || result["client"] != "kimi" || result["runtime_attempt_complete"] != false || result["vendor_authentication_qualified"] != false || result["real_model_qualified"] != false || result["test_failed"] != true { - return fmt.Errorf("Kimi initial capture cannot claim successful qualification") + if err != nil || result["client"] != "kimi" || result["vendor_authentication_qualified"] != false || result["real_model_qualified"] != false { + return fmt.Errorf("Kimi capture boundary differs") + } + success := result["runtime_attempt_complete"] == true && result["test_failed"] == false + failed := result["runtime_attempt_complete"] == false && result["test_failed"] == true + if !success && !failed { + return fmt.Errorf("Kimi completion/failure pair differs") } flags, ok := result["qualification"].(map[string]any) if !ok || len(flags) != 5 { return fmt.Errorf("Kimi initial qualification flags absent") } for _, key := range []string{"project-lifecycle", "native-restart-removal", "mcp-initialize", "mcp-tools-list", "mcp-tools-call"} { - if flags[key] != false { - return fmt.Errorf("Kimi initial capture promoted unobserved qualification") + want := success && key != "project-lifecycle" && key != "native-restart-removal" + if flags[key] != want { + return fmt.Errorf("Kimi capture promoted unobserved qualification") } } + if success { + return kimiAttemptSuccessfulFiles(files, result) + } return nil } diff --git a/repotests/agentplugins_kimi_project_native_history_contract_test.go b/repotests/agentplugins_kimi_project_native_history_contract_test.go new file mode 100644 index 000000000..94e68c6b1 --- /dev/null +++ b/repotests/agentplugins_kimi_project_native_history_contract_test.go @@ -0,0 +1,549 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "encoding/json" + "net/url" + "path/filepath" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" +) + +func kimiHistoryEncode(t *testing.T, value any) []byte { + t.Helper() + body, err := json.Marshal(value) + if err != nil { + t.Fatal(err) + } + return body +} + +func kimiHistoryDecode(t *testing.T, body []byte) map[string]any { + t.Helper() + value, err := projectNativeRuntimeJSON(body) + if err != nil { + t.Fatal(err) + } + row, ok := value.(map[string]any) + if !ok { + t.Fatal("contract object required") + } + return row +} + +// Every record below is independently authored for an inert contract. These +// strings are never executed and no captured native stream is stored in Git. +func kimiSyntheticFailedHistoryFiles(t *testing.T) map[string][]byte { + t.Helper() + nonce := strings.Repeat("c", 32) + installation := "inert-contract-installation" + physical := domain.ComputePhysicalArtifactID("uap-kimi-native-a", installation) + root := "/inert/kimi-finite-completion" + projects := map[string]string{"a": root + "/project", "b": root + "/project-b", "c": root + "/project-c", "user": root + "/outside"} + client := "/inert/never-executed/kimi" + active := projects["a"] + "/.uap/managed/clients/kimi/" + physical + data := projects["a"] + "/.uap/plugin-data/" + physical + profile := projects["a"] + "/.kimi-code" + nativeSession := "session_11111111-2222-4333-8444-555555555555" + callID, approvalID := "uap_"+nonce+"_attempt", "approval_inert-owned" + prompt := "Call the one installed fixture tool once with the exact nonce and marker; then report completion. " + nonce + "-a" + marker := "retained-" + nonce + stamp := "2026-01-02T03:04:07Z" + args := map[string]any{"nonce": nonce + "-a", "operation": "write", "marker": marker} + facts := map[string]any{"nonce": nonce + "-a", "revision": "a-one", "cwd": active, "argv": []any{"Kimi owned a", active + "/argument"}, "plugin_root": active, "plugin_data": data, "literal": "", "once": "", "marker": marker, "pid": 17, "session": "17-1767323048000000000"} + binding := domain.ClientBinding{ClientBindingID: domain.ComputeClientBindingID(installation, "kimi", "project", active), ClientID: "kimi", Scope: "project", TargetLocator: active, PhysicalArtifact: physical, + NativeProfileRoot: profile, DataReceiptID: "data_" + kimiProjectProofSHA([]byte(strings.Join([]string{installation, physical, "project"}, "\x00")))[:24], Materialization: domain.MaterializationMaterialized, Activation: domain.ActivationPrepared, Authentication: domain.AuthenticationNotChecked, Verification: domain.VerificationInstalled} + server := kimiProjectExpectedServer(&kimiProjectNativePackage{Revision: "a-one", Data: data, Binding: binding}, "a", root, nonce) + files := map[string][]byte{} + receipt := func(label, cwd string, argv []string) { + files[label+".command.json"] = kimiHistoryEncode(t, map[string]any{"argv": argv, "cwd": cwd, "exit": 0, "error": false, "deadline": false, "cleanup": "ordinary full-descendant supervision; forced termination fails"}) + files[label+".stdout.log"], files[label+".stderr.log"] = []byte{}, []byte{} + } + for _, scope := range []string{"a", "b", "c"} { + receipt("git-"+scope, projects[scope], []string{"/inert/never-executed/git", "-c", "core.hooksPath=/dev/null", "init", "--quiet"}) + } + receipt("version", projects["user"], []string{client, "--version"}) + files["version.stdout.log"] = []byte("2.3.2\n") + receipt("add-a", projects["a"], []string{"/inert/never-executed/agentplugins", "add", root + "/sources/a", "--no-save", "--target", "kimi", "--format", "json", "--scope", "project", "--project-root", projects["a"]}) + files["add-a.stdout.log"] = kimiHistoryEncode(t, map[string]any{"schema_version": 1, "command": "add", "result": "success", "data": map[string]any{"targets": []any{map[string]any{"target": "kimi", "output": map[string]any{"result": map[string]any{"installation_id": installation, "mutated": true}}}}}}) + receipt("native-attempt", projects["user"], []string{"/inert/never-executed/python", "/inert/checked-source/scripts/project_native_collectors/kimi_attempt.py", "--attempt-request", root + "/attempt-request.json"}) + files[".kimi-code-mcp.json.installed"] = kimiHistoryEncode(t, map[string]any{"mcpServers": map[string]any{"uap-kimi-native-a": server}}) + files["uap-kimi-native-a-SKILL.md.installed"] = []byte("---\nname: uap-kimi-native-a\ndescription: Kimi installed a-one\n---\nUAP_KIMI_a-one_" + nonce + "\n") + for index, method := range []string{"initialize", "tools/list", "tools/call"} { + event := map[string]any{"method": method, "pid": 17, "session": facts["session"], "nonce": nonce + "-a", "timestamp": "2026-01-02T03:04:08.00" + strconv.Itoa(index) + "Z", "facts": nil} + if method == "tools/call" { + event["facts"] = facts + } + files["probe-events.jsonl"] = append(files["probe-events.jsonl"], append(kimiHistoryEncode(t, event), '\n')...) + } + first := kimiAttemptContractBody() + first["prompt_cache_key"] = nativeSession + first["messages"] = []any{map[string]any{"role": "user", "content": prompt}} + files["provider-request-1.json"] = kimiHistoryEncode(t, first) + second := kimiHistoryDecode(t, files["provider-request-1.json"]) + second["messages"] = append(second["messages"].([]any), map[string]any{"role": "assistant", "content": nil, "tool_calls": []any{map[string]any{"id": callID, "type": "function", "function": map[string]any{"name": kimiAttemptTool, "arguments": string(kimiHistoryEncode(t, args))}}}}, map[string]any{"role": "tool", "tool_call_id": callID, "content": "Wall time: 0.004 seconds\n" + string(kimiHistoryEncode(t, facts))}) + files["provider-request-2.json"] = kimiHistoryEncode(t, second) + messages := []any{} + message := func(id, role string, content []any, metadata any) { + row := map[string]any{"id": id, "session_id": nativeSession, "role": role, "content": content, "created_at": stamp} + if id == "msg_inert_result" { + row["created_at"] = "2026-01-02T03:04:08.003Z" + } + if id == "msg_inert_final" { + row["created_at"] = "2026-01-02T03:04:08.004Z" + } + if metadata != nil { + row["metadata"] = metadata + } + messages = append(messages, row) + } + message("msg_inert_prompt", "user", []any{map[string]any{"type": "text", "text": prompt}}, map[string]any{"origin": map[string]any{"kind": "user"}}) + reminder := "\nToday's date is 2026-01-02. The current date is restated in a reminder whenever it changes; rely on the latest such reminder for the current date. DO NOT mention this to the user explicitly.\n" + message("msg_inert_date", "user", []any{map[string]any{"type": "text", "text": reminder}}, map[string]any{"origin": map[string]any{"kind": "injection", "variant": "date_change", "disclosure": map[string]any{"kind": "date", "renderGeneration": 1, "localDate": "2026-01-02", "timeZone": "UTC"}}}) + message("msg_inert_call", "assistant", []any{map[string]any{"type": "tool_use", "tool_call_id": callID, "tool_name": kimiAttemptTool, "input": args}}, nil) + message("msg_inert_result", "tool", []any{map[string]any{"type": "tool_result", "tool_call_id": callID, "output": string(kimiHistoryEncode(t, facts))}}, nil) + message("msg_inert_final", "assistant", []any{}, nil) + raw := map[string]any{"kimi-single-tool.stdout.log": "inert synthetic service stream; never executed\n", "kimi-single-tool.stderr.log": ""} + command := map[string]any{"argv": []string{client, "web", "--no-open", "--host", "127.0.0.1", "--port", "0"}, "cwd": projects["a"], "probe": "kimi-single-tool", "elapsed_seconds": 0.01, "error": nil, "exit": 0, + "cleanup": map[string]bool{"forced_kill": false, "group_reaped": true, "port_closed": true, "shutdown_acknowledged": true, "sigterm_sent": false}} + for _, channel := range []string{"stdout", "stderr"} { + name := "kimi-single-tool." + channel + ".log" + body := []byte(raw[name].(string)) + command[channel+"_file"] = name + command[channel+"_bytes"] = len(body) + command[channel+"_sha256"] = kimiProjectProofSHA(body) + } + httpRows := []any{} + http := func(label, method, path string, auth bool, status, code int, data any) { + response := map[string]any{"code": code, "msg": "success", "data": data, "request_id": "inert-request-" + label} + if code != 0 { + response["msg"] = "inert unauthorized" + } + body := kimiHistoryEncode(t, response) + name := "kimi-single-tool-" + label + ".http.json" + raw[name] = string(body) + httpRows = append(httpRows, map[string]any{"authenticated": auth, "error": nil, "label": label, "method": method, "path": path, "response_bytes": len(body), "response_file": name, "response_sha256": kimiProjectProofSHA(body), "status": status}) + } + http("health", "GET", "/api/v1/healthz", false, 200, 0, map[string]bool{"ok": true}) + http("unauthorized", "GET", "/api/v1/meta", false, 401, 40101, nil) + http("meta", "GET", "/api/v1/meta", true, 200, 0, map[string]any{"server_version": "2.3.2", "capabilities": map[string]bool{"websocket": true, "file_upload": true, "fs_query": true, "mcp": true, "tasks": true, "terminal": true}, "server_id": "inert-server", "started_at": stamp, "open_in_apps": []any{}, "dangerous_bypass_auth": false, "backend": "v2"}) + workspaces := map[string]string{"a": "wd_project_111111111111", "b": "wd_project-b_222222222222", "c": "wd_project-c_333333333333"} + for _, scope := range []string{"a", "b", "c"} { + http("workspace-"+scope, "POST", "/api/v1/workspaces", true, 200, 0, map[string]any{"id": workspaces[scope], "root": projects[scope], "name": "uap-kimi-native-" + scope, "created_at": stamp, "last_opened_at": stamp, "session_count": 0}) + http("trust-before-"+scope, "GET", "/api/v1/workspaces/"+workspaces[scope]+"/trust", true, 200, 0, map[string]bool{"trusted": false}) + } + for _, scope := range []string{"a", "b"} { + for _, action := range []string{"set", "after"} { + method := "GET" + if action == "set" { + method = "POST" + } + http("trust-"+action+"-"+scope, method, "/api/v1/workspaces/"+workspaces[scope]+"/trust", true, 200, 0, map[string]bool{"trusted": true}) + } + } + http("trust-untrusted-c", "GET", "/api/v1/workspaces/"+workspaces["c"]+"/trust", true, 200, 0, map[string]bool{"trusted": false}) + projected := map[string]any{} + for key, value := range server { + projected[key] = value + } + delete(projected, "env") + projected["envKeys"] = []string{"PLUGIN_DATA", "PLUGIN_ROOT", "UAP_TEST_EVENTS", "UAP_TEST_NONCE", "UAP_TEST_REVISION", "UAP_TEST_ROOT"} + for _, scope := range []string{"a", "b", "c", "user"} { + rows := []any{} + if scope == "a" { + rows = append(rows, map[string]any{"name": "uap-kimi-native-a", "config": projected, "source": "global", "origin": profile + "/mcp.json", "mutable": false}) + } + http("catalog-"+scope, "GET", "/api/v2/mcp/servers?cwd="+url.QueryEscape(projects[scope]), true, 200, 0, rows) + } + http("skills-a", "GET", "/api/v1/workspaces/"+workspaces["a"]+"/skills", true, 200, 0, map[string]any{"skills": []any{map[string]any{"name": "uap-kimi-native-a", "description": "Kimi installed a-one", "path": profile + "/skills/uap-kimi-native-a/SKILL.md", "source": "project"}}}) + state := func(initial bool) map[string]any { + model, title := "uap-kimi-native-model", prompt + if initial { + model = "" + title = "" + } + row := map[string]any{"id": nativeSession, "workspace_id": workspaces["a"], "title": title, "created_at": stamp, "updated_at": "2026-01-02T03:04:10Z", "busy": false, "main_turn_active": false, "pending_interaction": "none", "archived": false, "metadata": map[string]any{"cwd": projects["a"]}, "agent_config": map[string]any{"model": model}, "usage": map[string]int{"input_tokens": 0, "output_tokens": 0, "cache_read_tokens": 0, "cache_creation_tokens": 0, "context_tokens": 0}, "permission_rules": []any{}, "message_count": 0, "last_seq": 0} + if !initial { + row["last_turn_reason"] = "failed" + row["last_prompt"] = prompt + } + return row + } + base := "/api/v1/sessions/" + nativeSession + http("create-session", "POST", "/api/v1/sessions", true, 200, 0, state(true)) + http("prompt", "POST", base+"/prompts", true, 200, 0, map[string]any{"prompt_id": "msg_inert_prompt", "user_message_id": "msg_inert_prompt", "status": "running", "content": []any{map[string]any{"type": "text", "text": prompt}}, "created_at": stamp}) + http("approvals-0", "GET", base+"/approvals?status=pending", true, 200, 0, map[string]any{"items": []any{map[string]any{"approval_id": approvalID, "session_id": nativeSession, "agent_id": "main", "turn_id": 0, "tool_call_id": callID, "tool_name": kimiAttemptTool, "action": "Approve " + kimiAttemptTool, "tool_input_display": map[string]any{"kind": "generic", "summary": "Approve " + kimiAttemptTool, "detail": args}, "created_at": stamp, "expires_at": "2026-01-03T03:04:07Z"}}}) + http("approve-once", "POST", base+"/approvals/"+approvalID, true, 200, 0, map[string]any{"resolved": true, "resolved_at": stamp}) + for index := 0; index < 2; index++ { + var inFlight any + if index == 0 { + inFlight = map[string]any{"turn_id": 0, "assistant_text": "", "thinking_text": "", "running_tools": []any{}} + } + http("snapshot-"+strconv.Itoa(index), "GET", base+"/snapshot", true, 200, 0, map[string]any{"as_of_seq": 20 + index, "epoch": "inert-epoch", "session": state(false), "messages": map[string]any{"items": messages, "has_more": false}, "in_flight_turn": inFlight, "pending_approvals": []any{}, "pending_questions": []any{}, "subagents": []any{}}) + } + reversed := []any{} + for index := len(messages) - 1; index >= 0; index-- { + reversed = append(reversed, messages[index]) + } + http("history", "GET", base+"/messages?page_size=20", true, 200, 0, map[string]any{"items": reversed, "has_more": false}) + http("shutdown", "POST", "/api/v1/shutdown", true, 200, 0, map[string]bool{"ok": true}) + flags := map[string]bool{"project-lifecycle": false, "native-restart-removal": false, "mcp-initialize": false, "mcp-tools-list": false, "mcp-tools-call": false} + research := map[string]any{"schema_version": 1, "client": "kimi", "phase": "installed-single-tool-attempt", "commands": []any{command}, "http_requests": httpRows, "qualification": flags, + "observations": map[string]any{"native_session_id": nativeSession, "native_turn_id": 0, "provider_call_id": callID, "approval_id": approvalID, "api_request_count": len(httpRows), "snapshot_count": 2, "native_capture_complete": false, "native_history_grammar_frozen": false, "boundary": "Bounded initial capture; no native history or qualification inferred from provider/probe success"}} + raw["research.json"] = string(kimiHistoryEncode(t, research)) + envelope := append(kimiHistoryEncode(t, map[string]any{"schema_version": 1, "files": raw}), '\n') + files["python-api.json"], files["native-attempt.stdout.log"] = envelope, envelope + files["runtime-attempt.json"] = kimiHistoryEncode(t, map[string]any{"client": "kimi", "version": "2.3.2", "phase": "installed-single-tool-attempt", "activation": "prepared", "provider_kind": "scripted-openai-chat-completions", "runtime_attempt_complete": false, "test_failed": true, "vendor_authentication_qualified": false, "real_model_qualified": false, "qualification": flags, "elapsed_ms": 8000, "provider_returned": false, "native_capture_complete": false, "native_session_id": nativeSession, "native_turn_id": 0, "provider_call_id": callID, "binding": binding, "data_locator": data, "acquisition": map[string]any{"runtime_files": []any{map[string]string{"path": client}}}, "source_identity": map[string]any{"installer_base_commit": strings.Repeat("a", 40), "installer_tree": strings.Repeat("b", 40), "installer_source_state": "committed", "installer_patch_sha256": []any{}}}) + return files +} + +func kimiHistoryFixtureExpectation(t *testing.T, files map[string][]byte) kimiAttemptHistoryExpectation { + t.Helper() + result := kimiHistoryDecode(t, files["runtime-attempt.json"]) + binding := result["binding"].(map[string]any) + profile := binding["native_profile_root"].(string) + project, root := filepath.Dir(profile), filepath.Dir(filepath.Dir(profile)) + probe := bytes.Split(bytes.TrimSpace(files["probe-events.jsonl"]), []byte{'\n'}) + facts := kimiHistoryDecode(t, probe[len(probe)-1])["facts"].(map[string]any) + config := kimiHistoryDecode(t, files[".kimi-code-mcp.json.installed"]) + server := config["mcpServers"].(map[string]any)["uap-kimi-native-a"].(map[string]any) + call := result["provider_call_id"].(string) + nonce := strings.TrimSuffix(strings.TrimPrefix(call, "uap_"), "_attempt") + return kimiAttemptHistoryExpectation{Client: result["acquisition"].(map[string]any)["runtime_files"].([]any)[0].(map[string]any)["path"].(string), Session: result["native_session_id"].(string), Call: call, + Approval: "approval_inert-owned", Prompt: "Call the one installed fixture tool once with the exact nonce and marker; then report completion. " + nonce + "-a", Sentinel: "UAP_KIMI_DONE_" + nonce, + Skill: filepath.Join(profile, "skills", "uap-kimi-native-a", "SKILL.md"), Projects: map[string]string{"a": project, "b": filepath.Join(root, "project-b"), "c": filepath.Join(root, "project-c"), "user": filepath.Join(root, "outside")}, Server: server, Facts: facts, + Started: time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC), Ended: time.Date(2026, 1, 2, 3, 4, 11, 0, time.UTC)} +} + +// This modifies only an in-memory derivative. It is explicitly synthetic and +// cannot establish a native outcome, source qualification or release claim. +func kimiSyntheticCompletedHistory(t *testing.T) (map[string][]byte, kimiAttemptHistoryExpectation) { + t.Helper() + files := kimiSyntheticFailedHistoryFiles(t) + expected := kimiHistoryFixtureExpectation(t, files) + kimiMutateHistoryEnvelope(t, files, func(raw map[string]any) { + for _, label := range []string{"snapshot-0", "snapshot-1", "history"} { + name := "kimi-single-tool-" + label + ".http.json" + response := kimiHistoryDecode(t, []byte(raw[name].(string))) + data := response["data"].(map[string]any) + var rows []any + if label == "history" { + rows = data["items"].([]any) + } else { + data["session"].(map[string]any)["last_turn_reason"] = "completed" + rows = data["messages"].(map[string]any)["items"].([]any) + } + for _, item := range rows { + row := item.(map[string]any) + if row["role"] == "assistant" && len(row["content"].([]any)) == 0 { + row["content"] = []any{map[string]any{"type": "text", "text": expected.Sentinel}} + } + } + raw[name] = string(kimiHistoryEncode(t, response)) + } + }) + proof, err := kimiAttemptHistory(files["python-api.json"], expected) + if err != nil { + t.Fatal("synthetic history fixture refused", err) + } + dataProof := kimiHistoryEncode(t, map[string]any{"schema_version": 1, "before": map[string]string{".": "020000000700:directory", ".agentplugins-data-owner.json": "0600:" + strings.Repeat("a", 64)}, "after_without_marker": map[string]string{".": "020000000700:directory", ".agentplugins-data-owner.json": "0600:" + strings.Repeat("a", 64)}, "marker_body": expected.Facts["marker"], "marker_bytes": len(expected.Facts["marker"].(string)), "marker_sha256": kimiProjectProofSHA([]byte(expected.Facts["marker"].(string)))}) + files["native-data-proof.json"] = dataProof + proof["data_proof_sha256"] = kimiProjectProofSHA(dataProof) + result := kimiHistoryDecode(t, files["runtime-attempt.json"]) + for _, key := range []string{"runtime_attempt_complete", "native_capture_complete", "native_history_grammar_frozen", "provider_returned"} { + result[key] = true + } + result["test_failed"] = false + result["probe_facts"], result["native_history"] = expected.Facts, proof + result["provider_cleanup"] = map[string]any{"shutdown_complete": true, "forced_close": false, "error": false, "requests": 2, "issued": true, "returned": true} + result["qualification"] = map[string]bool{"project-lifecycle": false, "native-restart-removal": false, "mcp-initialize": true, "mcp-tools-list": true, "mcp-tools-call": true} + files["runtime-attempt.json"] = kimiHistoryEncode(t, result) + return files, expected +} + +func kimiMutateHistoryEnvelope(t *testing.T, files map[string][]byte, mutate func(map[string]any)) { + t.Helper() + envelope := kimiHistoryDecode(t, files["python-api.json"]) + raw := envelope["files"].(map[string]any) + mutate(raw) + research := kimiHistoryDecode(t, []byte(raw["research.json"].(string))) + for _, item := range research["http_requests"].([]any) { + row := item.(map[string]any) + body, exists := raw[row["response_file"].(string)] + if exists { + stream := []byte(body.(string)) + row["response_bytes"] = len(stream) + row["response_sha256"] = kimiProjectProofSHA(stream) + } + } + raw["research.json"] = string(kimiHistoryEncode(t, research)) + body := append(kimiHistoryEncode(t, envelope), '\n') + files["python-api.json"], files["native-attempt.stdout.log"] = body, body +} + +func kimiMutateHTTP(t *testing.T, files map[string][]byte, label string, mutate func(map[string]any)) { + t.Helper() + kimiMutateHistoryEnvelope(t, files, func(raw map[string]any) { + name := "kimi-single-tool-" + label + ".http.json" + response := kimiHistoryDecode(t, []byte(raw[name].(string))) + mutate(response["data"].(map[string]any)) + raw[name] = string(kimiHistoryEncode(t, response)) + }) +} + +func TestKimiSingleToolSyntheticFailedHistoryAndModelFraming(t *testing.T) { + files := kimiSyntheticFailedHistoryFiles(t) + if err := kimiAttemptAdmit(files); err != nil { + t.Fatal("synthetic failed archive refused", err) + } + expected := kimiHistoryFixtureExpectation(t, files) + request := kimiHistoryDecode(t, files["provider-request-2.json"]) + messages := request["messages"].([]any) + text := messages[len(messages)-1].(map[string]any)["content"].(string) + if _, err := projectNativeRuntimeJSON([]byte(text)); err == nil { + t.Fatal("original whole-JSON refusal not reproduced") + } + jsonFacts, err := kimiAttemptModelFacts(text) + if err != nil || !reflect.DeepEqual(kimiHistoryDecode(t, jsonFacts), expected.Facts) { + t.Fatal("synthetic precise framing/facts join failed", err) + } + if _, err := kimiAttemptHistory(files["python-api.json"], expected); err == nil { + t.Fatal("synthetic failed/empty native assistant promoted") + } + result := kimiHistoryDecode(t, files["runtime-attempt.json"]) + if result["runtime_attempt_complete"] != false || result["test_failed"] != true { + t.Fatal("synthetic failed outcome changed") + } +} + +func TestKimiSingleToolModelFramingIsExactAndBounded(t *testing.T) { + for _, header := range []string{"Wall time: 0.000 seconds", "Wall time: 0.004 seconds", "Wall time: 60.000 seconds"} { + if raw, err := kimiAttemptModelFacts(header + "\n{\"owned\":true}"); err != nil || string(raw) != `{"owned":true}` { + t.Fatal("finite source header refused", err) + } + } + for _, body := range []string{`{"owned":true}`, "Wall time: +0.004 seconds\n{}", "Wall time: -0.004 seconds\n{}", "Wall time: 00.004 seconds\n{}", "Wall time: 0.04 seconds\n{}", "Wall time: 0.0040 seconds\n{}", "Wall time: 0.004 second\n{}", "Wall time: 0.004 seconds\r\n{}", "Wall time: 60.001 seconds\n{}", "Wall time: NaN seconds\n{}", "Wall time: 1e3 seconds\n{}", "Wall time: 0.004 seconds\nWall time: 0.004 seconds\n{}", "Wall time: 0.004 seconds\n{}extra", "Wall time: 0.004 seconds\n{\"a\":1,\"a\":2}", "Wall time: 0.004 seconds\n{\"a\":1e999}", "Wall time: 0.004 seconds\n\xff", "Wall time: 0.004 seconds\n" + strings.Repeat("[", 34) + "0" + strings.Repeat("]", 34), "Wall time: 0.004 seconds\n" + strings.Repeat("x", 1<<20)} { + if _, err := kimiAttemptModelFacts(body); err == nil { + t.Fatal("non-source model framing admitted", strconv.Quote(body[:min(len(body), 100)])) + } + } +} + +func TestKimiSingleToolSyntheticCompletedHistoryHasOnlyThreeMCPFlags(t *testing.T) { + files, _ := kimiSyntheticCompletedHistory(t) + if err := kimiAttemptAdmit(files); err != nil { + t.Fatal("synthetic finite complete contract refused", err) + } + for name := range files { + copyFiles := map[string][]byte{} + for key, body := range files { + if key != name { + copyFiles[key] = body + } + } + if err := kimiAttemptAdmit(copyFiles); err == nil { + t.Fatal("missing successful member admitted", name) + } + } + failed := kimiSyntheticFailedHistoryFiles(t) + failed["runtime-attempt.json"] = files["runtime-attempt.json"] + failed["native-data-proof.json"] = files["native-data-proof.json"] + if err := kimiAttemptAdmit(failed); err == nil { + t.Fatal("success flags/proof placed over synthetic failed raw admitted") + } +} + +func TestKimiSingleToolFiniteHistoryRejectsForeignOrIncompleteNativeData(t *testing.T) { + kinds := []string{"failed", "cancelled", "active", "in-flight", "pending-approval", "pending-question", "subagent", "session", "workspace", "cwd", "model", "permission", "epoch", "sequence", "history-order", "history-more", "snapshot-more", "extra-message", "duplicate-id", "stale-time", "future-time", "unknown-message", "unknown-part", "wrong-call", "wrong-tool", "wrong-args", "error-result", "wrong-facts", "missing-sentinel", "wrong-sentinel", "extra-sentinel", "date-metadata", "date-text", "unknown-file", "missing-file", "response-hash", "receipt-path", "receipt-method", "receipt-auth", "unknown-response", "capture-complete"} + for _, kind := range kinds { + t.Run(kind, func(t *testing.T) { + files, expected := kimiSyntheticCompletedHistory(t) + snapshotMutation := func(data map[string]any) { + session := data["session"].(map[string]any) + page := data["messages"].(map[string]any) + rows := page["items"].([]any) + switch kind { + case "failed", "cancelled": + session["last_turn_reason"] = kind + case "active": + session["busy"] = true + case "in-flight": + data["in_flight_turn"] = map[string]any{"turn_id": 0, "assistant_text": "", "thinking_text": "", "running_tools": []any{}} + case "pending-approval": + data["pending_approvals"] = []any{map[string]any{"unapproved": true}} + case "pending-question": + data["pending_questions"] = []any{map[string]any{"unapproved": true}} + case "subagent": + data["subagents"] = []any{map[string]any{"foreign": true}} + case "session": + session["id"] = "session_foreign" + case "workspace": + session["workspace_id"] = "foreign" + case "cwd": + session["metadata"].(map[string]any)["cwd"] = "/foreign" + case "model": + session["agent_config"].(map[string]any)["model"] = "foreign" + case "permission": + session["permission_rules"] = []any{map[string]any{"remembered": true}} + case "epoch": + data["epoch"] = "foreign" + case "sequence": + data["as_of_seq"] = -1 + case "snapshot-more": + page["has_more"] = true + case "extra-message": + page["items"] = append(rows, rows[0]) + case "duplicate-id": + rows[1].(map[string]any)["id"] = rows[0].(map[string]any)["id"] + case "stale-time": + rows[0].(map[string]any)["created_at"] = "2000-01-01T00:00:00Z" + case "future-time": + rows[0].(map[string]any)["created_at"] = "2099-01-01T00:00:00Z" + case "unknown-message": + rows[2].(map[string]any)["unknown"] = true + case "unknown-part": + rows[2].(map[string]any)["content"].([]any)[0].(map[string]any)["unknown"] = true + case "wrong-call": + rows[2].(map[string]any)["content"].([]any)[0].(map[string]any)["tool_call_id"] = "foreign" + case "wrong-tool": + rows[2].(map[string]any)["content"].([]any)[0].(map[string]any)["tool_name"] = "Bash" + case "wrong-args": + rows[2].(map[string]any)["content"].([]any)[0].(map[string]any)["input"].(map[string]any)["nonce"] = "foreign" + case "error-result": + rows[3].(map[string]any)["content"].([]any)[0].(map[string]any)["is_error"] = true + case "wrong-facts": + rows[3].(map[string]any)["content"].([]any)[0].(map[string]any)["output"] = `{"nonce":"foreign"}` + case "missing-sentinel": + rows[4].(map[string]any)["content"] = []any{} + case "wrong-sentinel": + rows[4].(map[string]any)["content"] = []any{map[string]any{"type": "text", "text": "foreign"}} + case "extra-sentinel": + rows[4].(map[string]any)["content"] = append(rows[4].(map[string]any)["content"].([]any), map[string]any{"type": "text", "text": "extra"}) + case "date-metadata": + rows[1].(map[string]any)["metadata"].(map[string]any)["origin"].(map[string]any)["disclosure"].(map[string]any)["timeZone"] = "foreign" + case "date-text": + rows[1].(map[string]any)["content"] = []any{map[string]any{"type": "text", "text": "foreign"}} + } + } + switch kind { + case "history-order": + kimiMutateHTTP(t, files, "history", func(data map[string]any) { rows := data["items"].([]any); rows[0], rows[1] = rows[1], rows[0] }) + case "history-more": + kimiMutateHTTP(t, files, "history", func(data map[string]any) { data["has_more"] = true }) + case "unknown-file", "missing-file", "response-hash", "receipt-path", "receipt-method", "receipt-auth", "unknown-response", "capture-complete": + kimiMutateHistoryEnvelope(t, files, func(raw map[string]any) { + if kind == "unknown-file" { + raw["unknown.json"] = "{}" + return + } + if kind == "missing-file" { + delete(raw, "kimi-single-tool-history.http.json") + return + } + research := kimiHistoryDecode(t, []byte(raw["research.json"].(string))) + row := research["http_requests"].([]any)[0].(map[string]any) + switch kind { + case "response-hash": + row["response_sha256"] = "wrong" + row["response_file"] = "foreign.json" + case "receipt-path": + row["path"] = "/foreign" + case "receipt-method": + row["method"] = "DELETE" + case "receipt-auth": + row["authenticated"] = true + case "unknown-response": + row["unknown"] = true + case "capture-complete": + research["observations"].(map[string]any)["native_capture_complete"] = true + } + raw["research.json"] = string(kimiHistoryEncode(t, research)) + }) + default: + kimiMutateHTTP(t, files, "snapshot-1", snapshotMutation) + } + if _, err := kimiAttemptHistory(files["python-api.json"], expected); err == nil { + t.Fatal("foreign/incomplete native history admitted") + } + }) + } +} + +func TestKimiSingleToolLateFlagsDataAndProviderClosure(t *testing.T) { + files, expected := kimiSyntheticCompletedHistory(t) + for _, kind := range []string{"marker", "marker-sha", "inventory", "unknown-inventory", "missing-owner", "oversized", "extra-field"} { + row := kimiHistoryDecode(t, files["native-data-proof.json"]) + switch kind { + case "marker": + row["marker_body"] = "foreign" + case "marker-sha": + row["marker_sha256"] = "foreign" + case "inventory": + row["after_without_marker"].(map[string]any)["extra"] = "0600:" + strings.Repeat("a", 64) + case "unknown-inventory": + row["before"].(map[string]any)["../foreign"] = "0600:" + strings.Repeat("a", 64) + row["after_without_marker"] = row["before"] + case "missing-owner": + delete(row["before"].(map[string]any), ".agentplugins-data-owner.json") + row["after_without_marker"] = row["before"] + case "oversized": + row["marker_body"] = strings.Repeat("x", 1<<20) + case "extra-field": + row["unknown"] = true + } + if err := kimiAttemptDataProof(kimiHistoryEncode(t, row), expected.Facts); err == nil { + t.Fatal("unverified data proof admitted", kind) + } + } + for _, kind := range []string{"forced", "error", "not-closed", "request-count", "not-returned"} { + row := kimiHistoryDecode(t, files["runtime-attempt.json"])["provider_cleanup"].(map[string]any) + switch kind { + case "forced": + row["forced_close"] = true + case "error": + row["error"] = true + case "not-closed": + row["shutdown_complete"] = false + case "request-count": + row["requests"] = 3 + case "not-returned": + row["returned"] = false + } + if kimiAttemptProviderClosed(row) { + t.Fatal("incomplete provider cleanup admitted", kind) + } + } + result := kimiHistoryDecode(t, files["runtime-attempt.json"]) + kimiAttemptClearCompletion(result) + if result["runtime_attempt_complete"] != false || result["native_capture_complete"] != false || result["native_history"] != nil { + t.Fatal("late failure retained successful proof") + } + for _, flag := range result["qualification"].(map[string]bool) { + if flag { + t.Fatal("late failure retained qualification") + } + } + p := &kimiAttemptProvider{key: "inert", id: "inert", issued: true, returned: true, requestCount: 2} + if err := p.start(); err != nil { + t.Fatal(err) + } + if !kimiAttemptProviderClosed(p.closeOriginal(time.Now())) { + t.Fatal("ordinary inert provider shutdown refused") + } + q := &kimiAttemptProvider{key: "inert", id: "inert", issued: true, returned: true, requestCount: 2} + if err := q.start(); err != nil { + t.Fatal(err) + } + // An exhausted original origin is failure even when no request is active. + cleanup := q.closeOriginal(time.Now().Add(-181 * time.Second)) + if kimiAttemptProviderClosed(cleanup) { + t.Fatal("expired original provider deadline admitted") + } +} diff --git a/repotests/agentplugins_kimi_project_native_history_test.go b/repotests/agentplugins_kimi_project_native_history_test.go new file mode 100644 index 000000000..3ec2e4f2d --- /dev/null +++ b/repotests/agentplugins_kimi_project_native_history_test.go @@ -0,0 +1,980 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" +) + +// Source b8dd3a1, toolResultRender.ts blob2bf13ed32a098c16c9b8a4094c49f93e569f58d0. +// Only model-visible MCP text carries this header. Native history/probe output +// remains a whole unframed JSON document and must not use this decoder. +var kimiAttemptWallTime = regexp.MustCompile(`^Wall time: (0|[1-9][0-9]?)\.[0-9]{3} seconds$`) +var kimiAttemptWorkspaceID = regexp.MustCompile(`^wd_[a-z0-9._-]+_[0-9a-f]{12}$`) +var kimiAttemptCallID = regexp.MustCompile(`^uap_([0-9a-f]{32})_attempt$`) +var kimiAttemptPhysicalID = regexp.MustCompile(`^uap-kimi-native-a-[0-9a-f]{12}$`) +var kimiAttemptInventoryValue = regexp.MustCompile(`^0[0-7]{1,12}:(directory|[0-9a-f]{64})$`) + +func kimiAttemptModelFacts(body string) ([]byte, error) { + if len(body) == 0 || len(body) > projectNativeRuntimeBodyLimit { + return nil, fmt.Errorf("Kimi model tool result bound differs") + } + header, raw, ok := strings.Cut(body, "\n") + if !ok || !kimiAttemptWallTime.MatchString(header) { + return nil, fmt.Errorf("Kimi model tool result framing differs") + } + seconds, err := strconv.ParseFloat(strings.TrimSuffix(strings.TrimPrefix(header, "Wall time: "), " seconds"), 64) + if err != nil || seconds < 0 || seconds > 60 { + return nil, fmt.Errorf("Kimi model tool duration exceeds original service bound") + } + if _, err := projectNativeRuntimeJSON([]byte(raw)); err != nil { + return nil, err + } + return []byte(raw), nil +} + +type kimiAttemptHistoryExpectation struct { + Client, Session, Call, Approval, Prompt, Sentinel, Skill string + Projects map[string]string + Server, Facts map[string]any + Started, Ended time.Time +} + +func kimiHistoryObject(value any, required, optional string) (map[string]any, error) { + row, ok := value.(map[string]any) + if !ok || projectNativeRuntimeKeys(row, required, optional) != nil { + return nil, fmt.Errorf("Kimi finite history object differs") + } + return row, nil +} + +func kimiHistoryInteger(value any, low, high int64) (int64, error) { + if !projectNativeRuntimeNumber(value, float64(low), float64(high), true) { + return 0, fmt.Errorf("Kimi finite history integer differs") + } + number, ok := value.(json.Number) + if !ok { + return 0, fmt.Errorf("Kimi finite history integer encoding differs") + } + result, err := number.Int64() + return result, err +} + +func kimiHistoryInstant(value any, started, ended time.Time) (time.Time, error) { + text, ok := value.(string) + if !ok || len(text) > 64 { + return time.Time{}, fmt.Errorf("Kimi finite history timestamp differs") + } + stamp, err := time.Parse(time.RFC3339Nano, text) + if err != nil || stamp.Before(started) || stamp.After(ended) { + return time.Time{}, fmt.Errorf("Kimi finite history timestamp outside original run") + } + return stamp, nil +} + +func kimiHistoryEmpty(value any) bool { + rows, ok := value.([]any) + return ok && len(rows) == 0 +} + +func kimiHistorySuccess(body []byte) (any, error) { + value, err := projectNativeRuntimeJSON(body) + if err != nil { + return nil, err + } + row, err := kimiHistoryObject(value, "code msg data request_id", "details stack") + if err != nil || row["code"] != json.Number("0") || row["msg"] != "success" || !projectNativeRuntimeText(row["request_id"], 256) || row["request_id"] == "" || row["details"] != nil || row["stack"] != nil { + return nil, fmt.Errorf("Kimi finite history success envelope differs") + } + return row["data"], nil +} + +func kimiHistoryModel(initial bool) string { + if initial { + return "" + } + return kimiAttemptModel +} + +func kimiHistorySession(value any, expected kimiAttemptHistoryExpectation, workspace string, terminal, initial bool) error { + row, err := kimiHistoryObject(value, "id workspace_id title created_at updated_at busy metadata agent_config usage permission_rules message_count last_seq", "main_turn_active pending_interaction last_turn_reason archived archived_at current_prompt_id last_prompt") + if err != nil || row["id"] != expected.Session || row["workspace_id"] != workspace || !projectNativeRuntimeText(row["title"], 4096) || (row["title"] != "" && row["title"] != expected.Prompt) || !reflect.DeepEqual(row["metadata"], map[string]any{"cwd": expected.Projects["a"]}) || !reflect.DeepEqual(row["agent_config"], map[string]any{"model": kimiHistoryModel(initial)}) || !kimiHistoryEmpty(row["permission_rules"]) { + return fmt.Errorf("Kimi finite history session authority differs") + } + created, err := kimiHistoryInstant(row["created_at"], expected.Started, expected.Ended) + if err != nil { + return err + } + if _, err := kimiHistoryInstant(row["updated_at"], created, expected.Ended); err != nil { + return err + } + for _, name := range []string{"message_count", "last_seq"} { + if _, err := kimiHistoryInteger(row[name], 0, 1<<40); err != nil { + return err + } + } + if _, ok := row["busy"].(bool); !ok { + return fmt.Errorf("Kimi finite history busy state differs") + } + for _, name := range []string{"main_turn_active", "archived"} { + if value, exists := row[name]; exists { + if _, ok := value.(bool); !ok || name == "archived" && value != false { + return fmt.Errorf("Kimi finite history session flag differs") + } + } + } + if value, exists := row["pending_interaction"]; exists && value != "none" { + return fmt.Errorf("Kimi finite history additional interaction") + } + if value, exists := row["last_turn_reason"]; exists && value != "completed" { + return fmt.Errorf("Kimi finite history failed or cancelled native turn") + } + if value, exists := row["last_prompt"]; exists && value != expected.Prompt { + return fmt.Errorf("Kimi finite history last prompt differs") + } + if _, exists := row["archived_at"]; exists { + return fmt.Errorf("Kimi finite history archived session") + } + if value, exists := row["current_prompt_id"]; exists && (!projectNativeRuntimeText(value, 256) || value == "") { + return fmt.Errorf("Kimi finite history current prompt identity differs") + } + usage, err := kimiHistoryObject(row["usage"], "input_tokens output_tokens cache_read_tokens cache_creation_tokens context_tokens", "total_cost_usd context_limit turn_count") + if err != nil { + return err + } + for name, value := range usage { + if !projectNativeRuntimeNumber(value, 0, 1<<40, name != "total_cost_usd") { + return fmt.Errorf("Kimi finite history usage differs") + } + } + if terminal && (row["busy"] != false || row["main_turn_active"] != false || row["pending_interaction"] != "none" || row["last_turn_reason"] != "completed" || row["archived"] != false) { + return fmt.Errorf("Kimi finite history native completion absent") + } + return nil +} + +func kimiHistoryMessages(value any, expected kimiAttemptHistoryExpectation, promptID string) ([]any, error) { + page, err := kimiHistoryObject(value, "items has_more", "") + if err != nil || page["has_more"] != false { + return nil, fmt.Errorf("Kimi finite complete native history absent") + } + rows, ok := page["items"].([]any) + if !ok || len(rows) < 4 || len(rows) > 5 { + return nil, fmt.Errorf("Kimi finite native history message count differs") + } + seen := map[string]bool{} + last := expected.Started + for _, raw := range rows { + row, err := kimiHistoryObject(raw, "id session_id role content created_at", "metadata") + if err != nil || !projectNativeRuntimeText(row["id"], 256) || row["id"] == "" || row["session_id"] != expected.Session { + return nil, fmt.Errorf("Kimi finite native history message identity differs") + } + id := row["id"].(string) + if seen[id] { + return nil, fmt.Errorf("Kimi finite native history duplicate message") + } + seen[id] = true + stamp, err := kimiHistoryInstant(row["created_at"], last, expected.Ended) + if err != nil { + return nil, err + } + last = stamp + } + first := rows[0].(map[string]any) + if first["id"] != promptID || first["role"] != "user" || !reflect.DeepEqual(first["content"], []any{map[string]any{"type": "text", "text": expected.Prompt}}) || !reflect.DeepEqual(first["metadata"], map[string]any{"origin": map[string]any{"kind": "user"}}) { + return nil, fmt.Errorf("Kimi finite native history accepted prompt differs") + } + index := 1 + if len(rows) == 5 { + row := rows[index].(map[string]any) + metadata, err := kimiHistoryObject(row["metadata"], "origin", "") + if err != nil { + return nil, err + } + origin, err := kimiHistoryObject(metadata["origin"], "kind variant disclosure", "") + if err != nil || origin["kind"] != "injection" || origin["variant"] != "date_change" { + return nil, fmt.Errorf("Kimi finite native history injection differs") + } + disclosure, err := kimiHistoryObject(origin["disclosure"], "kind renderGeneration localDate timeZone", "") + stamp, stampErr := kimiHistoryInstant(row["created_at"], expected.Started, expected.Ended) + if err != nil || stampErr != nil || disclosure["kind"] != "date" || disclosure["renderGeneration"] != json.Number("1") || disclosure["timeZone"] != "UTC" || disclosure["localDate"] != stamp.UTC().Format("2006-01-02") { + return nil, fmt.Errorf("Kimi finite native history date disclosure differs") + } + text := "\nToday's date is " + stamp.UTC().Format("2006-01-02") + ". The current date is restated in a reminder whenever it changes; rely on the latest such reminder for the current date. DO NOT mention this to the user explicitly.\n" + if row["role"] != "user" || !reflect.DeepEqual(row["content"], []any{map[string]any{"type": "text", "text": text}}) { + return nil, fmt.Errorf("Kimi finite native history date text differs") + } + index++ + } + args := map[string]any{"nonce": expected.Facts["nonce"], "operation": "write", "marker": expected.Facts["marker"]} + call := rows[index].(map[string]any) + if call["role"] != "assistant" || len(call) != 5 || !reflect.DeepEqual(call["content"], []any{map[string]any{"type": "tool_use", "tool_call_id": expected.Call, "tool_name": kimiAttemptTool, "input": args}}) { + return nil, fmt.Errorf("Kimi finite native history owned call differs") + } + result := rows[index+1].(map[string]any) + parts, ok := result["content"].([]any) + if result["role"] != "tool" || len(result) != 5 || !ok || len(parts) != 1 { + return nil, fmt.Errorf("Kimi finite native history owned result absent") + } + part, err := kimiHistoryObject(parts[0], "type tool_call_id output", "is_error") + if err != nil || part["type"] != "tool_result" || part["tool_call_id"] != expected.Call || (len(part) == 4 && part["is_error"] != false) { + return nil, fmt.Errorf("Kimi finite native history tool error or call differs") + } + output, ok := part["output"].(string) + if !ok { + return nil, fmt.Errorf("Kimi finite native history result text differs") + } + facts, err := projectNativeRuntimeJSON([]byte(output)) + if err != nil || !reflect.DeepEqual(facts, expected.Facts) { + return nil, fmt.Errorf("Kimi finite native history result/probe join differs") + } + final := rows[index+2].(map[string]any) + if final["role"] != "assistant" || len(final) != 5 || !reflect.DeepEqual(final["content"], []any{map[string]any{"type": "text", "text": expected.Sentinel}}) { + return nil, fmt.Errorf("Kimi finite native history final sentinel absent") + } + return rows, nil +} + +func kimiHistoryInFlight(value any, expected kimiAttemptHistoryExpectation, promptID string) error { + if value == nil { + return nil + } + row, err := kimiHistoryObject(value, "turn_id assistant_text thinking_text running_tools", "current_prompt_id") + text, textOK := row["assistant_text"].(string) + if err != nil || row["turn_id"] != json.Number("0") || !textOK || !strings.HasPrefix(expected.Sentinel, text) || row["thinking_text"] != "" || row["current_prompt_id"] != nil && row["current_prompt_id"] != promptID { + return fmt.Errorf("Kimi finite native history in-flight turn differs") + } + tools, ok := row["running_tools"].([]any) + if !ok || len(tools) > 1 { + return fmt.Errorf("Kimi finite native history running tool count differs") + } + for _, raw := range tools { + tool, err := kimiHistoryObject(raw, "tool_call_id name", "args") + if err != nil || tool["tool_call_id"] != expected.Call || tool["name"] != kimiAttemptTool || tool["args"] != nil && !reflect.DeepEqual(tool["args"], map[string]any{"nonce": expected.Facts["nonce"], "operation": "write", "marker": expected.Facts["marker"]}) { + return fmt.Errorf("Kimi finite native history foreign running tool") + } + } + return nil +} + +// The observer remains capture-only. This independent pure reader joins its +// closed raw service/HTTP receipts to the already validated provider/probe. +func kimiAttemptHistory(body []byte, expected kimiAttemptHistoryExpectation) (map[string]any, error) { + refused := func() (map[string]any, error) { return nil, fmt.Errorf("Kimi finite native history capture refused") } + if expected.Started.IsZero() || expected.Ended.Before(expected.Started) || expected.Ended.Sub(expected.Started) > 180*time.Second || expected.Client == "" || len(expected.Projects) != 4 || !kimiAttemptCacheKey.MatchString(expected.Session) || expected.Call == "" || expected.Approval == "" || expected.Sentinel == "" || expected.Facts == nil { + return refused() + } + value, err := projectNativeRuntimeJSON(body) + if err != nil { + return nil, err + } + envelope, err := kimiHistoryObject(value, "schema_version files", "") + if err != nil || envelope["schema_version"] != json.Number("1") { + return refused() + } + rawFiles, ok := envelope["files"].(map[string]any) + if !ok || len(rawFiles) < 4 || len(rawFiles) > 64 { + return refused() + } + files := map[string][]byte{} + for name, value := range rawFiles { + text, ok := value.(string) + if !ok || name == "." || name == ".." || strings.ContainsAny(name, "/\\") || len(text) > projectNativeRuntimeBodyLimit { + return refused() + } + files[name] = []byte(text) + } + researchValue, err := projectNativeRuntimeJSON(files["research.json"]) + if err != nil { + return nil, err + } + research, err := kimiHistoryObject(researchValue, "schema_version client phase commands http_requests observations qualification", "") + if err != nil || research["schema_version"] != json.Number("1") || research["client"] != "kimi" || research["phase"] != "installed-single-tool-attempt" { + return refused() + } + flags, err := kimiHistoryObject(research["qualification"], "project-lifecycle native-restart-removal mcp-initialize mcp-tools-list mcp-tools-call", "") + if err != nil { + return nil, err + } + for _, flag := range flags { + if flag != false { + return refused() + } + } + observation, err := kimiHistoryObject(research["observations"], "native_session_id native_turn_id provider_call_id approval_id api_request_count snapshot_count native_capture_complete native_history_grammar_frozen boundary", "") + if err != nil || observation["native_session_id"] != expected.Session || observation["native_turn_id"] != json.Number("0") || observation["provider_call_id"] != expected.Call || observation["approval_id"] != expected.Approval || observation["native_capture_complete"] != false || observation["native_history_grammar_frozen"] != false || observation["boundary"] != "Bounded initial capture; no native history or qualification inferred from provider/probe success" { + return refused() + } + snapshotCount, err := kimiHistoryInteger(observation["snapshot_count"], 1, 10) + if err != nil { + return nil, err + } + used := map[string]bool{"research.json": true} + readStream := func(row map[string]any, channel string) error { + name, ok := row[channel+"_file"].(string) + if !ok || used[name] || name != "kimi-single-tool."+channel+".log" { + return fmt.Errorf("Kimi finite native service stream differs") + } + stream, exists := files[name] + if !exists || row[channel+"_bytes"] != json.Number(strconv.Itoa(len(stream))) || row[channel+"_sha256"] != kimiProjectProofSHA(stream) || channel == "stderr" && len(stream) != 0 { + return fmt.Errorf("Kimi finite native service stream join differs") + } + used[name] = true + return nil + } + commands, ok := research["commands"].([]any) + if !ok || len(commands) != 1 { + return refused() + } + command, err := kimiHistoryObject(commands[0], "argv cwd probe elapsed_seconds error exit cleanup stdout_file stdout_bytes stdout_sha256 stderr_file stderr_bytes stderr_sha256", "") + cleanup := map[string]any{"forced_kill": false, "group_reaped": true, "port_closed": true, "shutdown_acknowledged": true, "sigterm_sent": false} + if err != nil || command["probe"] != "kimi-single-tool" || command["cwd"] != expected.Projects["a"] || command["error"] != nil || command["exit"] != json.Number("0") || !projectNativeRuntimeNumber(command["elapsed_seconds"], 0, 60, false) || !reflect.DeepEqual(command["argv"], []any{expected.Client, "web", "--no-open", "--host", "127.0.0.1", "--port", "0"}) || !reflect.DeepEqual(command["cleanup"], cleanup) { + return refused() + } + for _, channel := range []string{"stdout", "stderr"} { + if err := readStream(command, channel); err != nil { + return nil, err + } + } + httpRows, ok := research["http_requests"].([]any) + if !ok || len(httpRows) > 60 || observation["api_request_count"] != json.Number(strconv.Itoa(len(httpRows))) { + return refused() + } + responses := map[string]any{} + responseBodies := map[string][]byte{} + labels := []string{} + for _, raw := range httpRows { + row, err := kimiHistoryObject(raw, "authenticated error label method path response_bytes response_file response_sha256 status", "") + if err != nil { + return nil, err + } + label, labelOK := row["label"].(string) + name, nameOK := row["response_file"].(string) + stream, exists := files[name] + if !labelOK || !nameOK || used[name] || name != "kimi-single-tool-"+label+".http.json" || !exists || row["error"] != nil || row["response_bytes"] != json.Number(strconv.Itoa(len(stream))) || row["response_sha256"] != kimiProjectProofSHA(stream) { + return refused() + } + if _, exists := responses[label]; exists { + return refused() + } + used[name] = true + responseBodies[label] = stream + labels = append(labels, label) + if label == "unauthorized" { + value, err := projectNativeRuntimeJSON(stream) + if err != nil { + return nil, err + } + negative, err := kimiHistoryObject(value, "code msg data request_id", "details stack") + if err != nil || negative["code"] != json.Number("40101") || negative["data"] != nil || !projectNativeRuntimeText(negative["msg"], 1024) || !projectNativeRuntimeText(negative["request_id"], 256) { + return refused() + } + responses[label] = nil + } else { + value, err := kimiHistorySuccess(stream) + if err != nil { + return nil, err + } + responses[label] = value + } + } + if len(used) != len(files) { + return refused() + } + return kimiAttemptHistoryResponses(httpRows, labels, responses, responseBodies, snapshotCount, expected) +} + +func kimiAttemptHistoryResponses(httpRows []any, labels []string, responses map[string]any, bodies map[string][]byte, snapshotCount int64, expected kimiAttemptHistoryExpectation) (map[string]any, error) { + refused := func() (map[string]any, error) { + return nil, fmt.Errorf("Kimi finite native history response join refused") + } + base := "/api/v1/sessions/" + expected.Session + type endpoint struct { + method, path string + auth bool + status int + } + endpoints := map[string]endpoint{"health": {"GET", "/api/v1/healthz", false, 200}, "unauthorized": {"GET", "/api/v1/meta", false, 401}, "meta": {"GET", "/api/v1/meta", true, 200}, "create-session": {"POST", "/api/v1/sessions", true, 200}, "prompt": {"POST", base + "/prompts", true, 200}, "approve-once": {"POST", base + "/approvals/" + expected.Approval, true, 200}, "history": {"GET", base + "/messages?page_size=20", true, 200}, "shutdown": {"POST", "/api/v1/shutdown", true, 200}} + order := []string{"health", "unauthorized", "meta"} + workspaces := map[string]string{} + for _, scope := range []string{"a", "b", "c"} { + row, err := kimiHistoryObject(responses["workspace-"+scope], "id root name created_at last_opened_at session_count", "") + id, ok := row["id"].(string) + if err != nil || !ok || len(id) > 256 || !kimiAttemptWorkspaceID.MatchString(id) || row["root"] != expected.Projects[scope] || row["name"] != "uap-kimi-native-"+scope || row["session_count"] != json.Number("0") { + return refused() + } + for _, key := range []string{"created_at", "last_opened_at"} { + if _, err := kimiHistoryInstant(row[key], expected.Started, expected.Ended); err != nil { + return nil, err + } + } + workspaces[scope] = id + endpoints["workspace-"+scope] = endpoint{"POST", "/api/v1/workspaces", true, 200} + endpoints["trust-before-"+scope] = endpoint{"GET", "/api/v1/workspaces/" + id + "/trust", true, 200} + order = append(order, "workspace-"+scope, "trust-before-"+scope) + if !reflect.DeepEqual(responses["trust-before-"+scope], map[string]any{"trusted": false}) { + return refused() + } + } + if workspaces["a"] == workspaces["b"] || workspaces["a"] == workspaces["c"] || workspaces["b"] == workspaces["c"] { + return refused() + } + for _, scope := range []string{"a", "b"} { + for _, action := range []string{"set", "after"} { + label := "trust-" + action + "-" + scope + method := "GET" + if action == "set" { + method = "POST" + } + endpoints[label] = endpoint{method, "/api/v1/workspaces/" + workspaces[scope] + "/trust", true, 200} + order = append(order, label) + if !reflect.DeepEqual(responses[label], map[string]any{"trusted": true}) { + return refused() + } + } + } + endpoints["trust-untrusted-c"] = endpoint{"GET", "/api/v1/workspaces/" + workspaces["c"] + "/trust", true, 200} + order = append(order, "trust-untrusted-c") + if !reflect.DeepEqual(responses["trust-untrusted-c"], map[string]any{"trusted": false}) { + return refused() + } + for _, scope := range []string{"a", "b", "c", "user"} { + label := "catalog-" + scope + endpoints[label] = endpoint{"GET", "/api/v2/mcp/servers?cwd=" + url.QueryEscape(expected.Projects[scope]), true, 200} + order = append(order, label) + rows, ok := responses[label].([]any) + if !ok || scope != "a" && len(rows) != 0 || scope == "a" && len(rows) != 1 { + return refused() + } + if scope == "a" { + row, err := kimiHistoryObject(rows[0], "name config source origin mutable", "") + if err != nil || row["name"] != "uap-kimi-native-a" || row["source"] != "global" || row["mutable"] != false || row["origin"] != filepath.Join(expected.Projects["a"], ".kimi-code", "mcp.json") { + return refused() + } + projected := map[string]any{} + for key, value := range expected.Server { + projected[key] = value + } + delete(projected, "env") + projected["envKeys"] = []any{"PLUGIN_DATA", "PLUGIN_ROOT", "UAP_TEST_EVENTS", "UAP_TEST_NONCE", "UAP_TEST_REVISION", "UAP_TEST_ROOT"} + if !reflect.DeepEqual(row["config"], projected) { + return refused() + } + } + } + endpoints["skills-a"] = endpoint{"GET", "/api/v1/workspaces/" + workspaces["a"] + "/skills", true, 200} + order = append(order, "skills-a", "create-session", "prompt") + skills, err := kimiHistoryObject(responses["skills-a"], "skills", "") + if err != nil { + return nil, err + } + skillRows, ok := skills["skills"].([]any) + if !ok || len(skillRows) > 512 { + return refused() + } + owned := 0 + for _, raw := range skillRows { + row, err := kimiHistoryObject(raw, "name description path source", "type disable_model_invocation scopes") + name, ok := row["name"].(string) + if err != nil || !ok { + return refused() + } + if strings.HasPrefix(name, "uap-kimi-native-") { + owned++ + if name != "uap-kimi-native-a" || row["description"] != "Kimi installed a-one" || row["path"] != expected.Skill || row["source"] != "project" { + return refused() + } + } + } + if owned != 1 { + return refused() + } + if !reflect.DeepEqual(responses["health"], map[string]any{"ok": true}) || !reflect.DeepEqual(responses["shutdown"], map[string]any{"ok": true}) { + return refused() + } + meta, err := kimiHistoryObject(responses["meta"], "server_version capabilities server_id started_at open_in_apps dangerous_bypass_auth backend", "experimental_flags features web_title") + if err != nil || meta["server_version"] != "2.3.2" || meta["backend"] != "v2" || meta["dangerous_bypass_auth"] != false { + return refused() + } + if err := kimiHistorySession(responses["create-session"], expected, workspaces["a"], false, true); err != nil { + return nil, err + } + prompt, err := kimiHistoryObject(responses["prompt"], "prompt_id user_message_id status content created_at", "metadata") + promptID, ok := prompt["prompt_id"].(string) + if err != nil || !ok || promptID == "" || len(promptID) > 256 || prompt["user_message_id"] != promptID || prompt["status"] != "running" && prompt["status"] != "queued" && prompt["status"] != "blocked" || !reflect.DeepEqual(prompt["content"], []any{map[string]any{"type": "text", "text": expected.Prompt}}) { + return refused() + } + if _, err := kimiHistoryInstant(prompt["created_at"], expected.Started, expected.Ended); err != nil { + return nil, err + } + approvalCount := 0 + for index := 0; index < 20; index++ { + label := "approvals-" + strconv.Itoa(index) + value, exists := responses[label] + if !exists { + break + } + approvalCount++ + endpoints[label] = endpoint{"GET", base + "/approvals?status=pending", true, 200} + order = append(order, label) + pending, err := kimiHistoryObject(value, "items", "") + items, ok := pending["items"].([]any) + if err != nil || !ok || len(items) > 1 { + return refused() + } + if len(items) == 1 { + row, err := kimiHistoryObject(items[0], "approval_id session_id agent_id turn_id tool_call_id tool_name action tool_input_display created_at expires_at", "") + action := "Approve " + kimiAttemptTool + args := map[string]any{"nonce": expected.Facts["nonce"], "operation": "write", "marker": expected.Facts["marker"]} + if err != nil || row["approval_id"] != expected.Approval || row["session_id"] != expected.Session || row["agent_id"] != "main" || row["turn_id"] != json.Number("0") || row["tool_call_id"] != expected.Call || row["tool_name"] != kimiAttemptTool || row["action"] != action || !reflect.DeepEqual(row["tool_input_display"], map[string]any{"kind": "generic", "summary": action, "detail": args}) { + return refused() + } + created, err := kimiHistoryInstant(row["created_at"], expected.Started, expected.Ended) + expires, expiresErr := time.Parse(time.RFC3339Nano, fmt.Sprint(row["expires_at"])) + if err != nil || expiresErr != nil || !expires.After(created) { + return refused() + } + break + } + } + if approvalCount == 0 { + return refused() + } + lastPending, err := kimiHistoryObject(responses["approvals-"+strconv.Itoa(approvalCount-1)], "items", "") + if err != nil || len(lastPending["items"].([]any)) != 1 { + return refused() + } + order = append(order, "approve-once") + resolved, err := kimiHistoryObject(responses["approve-once"], "resolved resolved_at", "") + if err != nil || resolved["resolved"] != true { + return refused() + } + if _, err := kimiHistoryInstant(resolved["resolved_at"], expected.Started, expected.Ended); err != nil { + return nil, err + } + var terminal map[string]any + capturedSnapshots := []map[string]any{} + epoch := "" + lastSeq := int64(-1) + for index := int64(0); index < snapshotCount; index++ { + label := "snapshot-" + strconv.FormatInt(index, 10) + endpoints[label] = endpoint{"GET", base + "/snapshot", true, 200} + order = append(order, label) + snapshot, err := kimiHistoryObject(responses[label], "as_of_seq epoch session messages in_flight_turn pending_approvals pending_questions subagents", "") + if err != nil || !kimiHistoryEmpty(snapshot["pending_approvals"]) || !kimiHistoryEmpty(snapshot["pending_questions"]) || !kimiHistoryEmpty(snapshot["subagents"]) { + return refused() + } + seq, err := kimiHistoryInteger(snapshot["as_of_seq"], 0, 1<<40) + newEpoch, ok := snapshot["epoch"].(string) + if err != nil || seq < lastSeq || !ok || newEpoch == "" || len(newEpoch) > 256 || epoch != "" && epoch != newEpoch { + return refused() + } + epoch, lastSeq = newEpoch, seq + last := index == snapshotCount-1 + if err := kimiHistorySession(snapshot["session"], expected, workspaces["a"], last, false); err != nil { + return nil, err + } + if err := kimiHistoryInFlight(snapshot["in_flight_turn"], expected, promptID); err != nil { + return nil, err + } + if last && snapshot["in_flight_turn"] != nil || !last && snapshot["in_flight_turn"] == nil { + return refused() + } + terminal = snapshot + capturedSnapshots = append(capturedSnapshots, snapshot) + } + order = append(order, "history", "shutdown") + if !reflect.DeepEqual(labels, order) { + return refused() + } + for index, raw := range httpRows { + row := raw.(map[string]any) + endpoint, exists := endpoints[labels[index]] + if !exists || row["method"] != endpoint.method || row["path"] != endpoint.path || row["authenticated"] != endpoint.auth || row["status"] != json.Number(strconv.Itoa(endpoint.status)) { + return refused() + } + } + rows, err := kimiHistoryMessages(terminal["messages"], expected, promptID) + if err != nil { + return nil, err + } + for _, snapshot := range capturedSnapshots[:len(capturedSnapshots)-1] { + if err := kimiHistoryPartialMessages(snapshot["messages"], rows, expected.Sentinel); err != nil { + return nil, err + } + } + history, err := kimiHistoryObject(responses["history"], "items has_more", "") + if err != nil || history["has_more"] != false { + return refused() + } + descending, ok := history["items"].([]any) + if !ok || len(descending) != len(rows) { + return refused() + } + for index, row := range rows { + if !reflect.DeepEqual(row, descending[len(rows)-1-index]) { + return refused() + } + } + return map[string]any{"native_session_id": expected.Session, "native_turn_id": json.Number("0"), "provider_call_id": expected.Call, "approval_id": expected.Approval, "prompt_id": promptID, "terminal_reason": "completed", "message_count": json.Number(strconv.Itoa(len(rows))), "snapshot_count": json.Number(strconv.FormatInt(snapshotCount, 10)), "history_sha256": kimiProjectProofSHA(bodies["history"]), "terminal_snapshot_sha256": kimiProjectProofSHA(bodies["snapshot-"+strconv.FormatInt(snapshotCount-1, 10)]), "probe_pid": expected.Facts["pid"], "probe_session": expected.Facts["session"], "started_at": expected.Started.UTC().Format(time.RFC3339Nano), "ended_at": expected.Ended.UTC().Format(time.RFC3339Nano)}, nil +} + +func kimiHistoryPartialMessages(value any, terminal []any, sentinel string) error { + page, err := kimiHistoryObject(value, "items has_more", "") + rows, ok := page["items"].([]any) + if err != nil || !ok || len(rows) == 0 || len(rows) > len(terminal) || page["has_more"] != false { + return fmt.Errorf("Kimi finite intermediate history page differs") + } + for index, raw := range rows { + if reflect.DeepEqual(raw, terminal[index]) { + continue + } + row, err := kimiHistoryObject(raw, "id session_id role content created_at", "") + if err != nil || index != len(terminal)-1 || row["role"] != "assistant" { + return fmt.Errorf("Kimi finite intermediate history prefix differs") + } + content, ok := row["content"].([]any) + if !ok || len(content) > 1 { + return fmt.Errorf("Kimi finite intermediate assistant differs") + } + if len(content) == 1 { + part, err := kimiHistoryObject(content[0], "type text", "") + text, ok := part["text"].(string) + if err != nil || !ok || part["type"] != "text" || !strings.HasPrefix(sentinel, text) { + return fmt.Errorf("Kimi finite intermediate sentinel differs") + } + } + copyRow := map[string]any{} + for key, value := range row { + copyRow[key] = value + } + copyRow["content"] = terminal[index].(map[string]any)["content"] + if !reflect.DeepEqual(copyRow, terminal[index]) { + return fmt.Errorf("Kimi finite intermediate message identity differs") + } + } + return nil +} + +func kimiAttemptProbeJoin(body []byte, facts map[string]any, started, ended time.Time) error { + if len(body) == 0 || len(body) > projectNativeRuntimeBodyLimit || !bytes.HasSuffix(body, []byte{'\n'}) { + return fmt.Errorf("Kimi proof probe stream bound differs") + } + lines := bytes.Split(bytes.TrimSuffix(body, []byte{'\n'}), []byte{'\n'}) + if len(lines) < 3 || len(lines) > 6 { + return fmt.Errorf("Kimi proof probe count differs") + } + last := started + for index, line := range lines { + value, err := projectNativeRuntimeJSON(line) + if err != nil { + return err + } + row, err := kimiHistoryObject(value, "method pid session nonce timestamp facts", "") + if err != nil || row["pid"] != facts["pid"] || row["session"] != facts["session"] || row["nonce"] != facts["nonce"] { + return fmt.Errorf("Kimi proof probe identity differs") + } + stamp, err := kimiHistoryInstant(row["timestamp"], last, ended) + if err != nil { + return err + } + last = stamp + method := "tools/list" + if index == 0 { + method = "initialize" + } + if index == len(lines)-1 { + method = "tools/call" + } + if row["method"] != method || index != len(lines)-1 && row["facts"] != nil || index == len(lines)-1 && !reflect.DeepEqual(row["facts"], facts) { + return fmt.Errorf("Kimi proof probe method/facts differs") + } + } + return nil +} + +func kimiAttemptProviderClosed(value any) bool { + body, err := json.Marshal(value) + if err != nil { + return false + } + parsed, err := projectNativeRuntimeJSON(body) + if err != nil { + return false + } + row, err := kimiHistoryObject(parsed, "shutdown_complete forced_close error requests issued returned", "") + return err == nil && row["shutdown_complete"] == true && row["forced_close"] == false && row["error"] == false && row["requests"] == json.Number("2") && row["issued"] == true && row["returned"] == true +} + +func kimiAttemptClearCompletion(evidence map[string]any) { + evidence["runtime_attempt_complete"], evidence["native_capture_complete"], evidence["native_history_grammar_frozen"] = false, false, false + evidence["qualification"] = map[string]bool{"project-lifecycle": false, "native-restart-removal": false, "mcp-initialize": false, "mcp-tools-list": false, "mcp-tools-call": false} + delete(evidence, "native_history") +} + +// A successful label/proof over failed raw bytes is refused here as well as in +// the live fixture. No receiver-provided native outcome is an admission token. +func kimiAttemptSuccessfulFiles(files map[string][]byte, result map[string]any) error { + refused := func() error { return fmt.Errorf("Kimi successful raw completion/proof differs") } + if result["native_capture_complete"] != true || result["native_history_grammar_frozen"] != true || result["provider_returned"] != true || result["activation"] != "prepared" || result["version"] != "2.3.2" || result["phase"] != "installed-single-tool-attempt" || result["provider_kind"] != "scripted-openai-chat-completions" || !kimiAttemptProviderClosed(result["provider_cleanup"]) { + return refused() + } + required := map[string]bool{"runtime-attempt.json": true, "probe-events.jsonl": true, "python-api.json": true, "native-data-proof.json": true, ".kimi-code-mcp.json.installed": true, "uap-kimi-native-a-SKILL.md.installed": true, "provider-request-1.json": true, "provider-request-2.json": true} + commands := map[string]map[string]any{} + for _, label := range []string{"git-a", "git-b", "git-c", "version", "add-a", "native-attempt"} { + for _, suffix := range []string{".command.json", ".stdout.log", ".stderr.log"} { + required[label+suffix] = true + } + value, err := projectNativeRuntimeJSON(files[label+".command.json"]) + if err != nil { + return err + } + row, err := kimiHistoryObject(value, "argv cwd exit error deadline cleanup", "") + if err != nil || row["exit"] != json.Number("0") || row["error"] != false || row["deadline"] != false || row["cleanup"] != "ordinary full-descendant supervision; forced termination fails" { + return refused() + } + argv, ok := row["argv"].([]any) + if !ok || len(argv) == 0 || len(argv) > 32 { + return refused() + } + for _, arg := range argv { + if !projectNativeRuntimeText(arg, 4096) || arg == "" { + return refused() + } + } + if !projectNativeRuntimeText(row["cwd"], 4096) || row["cwd"] == "" { + return refused() + } + commands[label] = row + } + if len(required) != len(files) { + return refused() + } + for name := range required { + if _, exists := files[name]; !exists { + return refused() + } + if len(files[name]) == 0 && !strings.HasSuffix(name, ".stdout.log") && !strings.HasSuffix(name, ".stderr.log") { + return refused() + } + } + if !bytes.Equal(files["python-api.json"], files["native-attempt.stdout.log"]) || len(files["native-attempt.stderr.log"]) != 0 || string(files["version.stdout.log"]) != "2.3.2\n" || len(files["version.stderr.log"]) != 0 { + return refused() + } + identity, err := kimiHistoryObject(result["source_identity"], "installer_base_commit installer_tree installer_source_state installer_patch_sha256", "") + if err != nil || identity["installer_source_state"] != "committed" || !kimiHistoryEmpty(identity["installer_patch_sha256"]) { + return refused() + } + for _, key := range []string{"installer_base_commit", "installer_tree"} { + text, ok := identity[key].(string) + if !ok || len(text) != 40 || strings.Trim(text, "0123456789abcdef") != "" { + return refused() + } + } + proof, err := kimiHistoryObject(result["native_history"], "native_session_id native_turn_id provider_call_id approval_id prompt_id terminal_reason message_count snapshot_count history_sha256 terminal_snapshot_sha256 probe_pid probe_session started_at ended_at data_proof_sha256", "") + if err != nil || proof["terminal_reason"] != "completed" || proof["native_turn_id"] != json.Number("0") { + return refused() + } + started, startErr := time.Parse(time.RFC3339Nano, fmt.Sprint(proof["started_at"])) + ended, endErr := time.Parse(time.RFC3339Nano, fmt.Sprint(proof["ended_at"])) + if startErr != nil || endErr != nil || ended.Before(started) || ended.Sub(started) > 180*time.Second || !projectNativeRuntimeNumber(result["elapsed_ms"], float64(ended.Sub(started).Milliseconds()), 180000, true) { + return refused() + } + call, callOK := result["provider_call_id"].(string) + parts := kimiAttemptCallID.FindStringSubmatch(call) + session, sessionOK := result["native_session_id"].(string) + approval, approvalOK := proof["approval_id"].(string) + if !callOK || len(parts) != 2 || !sessionOK || !approvalOK || approval == "" || proof["native_session_id"] != session || proof["provider_call_id"] != call || result["native_turn_id"] != json.Number("0") { + return refused() + } + bindingBody, err := json.Marshal(result["binding"]) + if err != nil { + return err + } + var binding domain.ClientBinding + decoder := json.NewDecoder(bytes.NewReader(bindingBody)) + decoder.DisallowUnknownFields() + if decoder.Decode(&binding) != nil || binding.ClientID != "kimi" || binding.Scope != "project" || binding.Activation != domain.ActivationPrepared || binding.Authentication != domain.AuthenticationNotChecked || binding.Verification != domain.VerificationInstalled || binding.Materialization != domain.MaterializationMaterialized || !kimiAttemptPhysicalID.MatchString(binding.PhysicalArtifact) || filepath.Base(binding.NativeProfileRoot) != ".kimi-code" { + return refused() + } + project := filepath.Dir(binding.NativeProfileRoot) + root := filepath.Dir(project) + active := filepath.Join(project, ".uap", "managed", "clients", "kimi", binding.PhysicalArtifact) + data := filepath.Join(project, ".uap", "plugin-data", binding.PhysicalArtifact) + if !filepath.IsAbs(project) || filepath.Clean(project) != project || binding.TargetLocator != active || result["data_locator"] != data { + return refused() + } + add, err := kimiProjectObject(files["add-a.stdout.log"]) + if err != nil || add["schema_version"] != json.Number("1") || add["command"] != "add" || add["result"] != "success" { + return refused() + } + addData, ok := add["data"].(map[string]any) + if !ok { + return refused() + } + targets, ok := addData["targets"].([]any) + if !ok || len(targets) != 1 { + return refused() + } + target, ok := targets[0].(map[string]any) + if !ok || target["target"] != "kimi" { + return refused() + } + output, ok := target["output"].(map[string]any) + if !ok { + return refused() + } + public, ok := output["result"].(map[string]any) + if !ok || public["mutated"] != true { + return refused() + } + installation, ok := public["installation_id"].(string) + if !ok || installation == "" || binding.PhysicalArtifact != domain.ComputePhysicalArtifactID("uap-kimi-native-a", installation) || binding.ClientBindingID != domain.ComputeClientBindingID(installation, "kimi", "project", active) || binding.DataReceiptID != "data_"+kimiProjectProofSHA([]byte(strings.Join([]string{installation, binding.PhysicalArtifact, "project"}, "\x00")))[:24] { + return refused() + } + facts, err := kimiHistoryObject(result["probe_facts"], "nonce revision cwd argv plugin_root plugin_data literal once marker pid session", "") + if err != nil { + return err + } + want := map[string]any{"nonce": parts[1] + "-a", "revision": "a-one", "cwd": active, "argv": []any{"Kimi owned a", active + "/argument"}, "plugin_root": active, "plugin_data": data, "literal": "", "once": "", "marker": "retained-" + parts[1], "pid": facts["pid"], "session": facts["session"]} + pid, err := kimiHistoryInteger(facts["pid"], 1, 1<<30) + probeSession, ok := facts["session"].(string) + if err != nil || !ok || !strings.HasPrefix(probeSession, strconv.FormatInt(pid, 10)+"-") || !reflect.DeepEqual(facts, want) || proof["probe_pid"] != facts["pid"] || proof["probe_session"] != facts["session"] { + return refused() + } + if err := kimiAttemptProbeJoin(files["probe-events.jsonl"], facts, started, ended); err != nil { + return err + } + packageFixture := &kimiProjectNativePackage{Revision: "a-one", Data: data, Binding: binding} + server := kimiProjectExpectedServer(packageFixture, "a", root, parts[1]) + installed, err := kimiProjectObject(files[".kimi-code-mcp.json.installed"]) + if err != nil || !reflect.DeepEqual(installed, map[string]any{"mcpServers": map[string]any{"uap-kimi-native-a": server}}) { + return refused() + } + skill := "---\nname: uap-kimi-native-a\ndescription: Kimi installed a-one\n---\nUAP_KIMI_a-one_" + parts[1] + "\n" + if string(files["uap-kimi-native-a-SKILL.md.installed"]) != skill { + return refused() + } + first, err := kimiAttemptRequest(files["provider-request-1.json"]) + if err != nil { + return err + } + second, err := kimiAttemptRequest(files["provider-request-2.json"]) + if err != nil { + return err + } + before := first["messages"].([]any) + after := second["messages"].([]any) + if first["prompt_cache_key"] != session || second["prompt_cache_key"] != session || len(after) != len(before)+2 || !reflect.DeepEqual(before, after[:len(before)]) { + return refused() + } + for _, raw := range before { + row := raw.(map[string]any) + if row["role"] == "tool" || row["tool_calls"] != nil { + return refused() + } + } + assistant := after[len(before)].(map[string]any) + tool := after[len(before)+1].(map[string]any) + calls, ok := assistant["tool_calls"].([]any) + if assistant["role"] != "assistant" || !ok || len(calls) != 1 || tool["role"] != "tool" || tool["tool_call_id"] != call { + return refused() + } + owned := calls[0].(map[string]any) + function := owned["function"].(map[string]any) + args, err := projectNativeRuntimeJSON([]byte(function["arguments"].(string))) + if err != nil || owned["id"] != call || function["name"] != kimiAttemptTool || !reflect.DeepEqual(args, map[string]any{"nonce": facts["nonce"], "operation": "write", "marker": facts["marker"]}) { + return refused() + } + text, ok := tool["content"].(string) + if !ok { + return refused() + } + rawFacts, err := kimiAttemptModelFacts(text) + if err != nil { + return err + } + returned, err := projectNativeRuntimeJSON(rawFacts) + if err != nil || !reflect.DeepEqual(returned, facts) { + return refused() + } + expected := kimiAttemptHistoryExpectation{Client: fmt.Sprint(commands["version"]["argv"].([]any)[0]), Session: session, Call: call, Approval: approval, + Prompt: "Call the one installed fixture tool once with the exact nonce and marker; then report completion. " + parts[1] + "-a", Sentinel: "UAP_KIMI_DONE_" + parts[1], Skill: filepath.Join(binding.NativeProfileRoot, "skills", "uap-kimi-native-a", "SKILL.md"), + Projects: map[string]string{"a": project, "b": filepath.Join(root, "project-b"), "c": filepath.Join(root, "project-c"), "user": filepath.Join(root, "outside")}, Server: server, Facts: facts, Started: started, Ended: ended} + if !filepath.IsAbs(expected.Client) || !reflect.DeepEqual(commands["version"]["argv"], []any{expected.Client, "--version"}) || commands["version"]["cwd"] != expected.Projects["user"] { + return refused() + } + for _, scope := range []string{"a", "b", "c"} { + argv := commands["git-"+scope]["argv"].([]any) + if len(argv) != 5 || !filepath.IsAbs(fmt.Sprint(argv[0])) || !reflect.DeepEqual(argv[1:], []any{"-c", "core.hooksPath=/dev/null", "init", "--quiet"}) || commands["git-"+scope]["cwd"] != expected.Projects[scope] { + return refused() + } + } + addArgv := commands["add-a"]["argv"].([]any) + if len(addArgv) != 12 || !filepath.IsAbs(fmt.Sprint(addArgv[0])) || !reflect.DeepEqual(addArgv[1:], []any{"add", filepath.Join(root, "sources", "a"), "--no-save", "--target", "kimi", "--format", "json", "--scope", "project", "--project-root", project}) || commands["add-a"]["cwd"] != project { + return refused() + } + nativeArgv := commands["native-attempt"]["argv"].([]any) + if len(nativeArgv) != 4 || !filepath.IsAbs(fmt.Sprint(nativeArgv[0])) || !strings.HasSuffix(fmt.Sprint(nativeArgv[1]), "/scripts/project_native_collectors/kimi_attempt.py") || nativeArgv[2] != "--attempt-request" || nativeArgv[3] != filepath.Join(root, "attempt-request.json") || commands["native-attempt"]["cwd"] != expected.Projects["user"] { + return refused() + } + verified, err := kimiAttemptHistory(files["python-api.json"], expected) + if err != nil { + return refused() + } + if err := kimiAttemptDataProof(files["native-data-proof.json"], facts); err != nil { + return err + } + verified["data_proof_sha256"] = kimiProjectProofSHA(files["native-data-proof.json"]) + if !reflect.DeepEqual(verified, proof) { + return refused() + } + return nil +} + +// Only path/digest inventories already held for this owned plugin-data root are +// exported; unknown profile/file contents never enter this proof. +func kimiAttemptDataProof(body []byte, facts map[string]any) error { + value, err := projectNativeRuntimeJSON(body) + if err != nil { + return err + } + row, err := kimiHistoryObject(value, "schema_version before after_without_marker marker_body marker_bytes marker_sha256", "") + marker, ok := row["marker_body"].(string) + if err != nil || row["schema_version"] != json.Number("1") || !ok || marker != facts["marker"] || len(marker) > 256 || row["marker_bytes"] != json.Number(strconv.Itoa(len(marker))) || row["marker_sha256"] != kimiProjectProofSHA([]byte(marker)) || !reflect.DeepEqual(row["before"], row["after_without_marker"]) { + return fmt.Errorf("Kimi observed marker/data proof differs") + } + inventory, ok := row["before"].(map[string]any) + if !ok || len(inventory) == 0 || len(inventory) > 4096 || inventory[".agentplugins-data-owner.json"] == nil || inventory["native-marker.txt"] != nil { + return fmt.Errorf("Kimi owned data inventory closure differs") + } + for name, raw := range inventory { + text, ok := raw.(string) + if !ok || !kimiAttemptInventoryValue.MatchString(text) || len(name) > 4096 || strings.Contains(name, "\\") || filepath.IsAbs(name) || filepath.ToSlash(filepath.Clean(name)) != name || name == ".." || strings.HasPrefix(name, "../") || len(strings.Split(name, "/")) > 32 { + return fmt.Errorf("Kimi owned data inventory entry differs") + } + if name == "." && !strings.HasSuffix(text, ":directory") { + return fmt.Errorf("Kimi owned data root differs") + } + for parent := filepath.Dir(name); parent != "."; parent = filepath.Dir(parent) { + if !strings.HasSuffix(fmt.Sprint(inventory[parent]), ":directory") { + return fmt.Errorf("Kimi owned data parent closure differs") + } + } + } + if inventory["."] == nil || strings.HasSuffix(fmt.Sprint(inventory[".agentplugins-data-owner.json"]), ":directory") { + return fmt.Errorf("Kimi owned data root/owner missing") + } + return nil +} diff --git a/repotests/agentplugins_kimi_project_native_provider_test.go b/repotests/agentplugins_kimi_project_native_provider_test.go index 01b06949d..633c4b083 100644 --- a/repotests/agentplugins_kimi_project_native_provider_test.go +++ b/repotests/agentplugins_kimi_project_native_provider_test.go @@ -143,6 +143,39 @@ type kimiAttemptProvider struct { err error issued, returned bool validate func(string) error + cleanup map[string]any +} + +// Cleanup uses the constructor's original total origin. A forced close remains +// failure evidence and can never satisfy the successful artifact pair. +func (p *kimiAttemptProvider) closeOriginal(started time.Time) map[string]any { + p.mu.Lock() + if p.cleanup != nil { + result := p.cleanup + p.mu.Unlock() + return result + } + p.mu.Unlock() + deadline := time.Now().Add(5 * time.Second) + if original := started.Add(180 * time.Second); original.Before(deadline) { + deadline = original + } + ctx, cancel := context.WithDeadline(context.Background(), deadline) + err := ctx.Err() + if err == nil { + err = p.server.Config.Shutdown(ctx) + } + cancel() + if err != nil { + p.server.CloseClientConnections() + } + p.server.Close() + p.mu.Lock() + defer p.mu.Unlock() + p.cleanup = map[string]any{"shutdown_complete": err == nil, "forced_close": err != nil, + "error": err != nil || p.err != nil, "requests": p.requestCount, + "issued": p.issued, "returned": p.returned} + return p.cleanup } func (p *kimiAttemptProvider) start() error { diff --git a/scripts/project_native_runtime.py b/scripts/project_native_runtime.py index 9d0ec9f0f..67b0ee8fe 100644 --- a/scripts/project_native_runtime.py +++ b/scripts/project_native_runtime.py @@ -5,6 +5,8 @@ """ import argparse import base64 +from datetime import datetime, timezone +from urllib.parse import urlencode import hashlib import io import json @@ -677,11 +679,476 @@ def cline_members(bodies, source): return result + +def kimi_call_json(body): + if type(body) is not bytes or not 0 < len(body) <= 1 << 20: + raise ValueError("Kimi complete raw JSON byte bound differs") + value = strict_json(body) + nodes = 0 + def visit(item, depth): + nonlocal nodes + nodes += 1 + if depth > 32 or nodes > 16384: + raise ValueError("Kimi complete raw JSON structure bound differs") + if type(item) is dict: + if any(len(key) > 4096 for key in item): + raise ValueError("Kimi complete raw JSON key bound differs") + for child in item.values(): visit(child, depth + 1) + elif type(item) is list: + for child in item: visit(child, depth + 1) + visit(value, 0) + return value + + +def kimi_call_object(value, required, optional=()): + if type(value) is not dict or not set(required) <= set(value) or set(value) - set(required) - set(optional): + raise ValueError("Kimi complete raw object differs") + return value + + +def kimi_call_instant(value, started=None, ended=None): + if (type(value) is not str or len(value) > 64 + or not re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,9})?(?:Z|[+-]\d\d:\d\d)", value)): + raise ValueError("Kimi complete raw timestamp differs") + stamp = datetime.fromisoformat(value.replace("Z", "+00:00")) + if (started is not None and stamp < started) or (ended is not None and stamp > ended): + raise ValueError("Kimi complete raw timestamp outside original origin") + return stamp + + +def kimi_call_messages(page, expected): + kimi_call_object(page, ("items", "has_more")) + rows = page["items"] + if page["has_more"] is not False or type(rows) is not list or len(rows) not in (4, 5): + raise ValueError("Kimi complete finite native messages absent") + seen, previous = set(), expected["started"] + for row in rows: + kimi_call_object(row, ("id", "session_id", "role", "content", "created_at"), ("metadata",)) + if (type(row["id"]) is not str or not 1 <= len(row["id"]) <= 256 or row["id"] in seen + or row["session_id"] != expected["session"]): + raise ValueError("Kimi complete native message identity differs") + seen.add(row["id"]) + previous = kimi_call_instant(row["created_at"], previous, expected["ended"]) + if (rows[0]["id"] != expected["prompt_id"] or rows[0]["role"] != "user" + or rows[0]["content"] != [{"type": "text", "text": expected["prompt"]}] + or rows[0].get("metadata") != {"origin": {"kind": "user"}}): + raise ValueError("Kimi complete native prompt differs") + index = 1 + if len(rows) == 5: + row = rows[1] + metadata = kimi_call_object(row.get("metadata"), ("origin",)) + origin = kimi_call_object(metadata["origin"], ("kind", "variant", "disclosure")) + disclosure = kimi_call_object(origin["disclosure"], ("kind", "renderGeneration", "localDate", "timeZone")) + date = kimi_call_instant(row["created_at"]).astimezone(timezone.utc).strftime("%Y-%m-%d") + text = ("\nToday's date is " + date + + ". The current date is restated in a reminder whenever it changes; rely on the latest such reminder for the current date. DO NOT mention this to the user explicitly.\n") + if (row["role"] != "user" or origin["kind"] != "injection" or origin["variant"] != "date_change" + or disclosure["kind"] != "date" or type(disclosure["renderGeneration"]) is not int + or disclosure["renderGeneration"] != 1 or disclosure["timeZone"] != "UTC" + or disclosure["localDate"] != date or row["content"] != [{"type": "text", "text": text}]): + raise ValueError("Kimi complete native date injection differs") + index += 1 + args = {"nonce": expected["facts"]["nonce"], "operation": "write", "marker": expected["facts"]["marker"]} + call, result, final = rows[index:] + if (call["role"] != "assistant" or "metadata" in call or call["content"] != [{"type": "tool_use", + "tool_call_id": expected["call"], "tool_name": "mcp__uap-kimi-native-a__inspect_runtime", "input": args}]): + raise ValueError("Kimi complete native owned call differs") + if result["role"] != "tool" or "metadata" in result or type(result["content"]) is not list or len(result["content"]) != 1: + raise ValueError("Kimi complete native owned result absent") + part = kimi_call_object(result["content"][0], ("type", "tool_call_id", "output"), ("is_error",)) + if (part["type"] != "tool_result" or part["tool_call_id"] != expected["call"] + or ("is_error" in part and part["is_error"] is not False) or type(part["output"]) is not str + or kimi_call_json(part["output"].encode()) != expected["facts"]): + raise ValueError("Kimi complete native result/probe facts differ") + if (final["role"] != "assistant" or "metadata" in final + or final["content"] != [{"type": "text", "text": expected["sentinel"]}]): + raise ValueError("Kimi complete native sentinel absent") + return rows + + +def kimi_call_state(row, expected, workspace, project, *, initial=False, terminal=False): + kimi_call_object(row, ("id", "workspace_id", "title", "created_at", "updated_at", "busy", "metadata", "agent_config", "usage", "permission_rules", "message_count", "last_seq"), + ("main_turn_active", "pending_interaction", "last_turn_reason", "archived", "archived_at", "current_prompt_id", "last_prompt")) + if (row["id"] != expected["session"] or row["workspace_id"] != workspace or type(row["title"]) is not str + or row["title"] not in ("", expected["prompt"]) or row["metadata"] != {"cwd": project} + or row["agent_config"] != {"model": "" if initial else "uap-kimi-native-model"} or row["permission_rules"] != [] + or type(row["busy"]) is not bool or "archived_at" in row): + raise ValueError("Kimi complete finite native session authority differs") + created = kimi_call_instant(row["created_at"], expected["started"], expected["ended"]) + kimi_call_instant(row["updated_at"], created, expected["ended"]) + for key in ("message_count", "last_seq"): + if type(row[key]) is not int or not 0 <= row[key] <= 1 << 40: + raise ValueError("Kimi complete finite session sequence differs") + for key in ("main_turn_active", "archived"): + if key in row and (type(row[key]) is not bool or key == "archived" and row[key] is not False): + raise ValueError("Kimi complete finite session boolean differs") + if (("pending_interaction" in row and row["pending_interaction"] != "none") + or ("last_turn_reason" in row and row["last_turn_reason"] != "completed") + or ("last_prompt" in row and row["last_prompt"] != expected["prompt"])): + raise ValueError("Kimi complete native failure/additional interaction") + if "current_prompt_id" in row and (type(row["current_prompt_id"]) is not str or not 1 <= len(row["current_prompt_id"]) <= 256): + raise ValueError("Kimi complete native current prompt differs") + usage = kimi_call_object(row["usage"], ("input_tokens", "output_tokens", "cache_read_tokens", "cache_creation_tokens", "context_tokens"), ("total_cost_usd", "context_limit", "turn_count")) + for key, value in usage.items(): + if (type(value) not in ((int, float) if key == "total_cost_usd" else (int,)) or not 0 <= value <= 1 << 40): + raise ValueError("Kimi complete finite native usage differs") + if terminal and (row["busy"] is not False or row.get("main_turn_active") is not False or row.get("pending_interaction") != "none" + or row.get("last_turn_reason") != "completed" or row.get("archived") is not False): + raise ValueError("Kimi complete native terminal completion absent") + + +def kimi_call_partial(page, terminal, sentinel): + kimi_call_object(page, ("items", "has_more")) + rows = page["items"] + if page["has_more"] is not False or type(rows) is not list or not 1 <= len(rows) <= len(terminal): + raise ValueError("Kimi complete intermediate history page differs") + for index, row in enumerate(rows): + if row == terminal[index]: continue + kimi_call_object(row, ("id", "session_id", "role", "content", "created_at")) + if index != len(terminal) - 1 or row["role"] != "assistant" or type(row["content"]) is not list or len(row["content"]) > 1: + raise ValueError("Kimi complete intermediate native history prefix differs") + if row["content"]: + part = kimi_call_object(row["content"][0], ("type", "text")) + if part["type"] != "text" or type(part["text"]) is not str or not sentinel.startswith(part["text"]): + raise ValueError("Kimi complete intermediate native sentinel differs") + if {**row, "content": terminal[index]["content"]} != terminal[index]: + raise ValueError("Kimi complete intermediate native message identity differs") + + +def kimi_call_complete(bodies, result): + """Pure success admission recomputes raw joins; proof/flags are not authority.""" + required = {"runtime-attempt.json", "probe-events.jsonl", "python-api.json", "native-data-proof.json", + ".kimi-code-mcp.json.installed", "uap-kimi-native-a-SKILL.md.installed", + "provider-request-1.json", "provider-request-2.json"} + required |= {label + suffix for label in KIMI_CALL_COMMANDS for suffix in (".command.json", ".stdout.log", ".stderr.log")} + if (set(bodies) != required or result.get("native_capture_complete") is not True + or result.get("native_history_grammar_frozen") is not True or result.get("provider_returned") is not True + or bodies["native-attempt.stdout.log"] != bodies["python-api.json"] or bodies["native-attempt.stderr.log"]): + raise ValueError("Kimi complete closed successful capture required") + for label in KIMI_CALL_COMMANDS: + command = kimi_call_json(bodies[label + ".command.json"]) + if command["exit"] != 0 or command["error"] is not False or command["deadline"] is not False: + raise ValueError("Kimi complete public/native command failed") + cleanup = kimi_call_object(result.get("provider_cleanup"), ("shutdown_complete", "forced_close", "error", "requests", "issued", "returned")) + if (cleanup["shutdown_complete"] is not True or cleanup["forced_close"] is not False or cleanup["error"] is not False + or type(cleanup["requests"]) is not int or cleanup["requests"] != 2 + or cleanup["issued"] is not True or cleanup["returned"] is not True): + raise ValueError("Kimi complete ordinary provider closure absent") + proof = kimi_call_object(result.get("native_history"), ("native_session_id", "native_turn_id", "provider_call_id", "approval_id", + "prompt_id", "terminal_reason", "message_count", "snapshot_count", "history_sha256", "terminal_snapshot_sha256", + "probe_pid", "probe_session", "started_at", "ended_at", "data_proof_sha256")) + started, ended = kimi_call_instant(proof["started_at"]), kimi_call_instant(proof["ended_at"]) + if (not 0 <= (ended - started).total_seconds() <= 180 + or (ended - started).total_seconds() * 1000 > result["elapsed_ms"] + or type(proof["native_turn_id"]) is not int or proof["native_turn_id"] != 0 + or type(result.get("native_turn_id")) is not int or result["native_turn_id"] != 0 + or proof["terminal_reason"] != "completed" or type(proof["snapshot_count"]) is not int + or not 1 <= proof["snapshot_count"] <= 10): + raise ValueError("Kimi complete original native turn/time differs") + call = result.get("provider_call_id") + owned = re.fullmatch(r"uap_([0-9a-f]{32})_attempt", call) if type(call) is str else None + session = result.get("native_session_id") + if (not owned or type(session) is not str or not re.fullmatch(r"session_[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}", session) + or proof["native_session_id"] != session or proof["provider_call_id"] != call + or type(proof["approval_id"]) is not str or not proof["approval_id"]): + raise ValueError("Kimi complete session/call ownership differs") + nonce = owned[1] + binding = result.get("binding") + if (type(binding) is not dict or binding.get("client_id") != "kimi" or binding.get("scope") != "project" + or binding.get("activation") != "prepared" or binding.get("authentication") != "not_checked" + or binding.get("verification") != "installation_verified" or binding.get("materialization") != "materialized"): + raise ValueError("Kimi complete selected project binding absent") + active, data = binding.get("target_locator"), result.get("data_locator") + facts = kimi_call_object(result.get("probe_facts"), ("nonce", "revision", "cwd", "argv", "plugin_root", "plugin_data", "literal", "once", "marker", "pid", "session")) + expected_facts = {"nonce": nonce + "-a", "revision": "a-one", "cwd": active, "argv": ["Kimi owned a", str(active) + "/argument"], + "plugin_root": active, "plugin_data": data, "literal": "", "once": "", "marker": "retained-" + nonce, + "pid": facts["pid"], "session": facts["session"]} + if (type(active) is not str or not active.startswith("/") or type(data) is not str or not data.startswith("/") + or facts != expected_facts or type(facts["pid"]) is not int or not 1 <= facts["pid"] <= 1 << 30 + or type(facts["session"]) is not str or not re.fullmatch(str(facts["pid"]) + r"-[0-9]{15,20}", facts["session"]) + or proof["probe_pid"] != facts["pid"] or proof["probe_session"] != facts["session"]): + raise ValueError("Kimi complete returned runtime authority differs") + events = bodies["probe-events.jsonl"] + if not events.endswith(b"\n") or not 3 <= len(events.splitlines()) <= 6: + raise ValueError("Kimi complete probe stream differs") + previous = started + for index, line in enumerate(events.splitlines()): + event = kimi_call_object(kimi_call_json(line), ("method", "pid", "session", "nonce", "timestamp", "facts")) + method = "initialize" if index == 0 else "tools/call" if index == len(events.splitlines()) - 1 else "tools/list" + if (event["method"] != method or type(event["pid"]) is not int or event["pid"] != facts["pid"] + or event["session"] != facts["session"] or event["nonce"] != facts["nonce"] + or event["facts"] != (facts if method == "tools/call" else None)): + raise ValueError("Kimi complete fresh probe/result join differs") + previous = kimi_call_instant(event["timestamp"], previous, ended) + requests = [kimi_call_json(bodies[f"provider-request-{number}.json"]) for number in (1, 2)] + declaration = [{"type": "function", "function": {"name": "mcp__uap-kimi-native-a__inspect_runtime", "description": "Inspect only disposable fixture runtime facts.", "parameters": {"type": "object", "properties": {key: {"type": "string"} for key in ("nonce", "operation", "marker")}, "required": ["nonce"], "additionalProperties": False}}}] + for request in requests: + kimi_call_object(request, ("model", "messages", "tools", "stream", "stream_options"), ("prompt_cache_key", "max_tokens", "max_completion_tokens", "temperature", "top_p", "parallel_tool_calls")) + if (request.get("tools") != declaration or request.get("stream_options") != {"include_usage": True} or type(request) is not dict or request.get("model") != "uap-kimi-native-model" or request.get("stream") is not True + or request.get("prompt_cache_key") != session or type(request.get("messages")) is not list): + raise ValueError("Kimi complete provider request identity differs") + if request["stream_options"].get("include_usage") is not True or request["tools"][0]["function"]["parameters"].get("additionalProperties") is not False: + raise ValueError("Kimi complete provider declaration boolean differs") + for key in ("max_tokens", "max_completion_tokens"): + if key in request and (type(request[key]) is not int or not 1 <= request[key] <= 1 << 20): + raise ValueError("Kimi complete provider token bound differs") + for key, high in (("temperature", 2), ("top_p", 1)): + if key in request and (type(request[key]) not in (int, float) or not 0 <= request[key] <= high): + raise ValueError("Kimi complete provider sampling bound differs") + if "parallel_tool_calls" in request and type(request["parallel_tool_calls"]) is not bool: + raise ValueError("Kimi complete provider parallel flag differs") + first, second = requests[0]["messages"], requests[1]["messages"] + if (len(second) != len(first) + 2 or first != second[:-2] or any(row.get("role") == "tool" or "tool_calls" in row for row in first) + or second[-2].get("role") != "assistant" or second[-1].get("role") != "tool" or second[-1].get("tool_call_id") != call): + raise ValueError("Kimi complete provider two-turn ordering differs") + calls = second[-2].get("tool_calls") + args = {"nonce": nonce + "-a", "operation": "write", "marker": "retained-" + nonce} + if (type(calls) is not list or len(calls) != 1 or calls[0].get("id") != call or calls[0].get("type") != "function" + or calls[0].get("function", {}).get("name") != "mcp__uap-kimi-native-a__inspect_runtime" + or kimi_call_json(calls[0]["function"]["arguments"].encode()) != args): + raise ValueError("Kimi complete provider owned assistant call differs") + returned = second[-1].get("content") + match = re.fullmatch(r"Wall time: ((?:0|[1-9][0-9]?)\.[0-9]{3}) seconds\n([\s\S]+)", returned) if type(returned) is str else None + if not match or float(match[1]) > 60 or kimi_call_json(match[2].encode()) != facts: + raise ValueError("Kimi complete model/native result framing differs") + data_body = bodies["native-data-proof.json"] + data_proof = kimi_call_object(kimi_call_json(data_body), ("schema_version", "before", "after_without_marker", "marker_body", "marker_bytes", "marker_sha256")) + before = data_proof["before"] + if (type(data_proof["schema_version"]) is not int or data_proof["schema_version"] != 1 + or data_proof["marker_body"] != facts["marker"] or type(data_proof["marker_bytes"]) is not int + or data_proof["marker_bytes"] != len(facts["marker"].encode()) + or data_proof["marker_sha256"] != hashlib.sha256(facts["marker"].encode()).hexdigest() + or proof["data_proof_sha256"] != hashlib.sha256(data_body).hexdigest() + or type(before) is not dict or not 1 <= len(before) <= 4096 or before != data_proof["after_without_marker"] + or "." not in before or ".agentplugins-data-owner.json" not in before or "native-marker.txt" in before): + raise ValueError("Kimi complete observed marker/unrelated-data proof differs") + for name, value in before.items(): + path = Path(name) + if (type(name) is not str or len(name) > 4096 or path.is_absolute() or str(path) != name or ".." in path.parts + or "\\" in name or len(path.parts) > 32 or type(value) is not str + or not re.fullmatch(r"0[0-7]{1,12}:(?:directory|[0-9a-f]{64})", value)): + raise ValueError("Kimi complete owned inventory entry differs") + if any(not before.get(str(parent), "").endswith(":directory") for parent in path.parents): + raise ValueError("Kimi complete owned inventory parent missing") + if not before["."].endswith(":directory") or before[".agentplugins-data-owner.json"].endswith(":directory"): + raise ValueError("Kimi complete owned inventory root/owner differs") + envelope = kimi_call_object(kimi_call_json(bodies["python-api.json"]), ("schema_version", "files")) + raw_files = envelope["files"] + if (len(bodies["python-api.json"]) > 1 << 20 or type(envelope["schema_version"]) is not int or envelope["schema_version"] != 1 + or type(raw_files) is not dict or not 4 <= len(raw_files) <= 64 + or any(type(value) is not str or str(Path(name)) != name or len(Path(name).parts) != 1 or "\\" in name for name, value in raw_files.items())): + raise ValueError("Kimi complete closed raw envelope differs") + raw_files = {name: value.encode() for name, value in raw_files.items()} + research = kimi_call_object(kimi_call_json(raw_files["research.json"]), ("schema_version", "client", "phase", "commands", "http_requests", "observations", "qualification")) + if research["client"] != "kimi" or research["phase"] != "installed-single-tool-attempt" or research["qualification"] != dict.fromkeys(shared.QUALIFICATION_CASES, False): + raise ValueError("Kimi complete observer capture boundary differs") + observation = kimi_call_object(research["observations"], ("native_capture_complete", "native_history_grammar_frozen", "native_session_id", "native_turn_id", "provider_call_id", "approval_id", "snapshot_count", "api_request_count", "boundary")) + if (type(observation) is not dict or observation.get("native_capture_complete") is not False or observation.get("native_history_grammar_frozen") is not False + or observation.get("native_session_id") != session or observation.get("provider_call_id") != call + or observation.get("approval_id") != proof["approval_id"] or observation.get("native_turn_id") != 0 + or observation.get("snapshot_count") != proof["snapshot_count"] + or observation["boundary"] != "Bounded initial capture; no native history or qualification inferred from provider/probe success"): + raise ValueError("Kimi complete observer owned session differs") + commands = research["commands"] + native_cleanup = {"forced_kill": False, "group_reaped": True, "port_closed": True, "shutdown_acknowledged": True, "sigterm_sent": False} + if (type(commands) is not list or len(commands) != 1 or commands[0].get("exit") != 0 or commands[0].get("error") is not None + or commands[0].get("cleanup") != native_cleanup or any(type(value) is not bool for value in commands[0]["cleanup"].values())): + raise ValueError("Kimi complete ordinary native closure absent") + used = {"research.json"} + for channel in ("stdout", "stderr"): + command = commands[0] + name = command.get(channel + "_file") + stream = raw_files.get(name) + if (name != "kimi-single-tool." + channel + ".log" or stream is None + or type(command.get(channel + "_bytes")) is not int or len(stream) != command[channel + "_bytes"] + or hashlib.sha256(stream).hexdigest() != command.get(channel + "_sha256") or channel == "stderr" and stream): + raise ValueError("Kimi complete native stream receipt differs") + used.add(name) + http_rows = research["http_requests"] + if type(http_rows) is not list or not 1 <= len(http_rows) <= 60 or observation.get("api_request_count") != len(http_rows): + raise ValueError("Kimi complete native HTTP budget differs") + responses, response_bodies, labels, http_receipts = {}, {}, [], {} + for row in http_rows: + kimi_call_object(row, ("authenticated", "error", "label", "method", "path", "response_bytes", "response_file", "response_sha256", "status")) + label, name = row["label"], row["response_file"] + stream = raw_files.get(name) + if (type(label) is not str or label in responses or name in used or name != "kimi-single-tool-" + label + ".http.json" or stream is None + or row["error"] is not None or type(row["response_bytes"]) is not int or len(stream) != row["response_bytes"] + or hashlib.sha256(stream).hexdigest() != row["response_sha256"] or type(row["authenticated"]) is not bool + or type(row["status"]) is not int or row["status"] != (401 if label == "unauthorized" else 200) + or row["authenticated"] is not (label not in ("health", "unauthorized"))): + raise ValueError("Kimi complete native HTTP receipt differs") + used.add(name); labels.append(label); response_bodies[label] = stream; http_receipts[label] = row + response = kimi_call_object(kimi_call_json(stream), ("code", "msg", "data", "request_id"), ("details", "stack")) + if type(response["code"]) is not int or response["code"] != (40101 if label == "unauthorized" else 0): + raise ValueError("Kimi complete native HTTP envelope differs") + if label != "unauthorized" and (response["msg"] != "success" or response.get("details") is not None or response.get("stack") is not None): + raise ValueError("Kimi complete native HTTP success differs") + responses[label] = response["data"] + if set(raw_files) != used: + raise ValueError("Kimi complete unrecorded raw member") + expected = {"facts": facts, "session": session, "call": call, "prompt_id": proof["prompt_id"], "started": started, "ended": ended, + "prompt": "Call the one installed fixture tool once with the exact nonce and marker; then report completion. " + nonce + "-a", "sentinel": "UAP_KIMI_DONE_" + nonce} + project = str(Path(binding["native_profile_root"]).parent) + root = str(Path(project).parent) + projects = {"a": project, "b": root + "/project-b", "c": root + "/project-c", "user": root + "/outside"} + physical = binding.get("physical_artifact_id") + if (type(physical) is not str or not re.fullmatch(r"uap-kimi-native-a-[0-9a-f]{12}", physical) + or active != project + "/.uap/managed/clients/kimi/" + physical + or data != project + "/.uap/plugin-data/" + physical): + raise ValueError("Kimi complete fixed managed/data layout differs") + server = {"transport": "stdio", "command": active + "/io.github.777genius.agentplugins/managed-stdio-v1/agentplugins", + "args": ["--internal-stdio-v1", active, data, active, "plugin", "--", "./bin/probe", "Kimi owned a", active + "/argument"], + "env": {"PLUGIN_ROOT": active, "PLUGIN_DATA": data, "UAP_TEST_ROOT": root, "UAP_TEST_EVENTS": root + "/a-events.jsonl", + "UAP_TEST_NONCE": nonce + "-a", "UAP_TEST_REVISION": "a-one"}} + if kimi_call_json(bodies[".kimi-code-mcp.json.installed"]) != {"mcpServers": {"uap-kimi-native-a": server}}: + raise ValueError("Kimi complete installed source projection differs") + skill = "---\nname: uap-kimi-native-a\ndescription: Kimi installed a-one\n---\nUAP_KIMI_a-one_" + nonce + "\n" + if bodies["uap-kimi-native-a-SKILL.md.installed"] != skill.encode(): + raise ValueError("Kimi complete installed authored skill differs") + workspaces = {} + for scope in ("a", "b", "c"): + workspace = kimi_call_object(responses.get("workspace-" + scope), ("id", "root", "name", "created_at", "last_opened_at", "session_count")) + if (type(workspace["id"]) is not str or not re.fullmatch(r"wd_[a-z0-9._-]+_[0-9a-f]{12}", workspace["id"]) + or workspace["root"] != projects[scope] or workspace["name"] != "uap-kimi-native-" + scope + or type(workspace["session_count"]) is not int or workspace["session_count"] != 0 + or responses.get("trust-before-" + scope) != {"trusted": False}): + raise ValueError("Kimi complete native workspace/trust differs") + workspaces[scope] = workspace["id"] + for key in ("created_at", "last_opened_at"): kimi_call_instant(workspace[key], started, ended) + if len(set(workspaces.values())) != 3 or responses.get("trust-untrusted-c") != {"trusted": False}: + raise ValueError("Kimi complete distinct native workspace/trust differs") + for scope in ("a", "b"): + if responses.get("trust-set-" + scope) != {"trusted": True} or responses.get("trust-after-" + scope) != {"trusted": True}: + raise ValueError("Kimi complete owned native trust differs") + projected = {key: value for key, value in server.items() if key != "env"} + projected["envKeys"] = sorted(server["env"]) + expected_catalog = [{"name": "uap-kimi-native-a", "config": projected, "source": "global", + "origin": project + "/.kimi-code/mcp.json", "mutable": False}] + if responses.get("catalog-a") != expected_catalog or any(responses.get("catalog-" + scope) != [] for scope in ("b", "c", "user")): + raise ValueError("Kimi complete receipt-selected native catalog differs") + prompt = kimi_call_object(responses.get("prompt"), ("prompt_id", "user_message_id", "status", "content", "created_at"), ("metadata",)) + if (prompt["prompt_id"] != proof["prompt_id"] or prompt["user_message_id"] != proof["prompt_id"] + or prompt["status"] not in ("running", "queued", "blocked") or prompt["content"] != [{"type": "text", "text": expected["prompt"]}]): + raise ValueError("Kimi complete accepted native prompt differs") + kimi_call_instant(prompt["created_at"], started, ended) + base = "/api/v1/sessions/" + session + fixed_endpoints = {"health": ("GET", "/api/v1/healthz"), "unauthorized": ("GET", "/api/v1/meta"), "meta": ("GET", "/api/v1/meta"), + "create-session": ("POST", "/api/v1/sessions"), "prompt": ("POST", base + "/prompts"), + "approve-once": ("POST", base + "/approvals/" + proof["approval_id"]), "history": ("GET", base + "/messages?page_size=20"), + "shutdown": ("POST", "/api/v1/shutdown"), "skills-a": ("GET", "/api/v1/workspaces/" + workspaces["a"] + "/skills")} + for scope in ("a", "b", "c"): + fixed_endpoints["workspace-" + scope] = ("POST", "/api/v1/workspaces") + fixed_endpoints["trust-before-" + scope] = ("GET", "/api/v1/workspaces/" + workspaces[scope] + "/trust") + for scope in ("a", "b"): + fixed_endpoints["trust-set-" + scope] = ("POST", "/api/v1/workspaces/" + workspaces[scope] + "/trust") + fixed_endpoints["trust-after-" + scope] = ("GET", "/api/v1/workspaces/" + workspaces[scope] + "/trust") + fixed_endpoints["trust-untrusted-c"] = ("GET", "/api/v1/workspaces/" + workspaces["c"] + "/trust") + for scope in ("a", "b", "c", "user"): + fixed_endpoints["catalog-" + scope] = ("GET", "/api/v2/mcp/servers?" + urlencode({"cwd": projects[scope]})) + for label, receipt in http_receipts.items(): + endpoint = fixed_endpoints.get(label) + if label.startswith("approvals-"): endpoint = ("GET", base + "/approvals?status=pending") + if label.startswith("snapshot-"): endpoint = ("GET", base + "/snapshot") + if endpoint is None or (receipt["method"], receipt["path"]) != endpoint: + raise ValueError("Kimi complete exact native endpoint differs") + version = kimi_call_json(bodies["version.command.json"]) + argv = version["argv"] + if (type(argv) is not list or len(argv) != 2 or type(argv[0]) is not str or not argv[0].startswith("/") + or argv[1] != "--version" or version["cwd"] != projects["user"] + or bodies["version.stdout.log"] != b"2.3.2\n" or bodies["version.stderr.log"]): + raise ValueError("Kimi complete exact native version command differs") + client = argv[0] + if commands[0].get("argv") != [client, "web", "--no-open", "--host", "127.0.0.1", "--port", "0"] or commands[0].get("cwd") != project: + raise ValueError("Kimi complete original native service command differs") + for scope in ("a", "b", "c"): + command = kimi_call_json(bodies["git-" + scope + ".command.json"]) + argv = command["argv"] + if len(argv) != 5 or not argv[0].startswith("/") or argv[1:] != ["-c", "core.hooksPath=/dev/null", "init", "--quiet"] or command["cwd"] != projects[scope]: + raise ValueError("Kimi complete fixed Git command differs") + command = kimi_call_json(bodies["add-a.command.json"]) + if (len(command["argv"]) != 12 or not command["argv"][0].startswith("/") + or command["argv"][1:] != ["add", root + "/sources/a", "--no-save", "--target", "kimi", "--format", "json", "--scope", "project", "--project-root", project] + or command["cwd"] != project): + raise ValueError("Kimi complete fixed public add command differs") + command = kimi_call_json(bodies["native-attempt.command.json"]) + argv = command["argv"] + if (len(argv) != 4 or not argv[0].startswith("/") or not argv[1].endswith("/scripts/project_native_collectors/kimi_attempt.py") + or argv[2:] != ["--attempt-request", root + "/attempt-request.json"] or command["cwd"] != projects["user"]): + raise ValueError("Kimi complete fixed native collector command differs") + kimi_call_state(responses.get("create-session"), expected, workspaces["a"], project, initial=True) + previous_seq, epoch, terminal, snapshots = -1, None, None, [] + for index in range(proof["snapshot_count"]): + snapshot = kimi_call_object(responses.get("snapshot-" + str(index)), ("as_of_seq", "epoch", "session", "messages", "in_flight_turn", "pending_approvals", "pending_questions", "subagents")) + state = snapshot["session"] + if (type(snapshot["as_of_seq"]) is not int or snapshot["as_of_seq"] < previous_seq or type(snapshot["epoch"]) is not str + or not 1 <= len(snapshot["epoch"]) <= 256 or epoch is not None and snapshot["epoch"] != epoch + or snapshot["pending_approvals"] != [] or snapshot["pending_questions"] != [] or snapshot["subagents"] != [] + or type(state) is not dict or state.get("id") != session or state.get("last_turn_reason") in ("failed", "cancelled") + or state.get("metadata") != {"cwd": str(Path(binding["native_profile_root"]).parent)} + or state.get("agent_config") != {"model": "uap-kimi-native-model"} or state.get("permission_rules") != [] + or state.get("workspace_id") != workspaces["a"]): + raise ValueError("Kimi complete native snapshot authority/state differs") + kimi_call_state(state, expected, workspaces["a"], project, terminal=index == proof["snapshot_count"] - 1) + in_flight = snapshot["in_flight_turn"] + if index != proof["snapshot_count"] - 1: + kimi_call_object(in_flight, ("turn_id", "assistant_text", "thinking_text", "running_tools"), ("current_prompt_id",)) + if (type(in_flight["turn_id"]) is not int or in_flight["turn_id"] != 0 or type(in_flight["assistant_text"]) is not str + or not expected["sentinel"].startswith(in_flight["assistant_text"]) or in_flight["thinking_text"] != "" + or type(in_flight["running_tools"]) is not list or len(in_flight["running_tools"]) > 1 + or ("current_prompt_id" in in_flight and in_flight["current_prompt_id"] != proof["prompt_id"])): + raise ValueError("Kimi complete finite in-flight turn differs") + for tool in in_flight["running_tools"]: + kimi_call_object(tool, ("tool_call_id", "name"), ("args",)) + if tool["tool_call_id"] != call or tool["name"] != "mcp__uap-kimi-native-a__inspect_runtime" or ("args" in tool and tool["args"] != args): + raise ValueError("Kimi complete additional in-flight tool") + previous_seq, epoch = snapshot["as_of_seq"], snapshot["epoch"] + terminal = snapshot + snapshots.append(snapshot) + if (terminal["in_flight_turn"] is not None or terminal["session"].get("last_turn_reason") != "completed" + or terminal["session"].get("busy") is not False or terminal["session"].get("main_turn_active") is not False + or terminal["session"].get("pending_interaction") != "none" or terminal["session"].get("archived") is not False): + raise ValueError("Kimi complete successful terminal native turn absent") + rows = kimi_call_messages(terminal["messages"], expected) + for snapshot in snapshots[:-1]: kimi_call_partial(snapshot["messages"], rows, expected["sentinel"]) + history = kimi_call_object(responses.get("history"), ("items", "has_more")) + if history["has_more"] is not False or history["items"] != rows[::-1] or proof["message_count"] != len(rows): + raise ValueError("Kimi complete snapshot/history identity differs") + if (proof["history_sha256"] != hashlib.sha256(response_bodies["history"]).hexdigest() + or proof["terminal_snapshot_sha256"] != hashlib.sha256(response_bodies["snapshot-" + str(proof["snapshot_count"] - 1)]).hexdigest()): + raise ValueError("Kimi complete raw history proof digest differs") + pending = [label for label in labels if label.startswith("approvals-")] + if not 1 <= len(pending) <= 20 or pending != ["approvals-" + str(index) for index in range(len(pending))]: + raise ValueError("Kimi complete native approval sequence differs") + for index, label in enumerate(pending): + page = kimi_call_object(responses[label], ("items",)) + if type(page["items"]) is not list or len(page["items"]) != (1 if index == len(pending) - 1 else 0): + raise ValueError("Kimi complete sole native approval absent") + approval = kimi_call_object(responses[pending[-1]]["items"][0], ("approval_id", "session_id", "agent_id", "turn_id", "tool_call_id", "tool_name", "action", "tool_input_display", "created_at", "expires_at")) + kimi_call_instant(approval["created_at"], started, ended) + if kimi_call_instant(approval["expires_at"]) <= kimi_call_instant(approval["created_at"]): + raise ValueError("Kimi complete native approval expiry differs") + resolved = kimi_call_object(responses.get("approve-once"), ("resolved", "resolved_at")) + kimi_call_instant(resolved["resolved_at"], started, ended) + action = "Approve mcp__uap-kimi-native-a__inspect_runtime" + if (approval.get("approval_id") != proof["approval_id"] or approval.get("session_id") != session or approval.get("agent_id") != "main" + or type(approval.get("turn_id")) is not int or approval["turn_id"] != 0 or approval.get("tool_call_id") != call + or approval.get("tool_name") != "mcp__uap-kimi-native-a__inspect_runtime" or approval.get("action") != action + or approval.get("tool_input_display") != {"kind": "generic", "summary": action, "detail": args} + or responses.get("approve-once", {}).get("resolved") is not True): + raise ValueError("Kimi complete native one-call approval differs") + prefix = ["health", "unauthorized", "meta"] + for scope in ("a", "b", "c"): prefix += ["workspace-" + scope, "trust-before-" + scope] + prefix += ["trust-set-a", "trust-after-a", "trust-set-b", "trust-after-b", "trust-untrusted-c", "catalog-a", "catalog-b", "catalog-c", "catalog-user", "skills-a", "create-session", "prompt"] + if labels != prefix + pending + ["approve-once"] + ["snapshot-" + str(index) for index in range(proof["snapshot_count"])] + ["history", "shutdown"]: + raise ValueError("Kimi complete fixed native HTTP inventory differs") + + def kimi_call_members(bodies, source): if any(b"uap-kimi-key-" in body for body in bodies.values()): raise ValueError("unredacted Kimi fixture provider key refused") allowed = {"runtime-attempt.json", "probe-events.jsonl", "python-api.json", - ".kimi-code-mcp.json.installed", "uap-kimi-native-a-SKILL.md.installed"} + ".kimi-code-mcp.json.installed", "uap-kimi-native-a-SKILL.md.installed", "native-data-proof.json"} allowed |= {f"provider-request-{number}.json" for number in range(1, 9)} for label in KIMI_CALL_COMMANDS: triple = {label + suffix for suffix in (".command.json", ".stdout.log", ".stderr.log")} @@ -700,16 +1167,18 @@ def kimi_call_members(bodies, source): if "runtime-attempt.json" not in bodies or set(bodies) - allowed: raise ValueError("unrecorded Kimi single-call evidence member") result = strict_json(bodies["runtime-attempt.json"]) + success = result.get("runtime_attempt_complete") is True and result.get("test_failed") is False if isinstance(result, dict) else False + failed = result.get("runtime_attempt_complete") is False and result.get("test_failed") is True if isinstance(result, dict) else False if (not isinstance(result, dict) or result.get("client") != "kimi" or result.get("version") != "2.3.2" or result.get("phase") != "installed-single-tool-attempt" or result.get("activation") != "prepared" or result.get("provider_kind") != "scripted-openai-chat-completions" - or result.get("runtime_attempt_complete") is not False or result.get("test_failed") is not True + or not (success or failed) or result.get("vendor_authentication_qualified") is not False or result.get("real_model_qualified") is not False or type(result.get("elapsed_ms")) is not int or result["elapsed_ms"] < 0): - raise ValueError("initial Kimi single-call capture must remain explicitly unqualified") + raise ValueError("Kimi single-call completion/failure pair differs") qualification = result.get("qualification") if (not isinstance(qualification, dict) or set(qualification) != set(shared.QUALIFICATION_CASES) - or any(value is not False for value in qualification.values())): + or any(qualification[key] is not (success and key in ("mcp-initialize", "mcp-tools-list", "mcp-tools-call")) for key in shared.QUALIFICATION_CASES)): raise ValueError("Kimi single-call capture cannot inherit lifecycle qualifications") if result.get("source_identity") != {"installer_base_commit": source["source_commit"], "installer_tree": source["source_tree"], "installer_source_state": "committed", "installer_patch_sha256": []}: @@ -718,6 +1187,11 @@ def kimi_call_members(bodies, source): for name in bodies if name.startswith("provider-request-")) if numbers != list(range(1, len(numbers) + 1)): raise ValueError("Kimi provider request capture sequence has gaps") + if success: + try: + kimi_call_complete(bodies, result) + except (KeyError, TypeError, AttributeError, IndexError, RecursionError) as error: + raise ValueError("Kimi complete raw container refused") from error return result diff --git a/scripts/test_project_native_runtime.py b/scripts/test_project_native_runtime.py index c4e00723b..a9b93268d 100644 --- a/scripts/test_project_native_runtime.py +++ b/scripts/test_project_native_runtime.py @@ -460,6 +460,161 @@ def test_kimi_single_call_cannot_inherit_lifecycle_success_or_unknown_members(se with self.assertRaises(ValueError): runtime.kimi_call_members({**bodies, "git-c.command.json": json.dumps({**command, "cleanup": "assumed"}).encode()}, self.source) + def kimi_synthetic_completed_capture(self): + """Independent inert protocol records, never a measured native capture.""" + encode = lambda value: json.dumps(value, separators=(",", ":")).encode() + digest = lambda body: hashlib.sha256(body).hexdigest() + nonce, root = "c" * 32, "/inert/kimi-python-contract" + project, physical = root + "/project", "uap-kimi-native-a-" + "d" * 12 + profile = project + "/.kimi-code" + active, data = project + "/.uap/managed/clients/kimi/" + physical, project + "/.uap/plugin-data/" + physical + client, session = "/inert/never-executed/kimi", "session_11111111-2222-4333-8444-555555555555" + call, approval, prompt_id = "uap_" + nonce + "_attempt", "approval_inert-owned", "msg_inert_prompt" + stamp = "2026-01-02T03:04:07Z" + prompt = "Call the one installed fixture tool once with the exact nonce and marker; then report completion. " + nonce + "-a" + sentinel, marker = "UAP_KIMI_DONE_" + nonce, "retained-" + nonce + args = {"nonce": nonce + "-a", "operation": "write", "marker": marker} + facts = {"nonce": nonce + "-a", "revision": "a-one", "cwd": active, "argv": ["Kimi owned a", active + "/argument"], + "plugin_root": active, "plugin_data": data, "literal": "", "once": "", "marker": marker, "pid": 17, "session": "17-1767323048000000000"} + server = {"transport": "stdio", "command": active + "/io.github.777genius.agentplugins/managed-stdio-v1/agentplugins", + "args": ["--internal-stdio-v1", active, data, active, "plugin", "--", "./bin/probe", "Kimi owned a", active + "/argument"], + "env": {"PLUGIN_ROOT": active, "PLUGIN_DATA": data, "UAP_TEST_ROOT": root, "UAP_TEST_EVENTS": root + "/a-events.jsonl", "UAP_TEST_NONCE": nonce + "-a", "UAP_TEST_REVISION": "a-one"}} + bodies = {".kimi-code-mcp.json.installed": encode({"mcpServers": {"uap-kimi-native-a": server}}), + "uap-kimi-native-a-SKILL.md.installed": ("---\nname: uap-kimi-native-a\ndescription: Kimi installed a-one\n---\nUAP_KIMI_a-one_" + nonce + "\n").encode()} + for label in runtime.KIMI_CALL_COMMANDS: + command = {"argv": ["/inert/never-executed/" + label], "cwd": project, "exit": 0, "error": False, + "deadline": False, "cleanup": "ordinary full-descendant supervision; forced termination fails"} + bodies[label + ".command.json"], bodies[label + ".stdout.log"], bodies[label + ".stderr.log"] = encode(command), b"", b"" + projects_for_commands = {"a": project, "b": root + "/project-b", "c": root + "/project-c", "user": root + "/outside"} + for scope in ("a", "b", "c"): + command = json.loads(bodies["git-" + scope + ".command.json"]) + command.update({"argv": ["/inert/never-executed/git", "-c", "core.hooksPath=/dev/null", "init", "--quiet"], "cwd": projects_for_commands[scope]}) + bodies["git-" + scope + ".command.json"] = encode(command) + for label, argv in (("version", [client, "--version"]), + ("add-a", ["/inert/never-executed/agentplugins", "add", root + "/sources/a", "--no-save", "--target", "kimi", "--format", "json", "--scope", "project", "--project-root", project]), + ("native-attempt", ["/inert/never-executed/python", "/inert/checked-source/scripts/project_native_collectors/kimi_attempt.py", "--attempt-request", root + "/attempt-request.json"])): + command = json.loads(bodies[label + ".command.json"]) + command.update({"argv": argv, "cwd": project if label == "add-a" else projects_for_commands["user"]}) + bodies[label + ".command.json"] = encode(command) + bodies["version.stdout.log"] = b"2.3.2\n" + bodies["probe-events.jsonl"] = b"".join(encode({"method": method, "pid": 17, "session": facts["session"], "nonce": nonce + "-a", + "timestamp": f"2026-01-02T03:04:08.00{index}Z", "facts": facts if method == "tools/call" else None}) + b"\n" + for index, method in enumerate(("initialize", "tools/list", "tools/call"))) + declaration = {"type": "function", "function": {"name": "mcp__uap-kimi-native-a__inspect_runtime", "description": "Inspect only disposable fixture runtime facts.", + "parameters": {"type": "object", "properties": {key: {"type": "string"} for key in ("nonce", "operation", "marker")}, "required": ["nonce"], "additionalProperties": False}}} + first = {"model": "uap-kimi-native-model", "messages": [{"role": "user", "content": prompt}], "tools": [declaration], "stream": True, + "stream_options": {"include_usage": True}, "prompt_cache_key": session} + second = {**first, "messages": first["messages"] + [{"role": "assistant", "content": None, "tool_calls": [{"id": call, "type": "function", "function": {"name": declaration["function"]["name"], "arguments": encode(args).decode()}}]}, + {"role": "tool", "tool_call_id": call, "content": "Wall time: 0.004 seconds\n" + encode(facts).decode()}]} + bodies["provider-request-1.json"], bodies["provider-request-2.json"] = encode(first), encode(second) + messages = [{"id": prompt_id, "session_id": session, "role": "user", "content": [{"type": "text", "text": prompt}], "created_at": stamp, "metadata": {"origin": {"kind": "user"}}}, + {"id": "msg_inert_call", "session_id": session, "role": "assistant", "content": [{"type": "tool_use", "tool_call_id": call, "tool_name": declaration["function"]["name"], "input": args}], "created_at": stamp}, + {"id": "msg_inert_result", "session_id": session, "role": "tool", "content": [{"type": "tool_result", "tool_call_id": call, "output": encode(facts).decode()}], "created_at": "2026-01-02T03:04:08.003Z"}, + {"id": "msg_inert_final", "session_id": session, "role": "assistant", "content": [{"type": "text", "text": sentinel}], "created_at": "2026-01-02T03:04:08.004Z"}] + state = {"id": session, "workspace_id": "wd_project_111111111111", "metadata": {"cwd": project}, "agent_config": {"model": "uap-kimi-native-model"}, + "permission_rules": [], "last_turn_reason": "completed", "busy": False, "main_turn_active": False, "pending_interaction": "none", "archived": False} + state.update({"title": prompt, "created_at": stamp, "updated_at": "2026-01-02T03:04:10Z", "usage": {key: 0 for key in ("input_tokens", "output_tokens", "cache_read_tokens", "cache_creation_tokens", "context_tokens")}, "message_count": 0, "last_seq": 0}) + raw = {"kimi-single-tool.stdout.log": "independently authored inert service stream; never executed\n", "kimi-single-tool.stderr.log": ""} + native = {"argv": [client, "web", "--no-open", "--host", "127.0.0.1", "--port", "0"], "cwd": project, "probe": "kimi-single-tool", "elapsed_seconds": 0.01, "error": None, "exit": 0, + "cleanup": {"forced_kill": False, "group_reaped": True, "port_closed": True, "shutdown_acknowledged": True, "sigterm_sent": False}} + for channel in ("stdout", "stderr"): + name = "kimi-single-tool." + channel + ".log" + native.update({channel + "_file": name, channel + "_bytes": len(raw[name].encode()), channel + "_sha256": digest(raw[name].encode())}) + receipts = [] + def response(label, method, path, value, auth=True, status=200, code=0): + body = encode({"code": code, "msg": "success" if code == 0 else "inert unauthorized", "data": value, "request_id": "inert-request-" + label}) + name = "kimi-single-tool-" + label + ".http.json" + raw[name] = body.decode() + receipts.append({"authenticated": auth, "error": None, "label": label, "method": method, "path": path, "response_bytes": len(body), "response_file": name, "response_sha256": digest(body), "status": status}) + response("health", "GET", "/api/v1/healthz", {"ok": True}, False) + response("unauthorized", "GET", "/api/v1/meta", None, False, 401, 40101) + response("meta", "GET", "/api/v1/meta", {"server_version": "2.3.2", "backend": "v2", "dangerous_bypass_auth": False}) + projects = {"a": project, "b": root + "/project-b", "c": root + "/project-c", "user": root + "/outside"} + workspaces = {"a": "wd_project_111111111111", "b": "wd_project-b_222222222222", "c": "wd_project-c_333333333333"} + for scope in ("a", "b", "c"): + response("workspace-" + scope, "POST", "/api/v1/workspaces", {"id": workspaces[scope], "root": projects[scope], "name": "uap-kimi-native-" + scope, "created_at": stamp, "last_opened_at": stamp, "session_count": 0}) + response("trust-before-" + scope, "GET", "/api/v1/workspaces/" + workspaces[scope] + "/trust", {"trusted": False}) + for scope in ("a", "b"): + response("trust-set-" + scope, "POST", "/api/v1/workspaces/" + workspaces[scope] + "/trust", {"trusted": True}) + response("trust-after-" + scope, "GET", "/api/v1/workspaces/" + workspaces[scope] + "/trust", {"trusted": True}) + response("trust-untrusted-c", "GET", "/api/v1/workspaces/" + workspaces["c"] + "/trust", {"trusted": False}) + projected = {key: value for key, value in server.items() if key != "env"}; projected["envKeys"] = sorted(server["env"]) + for scope in ("a", "b", "c", "user"): + catalog = [{"name": "uap-kimi-native-a", "config": projected, "source": "global", "origin": profile + "/mcp.json", "mutable": False}] if scope == "a" else [] + response("catalog-" + scope, "GET", "/api/v2/mcp/servers?" + runtime.urlencode({"cwd": projects[scope]}), catalog) + response("skills-a", "GET", "/api/v1/workspaces/" + workspaces["a"] + "/skills", {"skills": []}) + response("create-session", "POST", "/api/v1/sessions", {**{key: value for key, value in state.items() if key != "last_turn_reason"}, "agent_config": {"model": ""}, "title": ""}) + base = "/api/v1/sessions/" + session + response("prompt", "POST", base + "/prompts", {"prompt_id": prompt_id, "user_message_id": prompt_id, "status": "running", "content": [{"type": "text", "text": prompt}], "created_at": stamp}) + action = "Approve " + declaration["function"]["name"] + response("approvals-0", "GET", base + "/approvals?status=pending", {"items": [{"approval_id": approval, "session_id": session, "agent_id": "main", "turn_id": 0, "tool_call_id": call, "tool_name": declaration["function"]["name"], "action": action, "tool_input_display": {"kind": "generic", "summary": action, "detail": args}, "created_at": stamp, "expires_at": "2026-01-03T03:04:07Z"}]}) + response("approve-once", "POST", base + "/approvals/" + approval, {"resolved": True, "resolved_at": stamp}) + response("snapshot-0", "GET", base + "/snapshot", {"as_of_seq": 1, "epoch": "inert-epoch", "session": state, "messages": {"items": messages, "has_more": False}, "in_flight_turn": None, "pending_approvals": [], "pending_questions": [], "subagents": []}) + response("history", "GET", base + "/messages?page_size=20", {"items": messages[::-1], "has_more": False}) + response("shutdown", "POST", "/api/v1/shutdown", {"ok": True}) + raw["research.json"] = encode({"schema_version": 1, "client": "kimi", "phase": "installed-single-tool-attempt", "commands": [native], "http_requests": receipts, + "qualification": dict.fromkeys(runtime.shared.QUALIFICATION_CASES, False), "observations": {"native_capture_complete": False, "native_history_grammar_frozen": False, + "native_session_id": session, "native_turn_id": 0, "provider_call_id": call, "approval_id": approval, "snapshot_count": 1, "api_request_count": len(receipts), "boundary": "Bounded initial capture; no native history or qualification inferred from provider/probe success"}}).decode() + bodies["python-api.json"] = bodies["native-attempt.stdout.log"] = encode({"schema_version": 1, "files": raw}) + inventory = {".": "020000000700:directory", ".agentplugins-data-owner.json": "0600:" + "e" * 64} + bodies["native-data-proof.json"] = encode({"schema_version": 1, "before": inventory, "after_without_marker": inventory, "marker_body": marker, "marker_bytes": len(marker), "marker_sha256": digest(marker.encode())}) + proof = {"native_session_id": session, "native_turn_id": 0, "provider_call_id": call, "approval_id": approval, "prompt_id": prompt_id, "terminal_reason": "completed", + "message_count": 4, "snapshot_count": 1, "history_sha256": digest(raw["kimi-single-tool-history.http.json"].encode()), "terminal_snapshot_sha256": digest(raw["kimi-single-tool-snapshot-0.http.json"].encode()), + "probe_pid": 17, "probe_session": facts["session"], "started_at": "2026-01-02T03:04:05Z", "ended_at": "2026-01-02T03:04:11Z", "data_proof_sha256": digest(bodies["native-data-proof.json"])} + result = {**self.cli_result("qoder"), "client": "kimi", "version": "2.3.2", "phase": "installed-single-tool-attempt", "provider_kind": "scripted-openai-chat-completions", "elapsed_ms": 8000, + "runtime_attempt_complete": True, "test_failed": False, "native_capture_complete": True, "native_history_grammar_frozen": True, "provider_returned": True, + "provider_call_id": call, "native_session_id": session, "native_turn_id": 0, "probe_facts": facts, "native_history": proof, + "binding": {"client_id": "kimi", "scope": "project", "activation": "prepared", "authentication": "not_checked", "verification": "installation_verified", "materialization": "materialized", "target_locator": active, "native_profile_root": profile, "physical_artifact_id": physical}, + "data_locator": data, "provider_cleanup": {"shutdown_complete": True, "forced_close": False, "error": False, "requests": 2, "issued": True, "returned": True}, + "qualification": {key: key in ("mcp-initialize", "mcp-tools-list", "mcp-tools-call") for key in runtime.shared.QUALIFICATION_CASES}} + bodies["runtime-attempt.json"] = encode(result) + return bodies + + def test_kimi_synthetic_complete_reader_recomputes_raw_proof(self): + bodies = self.kimi_synthetic_completed_capture() + result = runtime.kimi_call_members(bodies, self.source) + self.assertTrue(result["runtime_attempt_complete"]) + self.assertFalse(result["qualification"]["project-lifecycle"]) + self.assertFalse(result["qualification"]["native-restart-removal"]) + for name in bodies: + with self.subTest(missing=name), self.assertRaises(ValueError): + runtime.kimi_call_members({key: value for key, value in bodies.items() if key != name}, self.source) + + def test_kimi_synthetic_success_cannot_cover_failed_or_foreign_raw(self): + for kind in ("terminal-failed", "empty-sentinel", "wrong-call", "extra-message", "history-more", "forced", "changed-data", "wrong-marker", "wrong-proof", "foreign-source", "lifecycle-flag", "wrong-cache", "unknown-file"): + with self.subTest(kind=kind), self.assertRaises(ValueError): + bodies = self.kimi_synthetic_completed_capture() + result = json.loads(bodies["runtime-attempt.json"]) + if kind in ("terminal-failed", "empty-sentinel", "wrong-call", "extra-message", "history-more"): + envelope = json.loads(bodies["python-api.json"]) + raw = envelope["files"] + snapshot = json.loads(raw["kimi-single-tool-snapshot-0.http.json"]) + if kind == "terminal-failed": snapshot["data"]["session"]["last_turn_reason"] = "failed" + elif kind == "empty-sentinel": snapshot["data"]["messages"]["items"][-1]["content"] = [] + elif kind == "wrong-call": snapshot["data"]["messages"]["items"][1]["content"][0]["tool_call_id"] = "foreign" + elif kind == "extra-message": snapshot["data"]["messages"]["items"].append(snapshot["data"]["messages"]["items"][0]) + elif kind == "history-more": snapshot["data"]["messages"]["has_more"] = True + raw["kimi-single-tool-snapshot-0.http.json"] = json.dumps(snapshot) + research = json.loads(raw["research.json"]) + for receipt in research["http_requests"]: + body = raw[receipt["response_file"]].encode(); receipt["response_bytes"] = len(body); receipt["response_sha256"] = hashlib.sha256(body).hexdigest() + raw["research.json"] = json.dumps(research) + bodies["python-api.json"] = bodies["native-attempt.stdout.log"] = json.dumps(envelope).encode() + elif kind == "forced": result["provider_cleanup"]["forced_close"] = True + elif kind in ("changed-data", "wrong-marker"): + data = json.loads(bodies["native-data-proof.json"]) + if kind == "changed-data": data["after_without_marker"]["foreign"] = "0600:" + "e" * 64 + else: data["marker_body"] = "foreign" + bodies["native-data-proof.json"] = json.dumps(data).encode() + elif kind == "wrong-proof": result["native_history"]["history_sha256"] = "foreign" + elif kind == "foreign-source": result["source_identity"]["installer_tree"] = "f" * 40 + elif kind == "lifecycle-flag": result["qualification"]["project-lifecycle"] = True + elif kind == "wrong-cache": + request = json.loads(bodies["provider-request-2.json"]); request["prompt_cache_key"] = "foreign"; bodies["provider-request-2.json"] = json.dumps(request).encode() + elif kind == "unknown-file": bodies["foreign.json"] = b"{}" + bodies["runtime-attempt.json"] = json.dumps(result).encode() + runtime.kimi_call_members(bodies, self.source) + def load_tests(loader, tests, pattern): # The existing runtime workflow deliberately selects this exact module. From 9a642d1c086e18981e13705b8f86cc63172b3390 Mon Sep 17 00:00:00 2001 From: 777genius Date: Wed, 28 Oct 2026 00:33:42 +0100 Subject: [PATCH 39/42] test(kimi): close native completion reader parity gaps --- ...ECT_NATIVE_FINITE_COMPLETION_VALIDATION.md | 56 +++++ ...mi_project_native_history_contract_test.go | 142 ++++++++++- ...lugins_kimi_project_native_history_test.go | 112 ++++++++- scripts/project_native_runtime.py | 232 +++++++++++++++--- scripts/test_project_native_runtime.py | 206 +++++++++++++++- 5 files changed, 690 insertions(+), 58 deletions(-) diff --git a/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md b/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md index e8e317885..6e000fbc1 100644 --- a/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md +++ b/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md @@ -105,3 +105,59 @@ scratch replay with unchanged original raw outcomes, and two independent exact code reviews. Root alone commits/pushes. A fresh genuine current-source hosted pass plus independent raw audit is required before any tools/call qualification claim. No release is authorized by this work. + +## Independent code-review corrections + +The first exact implementation19f64f9c0cace7906333f589227f9d711f3b45b5 received +NO-GO from two full independent critics. Its focused Go and86 Python contracts +passed, while actual affected lint reported eight style issues and whole six +lint passed. These receipts do not supersede the review findings or qualify a +native outcome. + +The amended readers explicitly parse the public successful selected add result +and recompute the existing fixed-name physical artifact, client binding and data +receipt IDs. Python now mirrors the Go provider's closed message/content/call/ +function grammar, native research/command/HTTP types and closure, and mandatory +health, pinned metadata, selected skill and shutdown data predicates. Positive +fixtures carry coherent public IDs, selected skill and complete metadata. +Mutation controls update raw receipts and proof digests so semantic refusals +cannot be accidental stale-hash failures. + +Both success readers keep session `current_prompt_id` optional. It is absent at +new session creation; any later present value must equal the one accepted own +prompt. Null and foreign values are refused. Response metadata for the fixed +prompt is absent: pinned `routes/prompts.ts`, blob +e820ef142320ab3349f3bf0b4984c98fb0a847cd, projects only +`origin.clientMetadata?.[0]`; the unchanged fixture request supplies no metadata. +`rest-prompt.ts` blob20cc84b5da623069a3934cc26601cadf2e1b6f5f and the pinned session +schema support these distinctions. No user-origin wrapper or null allowance is +invented. History's already observed user-origin record remains a separate +unchanged predicate. The source files were read and Git-blob/SHA256 verified, +without executing vendor code. + +The exact upstream wire spelling of the canceled terminal reason is preserved +in one documented protocol constant. Human prose/test labels use American +spelling; the original misspell gate is retained. + +Both success readers also enforce the same finite Kimi binding authority. +The thirteen required scalar fields are `client_binding_id`, `client_id`, `scope`, +`target_locator`, `physical_artifact_id`, `materialization`, `activation`, +`authentication`, `policy`, `verification`, `data_receipt_id`, +`native_profile_root` and `updated_at`; each is a string. The only optional +carriers are the observed `package_revision`, `affected_surfaces`, +`native_objects` and `receipts`. Their nested keys and scalar/list/Boolean/integer +types are closed too. The package revision has only tree/manifest digests and an +optional version; native ownership and mutation records admit only the fields +emitted by this Kimi fixture. Other profile, delivery, pending-intent or receipt +authority is refused. This is a finite evidence reader, not another general +domain-state decoder. The native profile is canonical and absolute, with the +exact `.kimi-code` leaf, and joins the canonical project, managed and data paths. +Historical failed archive admission does not invoke this success grammar. + +Python independently caps a successful attempt at180000milliseconds and each +snapshot sequence at2^40, matching Go. Trust and catalog mutability require +actual JSON Booleans; numeric zero/one cannot stand in for false/true. Additional +synthetic controls mutate each of these predicates, including nested binding +types and authority keys, while updating affected HTTP receipts and terminal +proof digests. Original measured9e failures retain all false single-call flags; +none of these source corrections establishes a fresh native observation. diff --git a/repotests/agentplugins_kimi_project_native_history_contract_test.go b/repotests/agentplugins_kimi_project_native_history_contract_test.go index 94e68c6b1..b7fd3e917 100644 --- a/repotests/agentplugins_kimi_project_native_history_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_history_contract_test.go @@ -16,6 +16,9 @@ import ( "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" ) +// Exact Kimi2.3.2 wire enum; prose/test labels use American spelling. +const kimiAttemptCanceledWireReason = "cancelled" //nolint:misspell // Exact pinned upstream protocol spelling. + func kimiHistoryEncode(t *testing.T, value any) []byte { t.Helper() body, err := json.Marshal(value) @@ -181,7 +184,7 @@ func kimiSyntheticFailedHistoryFiles(t *testing.T) map[string][]byte { http("prompt", "POST", base+"/prompts", true, 200, 0, map[string]any{"prompt_id": "msg_inert_prompt", "user_message_id": "msg_inert_prompt", "status": "running", "content": []any{map[string]any{"type": "text", "text": prompt}}, "created_at": stamp}) http("approvals-0", "GET", base+"/approvals?status=pending", true, 200, 0, map[string]any{"items": []any{map[string]any{"approval_id": approvalID, "session_id": nativeSession, "agent_id": "main", "turn_id": 0, "tool_call_id": callID, "tool_name": kimiAttemptTool, "action": "Approve " + kimiAttemptTool, "tool_input_display": map[string]any{"kind": "generic", "summary": "Approve " + kimiAttemptTool, "detail": args}, "created_at": stamp, "expires_at": "2026-01-03T03:04:07Z"}}}) http("approve-once", "POST", base+"/approvals/"+approvalID, true, 200, 0, map[string]any{"resolved": true, "resolved_at": stamp}) - for index := 0; index < 2; index++ { + for index := range 2 { var inFlight any if index == 0 { inFlight = map[string]any{"turn_id": 0, "assistant_text": "", "thinking_text": "", "running_tools": []any{}} @@ -361,7 +364,7 @@ func TestKimiSingleToolSyntheticCompletedHistoryHasOnlyThreeMCPFlags(t *testing. } func TestKimiSingleToolFiniteHistoryRejectsForeignOrIncompleteNativeData(t *testing.T) { - kinds := []string{"failed", "cancelled", "active", "in-flight", "pending-approval", "pending-question", "subagent", "session", "workspace", "cwd", "model", "permission", "epoch", "sequence", "history-order", "history-more", "snapshot-more", "extra-message", "duplicate-id", "stale-time", "future-time", "unknown-message", "unknown-part", "wrong-call", "wrong-tool", "wrong-args", "error-result", "wrong-facts", "missing-sentinel", "wrong-sentinel", "extra-sentinel", "date-metadata", "date-text", "unknown-file", "missing-file", "response-hash", "receipt-path", "receipt-method", "receipt-auth", "unknown-response", "capture-complete"} + kinds := []string{"failed", "canceled", "active", "in-flight", "pending-approval", "pending-question", "subagent", "session", "workspace", "cwd", "model", "permission", "epoch", "sequence", "history-order", "history-more", "snapshot-more", "extra-message", "duplicate-id", "stale-time", "future-time", "unknown-message", "unknown-part", "wrong-call", "wrong-tool", "wrong-args", "error-result", "wrong-facts", "missing-sentinel", "wrong-sentinel", "extra-sentinel", "date-metadata", "date-text", "unknown-file", "missing-file", "response-hash", "receipt-path", "receipt-method", "receipt-auth", "unknown-response", "capture-complete"} for _, kind := range kinds { t.Run(kind, func(t *testing.T) { files, expected := kimiSyntheticCompletedHistory(t) @@ -370,8 +373,12 @@ func TestKimiSingleToolFiniteHistoryRejectsForeignOrIncompleteNativeData(t *test page := data["messages"].(map[string]any) rows := page["items"].([]any) switch kind { - case "failed", "cancelled": - session["last_turn_reason"] = kind + case "failed", "canceled": + reason := kind + if kind == "canceled" { + reason = kimiAttemptCanceledWireReason + } + session["last_turn_reason"] = reason case "active": session["busy"] = true case "in-flight": @@ -476,6 +483,133 @@ func TestKimiSingleToolFiniteHistoryRejectsForeignOrIncompleteNativeData(t *test } } +func TestKimiSingleToolPromptIdentityAndAbsentResponseMetadata(t *testing.T) { + files, expected := kimiSyntheticCompletedHistory(t) + for _, label := range []string{"snapshot-0", "snapshot-1"} { + kimiMutateHTTP(t, files, label, func(data map[string]any) { + data["session"].(map[string]any)["current_prompt_id"] = "msg_inert_prompt" + }) + } + proof, err := kimiAttemptHistory(files["python-api.json"], expected) + if err != nil { + t.Fatal("optional own session prompt identity refused", err) + } + proof["data_proof_sha256"] = kimiProjectProofSHA(files["native-data-proof.json"]) + result := kimiHistoryDecode(t, files["runtime-attempt.json"]) + result["native_history"] = proof + files["runtime-attempt.json"] = kimiHistoryEncode(t, result) + if err := kimiAttemptAdmit(files); err != nil { + t.Fatal("coherent own-prompt successful archive refused", err) + } + for _, kind := range []string{"session-foreign", "session-null", "create-own", "create-null", "in-flight-null", "metadata-user", "metadata-null", "metadata-foreign"} { + t.Run(kind, func(t *testing.T) { + files, expected := kimiSyntheticCompletedHistory(t) + switch kind { + case "session-foreign", "session-null": + kimiMutateHTTP(t, files, "snapshot-1", func(data map[string]any) { + var value any = "foreign" + if kind == "session-null" { + value = nil + } + data["session"].(map[string]any)["current_prompt_id"] = value + }) + case "create-own", "create-null": + kimiMutateHTTP(t, files, "create-session", func(data map[string]any) { + var value any = "msg_inert_prompt" + if kind == "create-null" { + value = nil + } + data["current_prompt_id"] = value + }) + case "in-flight-null": + kimiMutateHTTP(t, files, "snapshot-0", func(data map[string]any) { + data["in_flight_turn"].(map[string]any)["current_prompt_id"] = nil + }) + default: + kimiMutateHTTP(t, files, "prompt", func(data map[string]any) { + var metadata any = map[string]any{"origin": map[string]any{"kind": "user"}} + if kind == "metadata-null" { + metadata = nil + } else if kind == "metadata-foreign" { + metadata = map[string]any{"origin": map[string]any{"kind": "injection"}} + } + data["metadata"] = metadata + }) + } + // The envelope helper recalculates every HTTP digest and stream alias. + // Direct semantic validation cannot fail merely on an old proof hash. + if _, err := kimiAttemptHistory(files["python-api.json"], expected); err == nil { + t.Fatal("unjoined prompt identity/metadata admitted") + } + }) + } +} + +func kimiSyntheticBindingRecords(binding map[string]any) { + binding["package_revision"] = map[string]any{"version": "1.0.0", "tree_digest": "sha256:" + strings.Repeat("a", 64), "manifest_digest": "sha256:" + strings.Repeat("b", 64)} + binding["affected_surfaces"] = []any{"kimi"} + binding["native_objects"] = []any{map[string]any{"object_id": "package:kimi:inert", "kind": "managed_package_directory", "path": binding["target_locator"], "protection_class": "managed"}} + binding["receipts"] = []any{map[string]any{"operation_id": "inert-operation", "sequence": 1, "mutation_type": "directory_swap", "client_binding_id": binding["client_binding_id"], "phase": "committed"}} +} + +func TestKimiSingleToolBindingAuthorityIsClosedAndTyped(t *testing.T) { + files, _ := kimiSyntheticCompletedHistory(t) + result := kimiHistoryDecode(t, files["runtime-attempt.json"]) + kimiSyntheticBindingRecords(result["binding"].(map[string]any)) + files["runtime-attempt.json"] = kimiHistoryEncode(t, result) + if err := kimiAttemptAdmit(files); err != nil { + t.Fatal("independent typed Kimi binding refused", err) + } + for _, kind := range []string{"unknown", "authority", "missing", "scalar", "profile-leaf", "profile-parent", "profile-relative", "profile-double-root", "revision-extra", "revision-type", "surface-type", "object-extra", "object-type", "object-modified", "receipt-extra", "receipt-type", "receipt-sequence"} { + t.Run(kind, func(t *testing.T) { + files, _ := kimiSyntheticCompletedHistory(t) + result := kimiHistoryDecode(t, files["runtime-attempt.json"]) + binding := result["binding"].(map[string]any) + kimiSyntheticBindingRecords(binding) + switch kind { + case "unknown": + binding["unknown"] = true + case "authority": + binding["profile_authority"] = map[string]any{} + case "missing": + delete(binding, "policy") + case "scalar": + binding["updated_at"] = nil + case "profile-leaf": + binding["native_profile_root"] = "/inert/project/.foreign" + case "profile-parent": + binding["native_profile_root"] = "/inert/project/../project/.kimi-code" + case "profile-relative": + binding["native_profile_root"] = "relative/.kimi-code" + case "profile-double-root": + binding["native_profile_root"] = "/" + binding["native_profile_root"].(string) + case "revision-extra": + binding["package_revision"].(map[string]any)["unknown"] = true + case "revision-type": + binding["package_revision"].(map[string]any)["version"] = 1 + case "surface-type": + binding["affected_surfaces"] = []any{false} + case "object-extra": + binding["native_objects"].([]any)[0].(map[string]any)["unknown"] = true + case "object-type": + binding["native_objects"].([]any)[0].(map[string]any)["path"] = nil + case "object-modified": + binding["native_objects"].([]any)[0].(map[string]any)["user_modified"] = 0 + case "receipt-extra": + binding["receipts"].([]any)[0].(map[string]any)["unknown"] = true + case "receipt-type": + binding["receipts"].([]any)[0].(map[string]any)["phase"] = false + case "receipt-sequence": + binding["receipts"].([]any)[0].(map[string]any)["sequence"] = true + } + files["runtime-attempt.json"] = kimiHistoryEncode(t, result) + if err := kimiAttemptAdmit(files); err == nil { + t.Fatal("untyped or broadened Kimi binding admitted") + } + }) + } +} + func TestKimiSingleToolLateFlagsDataAndProviderClosure(t *testing.T) { files, expected := kimiSyntheticCompletedHistory(t) for _, kind := range []string{"marker", "marker-sha", "inventory", "unknown-inventory", "missing-owner", "oversized", "extra-field"} { diff --git a/repotests/agentplugins_kimi_project_native_history_test.go b/repotests/agentplugins_kimi_project_native_history_test.go index 3ec2e4f2d..8496f108d 100644 --- a/repotests/agentplugins_kimi_project_native_history_test.go +++ b/repotests/agentplugins_kimi_project_native_history_test.go @@ -20,14 +20,14 @@ import ( // Source b8dd3a1, toolResultRender.ts blob2bf13ed32a098c16c9b8a4094c49f93e569f58d0. // Only model-visible MCP text carries this header. Native history/probe output // remains a whole unframed JSON document and must not use this decoder. -var kimiAttemptWallTime = regexp.MustCompile(`^Wall time: (0|[1-9][0-9]?)\.[0-9]{3} seconds$`) +var kimiAttemptWallTime = regexp.MustCompile(`^Wall time: (0|[1-9]\d?)\.\d{3} seconds$`) var kimiAttemptWorkspaceID = regexp.MustCompile(`^wd_[a-z0-9._-]+_[0-9a-f]{12}$`) var kimiAttemptCallID = regexp.MustCompile(`^uap_([0-9a-f]{32})_attempt$`) var kimiAttemptPhysicalID = regexp.MustCompile(`^uap-kimi-native-a-[0-9a-f]{12}$`) var kimiAttemptInventoryValue = regexp.MustCompile(`^0[0-7]{1,12}:(directory|[0-9a-f]{64})$`) func kimiAttemptModelFacts(body string) ([]byte, error) { - if len(body) == 0 || len(body) > projectNativeRuntimeBodyLimit { + if body == "" || len(body) > projectNativeRuntimeBodyLimit { return nil, fmt.Errorf("Kimi model tool result bound differs") } header, raw, ok := strings.Cut(body, "\n") @@ -45,10 +45,10 @@ func kimiAttemptModelFacts(body string) ([]byte, error) { } type kimiAttemptHistoryExpectation struct { - Client, Session, Call, Approval, Prompt, Sentinel, Skill string - Projects map[string]string - Server, Facts map[string]any - Started, Ended time.Time + Client, Session, Call, Approval, Prompt, PromptID, Sentinel, Skill string + Projects map[string]string + Server, Facts map[string]any + Started, Ended time.Time } func kimiHistoryObject(value any, required, optional string) (map[string]any, error) { @@ -59,6 +59,80 @@ func kimiHistoryObject(value any, required, optional string) (map[string]any, er return row, nil } +func kimiHistoryStringObject(value any, required, optional, except string) (map[string]any, error) { + row, err := kimiHistoryObject(value, required, optional) + if err != nil { + return nil, err + } + for key, value := range row { + if key == except { + continue + } + if _, ok := value.(string); !ok { + return nil, fmt.Errorf("Kimi finite binding string differs") + } + } + return row, nil +} + +func kimiHistoryBindingRecord(value any, native bool) error { + if native { + row, err := kimiHistoryStringObject(value, "object_id kind protection_class", "logical_name path source_relative before_digest managed_digest user_modified", "user_modified") + if err != nil { + return err + } + if value, exists := row["user_modified"]; exists { + if _, ok := value.(bool); !ok { + return fmt.Errorf("Kimi finite binding modified type differs") + } + } + return nil + } + row, err := kimiHistoryStringObject(value, "operation_id sequence mutation_type client_binding_id phase", "data_receipt_id operation_group_id active_path staging_path backup_path before_digest after_digest", "sequence") + if err != nil { + return err + } + if _, err := kimiHistoryInteger(row["sequence"], -1<<63, 1<<63-1); err != nil { + return err + } + return nil +} + +func kimiHistoryBinding(value any) error { + // Only the fixed Kimi add's observed authority carriers are admitted. + row, err := kimiHistoryObject(value, "client_binding_id client_id scope target_locator physical_artifact_id materialization activation authentication policy verification data_receipt_id native_profile_root updated_at", "package_revision affected_surfaces native_objects receipts") + if err != nil { + return err + } + for key, value := range row { + switch key { + case "package_revision": + if _, err := kimiHistoryStringObject(value, "tree_digest manifest_digest", "version", ""); err != nil { + return err + } + case "affected_surfaces", "native_objects", "receipts": + values, ok := value.([]any) + if !ok { + return fmt.Errorf("Kimi finite binding list differs") + } + for _, value := range values { + if key == "affected_surfaces" { + if _, ok := value.(string); !ok { + return fmt.Errorf("Kimi finite binding surface differs") + } + } else if err := kimiHistoryBindingRecord(value, key == "native_objects"); err != nil { + return err + } + } + default: + if _, ok := value.(string); !ok { + return fmt.Errorf("Kimi finite binding scalar differs") + } + } + } + return nil +} + func kimiHistoryInteger(value any, low, high int64) (int64, error) { if !projectNativeRuntimeNumber(value, float64(low), float64(high), true) { return 0, fmt.Errorf("Kimi finite history integer differs") @@ -138,7 +212,7 @@ func kimiHistorySession(value any, expected kimiAttemptHistoryExpectation, works return fmt.Errorf("Kimi finite history additional interaction") } if value, exists := row["last_turn_reason"]; exists && value != "completed" { - return fmt.Errorf("Kimi finite history failed or cancelled native turn") + return fmt.Errorf("Kimi finite history failed or canceled native turn") } if value, exists := row["last_prompt"]; exists && value != expected.Prompt { return fmt.Errorf("Kimi finite history last prompt differs") @@ -146,7 +220,7 @@ func kimiHistorySession(value any, expected kimiAttemptHistoryExpectation, works if _, exists := row["archived_at"]; exists { return fmt.Errorf("Kimi finite history archived session") } - if value, exists := row["current_prompt_id"]; exists && (!projectNativeRuntimeText(value, 256) || value == "") { + if value, exists := row["current_prompt_id"]; exists && (initial || expected.PromptID == "" || value != expected.PromptID) { return fmt.Errorf("Kimi finite history current prompt identity differs") } usage, err := kimiHistoryObject(row["usage"], "input_tokens output_tokens cache_read_tokens cache_creation_tokens context_tokens", "total_cost_usd context_limit turn_count") @@ -246,13 +320,18 @@ func kimiHistoryMessages(value any, expected kimiAttemptHistoryExpectation, prom return rows, nil } +func kimiHistoryPresentPromptDiffers(row map[string]any, promptID string) bool { + value, exists := row["current_prompt_id"] + return exists && value != promptID +} + func kimiHistoryInFlight(value any, expected kimiAttemptHistoryExpectation, promptID string) error { if value == nil { return nil } row, err := kimiHistoryObject(value, "turn_id assistant_text thinking_text running_tools", "current_prompt_id") text, textOK := row["assistant_text"].(string) - if err != nil || row["turn_id"] != json.Number("0") || !textOK || !strings.HasPrefix(expected.Sentinel, text) || row["thinking_text"] != "" || row["current_prompt_id"] != nil && row["current_prompt_id"] != promptID { + if err != nil || row["turn_id"] != json.Number("0") || !textOK || !strings.HasPrefix(expected.Sentinel, text) || row["thinking_text"] != "" || kimiHistoryPresentPromptDiffers(row, promptID) { return fmt.Errorf("Kimi finite native history in-flight turn differs") } tools, ok := row["running_tools"].([]any) @@ -510,7 +589,7 @@ func kimiAttemptHistoryResponses(httpRows []any, labels []string, responses map[ if err := kimiHistorySession(responses["create-session"], expected, workspaces["a"], false, true); err != nil { return nil, err } - prompt, err := kimiHistoryObject(responses["prompt"], "prompt_id user_message_id status content created_at", "metadata") + prompt, err := kimiHistoryObject(responses["prompt"], "prompt_id user_message_id status content created_at", "") promptID, ok := prompt["prompt_id"].(string) if err != nil || !ok || promptID == "" || len(promptID) > 256 || prompt["user_message_id"] != promptID || prompt["status"] != "running" && prompt["status"] != "queued" && prompt["status"] != "blocked" || !reflect.DeepEqual(prompt["content"], []any{map[string]any{"type": "text", "text": expected.Prompt}}) { return refused() @@ -518,8 +597,12 @@ func kimiAttemptHistoryResponses(httpRows []any, labels []string, responses map[ if _, err := kimiHistoryInstant(prompt["created_at"], expected.Started, expected.Ended); err != nil { return nil, err } + // Fixed request supplies no client metadata; pinned prompts.ts projection + // therefore omits response metadata. Session prompt identity is optional, + // but any later present string must name this exact accepted prompt. + expected.PromptID = promptID approvalCount := 0 - for index := 0; index < 20; index++ { + for index := range 20 { label := "approvals-" + strconv.Itoa(index) value, exists := responses[label] if !exists { @@ -567,7 +650,7 @@ func kimiAttemptHistoryResponses(httpRows []any, labels []string, responses map[ capturedSnapshots := []map[string]any{} epoch := "" lastSeq := int64(-1) - for index := int64(0); index < snapshotCount; index++ { + for index := range snapshotCount { label := "snapshot-" + strconv.FormatInt(index, 10) endpoints[label] = endpoint{"GET", base + "/snapshot", true, 200} order = append(order, label) @@ -798,6 +881,9 @@ func kimiAttemptSuccessfulFiles(files map[string][]byte, result map[string]any) if !callOK || len(parts) != 2 || !sessionOK || !approvalOK || approval == "" || proof["native_session_id"] != session || proof["provider_call_id"] != call || result["native_turn_id"] != json.Number("0") { return refused() } + if err := kimiHistoryBinding(result["binding"]); err != nil { + return err + } bindingBody, err := json.Marshal(result["binding"]) if err != nil { return err @@ -805,7 +891,7 @@ func kimiAttemptSuccessfulFiles(files map[string][]byte, result map[string]any) var binding domain.ClientBinding decoder := json.NewDecoder(bytes.NewReader(bindingBody)) decoder.DisallowUnknownFields() - if decoder.Decode(&binding) != nil || binding.ClientID != "kimi" || binding.Scope != "project" || binding.Activation != domain.ActivationPrepared || binding.Authentication != domain.AuthenticationNotChecked || binding.Verification != domain.VerificationInstalled || binding.Materialization != domain.MaterializationMaterialized || !kimiAttemptPhysicalID.MatchString(binding.PhysicalArtifact) || filepath.Base(binding.NativeProfileRoot) != ".kimi-code" { + if decoder.Decode(&binding) != nil || binding.ClientID != "kimi" || binding.Scope != "project" || binding.Activation != domain.ActivationPrepared || binding.Authentication != domain.AuthenticationNotChecked || binding.Verification != domain.VerificationInstalled || binding.Materialization != domain.MaterializationMaterialized || !kimiAttemptPhysicalID.MatchString(binding.PhysicalArtifact) || filepath.Base(binding.NativeProfileRoot) != ".kimi-code" || filepath.Clean(binding.NativeProfileRoot) != binding.NativeProfileRoot { return refused() } project := filepath.Dir(binding.NativeProfileRoot) diff --git a/scripts/project_native_runtime.py b/scripts/project_native_runtime.py index 67b0ee8fe..b063ed5a5 100644 --- a/scripts/project_native_runtime.py +++ b/scripts/project_native_runtime.py @@ -786,7 +786,7 @@ def kimi_call_state(row, expected, workspace, project, *, initial=False, termina or ("last_turn_reason" in row and row["last_turn_reason"] != "completed") or ("last_prompt" in row and row["last_prompt"] != expected["prompt"])): raise ValueError("Kimi complete native failure/additional interaction") - if "current_prompt_id" in row and (type(row["current_prompt_id"]) is not str or not 1 <= len(row["current_prompt_id"]) <= 256): + if "current_prompt_id" in row and (initial or row["current_prompt_id"] != expected["prompt_id"]): raise ValueError("Kimi complete native current prompt differs") usage = kimi_call_object(row["usage"], ("input_tokens", "output_tokens", "cache_read_tokens", "cache_creation_tokens", "context_tokens"), ("total_cost_usd", "context_limit", "turn_count")) for key, value in usage.items(): @@ -815,6 +815,119 @@ def kimi_call_partial(page, terminal, sentinel): raise ValueError("Kimi complete intermediate native message identity differs") +def kimi_call_text(value, limit): + return type(value) is str and len(value.encode("utf-8")) <= limit and "\x00" not in value + + +def kimi_call_number(value, low, high, integer=False): + return type(value) in (int, float) and low <= value <= high and math.isfinite(value) and (not integer or value % 1 == 0) + + +def kimi_call_binding(value): + # This fixed Kimi project add has no profile/delivery/pending-intent authority. + fields = ("client_binding_id", "client_id", "scope", "target_locator", "physical_artifact_id", "materialization", + "activation", "authentication", "policy", "verification", "data_receipt_id", "native_profile_root", "updated_at") + binding = kimi_call_object(value, fields, ("package_revision", "affected_surfaces", "native_objects", "receipts")) + if any(type(binding[key]) is not str for key in fields): + raise ValueError("Kimi complete binding scalar type differs") + profile = binding["native_profile_root"] + if (not profile.startswith("/") or profile.startswith("//") or os.path.normpath(profile) != profile + or Path(profile).name != ".kimi-code"): + raise ValueError("Kimi complete canonical native profile/project differs") + if "package_revision" in binding: + revision = kimi_call_object(binding["package_revision"], ("tree_digest", "manifest_digest"), ("version",)) + if any(type(value) is not str for value in revision.values()): + raise ValueError("Kimi complete binding revision type differs") + if "affected_surfaces" in binding and (type(binding["affected_surfaces"]) is not list or any(type(value) is not str for value in binding["affected_surfaces"])): + raise ValueError("Kimi complete binding surfaces type differs") + for key in ("native_objects", "receipts"): + if key not in binding: + continue + if type(binding[key]) is not list: + raise ValueError("Kimi complete binding records type differs") + for record in binding[key]: + if key == "native_objects": + record = kimi_call_object(record, ("object_id", "kind", "protection_class"), + ("logical_name", "path", "source_relative", "before_digest", "managed_digest", "user_modified")) + if "user_modified" in record and type(record["user_modified"]) is not bool: + raise ValueError("Kimi complete binding native object type differs") + scalars = set(record) - {"user_modified"} + else: + record = kimi_call_object(record, ("operation_id", "sequence", "mutation_type", "client_binding_id", "phase"), + ("data_receipt_id", "operation_group_id", "active_path", "staging_path", "backup_path", "before_digest", "after_digest")) + if type(record["sequence"]) is not int or not -(1 << 63) <= record["sequence"] < 1 << 63: + raise ValueError("Kimi complete binding receipt sequence differs") + scalars = set(record) - {"sequence"} + if any(type(record[key]) is not str for key in scalars): + raise ValueError("Kimi complete binding record scalar type differs") + return binding + + +def kimi_call_request(body): + # Pure parity with unchanged Go kimiAttemptRequest; no requester is invoked. + request = kimi_call_object(kimi_call_json(body), ("model", "messages", "tools", "stream", "stream_options"), + ("max_tokens", "max_completion_tokens", "temperature", "top_p", "parallel_tool_calls", "prompt_cache_key")) + if (request["model"] != "uap-kimi-native-model" or request["stream"] is not True + or request["stream_options"] != {"include_usage": True} + or request["stream_options"]["include_usage"] is not True): + raise ValueError("Kimi complete provider envelope differs") + if "prompt_cache_key" in request and (type(request["prompt_cache_key"]) is not str + or not re.fullmatch(r"session_[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}", request["prompt_cache_key"])): + raise ValueError("Kimi complete provider cache metadata differs") + for key in ("max_tokens", "max_completion_tokens"): + if key in request and not kimi_call_number(request[key], 1, 1 << 20, True): + raise ValueError("Kimi complete provider token bound differs") + for key, high in (("temperature", 2), ("top_p", 1)): + if key in request and not kimi_call_number(request[key], 0, high): + raise ValueError("Kimi complete provider sampling bound differs") + if "parallel_tool_calls" in request and type(request["parallel_tool_calls"]) is not bool: + raise ValueError("Kimi complete provider parallel flag differs") + tools = request["tools"] + if type(tools) is not list or len(tools) != 1: + raise ValueError("Kimi complete provider sole installed declaration absent") + declaration = kimi_call_object(tools[0], ("type", "function")) + function = kimi_call_object(declaration["function"], ("name", "description", "parameters")) + parameters = {"type": "object", "properties": {key: {"type": "string"} for key in ("nonce", "operation", "marker")}, "required": ["nonce"], "additionalProperties": False} + if (declaration["type"] != "function" or function["name"] != "mcp__uap-kimi-native-a__inspect_runtime" + or function["description"] != "Inspect only disposable fixture runtime facts." + or function["parameters"] != parameters or function["parameters"]["additionalProperties"] is not False): + raise ValueError("Kimi complete provider installed declaration differs") + messages = request["messages"] + if type(messages) is not list or not 1 <= len(messages) <= 256: + raise ValueError("Kimi complete provider message inventory differs") + for message in messages: + kimi_call_object(message, ("role", "content"), ("tool_call_id", "tool_calls")) + role, content = message["role"], message["content"] + if role not in ("system", "user", "assistant", "tool"): + raise ValueError("Kimi complete provider message role differs") + if content is None: + if role != "assistant" or message.get("tool_calls") is None: + raise ValueError("Kimi complete provider null content differs") + elif not kimi_call_text(content, 1 << 20): + if type(content) is not list or not 1 <= len(content) <= 128: + raise ValueError("Kimi complete provider content inventory differs") + for part in content: + kimi_call_object(part, ("type", "text")) + if part["type"] != "text" or not kimi_call_text(part["text"], 1 << 20): + raise ValueError("Kimi complete provider content text differs") + if "tool_call_id" in message and (role != "tool" or not kimi_call_text(message["tool_call_id"], 256)): + raise ValueError("Kimi complete provider result identity differs") + if role == "tool" and message.get("tool_call_id") is None: + raise ValueError("Kimi complete provider result identity absent") + if "tool_calls" in message: + calls = message["tool_calls"] + if role != "assistant" or type(calls) is not list or len(calls) != 1: + raise ValueError("Kimi complete provider assistant call inventory differs") + call = kimi_call_object(calls[0], ("id", "type", "function")) + function = kimi_call_object(call["function"], ("name", "arguments")) + if (call["type"] != "function" or not kimi_call_text(call["id"], 256) + or function["name"] != "mcp__uap-kimi-native-a__inspect_runtime" + or not kimi_call_text(function["arguments"], 4096)): + raise ValueError("Kimi complete provider assistant function differs") + kimi_call_json(function["arguments"].encode()) + return request + + def kimi_call_complete(bodies, result): """Pure success admission recomputes raw joins; proof/flags are not authority.""" required = {"runtime-attempt.json", "probe-events.jsonl", "python-api.json", "native-data-proof.json", @@ -838,7 +951,7 @@ def kimi_call_complete(bodies, result): "prompt_id", "terminal_reason", "message_count", "snapshot_count", "history_sha256", "terminal_snapshot_sha256", "probe_pid", "probe_session", "started_at", "ended_at", "data_proof_sha256")) started, ended = kimi_call_instant(proof["started_at"]), kimi_call_instant(proof["ended_at"]) - if (not 0 <= (ended - started).total_seconds() <= 180 + if (not 0 <= result["elapsed_ms"] <= 180000 or not 0 <= (ended - started).total_seconds() <= 180 or (ended - started).total_seconds() * 1000 > result["elapsed_ms"] or type(proof["native_turn_id"]) is not int or proof["native_turn_id"] != 0 or type(result.get("native_turn_id")) is not int or result["native_turn_id"] != 0 @@ -853,12 +966,28 @@ def kimi_call_complete(bodies, result): or type(proof["approval_id"]) is not str or not proof["approval_id"]): raise ValueError("Kimi complete session/call ownership differs") nonce = owned[1] - binding = result.get("binding") - if (type(binding) is not dict or binding.get("client_id") != "kimi" or binding.get("scope") != "project" + public = kimi_call_object(kimi_call_json(bodies["add-a.stdout.log"]), ("schema_version", "command", "result", "data")) + if type(public["schema_version"]) is not int or public["schema_version"] != 1 or public["command"] != "add" or public["result"] != "success" or type(public["data"]) is not dict: + raise ValueError("Kimi complete public add result differs") + targets = public["data"].get("targets") + if type(targets) is not list or len(targets) != 1 or type(targets[0]) is not dict or targets[0].get("target") != "kimi" or type(targets[0].get("output")) is not dict: + raise ValueError("Kimi complete public selected target differs") + installation = targets[0]["output"].get("result") + if (type(installation) is not dict or installation.get("mutated") is not True + or not kimi_call_text(installation.get("installation_id"), 64) or not installation["installation_id"]): + raise ValueError("Kimi complete public installation result differs") + installation_id = installation["installation_id"] + physical_id = "uap-kimi-native-a-" + hashlib.sha256(installation_id.encode()).hexdigest()[:12] + binding = kimi_call_binding(result.get("binding")) + if (binding.get("client_id") != "kimi" or binding.get("scope") != "project" or binding.get("activation") != "prepared" or binding.get("authentication") != "not_checked" or binding.get("verification") != "installation_verified" or binding.get("materialization") != "materialized"): raise ValueError("Kimi complete selected project binding absent") active, data = binding.get("target_locator"), result.get("data_locator") + if (type(active) is not str or binding.get("physical_artifact_id") != physical_id + or binding.get("client_binding_id") != "client_" + hashlib.sha256("\x00".join((installation_id, "kimi", "project", active)).encode()).hexdigest()[:24] + or binding.get("data_receipt_id") != "data_" + hashlib.sha256("\x00".join((installation_id, physical_id, "project")).encode()).hexdigest()[:24]): + raise ValueError("Kimi complete public installation/binding/data identity differs") facts = kimi_call_object(result.get("probe_facts"), ("nonce", "revision", "cwd", "argv", "plugin_root", "plugin_data", "literal", "once", "marker", "pid", "session")) expected_facts = {"nonce": nonce + "-a", "revision": "a-one", "cwd": active, "argv": ["Kimi owned a", str(active) + "/argument"], "plugin_root": active, "plugin_data": data, "literal": "", "once": "", "marker": "retained-" + nonce, @@ -880,23 +1009,9 @@ def kimi_call_complete(bodies, result): or event["facts"] != (facts if method == "tools/call" else None)): raise ValueError("Kimi complete fresh probe/result join differs") previous = kimi_call_instant(event["timestamp"], previous, ended) - requests = [kimi_call_json(bodies[f"provider-request-{number}.json"]) for number in (1, 2)] - declaration = [{"type": "function", "function": {"name": "mcp__uap-kimi-native-a__inspect_runtime", "description": "Inspect only disposable fixture runtime facts.", "parameters": {"type": "object", "properties": {key: {"type": "string"} for key in ("nonce", "operation", "marker")}, "required": ["nonce"], "additionalProperties": False}}}] - for request in requests: - kimi_call_object(request, ("model", "messages", "tools", "stream", "stream_options"), ("prompt_cache_key", "max_tokens", "max_completion_tokens", "temperature", "top_p", "parallel_tool_calls")) - if (request.get("tools") != declaration or request.get("stream_options") != {"include_usage": True} or type(request) is not dict or request.get("model") != "uap-kimi-native-model" or request.get("stream") is not True - or request.get("prompt_cache_key") != session or type(request.get("messages")) is not list): - raise ValueError("Kimi complete provider request identity differs") - if request["stream_options"].get("include_usage") is not True or request["tools"][0]["function"]["parameters"].get("additionalProperties") is not False: - raise ValueError("Kimi complete provider declaration boolean differs") - for key in ("max_tokens", "max_completion_tokens"): - if key in request and (type(request[key]) is not int or not 1 <= request[key] <= 1 << 20): - raise ValueError("Kimi complete provider token bound differs") - for key, high in (("temperature", 2), ("top_p", 1)): - if key in request and (type(request[key]) not in (int, float) or not 0 <= request[key] <= high): - raise ValueError("Kimi complete provider sampling bound differs") - if "parallel_tool_calls" in request and type(request["parallel_tool_calls"]) is not bool: - raise ValueError("Kimi complete provider parallel flag differs") + requests = [kimi_call_request(bodies[f"provider-request-{number}.json"]) for number in (1, 2)] + if any(request.get("prompt_cache_key") != session for request in requests): + raise ValueError("Kimi complete provider session metadata changed") first, second = requests[0]["messages"], requests[1]["messages"] if (len(second) != len(first) + 2 or first != second[:-2] or any(row.get("role") == "tool" or "tool_calls" in row for row in first) or second[-2].get("role") != "assistant" or second[-1].get("role") != "tool" or second[-1].get("tool_call_id") != call): @@ -940,20 +1055,28 @@ def kimi_call_complete(bodies, result): raise ValueError("Kimi complete closed raw envelope differs") raw_files = {name: value.encode() for name, value in raw_files.items()} research = kimi_call_object(kimi_call_json(raw_files["research.json"]), ("schema_version", "client", "phase", "commands", "http_requests", "observations", "qualification")) - if research["client"] != "kimi" or research["phase"] != "installed-single-tool-attempt" or research["qualification"] != dict.fromkeys(shared.QUALIFICATION_CASES, False): + if (type(research["schema_version"]) is not int or research["schema_version"] != 1 + or research["client"] != "kimi" or research["phase"] != "installed-single-tool-attempt" + or type(research["qualification"]) is not dict or set(research["qualification"]) != set(shared.QUALIFICATION_CASES) + or any(value is not False for value in research["qualification"].values())): raise ValueError("Kimi complete observer capture boundary differs") observation = kimi_call_object(research["observations"], ("native_capture_complete", "native_history_grammar_frozen", "native_session_id", "native_turn_id", "provider_call_id", "approval_id", "snapshot_count", "api_request_count", "boundary")) if (type(observation) is not dict or observation.get("native_capture_complete") is not False or observation.get("native_history_grammar_frozen") is not False or observation.get("native_session_id") != session or observation.get("provider_call_id") != call - or observation.get("approval_id") != proof["approval_id"] or observation.get("native_turn_id") != 0 - or observation.get("snapshot_count") != proof["snapshot_count"] + or observation.get("approval_id") != proof["approval_id"] or type(observation.get("native_turn_id")) is not int or observation["native_turn_id"] != 0 + or type(observation.get("snapshot_count")) is not int or observation["snapshot_count"] != proof["snapshot_count"] or observation["boundary"] != "Bounded initial capture; no native history or qualification inferred from provider/probe success"): raise ValueError("Kimi complete observer owned session differs") commands = research["commands"] native_cleanup = {"forced_kill": False, "group_reaped": True, "port_closed": True, "shutdown_acknowledged": True, "sigterm_sent": False} - if (type(commands) is not list or len(commands) != 1 or commands[0].get("exit") != 0 or commands[0].get("error") is not None - or commands[0].get("cleanup") != native_cleanup or any(type(value) is not bool for value in commands[0]["cleanup"].values())): - raise ValueError("Kimi complete ordinary native closure absent") + if type(commands) is not list or len(commands) != 1: + raise ValueError("Kimi complete native command inventory differs") + command = kimi_call_object(commands[0], ("argv", "cwd", "probe", "elapsed_seconds", "error", "exit", "cleanup", + "stdout_file", "stdout_bytes", "stdout_sha256", "stderr_file", "stderr_bytes", "stderr_sha256")) + if (type(command["exit"]) is not int or command["exit"] != 0 or command["error"] is not None or command["probe"] != "kimi-single-tool" + or not kimi_call_number(command["elapsed_seconds"], 0, 60) or command["cleanup"] != native_cleanup + or any(type(value) is not bool for value in command["cleanup"].values())): + raise ValueError("Kimi complete native command schema/ordinary closure differs") used = {"research.json"} for channel in ("stdout", "stderr"): command = commands[0] @@ -965,7 +1088,8 @@ def kimi_call_complete(bodies, result): raise ValueError("Kimi complete native stream receipt differs") used.add(name) http_rows = research["http_requests"] - if type(http_rows) is not list or not 1 <= len(http_rows) <= 60 or observation.get("api_request_count") != len(http_rows): + if (type(http_rows) is not list or not 1 <= len(http_rows) <= 60 or type(observation.get("api_request_count")) is not int + or observation["api_request_count"] != len(http_rows)): raise ValueError("Kimi complete native HTTP budget differs") responses, response_bodies, labels, http_receipts = {}, {}, [], {} for row in http_rows: @@ -982,11 +1106,24 @@ def kimi_call_complete(bodies, result): response = kimi_call_object(kimi_call_json(stream), ("code", "msg", "data", "request_id"), ("details", "stack")) if type(response["code"]) is not int or response["code"] != (40101 if label == "unauthorized" else 0): raise ValueError("Kimi complete native HTTP envelope differs") + if (not kimi_call_text(response["request_id"], 256) or not response["request_id"] + or not kimi_call_text(response["msg"], 1024) or label == "unauthorized" and response["data"] is not None): + raise ValueError("Kimi complete native HTTP response identity/data differs") + if "stack" in response and response["stack"] is not None and not kimi_call_text(response["stack"], 32768): + raise ValueError("Kimi complete native negative stack differs") if label != "unauthorized" and (response["msg"] != "success" or response.get("details") is not None or response.get("stack") is not None): raise ValueError("Kimi complete native HTTP success differs") responses[label] = response["data"] if set(raw_files) != used: raise ValueError("Kimi complete unrecorded raw member") + for label in ("health", "shutdown"): + value = kimi_call_object(responses.get(label), ("ok",)) + if value["ok"] is not True: + raise ValueError("Kimi complete native health/shutdown acknowledgement differs") + meta = kimi_call_object(responses.get("meta"), ("server_version", "capabilities", "server_id", "started_at", "open_in_apps", "dangerous_bypass_auth", "backend"), + ("experimental_flags", "features", "web_title")) + if meta["server_version"] != "2.3.2" or meta["backend"] != "v2" or meta["dangerous_bypass_auth"] is not False: + raise ValueError("Kimi complete pinned native metadata differs") expected = {"facts": facts, "session": session, "call": call, "prompt_id": proof["prompt_id"], "started": started, "ended": ended, "prompt": "Call the one installed fixture tool once with the exact nonce and marker; then report completion. " + nonce + "-a", "sentinel": "UAP_KIMI_DONE_" + nonce} project = str(Path(binding["native_profile_root"]).parent) @@ -1007,27 +1144,47 @@ def kimi_call_complete(bodies, result): if bodies["uap-kimi-native-a-SKILL.md.installed"] != skill.encode(): raise ValueError("Kimi complete installed authored skill differs") workspaces = {} + for scope in ("a", "b", "c"): + trust = kimi_call_object(responses.get("trust-before-" + scope), ("trusted",)) + if trust["trusted"] is not False: + raise ValueError("Kimi complete native workspace/trust differs") + for label in ("trust-set-a", "trust-after-a", "trust-set-b", "trust-after-b", "trust-untrusted-c"): + trust = kimi_call_object(responses.get(label), ("trusted",)) + if trust["trusted"] is not (label != "trust-untrusted-c"): + raise ValueError("Kimi complete owned native trust differs") for scope in ("a", "b", "c"): workspace = kimi_call_object(responses.get("workspace-" + scope), ("id", "root", "name", "created_at", "last_opened_at", "session_count")) if (type(workspace["id"]) is not str or not re.fullmatch(r"wd_[a-z0-9._-]+_[0-9a-f]{12}", workspace["id"]) or workspace["root"] != projects[scope] or workspace["name"] != "uap-kimi-native-" + scope - or type(workspace["session_count"]) is not int or workspace["session_count"] != 0 - or responses.get("trust-before-" + scope) != {"trusted": False}): + or type(workspace["session_count"]) is not int or workspace["session_count"] != 0): raise ValueError("Kimi complete native workspace/trust differs") workspaces[scope] = workspace["id"] for key in ("created_at", "last_opened_at"): kimi_call_instant(workspace[key], started, ended) - if len(set(workspaces.values())) != 3 or responses.get("trust-untrusted-c") != {"trusted": False}: + if len(set(workspaces.values())) != 3: raise ValueError("Kimi complete distinct native workspace/trust differs") - for scope in ("a", "b"): - if responses.get("trust-set-" + scope) != {"trusted": True} or responses.get("trust-after-" + scope) != {"trusted": True}: - raise ValueError("Kimi complete owned native trust differs") projected = {key: value for key, value in server.items() if key != "env"} projected["envKeys"] = sorted(server["env"]) expected_catalog = [{"name": "uap-kimi-native-a", "config": projected, "source": "global", "origin": project + "/.kimi-code/mcp.json", "mutable": False}] - if responses.get("catalog-a") != expected_catalog or any(responses.get("catalog-" + scope) != [] for scope in ("b", "c", "user")): + if (responses.get("catalog-a") != expected_catalog or responses["catalog-a"][0]["mutable"] is not False + or any(responses.get("catalog-" + scope) != [] for scope in ("b", "c", "user"))): raise ValueError("Kimi complete receipt-selected native catalog differs") - prompt = kimi_call_object(responses.get("prompt"), ("prompt_id", "user_message_id", "status", "content", "created_at"), ("metadata",)) + skills = kimi_call_object(responses.get("skills-a"), ("skills",))["skills"] + if type(skills) is not list or len(skills) > 512: + raise ValueError("Kimi complete native skill inventory differs") + selected = [] + for skill in skills: + kimi_call_object(skill, ("name", "description", "path", "source"), ("type", "disable_model_invocation", "scopes")) + if not kimi_call_text(skill["name"], 4096): + raise ValueError("Kimi complete native skill name differs") + if skill["name"].startswith("uap-kimi-native-"): + selected.append(skill) + if (len(selected) != 1 or selected[0]["name"] != "uap-kimi-native-a" or selected[0]["description"] != "Kimi installed a-one" + or selected[0]["path"] != project + "/.kimi-code/skills/uap-kimi-native-a/SKILL.md" or selected[0]["source"] != "project"): + raise ValueError("Kimi complete selected native skill identity differs") + # Pinned prompts.ts projects only clientMetadata[0]. The fixed request has + # no metadata, so even an origin wrapper/null would be unrequested here. + prompt = kimi_call_object(responses.get("prompt"), ("prompt_id", "user_message_id", "status", "content", "created_at")) if (prompt["prompt_id"] != proof["prompt_id"] or prompt["user_message_id"] != proof["prompt_id"] or prompt["status"] not in ("running", "queued", "blocked") or prompt["content"] != [{"type": "text", "text": expected["prompt"]}]): raise ValueError("Kimi complete accepted native prompt differs") @@ -1081,7 +1238,8 @@ def kimi_call_complete(bodies, result): for index in range(proof["snapshot_count"]): snapshot = kimi_call_object(responses.get("snapshot-" + str(index)), ("as_of_seq", "epoch", "session", "messages", "in_flight_turn", "pending_approvals", "pending_questions", "subagents")) state = snapshot["session"] - if (type(snapshot["as_of_seq"]) is not int or snapshot["as_of_seq"] < previous_seq or type(snapshot["epoch"]) is not str + if (type(snapshot["as_of_seq"]) is not int or not 0 <= snapshot["as_of_seq"] <= 1 << 40 + or snapshot["as_of_seq"] < previous_seq or type(snapshot["epoch"]) is not str or not 1 <= len(snapshot["epoch"]) <= 256 or epoch is not None and snapshot["epoch"] != epoch or snapshot["pending_approvals"] != [] or snapshot["pending_questions"] != [] or snapshot["subagents"] != [] or type(state) is not dict or state.get("id") != session or state.get("last_turn_reason") in ("failed", "cancelled") diff --git a/scripts/test_project_native_runtime.py b/scripts/test_project_native_runtime.py index a9b93268d..af4f8861a 100644 --- a/scripts/test_project_native_runtime.py +++ b/scripts/test_project_native_runtime.py @@ -465,7 +465,7 @@ def kimi_synthetic_completed_capture(self): encode = lambda value: json.dumps(value, separators=(",", ":")).encode() digest = lambda body: hashlib.sha256(body).hexdigest() nonce, root = "c" * 32, "/inert/kimi-python-contract" - project, physical = root + "/project", "uap-kimi-native-a-" + "d" * 12 + project, physical = root + "/project", "uap-kimi-native-a-20242674556d" profile = project + "/.kimi-code" active, data = project + "/.uap/managed/clients/kimi/" + physical, project + "/.uap/plugin-data/" + physical client, session = "/inert/never-executed/kimi", "session_11111111-2222-4333-8444-555555555555" @@ -496,6 +496,7 @@ def kimi_synthetic_completed_capture(self): command = json.loads(bodies[label + ".command.json"]) command.update({"argv": argv, "cwd": project if label == "add-a" else projects_for_commands["user"]}) bodies[label + ".command.json"] = encode(command) + bodies["add-a.stdout.log"] = encode({"schema_version": 1, "command": "add", "result": "success", "data": {"targets": [{"target": "kimi", "output": {"result": {"installation_id": "inert-python-installation", "mutated": True}}}]}}) bodies["version.stdout.log"] = b"2.3.2\n" bodies["probe-events.jsonl"] = b"".join(encode({"method": method, "pid": 17, "session": facts["session"], "nonce": nonce + "-a", "timestamp": f"2026-01-02T03:04:08.00{index}Z", "facts": facts if method == "tools/call" else None}) + b"\n" @@ -528,7 +529,7 @@ def response(label, method, path, value, auth=True, status=200, code=0): receipts.append({"authenticated": auth, "error": None, "label": label, "method": method, "path": path, "response_bytes": len(body), "response_file": name, "response_sha256": digest(body), "status": status}) response("health", "GET", "/api/v1/healthz", {"ok": True}, False) response("unauthorized", "GET", "/api/v1/meta", None, False, 401, 40101) - response("meta", "GET", "/api/v1/meta", {"server_version": "2.3.2", "backend": "v2", "dangerous_bypass_auth": False}) + response("meta", "GET", "/api/v1/meta", {"server_version": "2.3.2", "backend": "v2", "dangerous_bypass_auth": False, "capabilities": {key: True for key in ("websocket", "file_upload", "fs_query", "mcp", "tasks", "terminal")}, "server_id": "inert-server", "started_at": stamp, "open_in_apps": []}) projects = {"a": project, "b": root + "/project-b", "c": root + "/project-c", "user": root + "/outside"} workspaces = {"a": "wd_project_111111111111", "b": "wd_project-b_222222222222", "c": "wd_project-c_333333333333"} for scope in ("a", "b", "c"): @@ -542,7 +543,7 @@ def response(label, method, path, value, auth=True, status=200, code=0): for scope in ("a", "b", "c", "user"): catalog = [{"name": "uap-kimi-native-a", "config": projected, "source": "global", "origin": profile + "/mcp.json", "mutable": False}] if scope == "a" else [] response("catalog-" + scope, "GET", "/api/v2/mcp/servers?" + runtime.urlencode({"cwd": projects[scope]}), catalog) - response("skills-a", "GET", "/api/v1/workspaces/" + workspaces["a"] + "/skills", {"skills": []}) + response("skills-a", "GET", "/api/v1/workspaces/" + workspaces["a"] + "/skills", {"skills": [{"name": "uap-kimi-native-a", "description": "Kimi installed a-one", "path": profile + "/skills/uap-kimi-native-a/SKILL.md", "source": "project"}]}) response("create-session", "POST", "/api/v1/sessions", {**{key: value for key, value in state.items() if key != "last_turn_reason"}, "agent_config": {"model": ""}, "title": ""}) base = "/api/v1/sessions/" + session response("prompt", "POST", base + "/prompts", {"prompt_id": prompt_id, "user_message_id": prompt_id, "status": "running", "content": [{"type": "text", "text": prompt}], "created_at": stamp}) @@ -564,12 +565,43 @@ def response(label, method, path, value, auth=True, status=200, code=0): result = {**self.cli_result("qoder"), "client": "kimi", "version": "2.3.2", "phase": "installed-single-tool-attempt", "provider_kind": "scripted-openai-chat-completions", "elapsed_ms": 8000, "runtime_attempt_complete": True, "test_failed": False, "native_capture_complete": True, "native_history_grammar_frozen": True, "provider_returned": True, "provider_call_id": call, "native_session_id": session, "native_turn_id": 0, "probe_facts": facts, "native_history": proof, - "binding": {"client_id": "kimi", "scope": "project", "activation": "prepared", "authentication": "not_checked", "verification": "installation_verified", "materialization": "materialized", "target_locator": active, "native_profile_root": profile, "physical_artifact_id": physical}, + "binding": {"client_binding_id": "client_6e8a61a9620b87be9881f1ef", "data_receipt_id": "data_9797d862dba4e5336eb02e08", "client_id": "kimi", "scope": "project", "activation": "prepared", "authentication": "not_checked", "verification": "installation_verified", "materialization": "materialized", "target_locator": active, "native_profile_root": profile, "physical_artifact_id": physical, "policy": "allowed", "updated_at": stamp, + "package_revision": {"version": "1.0.0", "tree_digest": "sha256:" + "a" * 64, "manifest_digest": "sha256:" + "b" * 64}, "affected_surfaces": ["kimi"], + "native_objects": [{"object_id": "package:kimi:" + physical, "kind": "managed_package_directory", "path": active, "protection_class": "managed"}], + "receipts": [{"operation_id": "inert-operation", "sequence": 1, "mutation_type": "directory_swap", "client_binding_id": "client_6e8a61a9620b87be9881f1ef", "phase": "committed"}]}, "data_locator": data, "provider_cleanup": {"shutdown_complete": True, "forced_close": False, "error": False, "requests": 2, "issued": True, "returned": True}, "qualification": {key: key in ("mcp-initialize", "mcp-tools-list", "mcp-tools-call") for key in runtime.shared.QUALIFICATION_CASES}} bodies["runtime-attempt.json"] = encode(result) return bodies + def kimi_mutate_raw_coherently(self, bodies, mutate): + envelope = json.loads(bodies["python-api.json"]) + raw = envelope["files"] + mutate(raw) + research = json.loads(raw["research.json"]) + for receipt in research["http_requests"]: + name = receipt["response_file"] + if name in raw: + body = raw[name].encode() + receipt["response_bytes"] = len(body) + receipt["response_sha256"] = hashlib.sha256(body).hexdigest() + raw["research.json"] = json.dumps(research) + bodies["python-api.json"] = bodies["native-attempt.stdout.log"] = json.dumps(envelope).encode() + result = json.loads(bodies["runtime-attempt.json"]) + proof = result["native_history"] + proof["history_sha256"] = hashlib.sha256(raw["kimi-single-tool-history.http.json"].encode()).hexdigest() + name = "kimi-single-tool-snapshot-" + str(proof["snapshot_count"] - 1) + ".http.json" + proof["terminal_snapshot_sha256"] = hashlib.sha256(raw[name].encode()).hexdigest() + bodies["runtime-attempt.json"] = json.dumps(result).encode() + + def kimi_mutate_http_coherently(self, bodies, label, mutate): + def update(raw): + name = "kimi-single-tool-" + label + ".http.json" + response = json.loads(raw[name]) + mutate(response) + raw[name] = json.dumps(response) + self.kimi_mutate_raw_coherently(bodies, update) + def test_kimi_synthetic_complete_reader_recomputes_raw_proof(self): bodies = self.kimi_synthetic_completed_capture() result = runtime.kimi_call_members(bodies, self.source) @@ -580,6 +612,172 @@ def test_kimi_synthetic_complete_reader_recomputes_raw_proof(self): with self.subTest(missing=name), self.assertRaises(ValueError): runtime.kimi_call_members({key: value for key, value in bodies.items() if key != name}, self.source) + def test_kimi_public_receipt_and_derived_ids_are_independent_authority(self): + cases = ("empty", "failed", "schema", "target", "missing-result", "not-mutated", "installation", "physical", "client", "data") + for kind in cases: + with self.subTest(kind=kind), self.assertRaisesRegex(ValueError, "public|JSON byte bound"): + bodies = self.kimi_synthetic_completed_capture() + result = json.loads(bodies["runtime-attempt.json"]) + public = json.loads(bodies["add-a.stdout.log"]) + target = public["data"]["targets"][0] + if kind == "empty": bodies["add-a.stdout.log"] = b"" + elif kind == "failed": public["result"] = "failure" + elif kind == "schema": public["schema_version"] = True + elif kind == "target": target["target"] = "foreign" + elif kind == "missing-result": del target["output"]["result"] + elif kind == "not-mutated": target["output"]["result"]["mutated"] = False + elif kind == "installation": target["output"]["result"]["installation_id"] = "foreign-installation" + elif kind == "physical": result["binding"]["physical_artifact_id"] = "uap-kimi-native-a-" + "f" * 12 + elif kind == "client": result["binding"]["client_binding_id"] = "client_" + "f" * 24 + elif kind == "data": result["binding"]["data_receipt_id"] = "data_" + "f" * 24 + if kind != "empty": bodies["add-a.stdout.log"] = json.dumps(public).encode() + bodies["runtime-attempt.json"] = json.dumps(result).encode() + runtime.kimi_call_members(bodies, self.source) + + def test_kimi_native_mandatory_data_semantics_survive_recomputed_hashes(self): + cases = (("health", "ok", "health/shutdown"), ("shutdown", "ok", "health/shutdown"), + ("meta", "version", "pinned native metadata"), ("meta", "backend", "pinned native metadata"), + ("meta", "auth", "pinned native metadata"), ("meta", "missing", "raw object"), + ("meta", "unknown", "raw object"), ("skills-a", "empty", "selected native skill"), + ("skills-a", "foreign", "selected native skill"), ("skills-a", "path", "selected native skill"), + ("skills-a", "source", "selected native skill"), ("skills-a", "duplicate", "selected native skill")) + for label, kind, reason in cases: + with self.subTest(label=label, kind=kind), self.assertRaisesRegex(ValueError, reason): + bodies = self.kimi_synthetic_completed_capture() + def mutate(response): + data = response["data"] + if kind == "ok": data["ok"] = False + elif kind == "version": data["server_version"] = "future" + elif kind == "backend": data["backend"] = "foreign" + elif kind == "auth": data["dangerous_bypass_auth"] = True + elif kind == "missing": del data["server_id"] + elif kind == "unknown": data["unknown"] = True + elif kind == "empty": data["skills"] = [] + elif kind == "foreign": data["skills"][0]["name"] = "uap-kimi-native-foreign" + elif kind == "path": data["skills"][0]["path"] = "/foreign/SKILL.md" + elif kind == "source": data["skills"][0]["source"] = "extra" + elif kind == "duplicate": data["skills"].append(data["skills"][0]) + self.kimi_mutate_http_coherently(bodies, label, mutate) + runtime.kimi_call_members(bodies, self.source) + + def test_kimi_native_command_and_http_schemas_are_closed_typed_and_bounded(self): + for kind in ("research-version", "research-bool", "command-extra", "command-missing", "probe", "elapsed-bool", "elapsed-high", "elapsed-negative"): + with self.subTest(kind=kind), self.assertRaisesRegex(ValueError, "capture boundary|raw object|command schema"): + bodies = self.kimi_synthetic_completed_capture() + def mutate(raw): + research = json.loads(raw["research.json"]) + command = research["commands"][0] + if kind == "research-version": research["schema_version"] = 2 + elif kind == "research-bool": research["schema_version"] = True + elif kind == "command-extra": command["unknown"] = True + elif kind == "command-missing": del command["elapsed_seconds"] + elif kind == "probe": command["probe"] = "foreign" + elif kind == "elapsed-bool": command["elapsed_seconds"] = True + elif kind == "elapsed-high": command["elapsed_seconds"] = 60.001 + elif kind == "elapsed-negative": command["elapsed_seconds"] = -0.001 + raw["research.json"] = json.dumps(research) + self.kimi_mutate_raw_coherently(bodies, mutate) + runtime.kimi_call_members(bodies, self.source) + for label, kind in (("health", "request-empty"), ("health", "request-type"), ("health", "unknown"), + ("unauthorized", "data"), ("unauthorized", "msg"), ("unauthorized", "stack")): + with self.subTest(label=label, kind=kind), self.assertRaisesRegex(ValueError, "response identity/data|raw object|negative stack"): + bodies = self.kimi_synthetic_completed_capture() + def mutate(response): + if kind == "request-empty": response["request_id"] = "" + elif kind == "request-type": response["request_id"] = True + elif kind == "unknown": response["unknown"] = True + elif kind == "data": response["data"] = {"unobserved": True} + elif kind == "msg": response["msg"] = {"unobserved": True} + elif kind == "stack": response["stack"] = ["unobserved"] + self.kimi_mutate_http_coherently(bodies, label, mutate) + runtime.kimi_call_members(bodies, self.source) + + def test_kimi_provider_nested_grammar_is_closed_before_prefix_comparison(self): + kinds = ("message-extra", "message-missing", "role", "content-null", "content-empty", "content-count", "content-part-extra", + "content-nul", "message-count", "call-extra", "function-extra", "function-missing", "tool-result-id") + for kind in kinds: + with self.subTest(kind=kind), self.assertRaisesRegex(ValueError, "raw object|provider message|provider null|provider content|provider result"): + bodies = self.kimi_synthetic_completed_capture() + first, second = (json.loads(bodies[f"provider-request-{number}.json"]) for number in (1, 2)) + if kind.startswith("call-") or kind.startswith("function-"): + call = second["messages"][-2]["tool_calls"][0] + if kind == "call-extra": call["unknown"] = True + elif kind == "function-extra": call["function"]["unknown"] = True + elif kind == "function-missing": del call["function"]["arguments"] + elif kind == "tool-result-id": del second["messages"][-1]["tool_call_id"] + else: + if kind == "message-extra": first["messages"][0]["unknown"] = True + elif kind == "message-missing": del first["messages"][0]["content"] + elif kind == "role": first["messages"][0]["role"] = "foreign" + elif kind == "content-null": first["messages"][0]["content"] = None + elif kind == "content-empty": first["messages"][0]["content"] = [] + elif kind == "content-count": first["messages"][0]["content"] = [{"type": "text", "text": "inert"}] * 129 + elif kind == "content-part-extra": first["messages"][0]["content"] = [{"type": "text", "text": "inert", "unknown": True}] + elif kind == "content-nul": first["messages"][0]["content"] = "inert\x00" + elif kind == "message-count": first["messages"] *= 257 + # Preserve the same malformed prefix in request2. Rejection + # must come from closed grammar, not an easy unequal prefix. + second["messages"] = first["messages"] + second["messages"][-2:] + bodies["provider-request-1.json"], bodies["provider-request-2.json"] = json.dumps(first).encode(), json.dumps(second).encode() + runtime.kimi_call_members(bodies, self.source) + + def test_kimi_prompt_identity_and_absent_response_metadata_are_source_bound(self): + bodies = self.kimi_synthetic_completed_capture() + prompt = json.loads(bodies["runtime-attempt.json"])["native_history"]["prompt_id"] + self.kimi_mutate_http_coherently(bodies, "snapshot-0", lambda response: response["data"]["session"].update(current_prompt_id=prompt)) + self.assertTrue(runtime.kimi_call_members(bodies, self.source)["runtime_attempt_complete"]) + for label, value in (("snapshot-0", "foreign"), ("snapshot-0", None), ("create-session", prompt), ("create-session", None)): + with self.subTest(label=label, value=value), self.assertRaisesRegex(ValueError, "current prompt"): + bodies = self.kimi_synthetic_completed_capture() + self.kimi_mutate_http_coherently(bodies, label, lambda response: response["data"].update(current_prompt_id=value) if label == "create-session" else response["data"]["session"].update(current_prompt_id=value)) + runtime.kimi_call_members(bodies, self.source) + for metadata in (None, {}, {"origin": {"kind": "user"}}, {"origin": {"kind": "injection"}}): + with self.subTest(metadata=metadata), self.assertRaisesRegex(ValueError, "raw object"): + bodies = self.kimi_synthetic_completed_capture() + self.kimi_mutate_http_coherently(bodies, "prompt", lambda response: response["data"].update(metadata=metadata)) + runtime.kimi_call_members(bodies, self.source) + + def test_kimi_success_bounds_binding_and_boolean_types_are_semantic(self): + for kind in ("elapsed-high", "sequence-high", "sequence-negative", "profile-leaf", "profile-relative", "profile-parent", "profile-double-root", + "binding-unknown", "binding-authority", "binding-missing", "binding-scalar", "revision-extra", "revision-type", "surfaces-type", + "object-extra", "object-type", "object-modified", "receipt-extra", "receipt-type", "receipt-sequence"): + reason = "native turn/time|snapshot authority|canonical native profile|raw object|binding" + with self.subTest(kind=kind), self.assertRaisesRegex(ValueError, reason): + bodies = self.kimi_synthetic_completed_capture() + if kind.startswith("sequence-"): + self.kimi_mutate_http_coherently(bodies, "snapshot-0", lambda response: response["data"].update(as_of_seq=(1 << 40) + 1 if kind == "sequence-high" else -1)) + else: + result = json.loads(bodies["runtime-attempt.json"]) + binding = result["binding"] + if kind == "elapsed-high": result["elapsed_ms"] = 180001 + elif kind == "profile-leaf": binding["native_profile_root"] = "/inert/kimi-python-contract/project/.foreign" + elif kind == "profile-relative": binding["native_profile_root"] = "relative/.kimi-code" + elif kind == "profile-parent": binding["native_profile_root"] = "/inert/kimi-python-contract/project/../project/.kimi-code" + elif kind == "profile-double-root": binding["native_profile_root"] = "/" + binding["native_profile_root"] + elif kind == "binding-unknown": binding["unknown"] = True + elif kind == "binding-authority": binding["profile_authority"] = {} + elif kind == "binding-missing": del binding["policy"] + elif kind == "binding-scalar": binding["updated_at"] = None + elif kind == "revision-extra": binding["package_revision"]["unknown"] = True + elif kind == "revision-type": binding["package_revision"]["version"] = 1 + elif kind == "surfaces-type": binding["affected_surfaces"] = [False] + elif kind == "object-extra": binding["native_objects"][0]["unknown"] = True + elif kind == "object-type": binding["native_objects"][0]["path"] = None + elif kind == "object-modified": binding["native_objects"][0]["user_modified"] = 0 + elif kind == "receipt-extra": binding["receipts"][0]["unknown"] = True + elif kind == "receipt-type": binding["receipts"][0]["phase"] = False + elif kind == "receipt-sequence": binding["receipts"][0]["sequence"] = True + bodies["runtime-attempt.json"] = json.dumps(result).encode() + runtime.kimi_call_members(bodies, self.source) + labels = ("trust-before-a", "trust-before-b", "trust-before-c", "trust-set-a", "trust-after-a", "trust-set-b", "trust-after-b", "trust-untrusted-c", "catalog-a") + for label in labels: + with self.subTest(label=label), self.assertRaisesRegex(ValueError, "trust differs|catalog differs"): + bodies = self.kimi_synthetic_completed_capture() + def mutate(response): + if label == "catalog-a": response["data"][0]["mutable"] = 0 + else: response["data"]["trusted"] = int(response["data"]["trusted"]) + self.kimi_mutate_http_coherently(bodies, label, mutate) + runtime.kimi_call_members(bodies, self.source) def test_kimi_synthetic_success_cannot_cover_failed_or_foreign_raw(self): for kind in ("terminal-failed", "empty-sentinel", "wrong-call", "extra-message", "history-more", "forced", "changed-data", "wrong-marker", "wrong-proof", "foreign-source", "lifecycle-flag", "wrong-cache", "unknown-file"): with self.subTest(kind=kind), self.assertRaises(ValueError): From 569d252624c4088ee20763f7adf732b228f4cc96 Mon Sep 17 00:00:00 2001 From: 777genius Date: Wed, 28 Oct 2026 00:50:50 +0100 Subject: [PATCH 40/42] test(kimi): enforce exact byte and numeric completion bounds --- ...ECT_NATIVE_FINITE_COMPLETION_VALIDATION.md | 15 ++++ ...mi_project_native_history_contract_test.go | 17 ++++ ...lugins_kimi_project_native_history_test.go | 2 +- scripts/project_native_runtime.py | 30 +++++-- scripts/test_project_native_runtime.py | 87 ++++++++++++++++++- 5 files changed, 139 insertions(+), 12 deletions(-) diff --git a/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md b/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md index 6e000fbc1..1e1080cbd 100644 --- a/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md +++ b/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md @@ -161,3 +161,18 @@ synthetic controls mutate each of these predicates, including nested binding types and authority keys, while updating affected HTTP receipts and terminal proof digests. Original measured9e failures retain all false single-call flags; none of these source corrections establishes a fresh native observation. + +The corrected `9a642` candidate received a further pair of NO-GOs for success-reader +boundary parity. Python's finite JSON walk now checks every number for finite +float64 conversion and counts keys as UTF8 bytes. Native message IDs require +nonempty, NUL-free text within 256 bytes; workspace IDs and epochs retain Go's +256 byte limit. Native proof message count and probe PID require actual integers, +including refusal of Python Boolean/numeric equality substitutes. Successful +outer command records retain the exact closed shape and require 1..32 nonempty, +NUL-free arguments and a nonempty cwd, each at most 4096 UTF8 bytes. Both readers +refuse NUL in owned inventory paths; the existing 4096 byte key bound remains. +Coherent synthetic controls exercise these findings, including valid byte-limit +cases and a fully joined PID 1 record before a Boolean proof mutation. Historical +failed/component reader bodies, live collectors, commands, protocol, budgets and +authorities remain unchanged. These controls still require exact-source executed +gates and fresh independent CODE reviews before publication. diff --git a/repotests/agentplugins_kimi_project_native_history_contract_test.go b/repotests/agentplugins_kimi_project_native_history_contract_test.go index b7fd3e917..113292f00 100644 --- a/repotests/agentplugins_kimi_project_native_history_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_history_contract_test.go @@ -610,6 +610,23 @@ func TestKimiSingleToolBindingAuthorityIsClosedAndTyped(t *testing.T) { } } +func TestKimiSingleToolOwnedInventoryRejectsNULAndUTF8Overflow(t *testing.T) { + for _, name := range []string{"owned\x00name", strings.Repeat("é", 2049)} { + files, _ := kimiSyntheticCompletedHistory(t) + data := kimiHistoryDecode(t, files["native-data-proof.json"]) + for _, key := range []string{"before", "after_without_marker"} { + data[key].(map[string]any)[name] = "0600:" + strings.Repeat("a", 64) + } + files["native-data-proof.json"] = kimiHistoryEncode(t, data) + result := kimiHistoryDecode(t, files["runtime-attempt.json"]) + result["native_history"].(map[string]any)["data_proof_sha256"] = kimiProjectProofSHA(files["native-data-proof.json"]) + files["runtime-attempt.json"] = kimiHistoryEncode(t, result) + if err := kimiAttemptAdmit(files); err == nil { + t.Fatal("invalid owned path admitted with coherent data proof", name) + } + } +} + func TestKimiSingleToolLateFlagsDataAndProviderClosure(t *testing.T) { files, expected := kimiSyntheticCompletedHistory(t) for _, kind := range []string{"marker", "marker-sha", "inventory", "unknown-inventory", "missing-owner", "oversized", "extra-field"} { diff --git a/repotests/agentplugins_kimi_project_native_history_test.go b/repotests/agentplugins_kimi_project_native_history_test.go index 8496f108d..05f5c4477 100644 --- a/repotests/agentplugins_kimi_project_native_history_test.go +++ b/repotests/agentplugins_kimi_project_native_history_test.go @@ -1047,7 +1047,7 @@ func kimiAttemptDataProof(body []byte, facts map[string]any) error { } for name, raw := range inventory { text, ok := raw.(string) - if !ok || !kimiAttemptInventoryValue.MatchString(text) || len(name) > 4096 || strings.Contains(name, "\\") || filepath.IsAbs(name) || filepath.ToSlash(filepath.Clean(name)) != name || name == ".." || strings.HasPrefix(name, "../") || len(strings.Split(name, "/")) > 32 { + if !ok || !kimiAttemptInventoryValue.MatchString(text) || len(name) > 4096 || strings.ContainsRune(name, 0) || strings.Contains(name, "\\") || filepath.IsAbs(name) || filepath.ToSlash(filepath.Clean(name)) != name || name == ".." || strings.HasPrefix(name, "../") || len(strings.Split(name, "/")) > 32 { return fmt.Errorf("Kimi owned data inventory entry differs") } if name == "." && !strings.HasSuffix(text, ":directory") { diff --git a/scripts/project_native_runtime.py b/scripts/project_native_runtime.py index b063ed5a5..c83e24eac 100644 --- a/scripts/project_native_runtime.py +++ b/scripts/project_native_runtime.py @@ -691,11 +691,18 @@ def visit(item, depth): if depth > 32 or nodes > 16384: raise ValueError("Kimi complete raw JSON structure bound differs") if type(item) is dict: - if any(len(key) > 4096 for key in item): + if any(len(key.encode("utf-8")) > 4096 for key in item): raise ValueError("Kimi complete raw JSON key bound differs") for child in item.values(): visit(child, depth + 1) elif type(item) is list: for child in item: visit(child, depth + 1) + elif type(item) in (int, float): + try: + finite = math.isfinite(float(item)) + except OverflowError: + finite = False + if not finite: + raise ValueError("Kimi complete raw JSON finite float64 number differs") visit(value, 0) return value @@ -724,7 +731,7 @@ def kimi_call_messages(page, expected): seen, previous = set(), expected["started"] for row in rows: kimi_call_object(row, ("id", "session_id", "role", "content", "created_at"), ("metadata",)) - if (type(row["id"]) is not str or not 1 <= len(row["id"]) <= 256 or row["id"] in seen + if (not kimi_call_text(row["id"], 256) or not row["id"] or row["id"] in seen or row["session_id"] != expected["session"]): raise ValueError("Kimi complete native message identity differs") seen.add(row["id"]) @@ -930,6 +937,7 @@ def kimi_call_request(body): def kimi_call_complete(bodies, result): """Pure success admission recomputes raw joins; proof/flags are not authority.""" + result = kimi_call_json(bodies["runtime-attempt.json"]) required = {"runtime-attempt.json", "probe-events.jsonl", "python-api.json", "native-data-proof.json", ".kimi-code-mcp.json.installed", "uap-kimi-native-a-SKILL.md.installed", "provider-request-1.json", "provider-request-2.json"} @@ -939,9 +947,14 @@ def kimi_call_complete(bodies, result): or bodies["native-attempt.stdout.log"] != bodies["python-api.json"] or bodies["native-attempt.stderr.log"]): raise ValueError("Kimi complete closed successful capture required") for label in KIMI_CALL_COMMANDS: - command = kimi_call_json(bodies[label + ".command.json"]) - if command["exit"] != 0 or command["error"] is not False or command["deadline"] is not False: + command = kimi_call_object(kimi_call_json(bodies[label + ".command.json"]), ("argv", "cwd", "exit", "error", "deadline", "cleanup")) + if (type(command["exit"]) is not int or command["exit"] != 0 or command["error"] is not False or command["deadline"] is not False + or command["cleanup"] != "ordinary full-descendant supervision; forced termination fails"): raise ValueError("Kimi complete public/native command failed") + if (type(command["argv"]) is not list or not 1 <= len(command["argv"]) <= 32 + or any(not kimi_call_text(arg, 4096) or not arg for arg in command["argv"]) + or not kimi_call_text(command["cwd"], 4096) or not command["cwd"]): + raise ValueError("Kimi complete public/native command text/arity differs") cleanup = kimi_call_object(result.get("provider_cleanup"), ("shutdown_complete", "forced_close", "error", "requests", "issued", "returned")) if (cleanup["shutdown_complete"] is not True or cleanup["forced_close"] is not False or cleanup["error"] is not False or type(cleanup["requests"]) is not int or cleanup["requests"] != 2 @@ -950,6 +963,9 @@ def kimi_call_complete(bodies, result): proof = kimi_call_object(result.get("native_history"), ("native_session_id", "native_turn_id", "provider_call_id", "approval_id", "prompt_id", "terminal_reason", "message_count", "snapshot_count", "history_sha256", "terminal_snapshot_sha256", "probe_pid", "probe_session", "started_at", "ended_at", "data_proof_sha256")) + if (type(proof["message_count"]) is not int or proof["message_count"] not in (4, 5) + or type(proof["probe_pid"]) is not int or not 1 <= proof["probe_pid"] <= 1 << 30): + raise ValueError("Kimi complete native proof integer types/bounds differ") started, ended = kimi_call_instant(proof["started_at"]), kimi_call_instant(proof["ended_at"]) if (not 0 <= result["elapsed_ms"] <= 180000 or not 0 <= (ended - started).total_seconds() <= 180 or (ended - started).total_seconds() * 1000 > result["elapsed_ms"] @@ -1039,7 +1055,7 @@ def kimi_call_complete(bodies, result): raise ValueError("Kimi complete observed marker/unrelated-data proof differs") for name, value in before.items(): path = Path(name) - if (type(name) is not str or len(name) > 4096 or path.is_absolute() or str(path) != name or ".." in path.parts + if (not kimi_call_text(name, 4096) or path.is_absolute() or str(path) != name or ".." in path.parts or "\\" in name or len(path.parts) > 32 or type(value) is not str or not re.fullmatch(r"0[0-7]{1,12}:(?:directory|[0-9a-f]{64})", value)): raise ValueError("Kimi complete owned inventory entry differs") @@ -1154,7 +1170,7 @@ def kimi_call_complete(bodies, result): raise ValueError("Kimi complete owned native trust differs") for scope in ("a", "b", "c"): workspace = kimi_call_object(responses.get("workspace-" + scope), ("id", "root", "name", "created_at", "last_opened_at", "session_count")) - if (type(workspace["id"]) is not str or not re.fullmatch(r"wd_[a-z0-9._-]+_[0-9a-f]{12}", workspace["id"]) + if (not kimi_call_text(workspace["id"], 256) or not re.fullmatch(r"wd_[a-z0-9._-]+_[0-9a-f]{12}", workspace["id"]) or workspace["root"] != projects[scope] or workspace["name"] != "uap-kimi-native-" + scope or type(workspace["session_count"]) is not int or workspace["session_count"] != 0): raise ValueError("Kimi complete native workspace/trust differs") @@ -1240,7 +1256,7 @@ def kimi_call_complete(bodies, result): state = snapshot["session"] if (type(snapshot["as_of_seq"]) is not int or not 0 <= snapshot["as_of_seq"] <= 1 << 40 or snapshot["as_of_seq"] < previous_seq or type(snapshot["epoch"]) is not str - or not 1 <= len(snapshot["epoch"]) <= 256 or epoch is not None and snapshot["epoch"] != epoch + or not 1 <= len(snapshot["epoch"].encode("utf-8")) <= 256 or epoch is not None and snapshot["epoch"] != epoch or snapshot["pending_approvals"] != [] or snapshot["pending_questions"] != [] or snapshot["subagents"] != [] or type(state) is not dict or state.get("id") != session or state.get("last_turn_reason") in ("failed", "cancelled") or state.get("metadata") != {"cwd": str(Path(binding["native_profile_root"]).parent)} diff --git a/scripts/test_project_native_runtime.py b/scripts/test_project_native_runtime.py index af4f8861a..d2994c490 100644 --- a/scripts/test_project_native_runtime.py +++ b/scripts/test_project_native_runtime.py @@ -460,7 +460,7 @@ def test_kimi_single_call_cannot_inherit_lifecycle_success_or_unknown_members(se with self.assertRaises(ValueError): runtime.kimi_call_members({**bodies, "git-c.command.json": json.dumps({**command, "cleanup": "assumed"}).encode()}, self.source) - def kimi_synthetic_completed_capture(self): + def kimi_synthetic_completed_capture(self, probe_pid=17): """Independent inert protocol records, never a measured native capture.""" encode = lambda value: json.dumps(value, separators=(",", ":")).encode() digest = lambda body: hashlib.sha256(body).hexdigest() @@ -475,7 +475,7 @@ def kimi_synthetic_completed_capture(self): sentinel, marker = "UAP_KIMI_DONE_" + nonce, "retained-" + nonce args = {"nonce": nonce + "-a", "operation": "write", "marker": marker} facts = {"nonce": nonce + "-a", "revision": "a-one", "cwd": active, "argv": ["Kimi owned a", active + "/argument"], - "plugin_root": active, "plugin_data": data, "literal": "", "once": "", "marker": marker, "pid": 17, "session": "17-1767323048000000000"} + "plugin_root": active, "plugin_data": data, "literal": "", "once": "", "marker": marker, "pid": probe_pid, "session": str(probe_pid) + "-1767323048000000000"} server = {"transport": "stdio", "command": active + "/io.github.777genius.agentplugins/managed-stdio-v1/agentplugins", "args": ["--internal-stdio-v1", active, data, active, "plugin", "--", "./bin/probe", "Kimi owned a", active + "/argument"], "env": {"PLUGIN_ROOT": active, "PLUGIN_DATA": data, "UAP_TEST_ROOT": root, "UAP_TEST_EVENTS": root + "/a-events.jsonl", "UAP_TEST_NONCE": nonce + "-a", "UAP_TEST_REVISION": "a-one"}} @@ -498,7 +498,7 @@ def kimi_synthetic_completed_capture(self): bodies[label + ".command.json"] = encode(command) bodies["add-a.stdout.log"] = encode({"schema_version": 1, "command": "add", "result": "success", "data": {"targets": [{"target": "kimi", "output": {"result": {"installation_id": "inert-python-installation", "mutated": True}}}]}}) bodies["version.stdout.log"] = b"2.3.2\n" - bodies["probe-events.jsonl"] = b"".join(encode({"method": method, "pid": 17, "session": facts["session"], "nonce": nonce + "-a", + bodies["probe-events.jsonl"] = b"".join(encode({"method": method, "pid": probe_pid, "session": facts["session"], "nonce": nonce + "-a", "timestamp": f"2026-01-02T03:04:08.00{index}Z", "facts": facts if method == "tools/call" else None}) + b"\n" for index, method in enumerate(("initialize", "tools/list", "tools/call"))) declaration = {"type": "function", "function": {"name": "mcp__uap-kimi-native-a__inspect_runtime", "description": "Inspect only disposable fixture runtime facts.", @@ -561,7 +561,7 @@ def response(label, method, path, value, auth=True, status=200, code=0): bodies["native-data-proof.json"] = encode({"schema_version": 1, "before": inventory, "after_without_marker": inventory, "marker_body": marker, "marker_bytes": len(marker), "marker_sha256": digest(marker.encode())}) proof = {"native_session_id": session, "native_turn_id": 0, "provider_call_id": call, "approval_id": approval, "prompt_id": prompt_id, "terminal_reason": "completed", "message_count": 4, "snapshot_count": 1, "history_sha256": digest(raw["kimi-single-tool-history.http.json"].encode()), "terminal_snapshot_sha256": digest(raw["kimi-single-tool-snapshot-0.http.json"].encode()), - "probe_pid": 17, "probe_session": facts["session"], "started_at": "2026-01-02T03:04:05Z", "ended_at": "2026-01-02T03:04:11Z", "data_proof_sha256": digest(bodies["native-data-proof.json"])} + "probe_pid": probe_pid, "probe_session": facts["session"], "started_at": "2026-01-02T03:04:05Z", "ended_at": "2026-01-02T03:04:11Z", "data_proof_sha256": digest(bodies["native-data-proof.json"])} result = {**self.cli_result("qoder"), "client": "kimi", "version": "2.3.2", "phase": "installed-single-tool-attempt", "provider_kind": "scripted-openai-chat-completions", "elapsed_ms": 8000, "runtime_attempt_complete": True, "test_failed": False, "native_capture_complete": True, "native_history_grammar_frozen": True, "provider_returned": True, "provider_call_id": call, "native_session_id": session, "native_turn_id": 0, "probe_facts": facts, "native_history": proof, @@ -778,6 +778,85 @@ def mutate(response): else: response["data"]["trusted"] = int(response["data"]["trusted"]) self.kimi_mutate_http_coherently(bodies, label, mutate) runtime.kimi_call_members(bodies, self.source) + def test_kimi_success_json_numbers_and_keys_match_go_byte_reader(self): + for kind in ("integer-overflow", "negative-integer-overflow", "key-utf8"): + with self.subTest(kind=kind), self.assertRaisesRegex(ValueError, "finite float64 number|JSON key bound"): + bodies = self.kimi_synthetic_completed_capture() + def mutate(response): + key = "é" * 2049 if kind == "key-utf8" else "inert-number" + response["data"]["capabilities"][key] = (-(10 ** 400) if kind == "negative-integer-overflow" else 10 ** 400) if kind != "key-utf8" else True + self.kimi_mutate_http_coherently(bodies, "meta", mutate) + runtime.kimi_call_members(bodies, self.source) + + bodies = self.kimi_synthetic_completed_capture() + self.kimi_mutate_http_coherently(bodies, "meta", lambda response: response["data"]["capabilities"].update({"é" * 2048: 10 ** 308})) + self.assertTrue(runtime.kimi_call_members(bodies, self.source)["runtime_attempt_complete"]) + + def test_kimi_native_identifier_and_epoch_limits_are_utf8_bytes(self): + cases = (("message", "msg_\x00owned", False), ("message", "é" * 200, False), ("message", "é" * 128, True), + ("epoch", "é" * 200, False), ("epoch", "é" * 128, True), + ("workspace", "wd_" + "a" * 250 + "_" + "1" * 12, False), ("workspace", "wd_" + "a" * 240 + "_" + "1" * 12, True)) + for kind, replacement, accepted in cases: + with self.subTest(kind=kind, utf8_bytes=len(replacement.encode())): + bodies = self.kimi_synthetic_completed_capture() + def mutate(raw): + if kind == "epoch": + response = json.loads(raw["kimi-single-tool-snapshot-0.http.json"]) + response["data"]["epoch"] = replacement + raw["kimi-single-tool-snapshot-0.http.json"] = json.dumps(response) + else: + old = "msg_inert_call" if kind == "message" else "wd_project_111111111111" + before, after = (json.dumps(old), json.dumps(replacement)) if kind == "message" else (old, replacement) + for name in raw: raw[name] = raw[name].replace(before, after) + self.kimi_mutate_raw_coherently(bodies, mutate) + if accepted: + self.assertTrue(runtime.kimi_call_members(bodies, self.source)["runtime_attempt_complete"]) + else: + with self.assertRaisesRegex(ValueError, "message identity|snapshot authority|workspace/trust"): + runtime.kimi_call_members(bodies, self.source) + + def test_kimi_success_outer_commands_have_go_text_and_arity_bounds(self): + for kind in ("argv-nul", "argv-ascii-long", "argv-utf8-long", "argv-empty", "argc", "cwd-nul", "cwd-ascii-long", "cwd-utf8-long", "cwd-empty"): + with self.subTest(kind=kind), self.assertRaisesRegex(ValueError, "command text/arity"): + bodies = self.kimi_synthetic_completed_capture() + command = json.loads(bodies["git-a.command.json"]) + if kind == "argc": command["argv"] += ["inert"] * 28 + else: + field = "cwd" if kind.startswith("cwd") else "argv" + value = "/inert/\x00git" if kind.endswith("nul") else "/" + "a" * 4096 if kind.endswith("ascii-long") else "/" + "é" * 2048 if kind.endswith("utf8-long") else "" + if field == "cwd": command[field] = value + else: command[field][0] = value + bodies["git-a.command.json"] = json.dumps(command).encode() + runtime.kimi_call_members(bodies, self.source) + bodies = self.kimi_synthetic_completed_capture() + command = json.loads(bodies["git-a.command.json"]) + command["argv"][0] = "/" + "a" * 4095 + bodies["git-a.command.json"] = json.dumps(command).encode() + self.assertTrue(runtime.kimi_call_members(bodies, self.source)["runtime_attempt_complete"]) + + def test_kimi_native_proof_integers_refuse_float_and_boolean_equality(self): + self.assertTrue(runtime.kimi_call_members(self.kimi_synthetic_completed_capture(probe_pid=1), self.source)["runtime_attempt_complete"]) + for field, value, pid in (("message_count", 4.0, 17), ("message_count", True, 17), ("message_count", False, 17), + ("probe_pid", 17.0, 17), ("probe_pid", True, 1), ("probe_pid", False, 1)): + with self.subTest(field=field, value=value, pid=pid), self.assertRaisesRegex(ValueError, "proof integer types/bounds"): + bodies = self.kimi_synthetic_completed_capture(probe_pid=pid) + result = json.loads(bodies["runtime-attempt.json"]) + result["native_history"][field] = value + bodies["runtime-attempt.json"] = json.dumps(result).encode() + runtime.kimi_call_members(bodies, self.source) + + def test_kimi_owned_inventory_nul_and_utf8_paths_refuse_coherent_proof(self): + for path in ("owned\x00name", "é" * 2049): + with self.subTest(utf8_bytes=len(path.encode())), self.assertRaisesRegex(ValueError, "owned inventory entry|JSON key bound"): + bodies = self.kimi_synthetic_completed_capture() + data = json.loads(bodies["native-data-proof.json"]) + for key in ("before", "after_without_marker"): data[key][path] = "0600:" + "a" * 64 + bodies["native-data-proof.json"] = json.dumps(data).encode() + result = json.loads(bodies["runtime-attempt.json"]) + result["native_history"]["data_proof_sha256"] = hashlib.sha256(bodies["native-data-proof.json"]).hexdigest() + bodies["runtime-attempt.json"] = json.dumps(result).encode() + runtime.kimi_call_members(bodies, self.source) + def test_kimi_synthetic_success_cannot_cover_failed_or_foreign_raw(self): for kind in ("terminal-failed", "empty-sentinel", "wrong-call", "extra-message", "history-more", "forced", "changed-data", "wrong-marker", "wrong-proof", "foreign-source", "lifecycle-flag", "wrong-cache", "unknown-file"): with self.subTest(kind=kind), self.assertRaises(ValueError): From 5d8229b528091963c0496e9bb403d989d83eb73e Mon Sep 17 00:00:00 2001 From: 777genius Date: Wed, 28 Oct 2026 00:56:04 +0100 Subject: [PATCH 41/42] test(kimi): use tagged metadata contract dispatch --- ...agentplugins_kimi_project_native_history_contract_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/repotests/agentplugins_kimi_project_native_history_contract_test.go b/repotests/agentplugins_kimi_project_native_history_contract_test.go index 113292f00..76fbc53ce 100644 --- a/repotests/agentplugins_kimi_project_native_history_contract_test.go +++ b/repotests/agentplugins_kimi_project_native_history_contract_test.go @@ -528,9 +528,10 @@ func TestKimiSingleToolPromptIdentityAndAbsentResponseMetadata(t *testing.T) { default: kimiMutateHTTP(t, files, "prompt", func(data map[string]any) { var metadata any = map[string]any{"origin": map[string]any{"kind": "user"}} - if kind == "metadata-null" { + switch kind { + case "metadata-null": metadata = nil - } else if kind == "metadata-foreign" { + case "metadata-foreign": metadata = map[string]any{"origin": map[string]any{"kind": "injection"}} } data["metadata"] = metadata From eb69a291aefe0ada8bfc9099e5553a00274e3344 Mon Sep 17 00:00:00 2001 From: 777genius Date: Wed, 28 Oct 2026 00:59:25 +0100 Subject: [PATCH 42/42] docs(kimi): record finite completion reviews and measured gates --- ...ECT_NATIVE_FINITE_COMPLETION_VALIDATION.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md b/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md index 1e1080cbd..3fe1dbab2 100644 --- a/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md +++ b/docs/KIMI_PROJECT_NATIVE_FINITE_COMPLETION_VALIDATION.md @@ -176,3 +176,61 @@ cases and a fully joined PID 1 record before a Boolean proof mutation. Historica failed/component reader bodies, live collectors, commands, protocol, budgets and authorities remain unchanged. These controls still require exact-source executed gates and fresh independent CODE reviews before publication. + + +## Final exact-source reviews and measured gates + +The corrected complete candidate569d252624c4088ee20763f7adf732b228f4cc96 received +fresh root and independent Gemini CODE GOs. Its full focused Go and97 Python +contracts passed. The actual affected all-enabled lint then found exactly one +QF1003 in a synthetic metadata control. The author replaced only that if/else +with an equivalent tagged switch; initial user-origin, nil and injection values +and every assertion remain unchanged. + +Final code5d8229b528091963c0496e9bb403d989d83eb73e, tree +d829e0db97f532d8fd6c1afd09dd5d164c891314, has both full-scope approvals with the +exact style delta reviewed: root receipt SHA256 +`d1b99dd8c29e5d0bb2e12fbddc023309bb2a1dd2a31575deabfbc7c23497e9fb` +and independent Gemini receipt +`a6d8000e06cdcbb923f29dea199d2142306f432ad0be66d1af221f8a00ee79fa`. +No implementation changed after this final review. + +Measured final gates use Go1.26.10 and golangci-lint2.13.2: + +- Full selected `^(TestKimi|TestProjectNativeRuntimeValidationContracts)` Go + contracts:38 top-level tests,166 case passes, no skips/failures; package + PASS0.776seconds. Own inert HTTP contracts used loopback permission; no vendor + client, public installer or scanner ran locally. +- Runtime Python40 PASS5.376seconds and Kimi Python57 PASS5.406seconds on569d. + Their entire scripts Git tree is identical in5d822; these97 passes transfer + by that exact identity across the sole Go-test style edit. +- Actual root all-enabled affected lint with + `--new-from-rev=a412ba5e137fdcb7e004393229b323211ed746e1`: zero issues. + Whole unfiltered revive/funlen/gocyclo/gocognit/dupl/depguard: zero issues. +- Independent full original9e and current Python modules re-admit both original + archives identically:25-member single-call failure/allfalse and67-member + component pass/four flags. Original source IDs, archive/member bytes and + outcomes are preserved. The original complete scripts tree was reconstructed + from the exact Git commit; no AST-selected substitute or native execution was + used for this final replay. Receipt SHA256 + `aa0f9362733b7d492a1719f6c0b13c38fbbf8429e7a74f6878d496083004c9eb`. +- Independently authored Python synthetic success passes the Go reader; the + independent Go synthetic success passes Python. The supplementary Go overlay + exists only in scratch and adds no raw capture or fixture to the repository. + Both directions preserve exactly the three MCP success flags. Receipt SHA256 + `40d5e4b5e534b0168ddf832e9b6694d05f11b2cd64dd9c31d6b357a7010bbd1c`. + +Consolidated exact-source gate/review receipt SHA256: +`e69df954b41e4fb53db341ab293f9c22140247106d713cad9cf5c676f2199396`. +Earlier real lint, local socket-capability and disk-exhaustion failures remain +retained; the exhausted link did not execute tests and is never counted PASS. +Only owned regenerable caches and obsolete clean checkout copies were removed, +with source refs and every actual evidence archive preserved. + +This record supports publication for a fresh genuine hosted single-call run. +It is not native completion evidence. The unchanged component lane must retain +its separate four-flag proof; only a new completed native turn with all semantic, +marker/data, original authority and closure checks can qualify three MCP flags +in this lane. Both independent raw reviews are required before a qualification +or merge claim. Authentication, real-model use, releases and deferred VS Code +remain outside this completion.