From f712123f96bbabbe3556f08f71c9e2b85d81697e 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:23 +0200 Subject: [PATCH 01/30] feat: add qoder pinned project research collector Integrates independently reviewed client leaf e4116ce9c8e8e742084de0781b04a333502859ad. Native runtime qualification remains separate. --- scripts/project_native_collectors/qoder.py | 30 +++++++++ scripts/test_project_native_qoder.py | 75 ++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 scripts/project_native_collectors/qoder.py create mode 100644 scripts/test_project_native_qoder.py diff --git a/scripts/project_native_collectors/qoder.py b/scripts/project_native_collectors/qoder.py new file mode 100644 index 000000000..ac6098904 --- /dev/null +++ b/scripts/project_native_collectors/qoder.py @@ -0,0 +1,30 @@ +"""Pinned Qoder archive inventory only; its exact member awaits pin review. + +Archive and binary digests are preserved from the existing 1.1.78 collector. +The shared runner must inventory the actual member spelling before a reviewed +amendment selects an entrypoint. No plugin import or session is authorized by +this acquisition leaf. +""" + +CLIENT = "qoder" +VERSION = "1.1.78" +AVAILABLE_PHASES = ("acquire",) +ACQUISITION_ONLY = True +ACQUISITION = { + "url": "https://qoder-ide.oss-accelerate.aliyuncs.com/qodercli/releases/1.1.78/qodercli-linux-x64.tar.gz", + "integrity": "sha256:5e3213d2d4256c86c5ad2c8bf73fd392f92ba80d0d38f822806542805a48a8ea", + "max_bytes": 96 << 20, + "expected_bytes": 78_115_289, + "format": "tar.gz", + "entrypoint": None, + "runtime": "native", + "binary_sha256": "0da0976e13fe00bc28f725d6fe7c2f0fe9fa9a8c37eeb2ee4e80ad4c0fc58953", +} + + +def collect(ctx): + """Refuse all native work until the exact member/phase amendment is reviewed.""" + raise RuntimeError( + "Qoder is acquisition-only: select the inventoried exact entrypoint " + "and review the native phase amendment before observation" + ) diff --git a/scripts/test_project_native_qoder.py b/scripts/test_project_native_qoder.py new file mode 100644 index 000000000..2b14c757b --- /dev/null +++ b/scripts/test_project_native_qoder.py @@ -0,0 +1,75 @@ +"""Inert Qoder acquisition contracts; no vendor payload is acquired or run.""" + +import ast +import importlib.util +from pathlib import Path +import subprocess +import unittest +from unittest.mock import patch +import urllib.request + + +SCRIPTS = Path(__file__).parent + + +def load_leaf(): + spec = importlib.util.spec_from_file_location( + "qoder_acquisition_contract", SCRIPTS / "project_native_collectors" / "qoder.py") + leaf = importlib.util.module_from_spec(spec) + spec.loader.exec_module(leaf) + return leaf + + +class ForbiddenContext: + def __getattribute__(self, name): + raise AssertionError("acquisition-only collector accessed context: " + name) + + +class QoderAcquisitionContract(unittest.TestCase): + def test_pending_member_cannot_become_a_native_observation(self): + with ( + patch.object(subprocess, "Popen", side_effect=AssertionError("vendor process")) as process, + patch.object(urllib.request, "urlopen", side_effect=AssertionError("network download")) as download, + ): + leaf = load_leaf() + self.assertEqual(leaf.AVAILABLE_PHASES, ("acquire",)) + self.assertIs(leaf.ACQUISITION_ONLY, True) + self.assertIsNone(leaf.ACQUISITION["entrypoint"]) + for context in (None, ForbiddenContext(), {"hosted": True, "native_opt_in": True}): + with self.subTest(context_type=type(context).__name__): + with self.assertRaisesRegex(RuntimeError, "acquisition-only.*phase amendment"): + leaf.collect(context) + process.assert_not_called() + download.assert_not_called() + + def test_flag_or_guessed_entrypoint_cannot_enable_the_collector(self): + leaf = load_leaf() + leaf.ACQUISITION_ONLY = False + leaf.ACQUISITION["entrypoint"] = "guessed/qodercli" + with self.assertRaisesRegex(RuntimeError, "acquisition-only"): + leaf.collect(ForbiddenContext()) + + def test_reuses_preserved_reviewed_archive_and_binary_identities(self): + # Read literal pins without importing the legacy acquisition machinery. + tree = ast.parse((SCRIPTS / "research-qoder-native.py").read_text(encoding="utf-8")) + preserved = { + node.targets[0].id: ast.literal_eval(node.value) + for node in tree.body + if isinstance(node, ast.Assign) and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in {"VERSION", "URL", "ARCHIVE_PIN", "BINARY_SHA256"} + } + leaf = load_leaf() + self.assertEqual(leaf.CLIENT, "qoder") + self.assertEqual(leaf.VERSION, preserved["VERSION"]) + self.assertEqual(leaf.ACQUISITION["url"], preserved["URL"]) + self.assertEqual(leaf.ACQUISITION["integrity"], preserved["ARCHIVE_PIN"]) + self.assertEqual(leaf.ACQUISITION["binary_sha256"], preserved["BINARY_SHA256"]) + self.assertIs(type(leaf.ACQUISITION["expected_bytes"]), int) + self.assertGreater(leaf.ACQUISITION["expected_bytes"], 0) + self.assertLess(leaf.ACQUISITION["expected_bytes"], leaf.ACQUISITION["max_bytes"]) + self.assertLessEqual(leaf.ACQUISITION["max_bytes"], 96 << 20) + + +if __name__ == "__main__": + unittest.main() From 76345f24915accae80da04782eaf0e66a39b2496 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:44:56 +0200 Subject: [PATCH 02/30] test: match measured Qoder help grammar --- scripts/project_native_collectors/qoder.py | 2 +- scripts/test_project_native_qoder.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/project_native_collectors/qoder.py b/scripts/project_native_collectors/qoder.py index d4984e424..546bd2dde 100644 --- a/scripts/project_native_collectors/qoder.py +++ b/scripts/project_native_collectors/qoder.py @@ -19,7 +19,7 @@ "binary_sha256": "0da0976e13fe00bc28f725d6fe7c2f0fe9fa9a8c37eeb2ee4e80ad4c0fc58953", } HELP = ( - ("top-help", ("--help",), "Usage: qodercli [options] [command]"), + ("top-help", ("--help",), "Usage: qodercli [options] [command] [query...]"), ("plugins-help", ("plugins", "--help"), "Usage: qodercli plugins|plugin [options] [command]"), ("plugins-list-help", ("plugins", "list", "--help"), "Usage: qodercli plugins list [options]"), ("mcp-help", ("mcp", "--help"), "Usage: qodercli mcp [options] [command]"), diff --git a/scripts/test_project_native_qoder.py b/scripts/test_project_native_qoder.py index 27aa58cf9..33c0005fd 100644 --- a/scripts/test_project_native_qoder.py +++ b/scripts/test_project_native_qoder.py @@ -118,6 +118,19 @@ def test_command_usage_is_anchored_and_help_is_required(self): with self.subTest(body=body), self.assertRaises(ValueError): leaf.parse_help(body, listing) + def test_measured_main_usage_requires_exact_query_suffix(self): + leaf = load_leaf() + usage = "Usage: qodercli [options] [command] [query...]" + body = (usage + "\n\nOptions:\n -h, --help display help for command\n").encode() + self.assertEqual(leaf.HELP[0][2], usage) + self.assertEqual(leaf.parse_help(body, usage), usage) + for wrong in [body.replace(b" [query...]", b""), + body.replace(b" [query...]", b" [query...] [extra]"), + body.replace(b"[command] [query...]", b"[query...] [command]"), + body.replace(b"qodercli ", b"qodercli-other ")]: + with self.subTest(body=wrong), self.assertRaises(ValueError): + leaf.parse_help(wrong, usage) + def test_reuses_preserved_reviewed_archive_and_binary_identities(self): # Read literal pins without importing the legacy acquisition machinery. tree = ast.parse((SCRIPTS / "research-qoder-native.py").read_text(encoding="utf-8")) From 400472f5eceef02654fb95be753d1cb2bb463f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D0=B8=D1=8F?= Date: Mon, 26 Oct 2026 19:54:50 +0200 Subject: [PATCH 03/30] test(qoder): verify installed package receipts with actual stager framing Retain receipt-selected catalog capture and full original-profile snapshot checks. Apply the independently reviewed strict framing and shared capture correction. --- .../remaining-native-qualification.md | 59 ++++++ ...plugins_project_native_cli_attempt_test.go | 2 +- ...lugins_project_native_cli_contract_test.go | 9 +- ...ns_project_native_framing_contract_test.go | 189 ++++++++++++++++++ ...gentplugins_project_native_runtime_test.go | 108 +++++++++- 5 files changed, 359 insertions(+), 8 deletions(-) create mode 100644 repotests/agentplugins_project_native_framing_contract_test.go diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index 2d06f9b32..d810c87d1 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1044,6 +1044,65 @@ before a later separately reviewed integration into the full lifecycle. This attempt cannot inherit component-lifecycle success or claim vendor OAuth or real-model qualification. +## Observed native CLI installed-package receipt framing correction + +Genuine Kiro run [40354148388](https://github.com/777genius/universal-agent-plugins/actions/runs/40354148388), +job `121708484979`, source `d650ef37ff5cd2f38f6b784ebcd9723c47e62cf7`, tree +`6b2be08d15092de328f2644478036f8c751e2b94`, completed public project add with +Prepared activation and installation verification, then refused the exact native +ownership comparison before any restricted startup. Native artifact `12672746098` +(SHA-256 `26403a83fe3bdf64819d29d652011a3265b78ee977310cbd068830d44dbad75b`) +and frontend artifact `12671093626` (SHA-256 +`4582142ca5697d5ab25576759d2431945caaebb5bc493876e2f1377886020ff4`) +retain that failure. Independent reconstruction from the captured selected skill +and MCP entry matches their two recorded object digests; the fixture's managed +package framing uses a different production contract. + +The exact fixed local-source/no-save public add in these four startup attempts +uses `runAddManyLoaded` and the existing lifecycle service. Kiro, Windsurf/Devin, +Grok and Qoder do not have `QualifiedLocalInstallerFacade`; that trait is set +only for Codex and Claude. `cli/cmd/agentplugins/compose.go` wires +`providers.Stager` into the lifecycle service. Its `assembleDelivery` records +`packagesnapshot.Builder.Build(...).Digest`, and its installed verification uses +that same builder. The shared attempt instead currently uses the newer, +domain-separated `agentplugins/adapters/packagedigest.DigestCaptured` result. +The four fixed specifications therefore need the same correction; no different +Grok/Qoder route or algorithm is inferred from their uncompleted runtime runs. + +Change only the independently computed expected installed managed-package +digest in these fixtures to the exact selected provider's existing framing: +lexically ordered held entries; `dir` or `file`, relative slash path, executable +boolean and decimal content length separated by NULs, followed by exact file +bytes. Keep the current bounded held reads, full ordinary-file ownership/link +checks, canonical parent checks, entry/depth/byte ceilings and the existing +captured-path policy validation. Use no client-configurable digest choice and no +alternate digest fallback. Exact object count, kind, ID, logical name, path and +digest equality remain mandatory. Production routes and digest implementations, +source identity, installed source/receipt checks, native argv, profiles, trust, +permissions, deadlines, cleanup, artifact members and qualification remain unchanged. + +Cline's separate installed ACP attempt uses that same non-facade lifecycle and +Stager route, and its installed `installedEntries` comparison has the identical +framing mismatch. Route that one installed-receipt expectation through the same +small strict held-entry helper in `agentplugins_project_native_runtime_test.go`. +Keep Cline's separate `sourceDigest` and package-revision expectation on +`packagedigest.DigestCaptured`: source acquisition/loading deliberately retains +the newer `PackageSnapshot.TreeDigest` identity. The installed receipt and +source digest must remain separate; neither is copied from a returned receipt. +The shared helper accepts only previously held ordinary files/directories, +retains captured-path policy validation, and offers no algorithm selector. + +Replace the self-selected newer-builder parity contract with inert staging +through the actual four registered client adapters plus Cline, project planner +and `providers.Stager`, including the mixed Kiro/Devin managed-launcher +projections and Cline's separate native transport projection. +Compare the helper's held-byte digest against the actual staged managed object +and `packagesnapshot` verifier; explicitly show the newer framing differs and +Cline's independent source identity still matches the newer source builder. +Exercise content/executable/directory changes and retained hardlink/alias +refusal. No vendor process executes. Propagate the reviewed shared-helper/contract and caller deltas to the relevant +leaves; all initial completion and qualification flags stay false. + ## Completion gate For each client, retain the exact source commit/tree, archive and executable diff --git a/repotests/agentplugins_project_native_cli_attempt_test.go b/repotests/agentplugins_project_native_cli_attempt_test.go index 84069ef6e..cb9929d73 100644 --- a/repotests/agentplugins_project_native_cli_attempt_test.go +++ b/repotests/agentplugins_project_native_cli_attempt_test.go @@ -596,7 +596,7 @@ func projectNativeCLIPackageDigest(ctx context.Context, root string) (string, er if err != nil { return "", err } - return packagedigest.DigestCaptured(ctx, captured) + return projectNativeRuntimeInstalledDigest(ctx, captured) } func projectNativeCLIClosedView(root, name string, skill []byte) error { expected := map[string]bool{".": true, ".qoder-plugin": true, ".qoder-plugin/plugin.json": true, "skills": true, "skills/" + name: true, "skills/" + name + "/SKILL.md": true} diff --git a/repotests/agentplugins_project_native_cli_contract_test.go b/repotests/agentplugins_project_native_cli_contract_test.go index e66c739a0..d104b542e 100644 --- a/repotests/agentplugins_project_native_cli_contract_test.go +++ b/repotests/agentplugins_project_native_cli_contract_test.go @@ -6,8 +6,8 @@ import ( "bytes" "context" "encoding/json" - "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/packagedigest" "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" + "github.com/777genius/plugin-kit-ai/install/integrationctl/packagesnapshot" "os" "path/filepath" "reflect" @@ -275,13 +275,12 @@ func TestProjectNativeCLIPackageDigestMatchesInstallerFramingFromHeldBytes(t *te if err != nil { t.Fatal(err) } - snapshot, err := (packagedigest.Builder{TempRoot: t.TempDir()}).Snapshot(context.Background(), root, domain.SourceIdentity{}) + want, err := (packagesnapshot.Builder{}).Digest(context.Background(), root) if err != nil { t.Fatal(err) } - defer packagedigest.Remove(snapshot) - if got != snapshot.TreeDigest { - t.Fatalf("held installed package digest differs from real installer framing: %s %s", got, snapshot.TreeDigest) + if got != want { + t.Fatalf("held installed package digest differs from selected lifecycle framing: %s %s", got, want) } if err := os.Link(filepath.Join(root, "plugin.json"), filepath.Join(t.TempDir(), "alias")); err != nil { t.Fatal(err) diff --git a/repotests/agentplugins_project_native_framing_contract_test.go b/repotests/agentplugins_project_native_framing_contract_test.go new file mode 100644 index 000000000..f4ecc9888 --- /dev/null +++ b/repotests/agentplugins_project_native_framing_contract_test.go @@ -0,0 +1,189 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/adapters/pathpolicy" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/loader" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/packagedigest" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/specregistry" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients/cline" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients/grok" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients/kiro" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients/qoder" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients/windsurf" + "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/agentplugins/planner" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/providers" +) + +// These are small inert Stager outputs, not acquired vendor trees or runtime +// observations. Each entry is held through the unchanged strict file reader. +func projectNativeFramingEntries(t *testing.T, root string) []packagedigest.CapturedEntry { + t.Helper() + var entries []packagedigest.CapturedEntry + err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(root, path) + if err != nil || rel == "." { + return err + } + if entry.IsDir() { + entries = append(entries, packagedigest.CapturedEntry{Path: filepath.ToSlash(rel), Kind: "directory"}) + return nil + } + body, err := projectNativeRuntimeRead(path, 1<<20) + if err != nil { + return err + } + info, err := entry.Info() + if err != nil { + return err + } + entries = append(entries, packagedigest.CapturedEntry{Path: filepath.ToSlash(rel), Kind: "file", Executable: info.Mode().Perm()&0111 != 0, Content: body}) + return nil + }) + if err != nil { + t.Fatal(err) + } + return entries +} + +func TestProjectNativeInstalledFramingMatchesActualSelectedStagers(t *testing.T) { + for _, adapter := range []clients.Adapter{qoder.New(), grok.New(), kiro.New(), windsurf.New(), cline.New()} { + t.Run(string(adapter.ID()), func(t *testing.T) { + if domain.ClientTraitsFor(adapter.ID()).QualifiedLocalInstallerFacade { + t.Fatal("selected public add route no longer uses the lifecycle Stager") + } + root := t.TempDir() + source, project, home := filepath.Join(root, "source"), filepath.Join(root, "A"), filepath.Join(root, "home") + projectNativeRuntimeWrite(t, filepath.Join(source, "plugin.json"), []byte(`{"$schema":"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json","name":"framing-probe","version":"1.0.0"}`), 0600) + projectNativeRuntimeWrite(t, filepath.Join(source, "skills", "framing-probe", "SKILL.md"), []byte("---\nname: framing-probe\ndescription: Inert receipt framing proof\n---\nHeld source bytes.\n"), 0600) + projectNativeRuntimeWrite(t, filepath.Join(source, "bin", "probe"), []byte("inert probe; never executed\n"), 0700) + if adapter.ID() == domain.ClientKiro || adapter.ID() == domain.ClientWindsurf || adapter.ID() == domain.ClientCline { + projectNativeRuntimeWrite(t, filepath.Join(source, "mcp.json"), []byte(`{"$schema":"https://agent-plugins.org/schemas/1.0.0/mcp.schema.json","mcpServers":{"framing-probe":{"type":"stdio","command":"./bin/probe","args":["${PLUGIN_ROOT}/arg","${PLUGIN_DATA}"],"cwd":"${PLUGIN_ROOT}"}}}`), 0600) + } + for _, path := range []string{project, filepath.Join(home, "."+string(adapter.ID()))} { + if err := os.MkdirAll(path, 0700); err != nil { + t.Fatal(err) + } + } + snapshot, err := (packagedigest.Builder{TempRoot: t.TempDir()}).Snapshot(t.Context(), source, domain.SourceIdentity{}) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := packagedigest.Remove(snapshot); err != nil { + t.Error(err) + } + }) + schemas, err := specregistry.New() + if err != nil { + t.Fatal(err) + } + envelope, err := (loader.Loader{Registry: schemas}).LoadSnapshot(t.Context(), snapshot) + if err != nil { + t.Fatal(err) + } + sourceEntries := projectNativeFramingEntries(t, snapshot.Root) + sourceDigest, err := packagedigest.DigestCaptured(t.Context(), sourceEntries) + if err != nil || sourceDigest != snapshot.TreeDigest || envelope.TreeDigest != sourceDigest { + t.Fatalf("acquired source identity changed: %s %s %s, %v", sourceDigest, snapshot.TreeDigest, envelope.TreeDigest, err) + } + registry, err := clients.NewRegistry(adapter) + if err != nil { + t.Fatal(err) + } + client := domain.DetectedClient{ClientID: adapter.ID(), ConfigRoot: filepath.Join(home, "."+string(adapter.ID())), ExecutablePath: filepath.Join(root, string(adapter.ID())), Status: domain.DetectionDetected} + if adapter.ID() == domain.ClientWindsurf { + client.ExecutablePath, client.ConfigRoot, client.Version = filepath.Join(root, "devin"), "", "3000.11.205" + } + planning := planner.Planner{ProjectRoot: project, ManagedRoot: filepath.Join(project, ".uap", "managed"), Paths: pathpolicy.Policy{}, Registry: registry} + plan, err := planning.Plan(t.Context(), domain.PlanRequest{Envelope: envelope, Client: client, Scope: domain.ScopeProject, ProjectRoot: project, PhysicalArtifactID: "framing-probe-0123456789ab"}) + if err != nil || plan.Status != domain.PlanReady { + t.Fatalf("actual selected project plan: %+v, %v", plan, err) + } + launcherPath := filepath.Join(root, "launcher") + projectNativeRuntimeWrite(t, launcherPath, []byte("inert managed launcher; never executed\n"), 0700) + launcher, err := managedstdio.NewSource(launcherPath, "inert-framing") + if err != nil { + t.Fatal(err) + } + stager := providers.Stager{Registry: registry, Paths: pathpolicy.Policy{}, LauncherSource: launcher, PluginDataRoot: filepath.Join(project, ".uap", "data")} + delivery, err := stager.Stage(t.Context(), envelope, plan, "inert-framing", domain.CompatibilityHints{}) + if err != nil { + t.Fatal(err) + } + entries := projectNativeFramingEntries(t, delivery.StagingPath) + got, err := projectNativeRuntimeInstalledDigest(t.Context(), entries) + if err != nil { + t.Fatal(err) + } + managed := 0 + for _, object := range delivery.NativeObjects { + if object.Kind == "managed_package_directory" { + managed++ + if object.ManagedDigest != got || object.Path != plan.ActivePath || stager.Verify(t.Context(), delivery.StagingPath, got) != nil { + t.Fatalf("held digest differs from actual selected Stager object: %+v, %s", object, got) + } + } + } + if managed != 1 { + t.Fatal("actual managed package receipt missing or duplicated") + } + newer, err := packagedigest.DigestCaptured(t.Context(), entries) + if err != nil || newer == got || stager.Verify(t.Context(), delivery.StagingPath, newer) == nil { + t.Fatal("newer source framing accepted as installed ownership") + } + if adapter.ID() == domain.ClientKiro || adapter.ID() == domain.ClientWindsurf { + if _, err := projectNativeRuntimeRead(filepath.Join(delivery.StagingPath, filepath.FromSlash(managedstdio.RelativeDirectory), managedstdio.ExecutableName), 1<<20); err != nil { + t.Fatalf("mixed managed launcher projection was not staged: %v", err) + } + } + if err := stager.Discard(t.Context(), delivery); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestProjectNativeInstalledFramingRetainsCapturedPolicyAndDistinctInputs(t *testing.T) { + valid := []packagedigest.CapturedEntry{{Path: "bin", Kind: "directory"}, {Path: "bin/probe", Kind: "file", Executable: true, Content: []byte("owned")}} + baseline, err := projectNativeRuntimeInstalledDigest(t.Context(), valid) + if err != nil { + t.Fatal(err) + } + for _, changed := range [][]packagedigest.CapturedEntry{ + {{Path: "bin", Kind: "directory"}, {Path: "bin/probe", Kind: "file", Executable: true, Content: []byte("other")}}, + {{Path: "bin", Kind: "directory"}, {Path: "bin/probe", Kind: "file", Content: []byte("owned")}}, + {{Path: "bin", Kind: "directory"}, {Path: "bin/probe", Kind: "file", Executable: true, Content: []byte("owned")}, {Path: "empty", Kind: "directory"}}, + } { + got, err := projectNativeRuntimeInstalledDigest(t.Context(), changed) + if err != nil || got == baseline { + t.Fatal("changed content, executable mode or directory membership retained digest") + } + } + for _, invalid := range [][]packagedigest.CapturedEntry{ + {{Path: "../alias", Kind: "file"}}, {{Path: "same", Kind: "file"}, {Path: "same", Kind: "file"}}, + {{Path: "Alias", Kind: "file"}, {Path: "alias", Kind: "file"}}, {{Path: "parent/file", Kind: "file"}}, + {{Path: "file", Kind: "file"}, {Path: "link", Kind: "symlink", Target: "file"}}, + } { + if _, err := projectNativeRuntimeInstalledDigest(t.Context(), invalid); err == nil { + t.Fatal("unsafe captured policy or nonordinary entry accepted") + } + } + ctx, cancel := context.WithCancel(context.Background()) + cancel() + if _, err := projectNativeRuntimeInstalledDigest(ctx, valid); err == nil { + t.Fatal("cancelled held digest continued") + } +} diff --git a/repotests/agentplugins_project_native_runtime_test.go b/repotests/agentplugins_project_native_runtime_test.go index 9e1fcc204..5751032c7 100644 --- a/repotests/agentplugins_project_native_runtime_test.go +++ b/repotests/agentplugins_project_native_runtime_test.go @@ -4,6 +4,7 @@ package pluginkitairepo_test import ( "bytes" + "context" "crypto/sha256" "encoding/hex" "encoding/json" @@ -13,10 +14,13 @@ import ( "os" "path/filepath" "reflect" + "sort" "strings" "syscall" "testing" "unicode/utf8" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/packagedigest" ) // Shared held-file mechanics for the source-only native project suites. @@ -61,7 +65,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 +82,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 +275,99 @@ 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) + } + } + }) +} + +// The public non-facade lifecycle records packagesnapshot framing for installed +// managed objects. Source PackageSnapshot.TreeDigest retains its separate v1 +// framing. Inputs here are previously bounded, held ordinary-file captures. +func projectNativeRuntimeInstalledDigest(ctx context.Context, captured []packagedigest.CapturedEntry) (string, error) { + if _, err := packagedigest.DigestCaptured(ctx, captured); err != nil { + return "", err + } + items := append([]packagedigest.CapturedEntry(nil), captured...) + sort.Slice(items, func(i, j int) bool { return items[i].Path < items[j].Path }) + hash := sha256.New() + for _, item := range items { + if err := ctx.Err(); err != nil { + return "", err + } + kind := "file" + switch item.Kind { + case "directory": + kind = "dir" + case "file": + default: + return "", fmt.Errorf("installed package capture requires ordinary files/directories") + } + _, _ = fmt.Fprintf(hash, "%s\x00%s\x00%t\x00%d\x00", kind, item.Path, item.Executable, len(item.Content)) + for body := item.Content; len(body) > 0; { + if err := ctx.Err(); err != nil { + return "", err + } + n := min(len(body), 32*1024) + _, _ = hash.Write(body[:n]) + body = body[n:] + } + } + return "sha256:" + hex.EncodeToString(hash.Sum(nil)), nil +} From 0d1bfa7225937f4f581958022ef103200ee14301 Mon Sep 17 00:00:00 2001 From: iliya Date: Mon, 26 Oct 2026 23:58:16 +0200 Subject: [PATCH 04/30] fix(ci): isolate manual runtime checks and replace stale PR runs Refs #523 --- .github/workflows/project-native-runtime.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/project-native-runtime.yml b/.github/workflows/project-native-runtime.yml index 338984cb2..66fb12161 100644 --- a/.github/workflows/project-native-runtime.yml +++ b/.github/workflows/project-native-runtime.yml @@ -14,8 +14,8 @@ on: permissions: contents: read concurrency: - group: project-native-runtime-${{ github.ref }} - cancel-in-progress: false + group: project-native-runtime-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: contracts: name: Runtime admission contracts and exact selection From 6406b8f71e0aec4703f832f882b48219509e8553 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:23:07 +0200 Subject: [PATCH 05/30] docs(qoder): plan bounded original-profile refusal metadata --- .../qoder-profile-refusal-diagnostic-plan.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-profile-refusal-diagnostic-plan.md diff --git a/docs/plans/project-install-e2e/qoder-profile-refusal-diagnostic-plan.md b/docs/plans/project-install-e2e/qoder-profile-refusal-diagnostic-plan.md new file mode 100644 index 000000000..4cf67aac8 --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-profile-refusal-diagnostic-plan.md @@ -0,0 +1,75 @@ +# Qoder original-profile refusal: bounded metadata diagnostic + +## Observed boundary and exact current source + +Published PR523 head `0d1bfa7225937f4f581958022ef103200ee14301`, tree +`74242f382c0c738b4a13a95547b2d8102b461566`, has genuine runtime run +`40408766184`. Native artifact `12699200113` and matching frontend +`12698695352` retain successful public project installation followed by +`Qoder profile ordinary single-link file required`. The failure happens during +the first complete original `HOME/.qoder` inventory, before the selected native +catalog or startup command. The closed capture does not identify the offending +entry, file type, link count or size; no particular cause is established. +All five native qualification flags remain false. + +Local baseline `4106c6f9a942f3fadd26b29e109f4ceb4dbe68a5` has exactly that +published tree. It preserves the original worker branch and mirrors only the +published runtime-workflow overlay, immutable blob +`66fb12161a3d1cfe6992cfeada8fbf44ad54fda6`. No other baseline bytes changed. + +## Narrow diagnostic change + +After two independent plan GOs, change only the existing ordinary/single-link +refusal in `projectNativeCLIQoderProfile` to retain its exact reason prefix and +append four metadata fields from the already obtained `os.Lstat` result: +the relative entry, `info.Mode()` as a finite mode string, `identity.Nlink`, +and `info.Size()`. Keep the current refusal predicate and its position before +the existing byte accounting or bounded body reader unchanged. + +Use a small private formatter for that error. The relative entry is limited to +256 input bytes with an explicit truncation marker, and `%q` quotes/escapes it +so names containing controls cannot produce extra log lines. The complete +diagnostic is finite (at most 2 KiB), contains no absolute HOME/profile root, +and includes no file contents, link target, credentials or directory dump. +Do not follow the entry, call Readlink, reopen it or add filesystem reads. +Keep all original ancestor/owner, directory, count/depth, byte, ordinary-file, +single-link and stable bounded-read checks and their order. Do not admit, +remove, move, relabel or repair the refused entry. The error still terminates +the native fixture before catalog/startup; artifact closure and qualification +flags remain unchanged. + +## Meaningful inert contracts + +Exercise the actual profile inventory with real temporary ordinary parent +directories and separate refused symlink and hardlink entries. Compare the +diagnostic metadata against the actual entry's `os.Lstat` mode, size and link +count; ensure the relative entry is reported and the absolute root, foreign +target path and sentinel target contents are absent. Verify both entries and +their outside target remain unchanged after refusal. An actual nested symlink +whose relative name exceeds 256 bytes and contains quoted/control characters +checks truncation, the finite diagnostic bound and absence of raw line breaks. +No native binary, installer, model, network or subprocess runs. + +Preserve the existing full-profile mutation contracts (bytes, modes, new empty +directory, new cache, alias and hardlink), opaque strict whole-JSON catalog +contract and finite command/argument/client/cwd/deadline controls. Add no skip, +fake filesystem metadata, synthetic native response or acceptance relaxation. + +## Scope, checks and publication + +Scope is exactly the two existing Linux Go test files +`repotests/agentplugins_project_native_cli_attempt_test.go` and +`repotests/agentplugins_project_native_cli_contract_test.go`, plus this plan and +validation record. No production/client adapter, installer, source/pins, user or +project authority, profile baseline, permissions/trust/authentication, original +deadline, process supervision, cleanup or qualification policy changes. +Useful legacy source remains preserved. VS Code is excluded by the owner's +latest instruction and is not part of this work. + +After the shared exclusive Go/lint slot is granted, run the affected existing +and new inert Qoder inventory/catalog/command contracts, then actual affected +all-enabled changed-line lint and the whole-file size/architecture gate. Run +gofmt/whitespace checks and obtain independent code review before root-owned +publication to existing PR523; no new PR or release. Fresh genuine hosted raw +evidence must identify the refused entry before a separate behavioral plan can +be considered. Passing inert diagnostics does not qualify native operation. From c14b4974ddd42d16233ad654f3dbc50bcbcabcf0 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:23:10 +0200 Subject: [PATCH 06/30] test(qoder): report bounded metadata when original profile refuses --- .../qoder-profile-refusal-diagnostic-plan.md | 17 +++++ ...plugins_project_native_cli_attempt_test.go | 11 +++- ...lugins_project_native_cli_contract_test.go | 66 +++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/docs/plans/project-install-e2e/qoder-profile-refusal-diagnostic-plan.md b/docs/plans/project-install-e2e/qoder-profile-refusal-diagnostic-plan.md index 4cf67aac8..dfa67fe25 100644 --- a/docs/plans/project-install-e2e/qoder-profile-refusal-diagnostic-plan.md +++ b/docs/plans/project-install-e2e/qoder-profile-refusal-diagnostic-plan.md @@ -73,3 +73,20 @@ gofmt/whitespace checks and obtain independent code review before root-owned publication to existing PR523; no new PR or release. Fresh genuine hosted raw evidence must identify the refused entry before a separate behavioral plan can be considered. Passing inert diagnostics does not qualify native operation. + +## Review and actual validation + +Plan SHA256 `65bb9cf95226fae7efb64928db07abb5827bc5720025c5e59f8130874deccb6c` +received independent CLI R1 and shell R2 GOs before plan commit +`255247bdc1870178cc272484c2350b3c1bd82779` and implementation. Both critics +reviewed the final code bytes, including the mechanically removed redundant +link-count conversion reported by actual unconvert lint. Final focused inert +Qoder inventory/catalog and exact command contracts PASS in 0.010 seconds, +including all three new actual link/metadata cases and all six retained profile +mutation cases. Actual all-enabled changed-line lint and the unfiltered +whole-file size/architecture gate each report 0 issues; gofmt and whitespace +checks are clean. An initial invocation stopped before tests because disabling +the repository's required Go workspace hid its local nested modules; the +unchanged existing go.work was then used for the successful checks. No vendor +or network process ran, no qualification changed, and fresh genuine hosted +diagnostic evidence remains required before any behavioral correction. diff --git a/repotests/agentplugins_project_native_cli_attempt_test.go b/repotests/agentplugins_project_native_cli_attempt_test.go index 091788843..1858376a4 100644 --- a/repotests/agentplugins_project_native_cli_attempt_test.go +++ b/repotests/agentplugins_project_native_cli_attempt_test.go @@ -724,7 +724,7 @@ func projectNativeCLIQoderProfile(root string) (map[string]string, error) { return nil } if !info.Mode().IsRegular() || identity.Nlink != 1 || info.Size() < 0 { - return fmt.Errorf("Qoder profile ordinary single-link file required") + return projectNativeCLIQoderProfileRefusal(rel, info, identity.Nlink) } total += info.Size() if total > 256<<20 { @@ -741,6 +741,15 @@ func projectNativeCLIQoderProfile(root string) (map[string]string, error) { return result, err } +// Report only metadata already held by the refusing Lstat, never link targets +// or entry contents. Quote a bounded relative name to keep controls inert. +func projectNativeCLIQoderProfileRefusal(rel string, info os.FileInfo, links uint64) error { + if len(rel) > 256 { + rel = rel[:256] + "...[truncated]" + } + return fmt.Errorf("Qoder profile ordinary single-link file required: entry=%q mode=%s nlink=%d size=%d", rel, info.Mode().String(), links, info.Size()) +} + func projectNativeCLIQoderCatalogJSON(body []byte) (string, error) { value, err := projectNativeRuntimeJSON(body) if err != nil { diff --git a/repotests/agentplugins_project_native_cli_contract_test.go b/repotests/agentplugins_project_native_cli_contract_test.go index 338891492..742b83ed7 100644 --- a/repotests/agentplugins_project_native_cli_contract_test.go +++ b/repotests/agentplugins_project_native_cli_contract_test.go @@ -6,10 +6,12 @@ import ( "bytes" "context" "encoding/json" + "fmt" "os" "path/filepath" "reflect" "strings" + "syscall" "testing" "time" @@ -224,6 +226,70 @@ func TestProjectNativeCLIQoderCatalogCannotMutateOriginalProfileCache(t *testing } } +func TestProjectNativeCLIQoderProfileRefusalReportsOnlyBoundedEntryMetadata(t *testing.T) { + for _, kind := range []string{"symlink", "hardlink", "long-control-symlink"} { + t.Run(kind, func(t *testing.T) { + root := filepath.Join(t.TempDir(), ".qoder") + target := filepath.Join(t.TempDir(), "outside-target") + body := []byte("foreign target contents must not enter diagnostics\n") + projectNativeRuntimeWrite(t, target, body, 0600) + rel := "entry" + if kind == "long-control-symlink" { + rel = filepath.Join(strings.Repeat("a", 100)+"\n\"", strings.Repeat("b", 100), strings.Repeat("c", 100), "entry") + } + path := filepath.Join(root, rel) + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + t.Fatal(err) + } + link := os.Symlink + if kind == "hardlink" { + link = os.Link + } + if err := link(target, path); err != nil { + t.Fatal(err) + } + info, err := os.Lstat(path) + if err != nil { + t.Fatal(err) + } + targetInfo, err := os.Lstat(target) + if err != nil { + t.Fatal(err) + } + _, err = projectNativeCLIQoderProfile(root) + projectNativeCLIQoderRefusalMetadata(t, root, target, rel, info, err) + after, err := os.Lstat(path) + if err != nil || !projectNativeRuntimeStableStat(info.Sys(), after.Sys()) { + t.Fatal("refusal changed the original entry") + } + targetAfter, err := os.Lstat(target) + if err != nil || !projectNativeRuntimeStableStat(targetInfo.Sys(), targetAfter.Sys()) { + t.Fatal("refusal changed foreign target identity") + } + got, err := os.ReadFile(target) + if err != nil || !bytes.Equal(got, body) { + t.Fatal("refusal changed foreign target contents") + } + }) + } +} + +func projectNativeCLIQoderRefusalMetadata(t *testing.T, root, target, rel string, info os.FileInfo, refusal error) { + t.Helper() + identity, ok := info.Sys().(*syscall.Stat_t) + if refusal == nil || !ok { + t.Fatal("refusal and actual Linux entry metadata required") + } + message := refusal.Error() + if len(rel) > 256 { + rel = rel[:256] + "...[truncated]" + } + want := fmt.Sprintf("Qoder profile ordinary single-link file required: entry=%q mode=%s nlink=%d size=%d", rel, info.Mode().String(), identity.Nlink, info.Size()) + if message != want || len(message) > 2<<10 || strings.ContainsAny(message, "\r\n") || strings.Contains(message, root) || strings.Contains(message, target) || strings.Contains(message, "foreign target contents") { + t.Fatalf("bounded entry metadata differs or leaks foreign authority: %q", message) + } +} + func TestProjectNativeCLIMixedServerRefusesExtraTransportCWDAndUnknownFields(t *testing.T) { args := []any{"--internal-stdio-v1", "/owned/package", "/owned/data", "/owned/package", "plugin", "--", "./bin/probe"} env := map[string]any{"PLUGIN_ROOT": "/owned/package", "PLUGIN_DATA": "/owned/data"} From 9dba4d0477bd71512ec7b96e6a1d1d48f0920b2a 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:07:39 +0200 Subject: [PATCH 07/30] docs(qoder): plan exact preserved original log link --- .../qoder-preserved-log-link-plan.md | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-preserved-log-link-plan.md diff --git a/docs/plans/project-install-e2e/qoder-preserved-log-link-plan.md b/docs/plans/project-install-e2e/qoder-preserved-log-link-plan.md new file mode 100644 index 000000000..05a519761 --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-preserved-log-link-plan.md @@ -0,0 +1,108 @@ +# Qoder exact original log link: preserve metadata without following aliases + +## Observed current refusal + +Published PR523 head `c14b4974ddd42d16233ad654f3dbc50bcbcabcf0`, tree +`9197585febf142cc586712c04590cd06a967c17a`, has genuine runtime run +`40455028917`, native job `122026305662`. Public add-a succeeds (exit0, +error=false/deadline=false, 569 ms), then the first original-profile inventory +refuses before native catalog/startup with: + +`Qoder profile ordinary single-link file required: entry="logs/latest" mode=Lrwxrwxrwx nlink=1 size=42` + +The owner check preceding that refusal passed. This proves an original-user-owned +symlink at this exact relative entry. It does not capture the target bytes, +target kind/ownership or containment, nor establish which earlier command +created it. No source or runtime assumption may fill those gaps. + +Native artifact `12711428594` outer SHA256 +`eee6372ce39902fe9269f652632792bb5ab24cb16e8f1cf63e30ac50ac9681a6` +and frontend `12711149158` outer SHA256 +`9ac977e326a940f96a3e1fa96e4e67edd91438aa9aa47750e7aca0c43cd033bb` +were checked as 13/25 bounded canonical unique ordinary flat ZIP members with +valid CRCs, exact source/tree, empty source-clean streams and matching frontend +command stream hashes. All qualifications remain false. + +Local `51accde02931ff08158f47b94c2a195b5881fdbe` has exactly the current +published tree. Its accepted bounded metadata diagnostic remains preserved. +The preceding worker branch is retained; this slice has its own branch/plan. + +## Decision and retained guarantee + +A symlink can be inventoried without granting it filesystem authority if its +own held identity and exact link bytes are preserved, it is never followed, +and its lexical target and every target ancestor are confined to ordinary owned +entries already covered by the same complete profile inventory. Implement this +only for the observed exact `logs/latest` entry. All other symlinks and every +hardlink remain refused. Do not exclude logs, caches, new directories, changed +modes, bytes or link targets from the complete pre/post catalog comparison. + +This does not assume the catalog is read-only in practice. If it rotates the +link, writes a log or otherwise changes the frozen profile, the existing fatal +comparison must still fail; no normal-logging exception is authorized here. + +## Finite implementation after two independent plan GOs + +Add one private Linux helper for that exact entry. Keep the current root/parent, +owner, entry/depth and ordinary-file checks and their order; call the helper +only after the existing Lstat/owner check and only when rel is exactly +`logs/latest` and the leaf is a symlink. Require its observed symlink mode, +original UID/GID, nlink1 and a positive size at most4096. Include link bytes in +the existing256MiB inventory budget; no new unbounded read is introduced. + +Hold the ordinary original profile and logs directories with O_DIRECTORY, +O_NOFOLLOW/O_CLOEXEC descriptors and compare held/linked inode identity. Read +only `latest` with bounded Readlinkat on that held logs descriptor; do not use +EvalSymlinks, open or read through the symlink. Compare the leaf's held +AT_SYMLINK_NOFOLLOW stat before/after the read with the already observed Lstat, +and recheck both linked ancestor identities. Require exact target byte length +and stable inode/device/mode/owner/link-count/size/timestamps. Close every held +descriptor with checked errors. A race, alias or close failure refuses. + +Interpret those bounded bytes only as a canonical nonempty relative path: +valid UTF8, no NUL/control, absolute/volume spelling, backslash or parent escape; +clean path equality and bounded component depth are mandatory. Resolve only +lexically beneath original `.qoder/logs`, then inspect each target component +relative to held descriptors without following symlinks. All target ancestors +must be ordinary original-user-owned directories; final target must be an +ordinary owned directory or single-link regular file. Self-links, dangling +links, absolute/outside targets, target/ancestor aliases, nonregular types and +multiply linked files refuse. Do not open or read target contents in this helper. + +The ordinary target and complete descendant closure must also appear in the +unchanged normal profile walk; validate that requirement before admitting the +inventory. Store only an unambiguous symlink record containing its mode/link +metadata, target byte count and SHA256 of exact target bytes. The target text +stays private. Exact before/after map equality freezes the link along with every +other entry; do not adopt a native-written baseline or retarget/delete the link. + +Record only finite no-follow/contained-target type, target byte count and target +SHA256 as a small original-log-link observation in existing runtime-attempt.json +after complete initial inventory succeeds. Unknown/unsafe target shapes keep +the original failure, adding only a fixed refusal category and bounded held +metadata; never serialize raw link text, absolute paths, log contents or errors. +If the actual42-byte target fails any rule, retain the capture and require a +separate source/evidence-backed plan; do not add a fallback or broaden acceptance. + +## Scope, meaningful checks and publication + +Scope is the existing shared CLI attempt file, one new Linux Qoder log-link +helper/contract file and this plan/validation record. Preserve all earlier +diagnostics, other-client paths, opaque catalog grammar, exact argv/cwd and +budgets, source/pins, original HOME/profile authority, installer/receipt/security, +native trust/authentication/permission, supervision/cleanup and all qualification +flags. No production adapter, user profile relocation, auth workaround, blanket +symlink allowance, ignored subtree, new process, new PR, release or VS Code work. + +Actual inert temp-file contracts must cover the sole contained ordinary link +and its unchanged complete target closure, plus other-entry links, hardlinks, +absolute/outside/unclean/self/dangling targets, aliased ancestor/target, FIFO, +oversized target text and changed link/target/empty directory/mode/content. +Verify refusal is before unsafe content reads, metadata records contain no +target text or private sentinel, link/target identity/bytes survive unchanged, +and changed target bytes or any new log entry cannot compare equal. Keep all +existing Qoder full-profile mutation and finite command/opaque JSON contracts. +Use only the root-granted exclusive Go/lint slot for focused contracts and both +affected lint gates; inspect exact scope, obtain independent code review, and +let root publish to existing PR523 using a fresh non-forced ref lease. Only a +fresh genuine source-bound run can establish whether catalog/startup proceeds. From ccd9dc9e12724b43bae001b3d3573d95c7994297 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:07:41 +0200 Subject: [PATCH 08/30] test(qoder): preserve exact contained original log link --- .../qoder-preserved-log-link-plan.md | 30 ++ ...plugins_project_native_cli_attempt_test.go | 19 + repotests/agentplugins_qoder_log_link_test.go | 425 ++++++++++++++++++ 3 files changed, 474 insertions(+) create mode 100644 repotests/agentplugins_qoder_log_link_test.go diff --git a/docs/plans/project-install-e2e/qoder-preserved-log-link-plan.md b/docs/plans/project-install-e2e/qoder-preserved-log-link-plan.md index 05a519761..7ff8c487d 100644 --- a/docs/plans/project-install-e2e/qoder-preserved-log-link-plan.md +++ b/docs/plans/project-install-e2e/qoder-preserved-log-link-plan.md @@ -106,3 +106,33 @@ Use only the root-granted exclusive Go/lint slot for focused contracts and both affected lint gates; inspect exact scope, obtain independent code review, and let root publish to existing PR523 using a fresh non-forced ref lease. Only a fresh genuine source-bound run can establish whether catalog/startup proceeds. + +## Actual implementation validation + +The original plan SHA256 `6e45e544bc368a0974ca94196472771642210d735e442c2a07744cace84cecae` +received independent CLI and shell plan GOs plus root review before code. Both +critics reviewed the complete two-file implementation and its final small +preallocation/positive-contract delta. The unchanged shared attempt file has +SHA256 `d2571a53a42f70486967c96298b6fd5c1ca34036d511814169eb18a36c796020`; +the final new helper/contracts have +SHA256 `cbfe3f4ffa325c742a0123939813354197eaf0dcf8a52d5a3bdbbb59c98ebeba`. + +Actual focused inert Go tests passed in `0.028s`: five new log-link suites with +23 directory/file, unsafe-target, alias/hardlink/FIFO and mutation subcases, +together with the retained Qoder catalog/profile/diagnostic and exact argv/scope +contracts. Actual all-enabled changed-line lint and unfiltered whole-file +size/complexity/architecture lint each reported `0 issues`; gofmt and whitespace +checks pass. The first linker attempt exhausted SHM, and the first lint pass +found one preallocation issue plus exhausted SHM fact-cache writes. After root +freed an independently verified duplicate toolchain, the byte-identical canonical +Go1.26.10 and task-owned disk temp/lint cache completed the checks. Direct source +indexing (`GODEBUG=goindex=0`) avoided the stale index after the compiler path +change. No vendor process ran locally and no native qualification was inferred. + +The symlink record includes the stable numeric link and target metadata as well +as exact target byte count/SHA256, so replacing a link with the same target text +cannot compare equal. Actual same-target rotation and unchanged original-link +identity contracts cover that boundary. Target text and contents remain private; +the ordinary target closure and existing complete pre/post map comparison remain +mandatory. Actual native target shape and any catalog log mutation remain for a +fresh genuine run; an unsafe target or changed log still fails. diff --git a/repotests/agentplugins_project_native_cli_attempt_test.go b/repotests/agentplugins_project_native_cli_attempt_test.go index 1858376a4..34b4144c3 100644 --- a/repotests/agentplugins_project_native_cli_attempt_test.go +++ b/repotests/agentplugins_project_native_cli_attempt_test.go @@ -699,6 +699,7 @@ func projectNativeCLIQoderProfile(root string) (map[string]string, error) { } result := map[string]string{} count, total := 0, int64(0) + var logLink *qoderLogLinkSnapshot err := filepath.WalkDir(root, func(path string, entry os.DirEntry, walkErr error) error { if walkErr != nil { return walkErr @@ -723,6 +724,18 @@ func projectNativeCLIQoderProfile(root string) (map[string]string, error) { result[rel] = fmt.Sprintf("directory:%o", info.Mode()) return nil } + if rel == filepath.Join("logs", "latest") && info.Mode()&os.ModeSymlink != 0 { + snapshot, err := qoderReadOriginalLogLink(root, info) + if err != nil { + return err + } + total += info.Size() + if total > 256<<20 { + return fmt.Errorf("Qoder profile inventory byte bound") + } + result[rel], logLink = snapshot.Record, &snapshot + return nil + } if !info.Mode().IsRegular() || identity.Nlink != 1 || info.Size() < 0 { return projectNativeCLIQoderProfileRefusal(rel, info, identity.Nlink) } @@ -738,6 +751,9 @@ func projectNativeCLIQoderProfile(root string) (map[string]string, error) { result[rel] = fmt.Sprintf("file:%o:%d:%s", info.Mode(), len(body), hex.EncodeToString(sum[:])) return nil }) + if err == nil && logLink != nil { + err = qoderVerifyOriginalLogClosure(root, *logLink, result) + } return result, err } @@ -783,6 +799,9 @@ func projectNativeCLIRuntimeAttempt(t *testing.T, client string) { if err != nil { t.Fatal(err) } + if record, ok := before[filepath.Join("logs", "latest")]; ok { + f.evidence["qoder_original_log_link"] = map[string]any{"no_follow": true, "contained_ordinary_target": true, "record": record} + } f.evidence["attempt_stage"] = "native-installed-catalog" result := f.command(t, "native-catalog", f.base.Project, []string{f.client, "plugins", "list", "--json", "--plugin-dir", f.view}, 30*time.Second) after, err := projectNativeCLIQoderProfile(profile) diff --git a/repotests/agentplugins_qoder_log_link_test.go b/repotests/agentplugins_qoder_log_link_test.go new file mode 100644 index 000000000..1341bf81c --- /dev/null +++ b/repotests/agentplugins_qoder_log_link_test.go @@ -0,0 +1,425 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "syscall" + "testing" + "unicode" + "unicode/utf8" + + "golang.org/x/sys/unix" +) + +type qoderLogLinkSnapshot struct { + Record, TargetRel string + Profile, Logs, Leaf, Target os.FileInfo +} + +type qoderLogEdge struct { + parent, file *os.File + name string + info os.FileInfo +} + +func qoderLogRefused(category string) error { + return fmt.Errorf("Qoder original log link refused: %s", category) +} + +func qoderLogOpenAt(parent *os.File, name string, directory bool) (*os.File, error) { + flags := unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC + if directory { + flags |= unix.O_DIRECTORY + } + fd, err := unix.Openat(int(parent.Fd()), name, flags, 0) + if err != nil { + return nil, qoderLogRefused("no-follow-open") + } + return os.NewFile(uintptr(fd), "qoder-held-node"), nil +} + +func qoderLogOpenRoot(path string) (*os.File, error) { + parts := strings.Split(strings.TrimPrefix(path, "/"), "/") + if !filepath.IsAbs(path) || filepath.Clean(path) != path || len(parts) > 64 || len(path) > 4096 { + return nil, qoderLogRefused("profile-path") + } + fd, err := unix.Open("/", unix.O_PATH|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return nil, qoderLogRefused("root-open") + } + current := os.NewFile(uintptr(fd), "qoder-held-root") + for _, part := range parts { + next, openErr := qoderLogOpenAt(current, part, true) + closeErr := current.Close() + if openErr != nil || closeErr != nil { + if next != nil { + if err := next.Close(); err != nil { + return nil, qoderLogRefused("ancestor-close") + } + } + return nil, qoderLogRefused("ancestor-open-or-close") + } + current = next + } + return current, nil +} + +func qoderLogOwned(info os.FileInfo, directory bool) bool { + identity, ok := info.Sys().(*syscall.Stat_t) + if !ok || identity.Uid != uint32(os.Getuid()) || identity.Gid != uint32(os.Getgid()) { + return false + } + if directory { + return info.IsDir() + } + return info.IsDir() || (info.Mode().IsRegular() && identity.Nlink == 1 && info.Size() >= 0) +} + +func qoderLogTargetParts(body []byte) ([]string, error) { + text := string(body) + parts := strings.Split(text, string(filepath.Separator)) + if !utf8.Valid(body) || text == "" || text == "." || filepath.IsAbs(text) || filepath.VolumeName(text) != "" || (len(text) >= 2 && text[1] == ':') || filepath.Clean(text) != text || strings.Contains(text, "\\") || len(parts) > 30 { + return nil, qoderLogRefused("target-path") + } + for _, character := range text { + if unicode.IsControl(character) { + return nil, qoderLogRefused("target-control") + } + } + for _, part := range parts { + if part == ".." || part == "" { + return nil, qoderLogRefused("target-escape") + } + } + return parts, nil +} + +func qoderLogTarget(logs *os.File, parts []string, edges *[]qoderLogEdge) (os.FileInfo, error) { + parent := logs + var target os.FileInfo + for index, part := range parts { + directory := index < len(parts)-1 + file, err := qoderLogOpenAt(parent, part, directory) + if err != nil { + return nil, err + } + info, statErr := file.Stat() + *edges = append(*edges, qoderLogEdge{parent: parent, file: file, name: part, info: info}) + if statErr != nil || !qoderLogOwned(info, directory) { + return nil, qoderLogRefused("target-type-or-owner") + } + parent, target = file, info + } + return target, nil +} + +func qoderLogCheckEdges(edges []qoderLogEdge) error { + for _, edge := range edges { + held, err := edge.file.Stat() + if err != nil || !projectNativeRuntimeStableStat(edge.info.Sys(), held.Sys()) { + return qoderLogRefused("held-identity-changed") + } + linked, err := qoderLogOpenAt(edge.parent, edge.name, false) + if err != nil { + return err + } + info, statErr := linked.Stat() + closeErr := linked.Close() + if statErr != nil || closeErr != nil || !projectNativeRuntimeStableStat(edge.info.Sys(), info.Sys()) { + return qoderLogRefused("linked-identity-or-close") + } + } + return nil +} + +func qoderReadOriginalLogLink(root string, observed os.FileInfo) (snapshot qoderLogLinkSnapshot, resultErr error) { + profile, err := qoderLogOpenRoot(root) + if err != nil { + return snapshot, err + } + edges := make([]qoderLogEdge, 0, 32) + defer func() { + for index := len(edges) - 1; index >= 0; index-- { + if err := edges[index].file.Close(); err != nil { + resultErr = qoderLogRefused("descriptor-close") + } + } + if err := profile.Close(); err != nil { + resultErr = qoderLogRefused("profile-close") + } + if resultErr != nil { + snapshot = qoderLogLinkSnapshot{} + } + }() + snapshot.Profile, err = profile.Stat() + if err != nil || !qoderLogOwned(snapshot.Profile, true) { + return snapshot, qoderLogRefused("profile-owner") + } + logs, err := qoderLogOpenAt(profile, "logs", true) + if err != nil { + return snapshot, err + } + snapshot.Logs, err = logs.Stat() + edges = append(edges, qoderLogEdge{parent: profile, file: logs, name: "logs", info: snapshot.Logs}) + if err != nil || !qoderLogOwned(snapshot.Logs, true) { + return snapshot, qoderLogRefused("logs-owner") + } + return qoderReadLogLeaf(root, logs, observed, snapshot, &edges) +} + +func qoderReadLogLeaf(root string, logs *os.File, observed os.FileInfo, snapshot qoderLogLinkSnapshot, edges *[]qoderLogEdge) (qoderLogLinkSnapshot, error) { + leaf, err := qoderLogOpenAt(logs, "latest", false) + if err != nil { + return snapshot, err + } + info, err := leaf.Stat() + *edges = append(*edges, qoderLogEdge{parent: logs, file: leaf, name: "latest", info: info}) + if err != nil || !projectNativeRuntimeStableStat(observed.Sys(), info.Sys()) { + return snapshot, qoderLogRefused("leaf-identity") + } + identity, ok := info.Sys().(*syscall.Stat_t) + if !ok || info.Mode() != os.ModeSymlink|0777 || identity.Nlink != 1 || identity.Uid != uint32(os.Getuid()) || identity.Gid != uint32(os.Getgid()) || info.Size() <= 0 || info.Size() > 4096 { + return snapshot, qoderLogRefused("leaf-type-owner-or-size") + } + buffer := make([]byte, 4097) + count, err := unix.Readlinkat(int(logs.Fd()), "latest", buffer) + if err != nil || count <= 0 || count > 4096 || int64(count) != info.Size() { + return snapshot, qoderLogRefused("link-read-size") + } + parts, err := qoderLogTargetParts(buffer[:count]) + if err != nil { + return snapshot, err + } + snapshot.Target, err = qoderLogTarget(logs, parts, edges) + if err != nil { + return snapshot, err + } + if err := qoderLogCheckEdges(*edges); err != nil { + return snapshot, err + } + linkedRoot, err := os.Lstat(root) + if err != nil || projectNativeRuntimeParents(filepath.Join(root, "leaf")) != nil || !projectNativeRuntimeStableStat(snapshot.Profile.Sys(), linkedRoot.Sys()) { + return snapshot, qoderLogRefused("profile-identity") + } + sum := sha256.Sum256(buffer[:count]) + snapshot.Leaf, snapshot.TargetRel = info, filepath.Join("logs", filepath.Join(parts...)) + snapshot.Record = fmt.Sprintf("symlink:%s:target:%s:%d:%s", qoderLogMetadata(info), qoderLogMetadata(snapshot.Target), count, hex.EncodeToString(sum[:])) + return snapshot, nil +} + +func qoderLogMetadata(info os.FileInfo) string { + stat := info.Sys().(*syscall.Stat_t) + return fmt.Sprintf("%d:%d:%o:%d:%d:%d:%d:%d:%d:%d:%d", stat.Dev, stat.Ino, stat.Mode, stat.Nlink, stat.Uid, stat.Gid, stat.Size, stat.Mtim.Sec, stat.Mtim.Nsec, stat.Ctim.Sec, stat.Ctim.Nsec) +} + +func qoderVerifyOriginalLogClosure(root string, before qoderLogLinkSnapshot, inventory map[string]string) error { + kind := "file:" + if before.Target.IsDir() { + kind = "directory:" + } + if !strings.HasPrefix(inventory[before.TargetRel], kind) { + return qoderLogRefused("target-not-in-complete-inventory") + } + after, err := qoderReadOriginalLogLink(root, before.Leaf) + if err != nil { + return err + } + if before.Record != after.Record || before.TargetRel != after.TargetRel || !projectNativeRuntimeStableStat(before.Profile.Sys(), after.Profile.Sys()) || !projectNativeRuntimeStableStat(before.Logs.Sys(), after.Logs.Sys()) || !projectNativeRuntimeStableStat(before.Target.Sys(), after.Target.Sys()) { + return qoderLogRefused("snapshot-changed-during-inventory") + } + return nil +} + +func qoderLogFixture(t *testing.T, target string) (string, string) { + t.Helper() + root := filepath.Join(t.TempDir(), ".qoder") + if err := os.MkdirAll(filepath.Join(root, "logs", "run-private-target"), 0700); err != nil { + t.Fatal(err) + } + projectNativeRuntimeWrite(t, filepath.Join(root, "logs", "run-private-target", "log"), []byte("private log contents\n"), 0600) + link := filepath.Join(root, "logs", "latest") + if err := os.Symlink(target, link); err != nil { + t.Fatal(err) + } + return root, link +} + +func TestQoderOriginalLogLinkIsMetadataInsideCompleteFrozenProfile(t *testing.T) { + for _, target := range []string{"run-private-target", "run-private-target/log"} { + t.Run(target, func(t *testing.T) { + root, link := qoderLogFixture(t, target) + leaf, err := os.Lstat(link) + if err != nil { + t.Fatal(err) + } + before, err := projectNativeCLIQoderProfile(root) + if err != nil { + t.Fatal(err) + } + record := before[filepath.Join("logs", "latest")] + if !strings.HasPrefix(record, "symlink:") || strings.Contains(record, "run-private-target") || strings.Contains(record, "private log") || !strings.HasPrefix(before[filepath.Join("logs", "run-private-target", "log")], "file:") { + t.Fatal("link followed, target omitted or private link text captured") + } + after, err := projectNativeCLIQoderProfile(root) + if err != nil || !reflect.DeepEqual(before, after) { + t.Fatal("unchanged complete original profile did not preserve exact log link") + } + final, err := os.Lstat(link) + if err != nil || !projectNativeRuntimeStableStat(leaf.Sys(), final.Sys()) { + t.Fatal("inventory changed the original symlink identity") + } + }) + } +} + +func TestQoderOriginalLogLinkCannotGrantAliasOrForeignAuthority(t *testing.T) { + for _, target := range []string{"/foreign", "C:foreign", "../outside", ".", "run-private-target/../run-private-target", "latest", "missing", "run-private-target\n", strings.Repeat("x", 4095)} { + t.Run(fmt.Sprint(len(target), target[:min(len(target), 12)]), func(t *testing.T) { + root, _ := qoderLogFixture(t, target) + if _, err := projectNativeCLIQoderProfile(root); err == nil { + t.Fatal("unsafe log target admitted") + } else if strings.Contains(err.Error(), target) && len(target) > 12 { + t.Fatal("private target escaped fixed refusal") + } + }) + } +} + +func TestQoderOriginalLogLinkRejectsOtherLinksAliasesAndNonordinaryTargets(t *testing.T) { + for _, kind := range []string{"other-link", "link-hardlink", "target-hardlink", "target-alias", "ancestor-alias", "fifo"} { + t.Run(kind, func(t *testing.T) { + root, link := qoderLogFixture(t, "run-private-target") + qoderLogUnsafeFixture(t, root, link, kind) + if _, err := projectNativeCLIQoderProfile(root); err == nil { + t.Fatal("nonordinary original profile admitted", kind) + } + }) + } +} + +func qoderLogUnsafeFixture(t *testing.T, root, link, kind string) { + t.Helper() + target := filepath.Join(root, "logs", "run-private-target") + switch kind { + case "other-link": + if err := os.Symlink(target, filepath.Join(root, "other")); err != nil { + t.Fatal(err) + } + case "link-hardlink": + if err := os.Link(link, filepath.Join(t.TempDir(), "outside")); err != nil { + t.Fatal(err) + } + case "target-hardlink": + if err := os.Link(filepath.Join(target, "log"), filepath.Join(t.TempDir(), "outside")); err != nil { + t.Fatal(err) + } + default: + if err := os.Rename(target, filepath.Join(t.TempDir(), "outside")); err != nil { + t.Fatal(err) + } + if kind == "fifo" { + if err := syscall.Mkfifo(target, 0600); err != nil { + t.Fatal(err) + } + return + } + if err := os.Symlink(t.TempDir(), target); err != nil { + t.Fatal(err) + } + if kind == "ancestor-alias" { + if err := os.Remove(link); err != nil { + t.Fatal(err) + } + if err := os.Symlink("run-private-target/child", link); err != nil { + t.Fatal(err) + } + } + } +} + +func TestQoderOriginalLogLinkNeverExemptsNativeLogMutationOrRotation(t *testing.T) { + for _, kind := range []string{"bytes", "mode", "empty-directory", "new-log", "rotation", "same-target-rotation"} { + t.Run(kind, func(t *testing.T) { + root, link := qoderLogFixture(t, "run-private-target") + before, err := projectNativeCLIQoderProfile(root) + if err != nil { + t.Fatal(err) + } + info, err := os.Lstat(link) + if err != nil { + t.Fatal(err) + } + snapshot, err := qoderReadOriginalLogLink(root, info) + if err != nil { + t.Fatal(err) + } + qoderLogMutateFixture(t, root, link, kind) + after, err := projectNativeCLIQoderProfile(root) + if err == nil && reflect.DeepEqual(before, after) { + t.Fatal("native log mutation became a profile exception") + } + if kind == "rotation" || kind == "same-target-rotation" { + if err := qoderVerifyOriginalLogClosure(root, snapshot, before); err == nil { + t.Fatal("original held link identity was adopted after rotation") + } + } + }) + } +} + +func qoderLogMutateFixture(t *testing.T, root, link, kind string) { + t.Helper() + path := filepath.Join(root, "logs", "run-private-target", "log") + var err error + switch kind { + case "bytes": + err = os.WriteFile(path, []byte("changed private contents\n"), 0600) + case "mode": + err = os.Chmod(path, 0640) + case "empty-directory": + err = os.Mkdir(filepath.Join(root, "logs", "empty"), 0700) + case "new-log": + err = os.WriteFile(filepath.Join(root, "logs", "new"), []byte("new private log\n"), 0600) + case "rotation": + if err = os.Mkdir(filepath.Join(root, "logs", "next-run"), 0700); err == nil { + err = os.Remove(link) + } + if err == nil { + err = os.Symlink("next-run", link) + } + case "same-target-rotation": + if err = os.Remove(link); err == nil { + err = os.Symlink("run-private-target", link) + } + } + if err != nil { + t.Fatal(err) + } +} + +func TestQoderOriginalLogLinkRequiresTargetInTheOrdinaryInventory(t *testing.T) { + root, link := qoderLogFixture(t, "run-private-target") + info, err := os.Lstat(link) + if err != nil { + t.Fatal(err) + } + snapshot, err := qoderReadOriginalLogLink(root, info) + if err != nil { + t.Fatal(err) + } + if err := qoderVerifyOriginalLogClosure(root, snapshot, map[string]string{}); err == nil { + t.Fatal("opaque log target without its ordinary closure admitted") + } + body, err := os.ReadFile(filepath.Join(root, "logs", "run-private-target", "log")) + if err != nil || !bytes.Equal(body, []byte("private log contents\n")) { + t.Fatal("link inventory changed private target contents") + } +} From 083928a5dbcb86eed07235cfe734db34fd2d82cc 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:26:18 +0200 Subject: [PATCH 09/30] docs(qoder): plan bounded original profile delta diagnostic --- .../qoder-original-profile-delta-plan.md | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-original-profile-delta-plan.md diff --git a/docs/plans/project-install-e2e/qoder-original-profile-delta-plan.md b/docs/plans/project-install-e2e/qoder-original-profile-delta-plan.md new file mode 100644 index 000000000..32370e8fa --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-original-profile-delta-plan.md @@ -0,0 +1,127 @@ +# Qoder catalog: measure the refused original-profile delta + +## Exact new genuine observation + +Start from published PR523 `ccd9dc9e12724b43bae001b3d3573d95c7994297`, +tree `ac550cf4f510b86b3f0405434a207d05883cbb5d`, identical to reviewed local +`213500c02df3b2cb06ff4339007633090a01d5d9`. A fresh actual git-ref read +still returned that source. Preserve the separate local lifecycle-design branch +and every prior plan/code/native capture. This is a new diagnostic-only slice. + +Genuine run `40459286447`, native job `122045321108`, passed hosted clean-source +admission and the new original-log-link checks. The original observed link has +42 target bytes, original UID/GID1001, nlink1 and a contained ordinary owned +directory target. The helper admitted that structure without following the +link; its exact target text stayed private. This is actual structural evidence, +not proof of a native project registry or safe future log mutation. + +Public add-a exited0/error=false/deadline=false in564ms. Native catalog then +exited0/error=false/deadline=false in1135ms, stderr0. Its766-byte stdout SHA256 +is `a07f47c35d5069aa7ed09e144c0d52a399e4381c991751af9bc7068161daea45`. +The actual strict JSON is a single flag-scoped row with exact name/version, +id `uap-qoder-attempt@flag`, source `uap-qoder-attempt@inline`, enabled=true, +canDisable=false and the exact receipt-selected native-view installPath. It +describes the one authored skill and empty agents/MCP/commands/hooks arrays. +Those are observed catalog fields, not a new approved runtime parser, loaded +skill-body proof, trust grant, MCP exchange or authenticated model result. + +The second complete original-profile inventory succeeded structurally, then +the existing exact before/after map comparison failed: +`Qoder catalog changed the complete original user profile/cache`. +Startup did not run. The capture does not identify which entries changed or +whether it was log content, link rotation, new files, registry/cache or settings. +Do not claim ordinary logging as the captured cause or waive any profile entry. + +Native12714971725 outer SHA256 +`162c5d94d7556e982a6433d1a1a93ad897382fb66b4d3ccc1dcbea021cb66c91`, +inner `296c4c9a3e900f9bc213f448486756d7588f247f438ecd0416327224233da9d5`, +has16 members. Frontend12714273659 outer +`a53edb7f7eee4edc72a00c90268389d107585d5fdac2c8a6d18f2ce0e44e4f8c`, +inner `41e23e9a74341b8290a3a195817f23fe86e836f6d7d15108d02c229bc0138de6`, +has25. API sizes/hashes, bounded canonical unique ordinary ZIP closure/CRCs, +exact committed source/tree/empty patch list, clean-source streams and all24 +frontend command-stream hashes were independently checked. All five native +qualifications, vendor authentication and real-model flags remain false. + +## Narrow held-map diagnostic after two independent plan GOs + +Scope: the existing shared CLI-attempt file's exact unequal-profile-map branch, +one new private Linux Qoder delta helper/contracts file, and this plan/validation +record. Do not change inventories, target acceptance, native args/cwd, reads, +budgets, original user authority, catalog JSON acceptance, pins or other clients. + +Immediately before its unchanged fatal, add a small optional original-profile +delta object to the existing runtime summary, derived ONLY from the two already +accepted complete inventory maps. No filesystem root is passed to the formatter; +it performs no read/stat/readlink/open, recursion, follow, repair or baseline +renewal. Only this post-catalog unequal-map branch uses it. An inventory error +continues to fail through its existing path without this projection. + +The finite schema reports status, before/after entry counts, exact totals for +added/removed/changed entries and fixed area counts. Areas are exact logs/latest +(`log-latest`), other descendants of logs (`logs`), exact settings.json +(`settings`), plugins/cache and its descendants (`plugin-cache`), other plugins +entries (`plugins`), cache and its descendants (`cache`), and everything else +(`other`). Area is only a static category; it confers no mutation permission. +Every changed entry contributes to the counts, including directories and links. + +At most three deterministic changed-entry details may be included. Select them +by the fixed area order above and then SHA256 of exact relative-key bytes; +never use raw names as serialized keys or expose their ordering/text. Each +detail has only that key SHA256, static area, added/removed/changed operation +and finite before/after metadata. The two input maps remain byte-identical. +An explicit omitted_count/status distinguishes a larger delta from a complete +three-entry report. It is a bounded diagnostic, never an alternate proof that +unreported changes are harmless. Input maps remain subject to the existing +4096-entry inventory cap; reject an oversized synthetic input rather than +processing an unbounded union. A union is therefore at most8192 keys. + +Metadata contains only absent/directory/file/symlink kind, existing numeric mode +and file byte count where present, and the existing file-content SHA256. For the +special link record, expose only a SHA256 of its existing complete numeric/ +target-digest record, not its raw record or target text. This retains a signal +for same-target inode/timestamp changes without adding a target read. Parse +only the existing fixed inventory record forms; malformed records produce a +fixed diagnostic-refused status and never arbitrary strings/errors. No body, +relative/absolute path, filename, target text, credential, unknown key or raw +parser/OS error is serialized. Hashes must be fixed lowercase SHA256 values. + +Keep the complete serialized delta at most2KiB, with a fixed bounded fallback +if serialization or its bound refuses. All exact count/entry-limit/refusal +statuses remain diagnostic facts. Do not truncate native output, change the +native archive/member/byte limits, ignore logs, replace before with after, +filter either profile map or change the original full-map fatal/flags. + +## Meaningful controls and genuine follow-up + +Use real owned temporary Qoder profiles and the actual existing complete +inventory before/after explicit controlled mutations. Cover unchanged maps, +log file byte/mode changes, added/removed logs and empty directories, settings +and plugin-cache changes, exact latest rotation and same-target rotation. +Check exact count/area/operation/type/size/hash facts against the held records, +with bounded deterministic detail selection and an explicit omitted count for +more than three changes. Private/control/long path names, target text and file +body canaries must never appear in serialized output. Taking the diagnostic +must not further change the already mutated profile's metadata or bytes. + +Synthetic malformed records, oversized maps and the maximal admitted metadata +shape must exercise fixed refusal/bounds without raw-string leakage. Mutate the +returned diagnostic, where applicable, to prove no alias into either original +map. Retain every existing Qoder ordinary-profile mutation, bounded refusal, +log-link no-follow/closure/rotation, opaque catalog and fixed argv/scope contract. +No positive hosted guard or native process is run locally. + +Obtain two independent plan GOs before source edits, then independent complete +code reviews and actual focused Qoder/shared contracts plus both affected and +unfiltered size/architecture lint gates in a granted exclusive slot. Preserve +the current source-bound16-member failure envelope; the unchanged Python +admission can replay it. Root alone publishes to existing PR523 using a fresh +non-forced expected-head lease. No release/new PR/VS work. + +A new genuine capture must establish the actual write class and metadata. +Any later preservation-policy, expected-fixture or logging-authority amendment +needs its own concrete source/evidence-backed plan and reviews. The current +catalog demonstrates exact flag-view inventory, while its original-profile +write still blocks read-only catalog preservation and all later native startup/ +full-cycle claims. The earlier component-cycle design remains conditional; +this diagnostic neither promotes its catalog schema nor implements that cycle. From 94e0c6b9807ed5f37301eca9927b3f3227c0d0cb 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:26:20 +0200 Subject: [PATCH 10/30] test(qoder): diagnose exact original profile delta --- .../qoder-original-profile-delta-plan.md | 25 ++ ...plugins_project_native_cli_attempt_test.go | 3 + .../agentplugins_qoder_profile_delta_test.go | 400 ++++++++++++++++++ 3 files changed, 428 insertions(+) create mode 100644 repotests/agentplugins_qoder_profile_delta_test.go diff --git a/docs/plans/project-install-e2e/qoder-original-profile-delta-plan.md b/docs/plans/project-install-e2e/qoder-original-profile-delta-plan.md index 32370e8fa..d54ec3649 100644 --- a/docs/plans/project-install-e2e/qoder-original-profile-delta-plan.md +++ b/docs/plans/project-install-e2e/qoder-original-profile-delta-plan.md @@ -125,3 +125,28 @@ catalog demonstrates exact flag-view inventory, while its original-profile write still blocks read-only catalog preservation and all later native startup/ full-cycle claims. The earlier component-cycle design remains conditional; this diagnostic neither promotes its catalog schema nor implements that cycle. + +## Validation record (2026-10-27) + +Root and shell independently approved the exact plan above before source edits. +Both reviewed the complete formatter, unchanged fatal boundary and actual-file +controls. Their findings corrected the formatter's file-record limit to the +existing inventory's 64MiB and added independently expected detail metadata, +link-record hashes and deterministic selection controls. Both approved the +corrected code; the sole later source delta uses Go1.22 integer range for the +4097-entry refusal test after the affected lint reported `intrange`. + +The final focused inert Qoder, log-link and shared argv/scope contract selection +passed in 0.049s with Go1.26.10, `-p=1`, `-count=1` and a 90-second timeout. +The all-enabled affected-line lint from plan commit `85c6e4c6` reported zero +issues. The unfiltered whole-file `revive,funlen,gocyclo,gocognit,dupl,depguard` +gate also reported zero issues. Gofmt and whitespace checks are clean. + +Final SHA256 values are `9af92f1e609ea12922851664058cd807950bab695e084311bef8cb9f462ca392` +for the existing shared attempt file and +`32a95141f2cd4cb51930ddea5d5430be8cfe70b4fe91284db326a511dafe4740` +for the new formatter/contracts file. The unchanged Python admission replay +accepted the audited source-bound `ccd9dc9e` 16-member failure with all five +qualifications, vendor authentication and real-model qualification false. +No vendor client or positive hosted admission ran locally. A fresh genuine +hosted capture remains necessary to identify the original-profile mutation. diff --git a/repotests/agentplugins_project_native_cli_attempt_test.go b/repotests/agentplugins_project_native_cli_attempt_test.go index 34b4144c3..d49dec342 100644 --- a/repotests/agentplugins_project_native_cli_attempt_test.go +++ b/repotests/agentplugins_project_native_cli_attempt_test.go @@ -806,6 +806,9 @@ func projectNativeCLIRuntimeAttempt(t *testing.T, client string) { result := f.command(t, "native-catalog", f.base.Project, []string{f.client, "plugins", "list", "--json", "--plugin-dir", f.view}, 30*time.Second) after, err := projectNativeCLIQoderProfile(profile) if err != nil || !reflect.DeepEqual(after, before) { + if err == nil { + f.evidence["qoder_original_profile_change"] = qoderOriginalProfileDelta(before, after) + } t.Fatal("Qoder catalog changed the complete original user profile/cache") } if len(result.Stderr) != 0 { diff --git a/repotests/agentplugins_qoder_profile_delta_test.go b/repotests/agentplugins_qoder_profile_delta_test.go new file mode 100644 index 000000000..b114bcc95 --- /dev/null +++ b/repotests/agentplugins_qoder_profile_delta_test.go @@ -0,0 +1,400 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + "reflect" + "sort" + "strconv" + "strings" + "testing" +) + +type qoderDeltaRecord struct { + Kind string `json:"kind"` + Mode string `json:"mode,omitempty"` + Bytes *int64 `json:"bytes,omitempty"` + ContentSHA256 string `json:"content_sha256,omitempty"` + RecordSHA256 string `json:"record_sha256,omitempty"` +} +type qoderDeltaEntry struct { + KeySHA256 string `json:"key_sha256"` + Area string `json:"area"` + Operation string `json:"operation"` + Before qoderDeltaRecord `json:"before"` + After qoderDeltaRecord `json:"after"` + rank int +} +type qoderProfileDelta struct { + Status string `json:"status"` + BeforeEntries int `json:"before_entries"` + AfterEntries int `json:"after_entries"` + Added int `json:"added"` + Removed int `json:"removed"` + Changed int `json:"changed"` + Areas map[string]int `json:"areas"` + OmittedCount int `json:"omitted_count"` + Entries []qoderDeltaEntry `json:"entries"` +} + +func qoderOriginalProfileDelta(before, after map[string]string) qoderProfileDelta { + delta := qoderProfileDelta{Status: "inventory-bound"} + if len(before) > 4096 || len(after) > 4096 { + return delta + } + delta.BeforeEntries, delta.AfterEntries = len(before), len(after) + delta.Areas = map[string]int{"log-latest": 0, "logs": 0, "settings": 0, "plugin-cache": 0, "plugins": 0, "cache": 0, "other": 0} + delta.Entries = []qoderDeltaEntry{} + keys := make(map[string]bool, len(before)+len(after)) + for key := range before { + keys[key] = true + } + for key := range after { + keys[key] = true + } + for key := range keys { + old, had := before[key] + current, has := after[key] + if had && has && old == current { + continue + } + entry, ok := qoderDeltaDetail(key, old, current, had, has) + if !ok { + return qoderProfileDelta{Status: "record-refused"} + } + switch entry.Operation { + case "added": + delta.Added++ + case "removed": + delta.Removed++ + case "changed": + delta.Changed++ + } + delta.Areas[entry.Area]++ + delta.Entries = append(delta.Entries, entry) + } + return qoderDeltaBound(delta) +} + +func qoderDeltaBound(delta qoderProfileDelta) qoderProfileDelta { + sort.Slice(delta.Entries, func(i, j int) bool { + a, b := delta.Entries[i], delta.Entries[j] + return a.rank < b.rank || (a.rank == b.rank && a.KeySHA256 < b.KeySHA256) + }) + delta.Status = "captured" + if len(delta.Entries) > 3 { + delta.OmittedCount, delta.Status = len(delta.Entries)-3, "entry-limit" + delta.Entries = delta.Entries[:3] + } + if raw, err := json.Marshal(delta); err != nil || len(raw) > 2<<10 { + return qoderProfileDelta{Status: "diagnostic-bound"} + } + return delta +} + +func qoderDeltaDetail(key, old, current string, had, has bool) (qoderDeltaEntry, bool) { + area, rank := qoderDeltaArea(key) + entry := qoderDeltaEntry{KeySHA256: qoderDeltaHash(key), Area: area, rank: rank, Operation: "changed"} + var beforeOK, afterOK bool + entry.Before, beforeOK = qoderDeltaValue(old, had, area == "log-latest") + entry.After, afterOK = qoderDeltaValue(current, has, area == "log-latest") + if !had { + entry.Operation = "added" + } + if !has { + entry.Operation = "removed" + } + return entry, beforeOK && afterOK +} + +func qoderDeltaArea(key string) (string, int) { + switch { + case key == "logs/latest": + return "log-latest", 0 + case key == "logs" || strings.HasPrefix(key, "logs/"): + return "logs", 1 + case key == "settings.json": + return "settings", 2 + case key == "plugins/cache" || strings.HasPrefix(key, "plugins/cache/"): + return "plugin-cache", 3 + case key == "plugins" || strings.HasPrefix(key, "plugins/"): + return "plugins", 4 + case key == "cache" || strings.HasPrefix(key, "cache/"): + return "cache", 5 + default: + return "other", 6 + } +} + +func qoderDeltaValue(record string, present, link bool) (qoderDeltaRecord, bool) { + value := qoderDeltaRecord{Kind: "absent"} + if !present { + return value, true + } + if len(record) > 1024 { + return value, false + } + parts := strings.Split(record, ":") + if len(parts) < 2 { + return value, false + } + if parts[0] == "symlink" { + return qoderDeltaRecord{Kind: "symlink", RecordSHA256: qoderDeltaHash(record)}, link && qoderDeltaLinkValid(parts) + } + mode, err := strconv.ParseUint(parts[1], 8, 32) + if err != nil || strconv.FormatUint(mode, 8) != parts[1] { + return value, false + } + value.Kind, value.Mode = parts[0], parts[1] + switch { + case value.Kind == "directory" && len(parts) == 2: + return value, os.FileMode(mode)&os.ModeType == os.ModeDir + case value.Kind == "file" && len(parts) == 4: + size, err := strconv.ParseInt(parts[2], 10, 64) + value.Bytes, value.ContentSHA256 = &size, parts[3] + return value, err == nil && size >= 0 && size <= 64<<20 && strconv.FormatInt(size, 10) == parts[2] && os.FileMode(mode).IsRegular() && qoderDeltaSHAValid(parts[3]) + default: + return value, false + } +} + +func qoderDeltaLinkValid(parts []string) bool { + if len(parts) != 26 || parts[12] != "target" || parts[3] != "120777" || parts[4] != "1" || !qoderDeltaSHAValid(parts[25]) { + return false + } + for index := 1; index < 25; index++ { + if index == 12 { + continue + } + base := 10 + if index == 3 || index == 15 { + base = 8 + } + if index == 8 || index == 10 || index == 20 || index == 22 { + if _, err := strconv.ParseInt(parts[index], base, 64); err != nil { + return false + } + } else if _, err := strconv.ParseUint(parts[index], base, 64); err != nil { + return false + } + } + size, err := strconv.ParseUint(parts[24], 10, 64) + return err == nil && size > 0 && size <= 4096 && parts[7] == parts[24] +} + +func qoderDeltaHash(value string) string { + sum := sha256.Sum256([]byte(value)) + return hex.EncodeToString(sum[:]) +} + +func qoderDeltaSHAValid(value string) bool { + if len(value) != 64 || strings.ToLower(value) != value { + return false + } + _, err := hex.DecodeString(value) + return err == nil +} + +func qoderDeltaFixture(t *testing.T) string { + t.Helper() + root, _ := qoderLogFixture(t, "run-private-target") + for _, path := range []string{"settings.json", "plugins/cache/private-entry", "cache/PRIVATE-KEY\n\x1b"} { + projectNativeRuntimeWrite(t, filepath.Join(root, path), []byte("PRIVATE-BODY\n"), 0600) + } + return root +} + +func qoderDeltaSnapshot(t *testing.T, root string) map[string]string { + t.Helper() + result, err := projectNativeCLIQoderProfile(root) + if err != nil { + t.Fatal(err) + } + return result +} + +func qoderDeltaContract(t *testing.T, before, after map[string]string) qoderProfileDelta { + t.Helper() + old, _ := json.Marshal(before) + current, _ := json.Marshal(after) + delta := qoderOriginalProfileDelta(before, after) + raw, err := json.Marshal(delta) + if err != nil || len(raw) > 2<<10 || bytes.ContainsAny(raw, "\r\n") { + t.Fatal("Qoder delta serialization bound") + } + for _, private := range []string{"PRIVATE-KEY", "PRIVATE-BODY", "run-private-target", "private-entry", "logs/latest", "settings.json"} { + if bytes.Contains(raw, []byte(private)) { + t.Fatal("Qoder delta exposed a private path, target or body") + } + } + unchanged, _ := json.Marshal(before) + stable, _ := json.Marshal(after) + if !bytes.Equal(old, unchanged) || !bytes.Equal(current, stable) { + t.Fatal("Qoder delta adopted or mutated either held inventory") + } + return delta +} + +func TestQoderProfileDeltaMeasuresActualFileAndLinkChangesWithoutExemptingThem(t *testing.T) { + for _, tc := range []struct { + kind string + added, removed, changed int + areas map[string]int + }{ + {"unchanged", 0, 0, 0, map[string]int{}}, {"bytes", 0, 0, 1, map[string]int{"logs": 1}}, {"mode", 0, 0, 1, map[string]int{"logs": 1}}, + {"new-log", 1, 0, 0, map[string]int{"logs": 1}}, {"empty-directory", 1, 0, 0, map[string]int{"logs": 1}}, + {"rotation", 1, 0, 1, map[string]int{"logs": 1, "log-latest": 1}}, {"same-target-rotation", 0, 0, 1, map[string]int{"log-latest": 1}}, + {"settings", 0, 0, 1, map[string]int{"settings": 1}}, {"plugin-cache", 0, 0, 1, map[string]int{"plugin-cache": 1}}, + {"private-name", 0, 0, 1, map[string]int{"cache": 1}}, {"removed-log", 0, 1, 1, map[string]int{"logs": 1, "log-latest": 1}}, + } { + t.Run(tc.kind, func(t *testing.T) { + root := qoderDeltaFixture(t) + before := qoderDeltaSnapshot(t, root) + qoderDeltaMutate(t, root, tc.kind) + after := qoderDeltaSnapshot(t, root) + unchanged := qoderDeltaUnchanged(t, root) + delta := qoderDeltaContract(t, before, after) + if delta.Status != "captured" || delta.OmittedCount != 0 || delta.Added != tc.added || delta.Removed != tc.removed || delta.Changed != tc.changed || delta.BeforeEntries != len(before) || delta.AfterEntries != len(after) { + t.Fatal("Qoder exact actual mutation counts differ") + } + for area, count := range delta.Areas { + if count != tc.areas[area] { + t.Fatal("Qoder exact static mutation area differs") + } + } + qoderDeltaExactDetails(t, tc.kind, delta, before, after) + unchanged() + if !reflect.DeepEqual(after, qoderDeltaSnapshot(t, root)) || (tc.kind != "unchanged" && reflect.DeepEqual(before, after)) { + t.Fatal("diagnostic changed a file or exempted the original unequal maps") + } + }) + } +} + +func qoderDeltaExactDetails(t *testing.T, kind string, delta qoderProfileDelta, before, after map[string]string) { + t.Helper() + size, changedSize, newSize := int64(21), int64(25), int64(16) + original := qoderDeltaRecord{Kind: "file", Mode: "600", Bytes: &size, ContentSHA256: "b049c3e0881bb9591f621e0e6faf7e5c3943177301a7d6c93ff8985b83e9bee5"} + file := qoderDeltaEntry{KeySHA256: "cd593b10c09608bd0d708f4ef261332f353942a53cbae4b3dede51b71339c2ab", Area: "logs", Operation: "changed", Before: original, After: original} + switch kind { + case "bytes": + file.After.Bytes, file.After.ContentSHA256 = &changedSize, "80635f06d1c96d49da1437c56a012c34aa38c378246fc206e10ececde076ecd5" + qoderDeltaRequireDetail(t, delta, file) + case "mode": + file.After.Mode = "640" + qoderDeltaRequireDetail(t, delta, file) + case "new-log": + qoderDeltaRequireDetail(t, delta, qoderDeltaEntry{KeySHA256: "22c92e7171f6f51a9ad9c4e8985ffeaccd63319a7d9d2f724560828ff9335de9", Area: "logs", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, After: qoderDeltaRecord{Kind: "file", Mode: "600", Bytes: &newSize, ContentSHA256: "567f723eb74f37a466e683a7e7605a76fb818db9fa929e93c611751dd3ff45a3"}}) + case "removed-log": + file.Operation, file.After = "removed", qoderDeltaRecord{Kind: "absent"} + qoderDeltaRequireDetail(t, delta, file) + } + if kind == "rotation" || kind == "same-target-rotation" || kind == "removed-log" { + old, current := sha256.Sum256([]byte(before["logs/latest"])), sha256.Sum256([]byte(after["logs/latest"])) + qoderDeltaRequireDetail(t, delta, qoderDeltaEntry{KeySHA256: "22abff24b603cb9288fe82d7f92a98a22a4e3b648b8b0e07db5bc03da0901228", Area: "log-latest", Operation: "changed", Before: qoderDeltaRecord{Kind: "symlink", RecordSHA256: hex.EncodeToString(old[:])}, After: qoderDeltaRecord{Kind: "symlink", RecordSHA256: hex.EncodeToString(current[:])}}) + } +} + +func qoderDeltaRequireDetail(t *testing.T, delta qoderProfileDelta, expected qoderDeltaEntry) { + t.Helper() + for _, entry := range delta.Entries { + if entry.KeySHA256 == expected.KeySHA256 { + entry.rank = 0 + if !reflect.DeepEqual(entry, expected) { + t.Fatal("Qoder exact independent operation/kind/mode/size/digest detail differs") + } + return + } + } + t.Fatal("Qoder exact independently expected detail missing") +} + +func qoderDeltaMutate(t *testing.T, root, kind string) { + t.Helper() + var err error + switch kind { + case "unchanged": + return + case "settings", "plugin-cache", "private-name": + path := map[string]string{"settings": "settings.json", "plugin-cache": "plugins/cache/private-entry", "private-name": "cache/PRIVATE-KEY\n\x1b"}[kind] + err = os.WriteFile(filepath.Join(root, path), []byte("CHANGED-PRIVATE-BODY\n"), 0600) + case "removed-log": + err = os.Remove(filepath.Join(root, "logs/run-private-target/log")) + default: + qoderLogMutateFixture(t, root, filepath.Join(root, "logs/latest"), kind) + } + if err != nil { + t.Fatal(err) + } +} + +func qoderDeltaUnchanged(t *testing.T, root string) func() { + t.Helper() + held := map[string]os.FileInfo{} + if err := filepath.WalkDir(root, func(path string, _ os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + info, err := os.Lstat(path) + held[path] = info + return err + }); err != nil { + t.Fatal(err) + } + return func() { + t.Helper() + for path, info := range held { + after, err := os.Lstat(path) + if err != nil || !projectNativeRuntimeStableStat(info.Sys(), after.Sys()) { + t.Fatal("Qoder diagnostic changed held profile metadata") + } + } + } +} + +func TestQoderProfileDeltaBoundsDetailsAndRefusesMalformedHeldRecords(t *testing.T) { + before, after := map[string]string{}, map[string]string{} + mode := strconv.FormatUint(uint64(os.ModeSetuid|os.ModeSetgid|os.ModeSticky|0777), 8) + for _, name := range []string{"PRIVATE-KEY-one", "PRIVATE-KEY-two", "PRIVATE-KEY-three", "PRIVATE-KEY-four"} { + before["cache/"+name] = "file:" + mode + ":67108864:" + strings.Repeat("a", 64) + after["cache/"+name] = "file:" + mode + ":67108864:" + strings.Repeat("b", 64) + } + delta := qoderDeltaContract(t, before, after) + if delta.Status != "entry-limit" || delta.Changed != 4 || delta.Areas["cache"] != 4 || delta.OmittedCount != 1 || len(delta.Entries) != 3 { + t.Fatal("larger delta was silently truncated") + } + if again := qoderOriginalProfileDelta(before, after); !reflect.DeepEqual(delta, again) { + t.Fatal("hashed bounded selection is not deterministic") + } + wantDigests := []string{"53dc54f1a425947b2f6b8175aee8b2cc6cd4abe4a5c5a57162bf0f834419e346", "62b14fde114d9eb0efda38990c4afb861568275b5ccb6d929f123f9e0ee2c140", "8018caba2e34bf352146720b5528dfa3b229b0899a268f4e4035b3107f24c9c5"} + size := int64(64 << 20) + for index, digest := range wantDigests { + qoderDeltaRequireDetail(t, delta, qoderDeltaEntry{KeySHA256: digest, Area: "cache", Operation: "changed", Before: qoderDeltaRecord{Kind: "file", Mode: mode, Bytes: &size, ContentSHA256: strings.Repeat("a", 64)}, After: qoderDeltaRecord{Kind: "file", Mode: mode, Bytes: &size, ContentSHA256: strings.Repeat("b", 64)}}) + if delta.Entries[index].KeySHA256 != digest { + t.Fatal("Qoder deterministic selected digest order differs") + } + } + delta.Areas["cache"] = -1 + delta.Entries[0].KeySHA256 = "PRIVATE-KEY" + if len(before) != 4 || len(after) != 4 || after["cache/PRIVATE-KEY-one"] != "file:"+mode+":67108864:"+strings.Repeat("b", 64) { + t.Fatal("returned diagnostic aliases an input inventory") + } + for _, record := range []string{"PRIVATE-BODY", "file:600:1:PRIVATE-BODY", "directory:600", "file:600:-1:" + strings.Repeat("a", 64), "file:600:67108865:" + strings.Repeat("a", 64), "symlink:PRIVATE-BODY"} { + if got := qoderDeltaContract(t, nil, map[string]string{"PRIVATE-KEY": record}); got.Status != "record-refused" || len(got.Entries) != 0 { + t.Fatal("malformed private held record escaped refusal") + } + } + oversized := map[string]string{} + for index := range 4097 { + oversized[strconv.Itoa(index)] = "directory:20000000700" + } + if got := qoderOriginalProfileDelta(oversized, nil); got.Status != "inventory-bound" { + t.Fatal("oversized held inventory escaped original count cap") + } +} From ed70a870fcde1bc6bc9327dede36c91e80f10f63 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:17:37 +0200 Subject: [PATCH 11/30] docs(qoder): recover reviewed other profile detail priority plan --- ...qoder-other-profile-delta-priority-plan.md | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-other-profile-delta-priority-plan.md diff --git a/docs/plans/project-install-e2e/qoder-other-profile-delta-priority-plan.md b/docs/plans/project-install-e2e/qoder-other-profile-delta-priority-plan.md new file mode 100644 index 000000000..bc04e9a93 --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-other-profile-delta-priority-plan.md @@ -0,0 +1,88 @@ +# Qoder: prioritize the measured unknown profile changes in the same diagnostic + +## Exact published baseline and newly closed result + +Current PR523 head `94e0c6b9807ed5f37301eca9927b3f3227c0d0cb` has tree +`358e54f9e94487c33c73d56306747e0bce348124`, identical to reviewed local +`854e769840b4da9d459fe24738bb33e83961c3af`. Preserve the original plan, +link policy, exact complete profile maps, three-entry diagnostic cap and all +native/public/source/authentication/cleanup/refusal gates. + +Genuine run40467129367/native job122068137876 passed public add-a, then native +catalog exit0/error=false/deadline=false in2999ms. Its catalog remains one raw +row; ordinary startup did not run. The original complete-profile comparison +still fails, as required. The new diagnostic measures before_entries8 and +after_entries18, added10/changed1/removed0. Exact area counts are log-latest1, +logs3, other7; settings, plugin-cache, plugins and cache are zero. + +The prior fixed area ordering selected the changed latest link plus two new +log files (994 and9133 bytes) and omitted8 changes. Thus none of the seven +other-area entries was projected. The output does not establish those paths, +kinds or purposes and cannot justify a logs-only exception. Do not assume the +other changes are harmless defaults, cache, identity, authentication or state. + +The closed native artifact12717861327 has16 members: outer SHA256 +`ef60ec6cea0222b828716a908ba04708dae5bd6dde80543df6d47aab13d2479f`, +inner `5a25c6fba75f9d78a2e91756f81e90acb0912bc6190a98cdd492396b87e0fd40`. +The closed frontend12717068850 has25 members: outer +`50ba00d16869be9e501c74eb7eb6e2faeb09c09a262102daad81ed3aeaef5f67`, +inner `8e31bc5894ee52df8cae4d07d1c8ed71e86af73adcc48ef3e23a404659ec96c3`. +API size/hash, canonical unique ordinary ZIP membership/CRC, source94e/tree358, +empty source/build-clean streams, all24 frontend command-stream byte/hash joins, +exact native source identity/empty patch and unchanged sixteen-member failure +admission were independently verified. All five qualifications, vendor auth, +real model and runtime_attempt_complete remain false. The finite audit lives +under /tmp/uap-qoder-current-94e/source-and-stream-audit.json. + +## Narrow selection-only amendment after two independent plan GOs + +Change only the existing delta formatter's detail ordering and one new bounded +Linux contract file, plus this plan's factual validation record. Do not change +the shared fatal hook, profile/link readers or accepted record parser. Preserve +all counts, area names, SHA projections, record grammar, 4096-map/8192-union +bounds, three-entry limit, omitted count/status and complete2KiB fallback. + +When the already computed exact other-area count is nonzero, sort other-area +details before every known-area detail. Within other retain exact key-SHA256 +ordering. After that subset retain the existing fixed relative order and +key-SHA256 tie-break for latest/logs/settings/plugin-cache/plugins/cache. With +zero other changes the old ordering remains byte-identical. This changes only +which already parsed details fit in the same bounded diagnostic; no new field, +raw name, private path, body, target, error or filesystem read is introduced. + +All changes continue contributing to exact totals even when omitted. In the +observed eleven-change shape, only three of seven other details can fit and +omitted_count remains8. Report that incompleteness truthfully; do not describe +three samples as a complete account of seven entries. No baseline adoption, +ignored area, native schema approval or logging permission is authorized. + +## Meaningful controls and genuine follow-up + +Use real owned Qoder fixture profiles and the unchanged complete inventory. +Add seven private-name ordinary files plus known log changes; independently +state the selected first three key digests and their exact added/file/mode/ +size/content-digest metadata, total/area/omitted counts and deterministic order. +Also cover one other change with known changes to prove the remaining slots +retain their original order, no-other changes to preserve old selection, +reverse map insertion order and returned-output/input-map non-aliasing. + +Retain original malformed/bound/maximal-record/link-rotation/64MiB/private +serialization contracts. The actual profile's bytes and metadata must remain +unchanged by projection, and complete diagnostic serialization stays below +2KiB. Keep the existing400-line file within its size gate by replacing only +the old comparator return with a call to a small pure comparator in the new +helper/contracts file. Do not reshuffle existing tests or suppress a lint gate. + +After root and another independent plan GO, implement the finite ordering +change, obtain independent complete code reviews and run focused existing/new +Qoder/shared inert contracts, all-enabled affected-line lint and unfiltered +whole-file size/architecture lint in an explicitly granted exclusive slot. +No local vendor/client or positive hosted guard. Root alone publishes reviewed +incremental commits to the existing PR523 using a fresh non-forced head lease. + +A new genuine source-bound capture must identify the selected other-area +metadata. Further diagnosis/source research may still be needed for omitted +entries or to attribute hashed names; no preservation-policy change follows +from this selection amendment. The original fatal profile comparison and all +qualification/authentication/model flags remain unchanged. No release, new PR +or VS Code work. From faf2fef5ae4c094698b6818b4b6380f11b0bd351 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:17:40 +0200 Subject: [PATCH 12/30] test(qoder): prioritize measured other profile changes --- ...qoder-other-profile-delta-priority-plan.md | 25 ++++ .../agentplugins_qoder_delta_priority_test.go | 107 ++++++++++++++++++ .../agentplugins_qoder_profile_delta_test.go | 2 +- 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 repotests/agentplugins_qoder_delta_priority_test.go diff --git a/docs/plans/project-install-e2e/qoder-other-profile-delta-priority-plan.md b/docs/plans/project-install-e2e/qoder-other-profile-delta-priority-plan.md index bc04e9a93..027dde7a1 100644 --- a/docs/plans/project-install-e2e/qoder-other-profile-delta-priority-plan.md +++ b/docs/plans/project-install-e2e/qoder-other-profile-delta-priority-plan.md @@ -86,3 +86,28 @@ entries or to attribute hashed names; no preservation-policy change follows from this selection amendment. The original fatal profile comparison and all qualification/authentication/model flags remain unchanged. No release, new PR or VS Code work. + +## Validation record (2026-10-27) + +Root and CLI independently approved the exact plan before implementation. +CLI and shell independently reviewed the complete two-file change and gave +code GO for SHA256 +`beffe0727d8a9285c5b4df22150d6ea83c6c6400a024285a0bf15eab8ed5700c` +(existing formatter) and +`149291846c252ff44c633b02e28ef473f9c02a992118e33d9700e85872c425fb` +(new pure comparator/contracts). No source changes followed those reviews. +The old formatter remains400 lines; the new helper/contracts file is107 lines. + +Actual focused existing/new Qoder, log-link and shared argv/scope contracts +passed in0.053s with Go1.26.10, `-p=1`, `-count=1` and a90-second timeout. +The all-enabled affected-line lint from plan commit `104cc6c9` and unfiltered +whole-file `revive,funlen,gocyclo,gocognit,dupl,depguard` gate each reported zero +issues. Gofmt and whitespace checks are clean. The actual-file seven-other +case reproduces10 additions/one change/three details/eight omissions; this is +an inert shape control, not identification of the seven genuine unknown paths. + +The recovered source-bound94e sixteen-member failure and all24 frontend +stream joins were reverified with unchanged failed/unqualified Python admission. +No vendor or positive hosted guard ran locally. A new genuine capture must +still identify the newly prioritized metadata before any preservation-policy +or native/lifecycle qualification amendment. diff --git a/repotests/agentplugins_qoder_delta_priority_test.go b/repotests/agentplugins_qoder_delta_priority_test.go new file mode 100644 index 000000000..f86fa9825 --- /dev/null +++ b/repotests/agentplugins_qoder_delta_priority_test.go @@ -0,0 +1,107 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "crypto/sha256" + "fmt" + "path/filepath" + "reflect" + "sort" + "testing" +) + +func qoderDeltaPriorityLess(a, b qoderDeltaEntry, otherFirst bool) bool { + if otherFirst && (a.Area == "other") != (b.Area == "other") { + return a.Area == "other" + } + return a.rank < b.rank || (a.rank == b.rank && a.KeySHA256 < b.KeySHA256) +} + +func qoderDeltaPriorityFixture(t *testing.T, count int) (string, map[string]string, map[string]string) { + t.Helper() + root := qoderDeltaFixture(t) + before := qoderDeltaSnapshot(t, root) + for index := range count { + projectNativeRuntimeWrite(t, filepath.Join(root, fmt.Sprintf("PRIVATE-KEY-%d", index)), []byte(fmt.Sprintf("PRIVATE-BODY-%d\n", index)), 0640) + } + for index := range 3 { + projectNativeRuntimeWrite(t, filepath.Join(root, "logs", "run-private-target", fmt.Sprintf("PRIVATE-KEY-log-%d", index)), []byte("PRIVATE-BODY-log\n"), 0600) + } + qoderDeltaMutate(t, root, "same-target-rotation") + return root, before, qoderDeltaSnapshot(t, root) +} + +func qoderDeltaPriorityExpected(before, after map[string]string, count int) []qoderDeltaEntry { + size, logSize := int64(15), int64(17) + added := func(key, digest string) qoderDeltaEntry { + return qoderDeltaEntry{KeySHA256: key, Area: "other", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, After: qoderDeltaRecord{Kind: "file", Mode: "640", Bytes: &size, ContentSHA256: digest}} + } + if count == 7 { + return []qoderDeltaEntry{ + added("1cf52e636502d4fbfad917f2d94f3671b785a2d8b4d4177a7c8d23f0b2f60bad", "0bf9b4479f31c38fe151c1ee8166988124d602088321bb0568bad105c279342d"), + added("4dabb223a442282b5ae1a0cf90be99ae89080e27501c03a2626d4f2c2f85d512", "6543378f10b203e977ea839d673fe1440461fd5c6184fb792dbfa4986988a883"), + added("53e6490e39a55ba83e28aee3b11eb97ad9cd8b2c37899f1429ada97ac70d293d", "0f31bcd41780292ffd34d27412592d3b411a84a4cf63ff8be35c01fb6b4641dc"), + } + } + link := qoderDeltaEntry{KeySHA256: "22abff24b603cb9288fe82d7f92a98a22a4e3b648b8b0e07db5bc03da0901228", Area: "log-latest", Operation: "changed", + Before: qoderDeltaRecord{Kind: "symlink", RecordSHA256: fmt.Sprintf("%x", sha256.Sum256([]byte(before["logs/latest"])))}, + After: qoderDeltaRecord{Kind: "symlink", RecordSHA256: fmt.Sprintf("%x", sha256.Sum256([]byte(after["logs/latest"])))}} + log := qoderDeltaEntry{KeySHA256: "85b711de57b6a2434a38cf3a2cb2b55ed13d60ca7ee98be2052a3022bf92d3cc", Area: "logs", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, After: qoderDeltaRecord{Kind: "file", Mode: "600", Bytes: &logSize, ContentSHA256: "e2b9c8e8c303354e03149e46c6dafcd17303e3998b2026fb7aee9c6960136a4f"}} + if count == 1 { + return []qoderDeltaEntry{added("809103e20c841c150f13982def42e437f1b3c2d60e7c11550d9ec2aeacd02155", "83e787826d4b24b98e62a63c91474209e44436757410c905e885fc36c1aad374"), link, log} + } + secondLog := log + secondLog.KeySHA256 = "a19f11735bceecab78bb9f5261fca65713cc6f5219a632be852509e26f519a4b" + return []qoderDeltaEntry{link, log, secondLog} +} + +func qoderDeltaReverseInsertion(source map[string]string) map[string]string { + keys := make([]string, 0, len(source)) + for key := range source { + keys = append(keys, key) + } + sort.Sort(sort.Reverse(sort.StringSlice(keys))) + result := make(map[string]string, len(source)) + for _, key := range keys { + result[key] = source[key] + } + return result +} + +func TestQoderProfileDeltaPrioritizesActualOtherEntriesWithoutChangingCountsOrGuards(t *testing.T) { + for _, count := range []int{0, 1, 7} { + t.Run(fmt.Sprint(count), func(t *testing.T) { + root, before, after := qoderDeltaPriorityFixture(t, count) + unchanged := qoderDeltaUnchanged(t, root) + delta := qoderDeltaContract(t, before, after) + areas := map[string]int{"log-latest": 1, "logs": 3, "settings": 0, "plugin-cache": 0, "plugins": 0, "cache": 0, "other": count} + if delta.Status != "entry-limit" || delta.BeforeEntries != len(before) || delta.AfterEntries != len(before)+count+3 || delta.Added != count+3 || delta.Changed != 1 || delta.Removed != 0 || delta.OmittedCount != count+1 || !reflect.DeepEqual(delta.Areas, areas) || len(delta.Entries) != 3 { + t.Fatal("priority changed exact whole-profile counts, three-detail bound or omissions") + } + for index, want := range qoderDeltaPriorityExpected(before, after, count) { + if delta.Entries[index].KeySHA256 != want.KeySHA256 { + t.Fatal("actual selected hashed-name order differs from independently stated expectation") + } + qoderDeltaRequireDetail(t, delta, want) + } + again := qoderDeltaContract(t, qoderDeltaReverseInsertion(before), qoderDeltaReverseInsertion(after)) + if !reflect.DeepEqual(delta, again) { + t.Fatal("map insertion order changed priority or retained known-area ordering") + } + unchanged() + if !reflect.DeepEqual(after, qoderDeltaSnapshot(t, root)) || reflect.DeepEqual(before, after) { + t.Fatal("projection mutated the profile or adopted the unequal original maps") + } + // Mutating the returned projection cannot renew either held input map. + again.Areas["other"] = 999 + again.Entries[0].After.Kind = "changed-returned-kind" + if again.Entries[0].After.Bytes != nil { + *again.Entries[0].After.Bytes = 999 + } + if !reflect.DeepEqual(delta, qoderDeltaContract(t, before, after)) { + t.Fatal("returned projection aliases a held input or later diagnostic") + } + }) + } +} diff --git a/repotests/agentplugins_qoder_profile_delta_test.go b/repotests/agentplugins_qoder_profile_delta_test.go index b114bcc95..13136deca 100644 --- a/repotests/agentplugins_qoder_profile_delta_test.go +++ b/repotests/agentplugins_qoder_profile_delta_test.go @@ -85,7 +85,7 @@ func qoderOriginalProfileDelta(before, after map[string]string) qoderProfileDelt func qoderDeltaBound(delta qoderProfileDelta) qoderProfileDelta { sort.Slice(delta.Entries, func(i, j int) bool { a, b := delta.Entries[i], delta.Entries[j] - return a.rank < b.rank || (a.rank == b.rank && a.KeySHA256 < b.KeySHA256) + return qoderDeltaPriorityLess(a, b, delta.Areas["other"] > 0) }) delta.Status = "captured" if len(delta.Entries) > 3 { From 81bb84d11bd73e459ec16adbe57972c0dad1c1e4 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 18:58:21 +0100 Subject: [PATCH 13/30] docs(qoder): plan held file delta detail priority --- .../qoder-file-profile-delta-priority-plan.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-file-profile-delta-priority-plan.md diff --git a/docs/plans/project-install-e2e/qoder-file-profile-delta-priority-plan.md b/docs/plans/project-install-e2e/qoder-file-profile-delta-priority-plan.md new file mode 100644 index 000000000..2c3114693 --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-file-profile-delta-priority-plan.md @@ -0,0 +1,76 @@ +# Qoder: prioritize held file changes within the same bounded diagnostic + +## Source-bound observation and passive source research + +PR523 source faf2fef5ae4c094698b6818b4b6380f11b0bd351 has tree +ed31f769953b661d46975078dfd5d29cb782c540, equal to the surviving local +48a11189bdb00e19283d653679df44fafe149d05 tree. Genuine runtime40495061122, +native job122168330253, produced artifact12732771926 with verified SHA256 +f52ca8dd0a55104125fee2c9e94bd90bcdbd0267fc8d6510adacd9cbc0aa254e. +The native catalog exited0 without diagnostics. The complete original-profile +comparison still refused ten additions and one change: logs3, latest-link1, +other7, with zero settings/plugin/cache area changes. Only three other +details fit: two0700 directories and one36-byte0644 ordinary file. Eight +details remain omitted; no ordinary startup command or qualification follows. + +Current publisher documentation preserves original ~/.qoder authority and +describes user settings/authentication/plugins, but does not establish every +observed path or authorize native writes. Official installation documentation +retains the npm compatibility package. Passive reading of the publisher's +exact @qoder-ai/qodercli1.1.78 npm archive verified its published SHA512 +integrity and SHA1; archive SHA256 is +c5b237d2fd84ceb44af5427f281a7f1bcadd8c888fbed49197b5ce2f37dada4f; +bundle/qodercli.js SHA256 is +ec7b2a2c1be206f525ca974c3b9455fbec9ce7411bc5dfdee55f1e82eb90df38. +Its public installation_id and .auth literals cryptographically match two +observed key hashes. Source-defined .auth/machine_id is a concrete future +comparison. The actual identifier value and remaining paths are unknown. +The npm source is not proof of standalone executable code identity. No +package, postinstall hook, JavaScript bundle or vendor client was executed. + +Sources checked2026-10-27: https://docs.qoder.com/cli/installation, +https://docs.qoder.com/cli/settings, https://docs.qoder.com/cli/config-scope, +https://registry.npmjs.org/@qoder-ai/qodercli/1.1.78 and its dist.tarball. +This evidence does not justify preauthoring/adopting identity or authentication +files, permitting logs, redirecting HOME, or renewing the preservation baseline. + +## Minimal pure ordering amendment after two independent plan GOs + +Change only qoderDeltaPriorityLess in the existing priority helper/contracts +file and add one bounded Linux contract file, plus this accepted record. +Keep the existing other-area-first rule. Within that area only, prefer an +entry if either already parsed Before.Kind or After.Kind is file. This covers +added, removed and type-changed regular files without losing those events. +Then retain the existing key-SHA256 order. Directory-only other entries remain +in their original relative order. All known-area ordering is unchanged; with +no other changes, or no mix of file/non-file other changes, selection remains +byte-identical. A file classification is observation only, never permission. + +Preserve every count/area/record/parser rule, 4096-map/8192-union bounds, +three-detail limit, exact omissions/status and complete2KiB bound. Add no JSON +field, raw path/body/target/error, filesystem read, native command, environment +variable, account operation, parser, deadline or admission/qualification rule. +The unchanged complete before/after comparison and fatal remain authoritative. +Never replace the held maps or exempt a mutation based on the diagnostic. + +## Meaningful inert verification and next genuine boundary + +Use real owned profiles to reproduce seven other additions comprising two +directories and five files plus the existing log/latest changes. Independently +state the selected file key hashes and file metadata, exact ten additions/one +change/three details/eight omissions, privacy and complete serialization bound. +Cover removed-file and file/directory replacement classification, all-directory +and all-file preservation of prior ordering, reverse map insertion order and +returned-projection/input non-aliasing. Retain the existing no-other/one-other, +malformed/bound/rotation/held-profile controls; compare filesystem bytes and +metadata before/after projection to prove no diagnostic mutation/adoption. + +Run focused existing/new Qoder, log-link and shared CLI inert contracts plus +actual all-enabled affected-line and unfiltered whole-file size/architecture +lint in an exclusive Go slot. Obtain independent exact-file code review. +Root alone publishes to existing PR523 after a fresh expected-head check. +The next genuine capture may expose additional file hashes for source-backed +identification; remaining omissions are still explicit and may need further +diagnosis. No current or future hash match alone establishes allowed writes, +complete preservation, native selection, authentication or lifecycle/MCP +qualification. All existing false flags and release/merge gates remain intact. From 9b2f5466c3b4a4a401229c31cd343894e751922b Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 19:06:06 +0100 Subject: [PATCH 14/30] test(qoder): prioritize held file profile delta details --- ...tplugins_qoder_delta_file_priority_test.go | 140 ++++++++++++++++++ .../agentplugins_qoder_delta_priority_test.go | 7 + 2 files changed, 147 insertions(+) create mode 100644 repotests/agentplugins_qoder_delta_file_priority_test.go diff --git a/repotests/agentplugins_qoder_delta_file_priority_test.go b/repotests/agentplugins_qoder_delta_file_priority_test.go new file mode 100644 index 000000000..afbfeeb62 --- /dev/null +++ b/repotests/agentplugins_qoder_delta_file_priority_test.go @@ -0,0 +1,140 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "testing" +) + +var qoderFilePriorityExpected = []struct{ key, digest string }{ + {"1c789d8c54f84d9a76bb9e9c466fd82edc93600e0ae1096beb1aa2caae60c045", "0bf9b4479f31c38fe151c1ee8166988124d602088321bb0568bad105c279342d"}, + {"5b16aaa9c4be8b1466c2722c7fa908a2b7e41e6ea4c487f9a19cd536f1a655e4", "83e787826d4b24b98e62a63c91474209e44436757410c905e885fc36c1aad374"}, + {"9c39a648892eec9283695b692d95e346f397274fb8d9e284c9a7eaf005d2fea0", "b012dc678157d20bc91a92eb1f4f2087a9d819be3d287674757833b62fd1d7fd"}, +} + +func qoderFilePriorityAddDirectories(t *testing.T, root string) { + t.Helper() + for index := range 2 { + if err := os.Mkdir(filepath.Join(root, fmt.Sprintf("PRIVATE-KEY-dir-%d", index)), 0700); err != nil { + t.Fatal(err) + } + } +} + +func qoderFilePriorityRequireUnchanged(t *testing.T, root string, before, after map[string]string, delta qoderProfileDelta, unchanged func()) { + t.Helper() + again := qoderDeltaContract(t, qoderDeltaReverseInsertion(before), qoderDeltaReverseInsertion(after)) + if !reflect.DeepEqual(delta, again) { + t.Fatal("file priority depends on map insertion order") + } + if again.Entries[0].After.Bytes != nil { + *again.Entries[0].After.Bytes = 999 + } + if again.Entries[0].Before.Bytes != nil { + *again.Entries[0].Before.Bytes = 999 + } + again.Entries[0].After.Kind = "PRIVATE-RETURNED-KIND" + again.Areas["other"] = 999 + if !reflect.DeepEqual(delta, qoderDeltaContract(t, before, after)) || !reflect.DeepEqual(after, qoderDeltaSnapshot(t, root)) { + t.Fatal("file priority aliases a held map or changes the original unequal profile") + } + unchanged() +} + +func TestQoderProfileDeltaPrioritizesHeldOtherFilesOverDirectoryOnlyChanges(t *testing.T) { + root := qoderDeltaFixture(t) + before := qoderDeltaSnapshot(t, root) + qoderFilePriorityAddDirectories(t, root) + for index := range 5 { + projectNativeRuntimeWrite(t, filepath.Join(root, fmt.Sprintf("PRIVATE-KEY-file-%d", index)), []byte(fmt.Sprintf("PRIVATE-BODY-%d\n", index)), 0640) + } + for index := range 3 { + projectNativeRuntimeWrite(t, filepath.Join(root, "logs", "run-private-target", fmt.Sprintf("PRIVATE-KEY-log-%d", index)), []byte("PRIVATE-BODY-log\n"), 0600) + } + qoderDeltaMutate(t, root, "same-target-rotation") + after := qoderDeltaSnapshot(t, root) + unchanged := qoderDeltaUnchanged(t, root) + delta := qoderDeltaContract(t, before, after) + areas := map[string]int{"log-latest": 1, "logs": 3, "settings": 0, "plugin-cache": 0, "plugins": 0, "cache": 0, "other": 7} + if delta.Status != "entry-limit" || delta.BeforeEntries != len(before) || delta.AfterEntries != len(before)+10 || delta.Added != 10 || delta.Removed != 0 || delta.Changed != 1 || delta.OmittedCount != 8 || len(delta.Entries) != 3 || !reflect.DeepEqual(delta.Areas, areas) { + t.Fatal("file priority changed complete counts, areas or three-detail/eight-omission bounds") + } + size := int64(15) + for index, want := range qoderFilePriorityExpected { + if delta.Entries[index].KeySHA256 != want.key { + t.Fatal("directory-only changes displaced the independently expected file hash") + } + qoderDeltaRequireDetail(t, delta, qoderDeltaEntry{KeySHA256: want.key, Area: "other", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, After: qoderDeltaRecord{Kind: "file", Mode: "640", Bytes: &size, ContentSHA256: want.digest}}) + } + qoderFilePriorityRequireUnchanged(t, root, before, after, delta, unchanged) +} + +func TestQoderProfileDeltaFilePriorityIncludesRemovedAndTypeChangedFiles(t *testing.T) { + for _, mode := range []string{"removed-file", "file-to-directory", "directory-to-file"} { + t.Run(mode, func(t *testing.T) { + root := qoderDeltaFixture(t) + path := filepath.Join(root, "PRIVATE-KEY-file-target") + if mode == "directory-to-file" { + if err := os.Mkdir(path, 0700); err != nil { + t.Fatal(err) + } + } else { + projectNativeRuntimeWrite(t, path, []byte("PRIVATE-BODY\n"), 0640) + } + before := qoderDeltaSnapshot(t, root) + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + switch mode { + case "file-to-directory": + if err := os.Mkdir(path, 0700); err != nil { + t.Fatal(err) + } + case "directory-to-file": + projectNativeRuntimeWrite(t, path, []byte("PRIVATE-BODY\n"), 0640) + } + qoderFilePriorityAddDirectories(t, root) + after := qoderDeltaSnapshot(t, root) + unchanged := qoderDeltaUnchanged(t, root) + delta := qoderDeltaContract(t, before, after) + removed, changed := 0, 1 + if mode == "removed-file" { + removed, changed = 1, 0 + } + if delta.Status != "captured" || delta.Added != 2 || delta.Removed != removed || delta.Changed != changed || delta.Areas["other"] != 3 || delta.OmittedCount != 0 || len(delta.Entries) != 3 || delta.Entries[0].KeySHA256 != "ee524f4d1c5fba7f6dcc3e6ab434d1906695e83b698f74078b7bca49caa5e8c9" { + t.Fatal("removed or type-changed file did not retain priority and exact change counts") + } + if delta.Entries[1].KeySHA256 != "7620e3e60c68eb4ed00340bd6c387fb68b29b908d85262bae429fcd3c32b0bb0" || delta.Entries[2].KeySHA256 != "f68f95691ecc72e4f29367a0d7edfe75ad68d46c77e4cac273e67c6a2fd31437" { + t.Fatal("directory-only changes lost their previous relative hash order") + } + qoderFilePriorityRequireUnchanged(t, root, before, after, delta, unchanged) + }) + } +} + +func TestQoderProfileDeltaAllDirectoryOtherChangesKeepPreviousHashOrder(t *testing.T) { + root := qoderDeltaFixture(t) + before := qoderDeltaSnapshot(t, root) + for index := range 5 { + if err := os.Mkdir(filepath.Join(root, fmt.Sprintf("PRIVATE-KEY-file-%d", index)), 0700); err != nil { + t.Fatal(err) + } + } + after := qoderDeltaSnapshot(t, root) + unchanged := qoderDeltaUnchanged(t, root) + delta := qoderDeltaContract(t, before, after) + if delta.Status != "entry-limit" || delta.Added != 5 || delta.Removed != 0 || delta.Changed != 0 || delta.Areas["other"] != 5 || delta.OmittedCount != 2 || len(delta.Entries) != 3 { + t.Fatal("directory-only priority changed previous counts or bounds") + } + for index, want := range qoderFilePriorityExpected { + if delta.Entries[index].KeySHA256 != want.key { + t.Fatal("directory-only priority changed previous hash order") + } + qoderDeltaRequireDetail(t, delta, qoderDeltaEntry{KeySHA256: want.key, Area: "other", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, After: qoderDeltaRecord{Kind: "directory", Mode: "20000000700"}}) + } + qoderFilePriorityRequireUnchanged(t, root, before, after, delta, unchanged) +} diff --git a/repotests/agentplugins_qoder_delta_priority_test.go b/repotests/agentplugins_qoder_delta_priority_test.go index f86fa9825..77829a5a2 100644 --- a/repotests/agentplugins_qoder_delta_priority_test.go +++ b/repotests/agentplugins_qoder_delta_priority_test.go @@ -15,6 +15,13 @@ func qoderDeltaPriorityLess(a, b qoderDeltaEntry, otherFirst bool) bool { if otherFirst && (a.Area == "other") != (b.Area == "other") { return a.Area == "other" } + if otherFirst && a.Area == "other" && b.Area == "other" { + aFile := a.Before.Kind == "file" || a.After.Kind == "file" + bFile := b.Before.Kind == "file" || b.After.Kind == "file" + if aFile != bFile { + return aFile + } + } return a.rank < b.rank || (a.rank == b.rank && a.KeySHA256 < b.KeySHA256) } From 1973a9551f87a6e349c3cfd7ba6a9b8ff11118b8 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 19:06:29 +0100 Subject: [PATCH 15/30] docs(qoder): record exact held-file priority validation --- ...-file-profile-delta-priority-validation.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-file-profile-delta-priority-validation.md diff --git a/docs/plans/project-install-e2e/qoder-file-profile-delta-priority-validation.md b/docs/plans/project-install-e2e/qoder-file-profile-delta-priority-validation.md new file mode 100644 index 000000000..63182f60a --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-file-profile-delta-priority-validation.md @@ -0,0 +1,53 @@ +# Qoder held-file detail priority: exact validation + +## Reviewed source boundary + +The published base is PR523 head +`faf2fef5ae4c094698b6818b4b6380f11b0bd351`, tree +`ed31f769953b661d46975078dfd5d29cb782c540`. Plan commit +`81bb84d11bd73e459ec16adbe57972c0dad1c1e4` preserves the exact independently +approved plan SHA256 +`69031dd2cfaaf9fb72706f33c4e6783d22d6e7dddb0f0fe3e5fa56cd3793c2a2`. +Root gave R1 GO and CLI gave independent R2 GO before source edits. + +Code commit `9b2f5466c3b4a4a401229c31cd343894e751922b` has reviewed tree +`1ca241e5d2218d7547136c83b08f9d1d97ac9e0c`. Only two code files changed: +the existing comparator has seven added lines and the new inert contract file +has 140 lines. Their SHA256 values are +`87f80a281af6f760c30f38d1e08847841785f829039c06c1fcfc3568d174588f` +and `5b8fbe1add6cf9f3db9166f54206f37749a93608f1f97aa6ab72dcf8e3ee40f0`. + +CLI independently read the complete two-file delta and existing bounded +inventory/parser/fatal contracts, reconstructed the fixed expected key/content +hashes in Python, and gave exact-tree CODE GO without findings. Root separately +reviewed the comparator and approved the same tree. No source changes followed +these reviews; this record does not alter reviewed code. + +## Actual verification + +Focused inert `^(TestQoder|TestProjectNativeCLI)` contracts passed in 0.157 +seconds with Go 1.26.10. The actual all-enabled affected-line root lint gate +against plan commit `81bb84d1` reported zero issues. The unchanged unfiltered +whole-root revive/funlen/gocyclo/gocognit/dupl/depguard gate also reported zero +issues. Gofmt and staged whitespace checks are clean. + +Real-file controls cover two directory and five file additions with the +measured log/latest shape, exact ten-addition/one-change counts, three selected +details and eight omissions. Removed files and both file/directory replacement +directions retain file priority; all-directory, existing all-file and known-area +ordering remain strict. Independent literal hashes, reverse map insertion, +before/after pointer non-aliasing, private serialization and full actual profile +metadata frames are checked without adopting the unequal before/after maps. + +The current genuine native artifact `12732771926`, from run `40495061122`, +has verified outer SHA256 +`f52ca8dd0a55104125fee2c9e94bd90bcdbd0267fc8d6510adacd9cbc0aa254e`. +Its actual sixteen-member archive replays through current admission unchanged +as failed/incomplete, with all five qualifications false and eight omissions. +No omitted path, body or authentication state is inferred from that replay. + +No vendor, package hook, public installer or positive hosted guard ran locally. +The diagnostic adds no read, field, permission, baseline renewal or preservation +exception. Root alone owns publication and fresh genuine capture review. The +complete original-profile mismatch remains fatal; lifecycle, MCP, account, +model, merge and release gates remain unchanged. From ff5c144de817423e5430b92ad9dc099ed01db760 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 20:58:23 +0100 Subject: [PATCH 16/30] test(qoder): author bounded anonymous fixture metadata --- .../remaining-native-qualification.md | 48 +++ ...plugins_project_native_cli_attempt_test.go | 12 + ...s_qoder_original_metadata_contract_test.go | 377 ++++++++++++++++++ ...entplugins_qoder_original_metadata_test.go | 245 ++++++++++++ 4 files changed, 682 insertions(+) create mode 100644 repotests/agentplugins_qoder_original_metadata_contract_test.go create mode 100644 repotests/agentplugins_qoder_original_metadata_test.go diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index abbd0ccd7..8fa27df06 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1243,3 +1243,51 @@ 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. + +## Qoder original anonymous-metadata fixture amendment + +Accepted bounded fixture correction. Native-finish R1 and independent CLI R2 approved exact plan SHA256 `0500e7dcd839dd3b27a70ebf3da1ae57ecdac43637d0ee6a2c1e9cba5858558b` before implementation. No native qualification or authentication claim. + +### Evidence and source boundary + +Current PR523 source 1973a9551f87a6e349c3cfd7ba6a9b8ff11118b8, tree 3e8fb0fc2c56162e6345f1797fd3c5b3419c984b, genuine runtime40566031581/job122407532341, native12761729753 and frontend12762294585, produces a successful exact flag-selected 765-byte catalog before the complete original .qoder profile guard fails. The full delta is10 additions,1 change,8 omissions. Existing file-priority diagnostic independently maps three held regular-file path hashes to published literals: installation_id (36bytes0644), .auth/.credential-transaction (0bytes0600), .auth/machine_id (36bytes0600). Actual ID contents were neither exported nor read. The unobserved eight entries remain unknown; no stability promise follows from these three observations. + +The published @qoder-ai/qodercli1.1.78 archive at https://registry.npmjs.org/@qoder-ai/qodercli/-/qodercli-1.1.78.tgz was checked against published SHA1 and SHA512 integrity. Its archive SHA256 is c5b237d2fd84ceb44af5427f281a7f1bcadd8c888fbed49197b5ce2f37dada4f; package/bundle/qodercli.js is34562870bytes, SHA256 ec7b2a2c1be206f525ca974c3b9455fbec9ce7411bc5dfdee55f1e82eb90df38. The npm bundle is source guidance only: its equivalence to the independently pinned standalone executable is not established. + +Root independently rechecked the entire archive/bundle hashes and these source paths, without executing Node, the installer or any client: + +- Installation reader at byte1016117 returns existing nonempty trimmed installation_id. getInstallationId at1016392 generates/writes a UUID only when absent, caching it separately from account credentials. +- Auth directory/transaction setup MAl at3843720 creates .auth0700, opens the empty .credential-transaction target in append mode0600 and enforces0600. The transient .credential-transaction.lock is separate and is not authored by this proposal. +- Machine reader FT at3850006 returns existing nonempty trimmed machine_id. getMachineId at3855626 returns the held value via PpA; absent identity takes the ordinary transaction/generation path, with hardware or random generation. PpA at3847740 records consistency of the identifier in process; it is not an account grant. +- Wet at3843328 selects distinct account credential files .auth/user or .auth/user.. No such credential file, token, login state or trust entry is created or modified here. The fixture remains without vendor account credentials; a later genuine authentication refusal stays a failure. + +### Proposed finite fixture change + +Change only the Qoder fixture constructor, before the first public/native child and before any preservation baseline. In its already-owned original disposable HOME/.qoder, create precisely these independently authored entries: + +1. installation_id: one fresh independent RFC4122 version4 UUID, exactly36 ASCII bytes with no newline, ordinary single-link file0644. +2. .auth: an ordinary directory0700, owned by the current original user; refuse any preexisting entry at this previously unowned exact path. +3. .auth/machine_id: a second fresh independent version4 UUID, distinct from installation_id, exactly36 ASCII bytes with no newline, ordinary single-link file0600. +4. .auth/.credential-transaction: an empty ordinary single-link file0600. + +Use fresh cryptographic randomness from the existing inert Go fixture process, not native-observed IDs, host hardware IDs, account data, imported HOME or native output. Do not reuse the public package/probe nonce as an ID or parse/copy any existing native auth file. Use existing held-parent/no-follow/exclusive-create helpers, exact modes and owner checks. Author only the above three files and their one absent parent; no unknown cache, directory, lock, registration or auth path is inferred from omitted diagnostics. If any target is unexpectedly present, special, linked or foreign-owned, fail without replacing it. + +Record the three expected byte digests in existing protected-file authority immediately, before any child. Also hold the exact initial recipe authority for these three ordinary files and their .auth parent: device/inode, UID/GID, full mode, single-link files, exact byte lengths and independent byte digests. Add a narrow Qoder-only fixed-recipe verifier to the existing f.check before/after each already-selected command, activated only after this constructor recipe exists. This verifier rejects same-byte mode changes, inode replacement, changed parent/type/owner/link count or bytes from the first child onward; it must not wait until the catalog snapshot to notice a version/public-child mutation. It accepts no extra file or directory authority and reads no account credential path. Existing intraread held identity checks remain unchanged; this is not a promise of immutable access/change timestamps after ordinary reads or same-mode chmod. Keep the complete original .qoder catalog before/after snapshot and equality guard unchanged, including full modes, membership, unknown files/directories, links and existing special original-log rule. Every later byte drift or missing marker is fatal; no post-run adoption, exception, exclusions, cleanup deletion or fallback recipe. Source/native divergence, unexpected chmod or additional native file creation remains a genuine failed observation. + +No new child command, native argv, provider, environment, permission, trust, source/installed digest, scanner, acquisition pin, deadline, supervision, raw member, diagnostic cap or admission schema is added. Current raw diagnostics stay three details/2KiB with full counts and omissions. All five qualification flags, runtime_attempt_complete, vendor authentication and real-model flags remain false, including a successful catalog or restricted startup. Project activation remains Prepared. No public adapter or installer production code changes. + +### Implementation and meaningful checks + +Keep the helper/client-only controls in new Qoder-specific test files with one guarded constructor authoring call and one guarded invocation in the existing f.check for spec.client==qoder; do not alter other clients. The extra held recipe field is private to this fixture and absent before construction; no common permission or profile snapshot policy changes. Unit tests call the helper on ordinary inert temporary directories only, never newProjectNativeCLIAttempt, a positive hosted guard or a vendor executable. + +Check the exact four-entry recipe, types/modes/owner/single-link/byte counts, canonical distinct valid UUIDs, protected digests and unchanged original settings. Verify preexisting target or .auth, symlink/hardlink/special/foreign authority refuses without overwrite, including partial-recipe failures. Exercise the narrow fixed-recipe verifier to show a real same-byte chmod or inode replacement before the catalog baseline fails, as well as after a command. Exercise the existing full profile snapshot to show metadata bytes/mode or any new sibling still cause inequality/refusal. No tests may bless the current eight omitted native entries or infer native source equivalence. + +Two independent plan GOs precede coding; independent exact code review and focused inert Go/affected-root/full-six lint gates precede root-only publication. Align actual program base a412ba5e137fdcb7e004393229b323211ed746e1 with separate preservation review. No force push, release, new PR or VS Code work. A fresh genuine hosted capture is required to learn whether the same exact catalog now preserves the profile; any remaining mutation/authentication/tool boundary is reported honestly rather than reclassified as success. + +The original Qoder leaf was aligned to actual Cline-containing program base in +`ca88f79a399f1e0f8280a38476f862fd9667eb2e`, tree +`65edbdc10526a653d0d225a3fb531dea2910d4b3`. Independent static preservation +review verified all 5777 nonconflicting tree entries and every nonblank plan +line from both parents; Qoder-owned source was unchanged by this merge. +Focused inert verification and exact-code review are recorded separately once +completed; this accepted plan does not claim those gates or native success. diff --git a/repotests/agentplugins_project_native_cli_attempt_test.go b/repotests/agentplugins_project_native_cli_attempt_test.go index d49dec342..35cd880d4 100644 --- a/repotests/agentplugins_project_native_cli_attempt_test.go +++ b/repotests/agentplugins_project_native_cli_attempt_test.go @@ -94,6 +94,7 @@ type projectNativeCLIAttempt struct { used map[string]bool evidence map[string]any binding domain.ClientBinding + qoderMetadata *qoderOriginalMetadata } func (f *projectNativeCLIAttempt) keep(t *testing.T, name string, body []byte) { @@ -109,6 +110,11 @@ func (f *projectNativeCLIAttempt) keep(t *testing.T, name string, body []byte) { } func (f *projectNativeCLIAttempt) check(t *testing.T) { t.Helper() + if f.spec.client == "qoder" && f.qoderMetadata != nil { + if err := f.qoderMetadata.check(); err != nil { + t.Fatal(err) + } + } for _, inventory := range []map[string]string{f.immutable, f.protected} { for path, want := range inventory { got, err := projectNativeRuntimeDigest(path) @@ -333,6 +339,12 @@ func newProjectNativeCLIAttempt(t *testing.T, spec projectNativeCLISpec) *projec f.protected[path] = nativeSHA(t, path) f.evidence["fixture_authored_updater_config"] = path } + if spec.client == "qoder" { + f.qoderMetadata, err = qoderAuthorOriginalMetadata(filepath.Join(f.base.Home, ".qoder"), f.protected) + if err != nil { + t.Fatal(err) + } + } return f } func (f *projectNativeCLIAttempt) setup(t *testing.T) { diff --git a/repotests/agentplugins_qoder_original_metadata_contract_test.go b/repotests/agentplugins_qoder_original_metadata_contract_test.go new file mode 100644 index 000000000..6ec58908c --- /dev/null +++ b/repotests/agentplugins_qoder_original_metadata_contract_test.go @@ -0,0 +1,377 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "errors" + "os" + "path/filepath" + "reflect" + "regexp" + "syscall" + "testing" +) + +func qoderMetadataFixture(t *testing.T) string { + t.Helper() + root := filepath.Join(t.TempDir(), ".qoder") + if err := os.Mkdir(root, 0700); err != nil { + t.Fatal(err) + } + projectNativeRuntimeWrite(t, filepath.Join(root, "settings.json"), []byte("{\"general\":{\"enableAutoUpdate\":false}}\n"), 0600) + return root +} + +func qoderMetadataAuthor(t *testing.T, root string) (*qoderOriginalMetadata, map[string]string) { + t.Helper() + protected := map[string]string{} + recipe, err := qoderAuthorOriginalMetadata(root, protected) + if err != nil { + t.Fatal(err) + } + return recipe, protected +} + +func TestQoderOriginalMetadataExactIndependentAnonymousRecipe(t *testing.T) { + root := qoderMetadataFixture(t) + before := qoderDeltaSnapshot(t, root) + recipe, protected := qoderMetadataAuthor(t, root) + if err := recipe.check(); err != nil { + t.Fatal(err) + } + after := qoderDeltaSnapshot(t, root) + if len(after) != len(before)+4 || after["settings.json"] != before["settings.json"] || len(protected) != 3 { + t.Fatal("recipe changed original settings or authored a different inventory") + } + for key, value := range before { + if key != "." && after[key] != value { + t.Fatal("preexisting profile entry changed") + } + } + id := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`) + values := make([][]byte, 0, 3) + for _, item := range qoderMetadataRecipe { + path := filepath.Join(root, item.name) + info, err := os.Lstat(path) + if err != nil || !qoderLogOwned(info, false) || info.Mode() != item.mode || info.Size() != item.size { + t.Fatal("anonymous file shape differs") + } + body, err := projectNativeRuntimeRead(path, 36) + if err != nil || (item.size == 36 && !id.Match(body)) || (item.size == 0 && len(body) != 0) { + t.Fatal("anonymous ID/empty transaction format differs") + } + sum := sha256.Sum256(body) + if protected[path] != hex.EncodeToString(sum[:]) { + t.Fatal("initial protected digest differs") + } + values = append(values, body) + } + if bytes.Equal(values[0], values[1]) || recipe.auth.Mode() != os.ModeDir|0700 || !qoderLogOwned(recipe.auth, true) { + t.Fatal("IDs were reused or anonymous parent authority differs") + } + if _, err := os.Lstat(filepath.Join(root, ".auth", "user")); !os.IsNotExist(err) { + t.Fatal("fixture authored account credentials") + } +} + +func TestQoderOriginalMetadataPreexistingTargetsRefusedWithoutOverwrite(t *testing.T) { + for _, mode := range []string{"installation-file", "installation-link", "installation-hardlink", "installation-fifo", "auth-directory", "auth-file", "auth-link"} { + t.Run(mode, func(t *testing.T) { + root := qoderMetadataFixture(t) + path := filepath.Join(root, "installation_id") + if mode[:4] == "auth" { + path = filepath.Join(root, ".auth") + } + outside := filepath.Join(t.TempDir(), "held-original") + projectNativeRuntimeWrite(t, outside, []byte("foreign original bytes"), 0600) + switch mode { + case "auth-directory": + if err := os.Mkdir(path, 0700); err != nil { + t.Fatal(err) + } + case "installation-link", "auth-link": + if err := os.Symlink(outside, path); err != nil { + t.Fatal(err) + } + case "installation-hardlink": + if err := os.Link(outside, path); err != nil { + t.Fatal(err) + } + case "installation-fifo": + if err := syscall.Mkfifo(path, 0600); err != nil { + t.Fatal(err) + } + default: + projectNativeRuntimeWrite(t, path, []byte("preserve-existing"), 0600) + } + before, err := os.Lstat(path) + if err != nil { + t.Fatal(err) + } + protected := map[string]string{} + if result, err := qoderAuthorOriginalMetadata(root, protected); err == nil || result != nil || len(protected) != 0 { + t.Fatal("preexisting target accepted or published partial recipe") + } + after, err := os.Lstat(path) + if err != nil || !projectNativeRuntimeStableStat(before.Sys(), after.Sys()) { + t.Fatal("refusal changed preexisting target") + } + body, err := os.ReadFile(outside) + if err != nil || string(body) != "foreign original bytes" { + t.Fatal("refusal changed aliased original data") + } + }) + } +} + +func qoderMetadataReplace(t *testing.T, path string) { + t.Helper() + body, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if err := os.Rename(path, path+".held-original"); err != nil { + t.Fatal(err) + } + projectNativeRuntimeWrite(t, path, body, info.Mode()) +} + +func TestQoderOriginalMetadataRejectsDriftBeforeCatalogBaseline(t *testing.T) { + for _, mode := range []string{"mode", "same-bytes-inode", "bytes", "removed", "symlink", "hardlink", "directory", "auth-mode", "auth-inode", "profile-inode"} { + t.Run(mode, func(t *testing.T) { + root := qoderMetadataFixture(t) + recipe, _ := qoderMetadataAuthor(t, root) + path := filepath.Join(root, ".auth", "machine_id") + switch mode { + case "mode", "auth-mode": + if mode == "auth-mode" { + path = filepath.Join(root, ".auth") + t.Cleanup(func() { + if err := os.Chmod(path, 0700); err != nil { + t.Error(err) + } + }) + } + if err := os.Chmod(path, 0644); err != nil { + t.Fatal(err) + } + case "same-bytes-inode": + qoderMetadataReplace(t, path) + case "bytes": + if err := os.WriteFile(path, bytes.Repeat([]byte("x"), 36), 0600); err != nil { + t.Fatal(err) + } + case "removed", "symlink", "hardlink", "directory": + if err := os.Rename(path, path+".held-original"); err != nil { + t.Fatal(err) + } + qoderMetadataAlterLeaf(t, path, mode) + case "auth-inode", "profile-inode": + parent := root + if mode == "auth-inode" { + parent = filepath.Join(root, ".auth") + } + if err := os.Rename(parent, parent+".held-original"); err != nil { + t.Fatal(err) + } + if err := os.Mkdir(parent, 0700); err != nil { + t.Fatal(err) + } + // Preserve every child inode and byte so only the held parent + // identity can reject this otherwise coherent replacement. + children := []string{"machine_id", ".credential-transaction"} + if mode == "profile-inode" { + children = []string{"installation_id", ".auth", "settings.json"} + } + for _, child := range children { + if err := os.Rename(filepath.Join(parent+".held-original", child), filepath.Join(parent, child)); err != nil { + t.Fatal(err) + } + } + } + // No profile snapshot has run since authoring: early version/public + // mutations cannot become the later catalog preservation baseline. + if err := recipe.check(); err == nil { + t.Fatal("pre-baseline identity/mode/byte drift accepted") + } + }) + } +} + +func qoderMetadataAlterLeaf(t *testing.T, path, mode string) { + t.Helper() + var err error + switch mode { + case "symlink": + err = os.Symlink(path+".held-original", path) + case "hardlink": + err = os.Link(path+".held-original", path) + case "directory": + err = os.Mkdir(path, 0600) + } + if err != nil { + t.Fatal(err) + } +} + +func TestQoderOriginalMetadataFullProfileGuardStillRefusesDriftAndNewSibling(t *testing.T) { + for _, mode := range []string{"bytes", "mode", "new-sibling"} { + t.Run(mode, func(t *testing.T) { + root := qoderMetadataFixture(t) + qoderMetadataAuthor(t, root) + before := qoderDeltaSnapshot(t, root) + path := filepath.Join(root, "installation_id") + switch mode { + case "bytes": + if err := os.WriteFile(path, bytes.Repeat([]byte("x"), 36), 0644); err != nil { + t.Fatal(err) + } + case "mode": + if err := os.Chmod(path, 0600); err != nil { + t.Fatal(err) + } + case "new-sibling": + projectNativeRuntimeWrite(t, filepath.Join(root, "unknown-native-entry"), nil, 0600) + } + after := qoderDeltaSnapshot(t, root) + if reflect.DeepEqual(before, after) { + t.Fatal("original whole-profile equality accepted metadata/sibling drift") + } + }) + } +} + +func TestQoderOriginalMetadataAllFilesRejectEarlySameByteModeAndIdentityChange(t *testing.T) { + for _, item := range qoderMetadataRecipe { + for _, change := range []string{"mode", "inode"} { + t.Run(item.name+"/"+change, func(t *testing.T) { + root := qoderMetadataFixture(t) + recipe, _ := qoderMetadataAuthor(t, root) + path := filepath.Join(root, item.name) + if change == "inode" { + qoderMetadataReplace(t, path) + } else if err := os.Chmod(path, item.mode^0040); err != nil { + t.Fatal(err) + } + if err := recipe.check(); err == nil { + t.Fatal("early same-byte mode/identity mutation accepted") + } + }) + } + } +} + +type qoderMetadataForeignInfo struct { + os.FileInfo + stat syscall.Stat_t +} + +func (i qoderMetadataForeignInfo) Sys() any { return &i.stat } + +func TestQoderOriginalMetadataRejectsForeignHeldOwnerAndGroup(t *testing.T) { + root := qoderMetadataFixture(t) + recipe, _ := qoderMetadataAuthor(t, root) + for _, info := range []os.FileInfo{recipe.profile, recipe.auth, recipe.files[0].info, recipe.files[1].info, recipe.files[2].info} { + for _, group := range []bool{false, true} { + foreign := qoderMetadataForeignInfo{info, *info.Sys().(*syscall.Stat_t)} + if group { + foreign.stat.Gid++ + } else { + foreign.stat.Uid++ + } + if qoderMetadataSame(info, foreign, info.IsDir()) || qoderLogOwned(foreign, info.IsDir()) { + t.Fatal("foreign held owner/group accepted") + } + } + } +} + +func TestQoderOriginalMetadataRejectsRealForeignOwnerWhenSupported(t *testing.T) { + if os.Getuid() != 0 { + t.Skip("real ownership mutation requires root in this inert fixture") + } + for _, target := range []string{".", ".auth", "installation_id", ".auth/machine_id", ".auth/.credential-transaction"} { + for _, group := range []bool{false, true} { + root := qoderMetadataFixture(t) + recipe, _ := qoderMetadataAuthor(t, root) + uid, gid := os.Getuid(), os.Getgid() + if group { + gid++ + } else { + uid++ + } + path := filepath.Join(root, target) + if err := os.Chown(path, uid, gid); errors.Is(err, syscall.EINVAL) || errors.Is(err, syscall.EPERM) { + t.Skip("current user namespace cannot represent a foreign UID/GID; held-stat ownership controls remain required") + } else if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.Chown(path, os.Getuid(), os.Getgid()); err != nil { + t.Error(err) + } + }) + if err := recipe.check(); err == nil { + t.Fatal("foreign owner/group accepted") + } + } + } +} + +func TestQoderOriginalMetadataExclusivePartialWriteAndParentAliasRefuse(t *testing.T) { + root := qoderMetadataFixture(t) + parent, err := qoderLogOpenRoot(root) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := parent.Close(); err != nil { + t.Error(err) + } + }() + if err := qoderMetadataCreate(parent, "installation_id", []byte("first independent authored bytes"), 0644); err != nil { + t.Fatal(err) + } + before, err := os.ReadFile(filepath.Join(root, "installation_id")) + if err != nil { + t.Fatal(err) + } + if err := qoderMetadataCreate(parent, "installation_id", []byte("replacement must refuse"), 0600); err == nil { + t.Fatal("exclusive partial-recipe failure replaced earlier file") + } + after, err := os.ReadFile(filepath.Join(root, "installation_id")) + if err != nil || !bytes.Equal(before, after) { + t.Fatal("partial-recipe refusal changed earlier authored bytes") + } + alias := filepath.Join(t.TempDir(), ".qoder") + if err := os.Symlink(root, alias); err != nil { + t.Fatal(err) + } + if result, err := qoderAuthorOriginalMetadata(alias, map[string]string{}); err == nil || result != nil { + t.Fatal("aliased original profile accepted") + } +} + +func TestQoderOriginalMetadataOrdinaryReadsAndSameModeChmodDoNotPinTimestamps(t *testing.T) { + root := qoderMetadataFixture(t) + recipe, _ := qoderMetadataAuthor(t, root) + for _, item := range qoderMetadataRecipe { + path := filepath.Join(root, item.name) + if _, err := os.ReadFile(path); err != nil { + t.Fatal(err) + } + if err := os.Chmod(path, item.mode); err != nil { + t.Fatal(err) + } + } + if err := recipe.check(); err != nil { + t.Fatal("recipe incorrectly pinned ordinary access/change timestamps") + } +} diff --git a/repotests/agentplugins_qoder_original_metadata_test.go b/repotests/agentplugins_qoder_original_metadata_test.go new file mode 100644 index 000000000..b815beac5 --- /dev/null +++ b/repotests/agentplugins_qoder_original_metadata_test.go @@ -0,0 +1,245 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "syscall" + + "golang.org/x/sys/unix" +) + +// Private, independently authored anonymous metadata, never account credentials. +// The pinned standalone may still rewrite it or create other unknown profile data. +type qoderMetadataFile struct { + info os.FileInfo + digest string +} +type qoderOriginalMetadata struct { + root string + profile, auth os.FileInfo + files [3]qoderMetadataFile +} + +var qoderMetadataRecipe = [...]struct { + name string + mode os.FileMode + size int64 +}{ + {"installation_id", 0644, 36}, + {".auth/machine_id", 0600, 36}, + {".auth/.credential-transaction", 0600, 0}, +} + +func qoderMetadataRefused() error { + return fmt.Errorf("Qoder authored anonymous metadata authority differs") +} + +func qoderMetadataUUID() (string, error) { + var raw [16]byte + if _, err := rand.Read(raw[:]); err != nil { + return "", err + } + raw[6], raw[8] = raw[6]&15|64, raw[8]&63|128 + return fmt.Sprintf("%x-%x-%x-%x-%x", raw[:4], raw[4:6], raw[6:8], raw[8:10], raw[10:]), nil +} + +func qoderMetadataSame(before, after os.FileInfo, directory bool) bool { + if before == nil || after == nil || !qoderLogOwned(after, directory) || before.Mode() != after.Mode() || !os.SameFile(before, after) { + return false + } + a, okA := before.Sys().(*syscall.Stat_t) + b, okB := after.Sys().(*syscall.Stat_t) + return okA && okB && a.Uid == b.Uid && a.Gid == b.Gid && (directory || (a.Nlink == b.Nlink && before.Size() == after.Size())) +} + +func qoderMetadataCreate(parent *os.File, name string, body []byte, mode os.FileMode) (err error) { + fd, err := unix.Openat(int(parent.Fd()), name, unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW|unix.O_CLOEXEC, uint32(mode)) + if err != nil { + return qoderMetadataRefused() + } + file := os.NewFile(uintptr(fd), "qoder-authored-anonymous-metadata") + defer func() { err = errors.Join(err, file.Close()) }() + if err := file.Chmod(mode); err != nil { + return qoderMetadataRefused() + } + if n, err := file.Write(body); err != nil || n != len(body) { + return qoderMetadataRefused() + } + return nil +} + +func qoderMetadataRead(parent *os.File, name string, mode os.FileMode, size int64) (result qoderMetadataFile, err error) { + fd, err := unix.Openat(int(parent.Fd()), name, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0) + if err != nil { + return result, qoderMetadataRefused() + } + file := os.NewFile(uintptr(fd), "qoder-held-anonymous-metadata") + defer func() { err = errors.Join(err, file.Close()) }() + before, err := file.Stat() + if err != nil || !qoderLogOwned(before, false) || !before.Mode().IsRegular() || before.Mode() != mode || before.Size() != size { + return result, qoderMetadataRefused() + } + body, err := io.ReadAll(io.LimitReader(file, 37)) + after, statErr := file.Stat() + var linked unix.Stat_t + linkErr := unix.Fstatat(int(parent.Fd()), name, &linked, unix.AT_SYMLINK_NOFOLLOW) + if err != nil || statErr != nil || linkErr != nil || int64(len(body)) != size || !projectNativeRuntimeStableStat(before.Sys(), after.Sys()) || !qoderMetadataLinked(before, &linked) { + return result, qoderMetadataRefused() + } + sum := sha256.Sum256(body) + return qoderMetadataFile{before, hex.EncodeToString(sum[:])}, nil +} + +func qoderMetadataLinked(info os.FileInfo, linked *unix.Stat_t) bool { + stat, ok := info.Sys().(*syscall.Stat_t) + return ok && stat.Dev == linked.Dev && stat.Ino == linked.Ino && stat.Mode == linked.Mode && stat.Nlink == linked.Nlink && stat.Uid == linked.Uid && stat.Gid == linked.Gid && stat.Size == linked.Size && stat.Mtim.Sec == linked.Mtim.Sec && stat.Mtim.Nsec == linked.Mtim.Nsec && stat.Ctim.Sec == linked.Ctim.Sec && stat.Ctim.Nsec == linked.Ctim.Nsec +} + +func qoderMetadataAbsent(parent *os.File, name string) bool { + var stat unix.Stat_t + return errors.Is(unix.Fstatat(int(parent.Fd()), name, &stat, unix.AT_SYMLINK_NOFOLLOW), unix.ENOENT) +} + +func qoderAuthorOriginalMetadata(root string, protected map[string]string) (result *qoderOriginalMetadata, err error) { + installation, err := qoderMetadataUUID() + if err != nil { + return nil, err + } + machine, err := qoderMetadataUUID() + if err != nil || machine == installation || protected == nil { + return nil, qoderMetadataRefused() + } + profile, err := qoderLogOpenRoot(root) + if err != nil { + return nil, err + } + defer func() { err = errors.Join(err, profile.Close()) }() + info, err := profile.Stat() + if err != nil || !qoderLogOwned(info, true) || !qoderMetadataAbsent(profile, "installation_id") || !qoderMetadataAbsent(profile, ".auth") { + return nil, qoderMetadataRefused() + } + if err := unix.Mkdirat(int(profile.Fd()), ".auth", 0700); err != nil { + return nil, qoderMetadataRefused() + } + auth, err := qoderLogOpenAt(profile, ".auth", true) + if err != nil { + return nil, err + } + defer func() { err = errors.Join(err, auth.Close()) }() + bodies := []string{installation, machine, ""} + for i, body := range bodies { + parent := auth + if i == 0 { + parent = profile + } + if err := qoderMetadataCreate(parent, filepath.Base(qoderMetadataRecipe[i].name), []byte(body), qoderMetadataRecipe[i].mode); err != nil { + return nil, err + } + } + result = &qoderOriginalMetadata{root: root} + if err := result.capture(profile, auth); err != nil { + return nil, err + } + for i, body := range bodies { + sum := sha256.Sum256([]byte(body)) + if result.files[i].digest != hex.EncodeToString(sum[:]) { + return nil, qoderMetadataRefused() + } + } + if err := result.check(); err != nil { + return nil, err + } + for i, item := range qoderMetadataRecipe { + protected[filepath.Join(root, item.name)] = result.files[i].digest + } + return result, nil +} + +func (r *qoderOriginalMetadata) capture(profile, auth *os.File) error { + var err error + r.profile, err = profile.Stat() + if err != nil || !qoderLogOwned(r.profile, true) { + return qoderMetadataRefused() + } + r.auth, err = auth.Stat() + if err != nil || !qoderLogOwned(r.auth, true) || r.auth.Mode() != os.ModeDir|0700 { + return qoderMetadataRefused() + } + for i, item := range qoderMetadataRecipe { + parent := auth + if i == 0 { + parent = profile + } + r.files[i], err = qoderMetadataRead(parent, filepath.Base(item.name), item.mode, item.size) + if err != nil { + return err + } + } + return nil +} + +func (r *qoderOriginalMetadata) check() (err error) { + profile, err := qoderLogOpenRoot(r.root) + if err != nil { + return err + } + defer func() { err = errors.Join(err, profile.Close()) }() + auth, err := qoderLogOpenAt(profile, ".auth", true) + if err != nil { + return err + } + defer func() { err = errors.Join(err, auth.Close()) }() + current := &qoderOriginalMetadata{root: r.root} + if err := current.capture(profile, auth); err != nil { + return err + } + if !qoderMetadataSame(r.profile, current.profile, true) || !qoderMetadataSame(r.auth, current.auth, true) { + return qoderMetadataRefused() + } + for i, before := range r.files { + if !qoderMetadataSame(before.info, current.files[i].info, false) || before.digest != current.files[i].digest { + return qoderMetadataRefused() + } + } + return current.linked(profile, auth) +} + +func (r *qoderOriginalMetadata) linked(profile, auth *os.File) (err error) { + current, err := qoderLogOpenRoot(r.root) + if err != nil { + return err + } + defer func() { err = errors.Join(err, current.Close()) }() + root, err := current.Stat() + var linked unix.Stat_t + if err != nil || !qoderMetadataSame(r.profile, root, true) || unix.Fstatat(int(profile.Fd()), ".auth", &linked, unix.AT_SYMLINK_NOFOLLOW) != nil || !qoderMetadataDirectoryLinked(r.auth, &linked) { + return qoderMetadataRefused() + } + held, err := auth.Stat() + if err != nil || !qoderMetadataSame(r.auth, held, true) { + return qoderMetadataRefused() + } + for i, item := range qoderMetadataRecipe { + parent := auth + if i == 0 { + parent = profile + } + if unix.Fstatat(int(parent.Fd()), filepath.Base(item.name), &linked, unix.AT_SYMLINK_NOFOLLOW) != nil || !qoderMetadataLinked(r.files[i].info, &linked) { + return qoderMetadataRefused() + } + } + return nil +} + +func qoderMetadataDirectoryLinked(info os.FileInfo, linked *unix.Stat_t) bool { + stat, ok := info.Sys().(*syscall.Stat_t) + return ok && stat.Dev == linked.Dev && stat.Ino == linked.Ino && stat.Mode == linked.Mode && stat.Uid == linked.Uid && stat.Gid == linked.Gid +} From 39f1267d5096a0f8c2e3cd0388e047978c2c7de6 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 20:59:00 +0100 Subject: [PATCH 17/30] docs(qoder): record anonymous metadata fixture validation --- .../remaining-native-qualification.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index 8fa27df06..1265c5500 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1291,3 +1291,43 @@ review verified all 5777 nonconflicting tree entries and every nonblank plan line from both parents; Qoder-owned source was unchanged by this merge. Focused inert verification and exact-code review are recorded separately once completed; this accepted plan does not claim those gates or native success. + +### Qoder anonymous-metadata amendment: exact implementation validation + +Code commit `ff5c144de817423e5430b92ad9dc099ed01db760`, tree +`0a3c1a1d2d51d1fb812b801664d8e14e1348eef7`, contains only the private +Qoder helper, its inert contracts, twelve shared-fixture hook lines and the +accepted record above. The helper opens the original profile and absent auth +parent through the existing held no-follow mechanics, creates only the three +exclusive ordinary files, and verifies their bytes against independently +generated inputs before publishing protected digests. Each existing selected +command retains its original before/after check. The new private recipe check +also pins initial device/inode, owner/group, full mode, file length/link count +and byte digest; current linked identities are checked after held reads. + +Root independently approved the helper/shared/accepted-record delta and +bounded test corrections. CLI independently reviewed exact final tree +`0a3c1a1d2d51d1fb812b801664d8e14e1348eef7` and gave CODE GO without remaining +findings. Tests preserve all child inodes during auth/profile parent replacement, +so those negatives specifically prove the initial parent identity check. Other +controls cover all three files' early same-byte chmod/replacement, changed +bytes/types/links, preexisting targets, exclusive partial-write refusal, exact +four-entry authoring and full-profile sibling/mode/byte inequality. Foreign +held UID/GID controls always run for all five nodes; an additional real chown +control explicitly reports unsupported privilege/user-namespace capability. +Ordinary reads and same-mode chmod do not pin access/change timestamps. + +Focused inert `^(TestQoder|TestProjectNativeCLI)` passed in 0.139 seconds with +Go 1.26.10. The actual all-enabled root lint gate with +`--new-from-merge-base=a412ba5e137fdcb7e004393229b323211ed746e1` reported zero +issues. The unchanged unfiltered whole-root +revive/funlen/gocyclo/gocognit/dupl/depguard gate reported zero issues. Gofmt and +staged whitespace checks were clean. No client, public installer, package hook +or positive hosted guard ran locally. + +No current native artifact has been relabeled or promoted. The genuine +1973a955 runtime 40566031581 still fails complete original-profile equality, +with all five qualification flags false and eight omitted entries unknown. +The npm/standalone equivalence caveat remains. Fresh genuine hosted evidence +must establish the next boundary; any additional profile drift or genuine +account refusal remains a failed observation. Root alone owns publication. From 2b63d444f9cbb25be9fa23a475e80e50e0a5b22a Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 21:02:11 +0100 Subject: [PATCH 18/30] docs(qoder): approve exact auth membership correction --- .../qoder-auth-membership-addendum-plan.md | 81 +++++++++++++++++++ .../qoder-auth-membership-plan-review.md | 16 ++++ 2 files changed, 97 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-auth-membership-addendum-plan.md create mode 100644 docs/plans/project-install-e2e/qoder-auth-membership-plan-review.md diff --git a/docs/plans/project-install-e2e/qoder-auth-membership-addendum-plan.md b/docs/plans/project-install-e2e/qoder-auth-membership-addendum-plan.md new file mode 100644 index 000000000..7bfd34e26 --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-auth-membership-addendum-plan.md @@ -0,0 +1,81 @@ +# Qoder anonymous metadata: exact auth-directory membership + +Status: proposed correction; no code in this slice has been changed. This addendum +responds to two independent reviews performed after the original metadata recipe +was implemented. It does not backdate those reviews into preimplementation GOs. + +## Frozen starting point and concrete defect + +Start from candidate 39f1267d5096a0f8c2e3cd0388e047978c2c7de6, tree +9e452cff096e0ab53b3cdf235258a4a2ddc9dd30, in a separate worktree. Preserve the +existing author's complete commits and contracts. The helper currently has SHA256 +be18f86af7d290b079d5b360c85b501e8451969a47005761fe8ad6455c9ea7f7; +contracts b2799da43d86c5ee961ba8eba0aec3f584155ec30914cae47e0cf6c6eb74736e; +shared fixture 557b178c29b065ba5e17a55b42ae3e1c3c77dce3402a5199e9ed4eb00110359e. + +The current fixed-file checks do not enumerate .auth. setup runs native version +and public add before the first complete-profile catalog snapshot. Consequently +an unknown .auth sibling can pass recipe.check and enter that later baseline. +This is a source counterexample, not observed native account creation. + +## Narrow implementation + +Add a private bounded verifier for the exact direct .auth names machine_id and +.credential-transaction, and no others. Do not enumerate the whole profile or +read unknown child bodies. Open a readable directory descriptor relative to the +already-held .auth descriptor with openat(".", O_RDONLY|O_DIRECTORY|O_NOFOLLOW| +O_CLOEXEC); never reopen an unchecked filesystem path or use O_PATH for readdir. +Check held ownership, directory type, exact0700 and identity against the captured +auth directory before and after enumeration. Use a fresh descriptor each call, +read at most three entry names, and require exactly the two expected unique names +and EOF, with all read and close errors refusing. Unknown names are not printed. +Keep existing fixed-file regular-file, owner, mode, size, link, identity and digest +checks; expected names still pass those independent checks, so no special file, +alias or changed expected child gains authority. + +Enforce membership from initial capture onward and around every existing recipe +check, including the final linked identity phase. Preserve held profile/auth +checks and linked Fstatat verification before/after the membership operation. +Do not pin directory access timestamps across commands; ordinary reads and +same-mode chmod retain their existing semantics. Inside a single enumeration, +verify stable directory identity and membership-relevant metadata to refuse a +concurrent mutation; any observed change is a refusal, never a new baseline. +Retain the full original-profile equality guard and all command boundaries. + +No changes to production adapters, vendor pins/argv, original HOME, credential or +trust handling, file contents, deadlines, cleanup, parser allowances, qualification +flags or VS Code. No native/vendor executable will run locally. The recipe still +does not establish equivalence between the public npm source and the pinned +standalone executable, or account authentication or successful native behavior. + +## Regression and validation + +Add actual recipe.check controls before any complete-profile snapshot: create +.auth/user, .auth/user.production or an unrelated sibling as an ordinary file; +also exercise a directory, symlink and FIFO without opening their bodies. Each +must refuse promptly, leave the unknown object and the authored bytes intact, +and keep the fixed-file protections. Add direct initial-capture rejection with +an unknown entry and repeated positive checks, including after ordinary reads and +same-mode chmod. Preserve every prior test. No mock positive native result. + +Run the new direct regression against the old helper using an inert Go overlay +to show the concrete gap (expected failing test), then the corrected focused +Qoder/shared inert Go contracts and actual affected all-enabled plus unfiltered +whole-file architecture/size lint gates. Serialize Go/lint with any active job; +do not delete another task's files. Use two independent plan GOs before coding and +two independent exact-code reviews after the correction. Record source hashes, +actual commands/results and their scope without attributing other workers' test +execution to this worker. + +## Publication and concurrency + +Do not edit /tmp/uap-qoder-publish or its publication manifest/checkpoint. Commit +this addendum, correction and actual validation separately in the isolated branch. +Publish only after fresh inspection of the existing PR523 head, full source review +and checks. A normal fast-forward may include the original author's39f126 ancestry; +if concurrent source differs, stop publication, preserve it and reconcile/review +the exact combined bytes. No force-push, replacement PR, merge of unqualified leaf, +release, cancellation of unrelated CI or change to the original negative captures. + +All existing five qualification flags remain false until genuine current-source +hosted evidence independently establishes applicable claims. diff --git a/docs/plans/project-install-e2e/qoder-auth-membership-plan-review.md b/docs/plans/project-install-e2e/qoder-auth-membership-plan-review.md new file mode 100644 index 000000000..bff4473d6 --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-auth-membership-plan-review.md @@ -0,0 +1,16 @@ +# Qoder auth-membership addendum: preimplementation review + +Plan SHA256: `ff0d090da140c389e0690990ab6cd7440b8c1f2c92c008f2f28bb64fb0ff2b3f`. + +Starting commit: `39f1267d5096a0f8c2e3cd0388e047978c2c7de6`. + +Before correction code was changed, two independent GPT-6.1 Sol xhigh reviewers +read the exact plan and starting source. `kimi_current_review` returned R1 GO, +and `native_current_review` returned R2 GO, without required plan corrections. +Both confirmed the bounded readable descriptor anchored to the held auth +directory, exact two-name set and EOF, initial and repeated checks, unchanged +fixed-file protections, and direct prebaseline hostile controls. + +These approvals apply only to this membership correction. They do not backdate +reviews of the original metadata implementation and do not establish a native +pass, account authentication, MCP capability, model use or release qualification. From b42ff698deb486b69b611b9d2adb6ead6accebb9 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 21:03:39 +0100 Subject: [PATCH 19/30] test(qoder): reject unknown auth members before baseline --- ...ins_qoder_auth_membership_contract_test.go | 117 ++++++++++++++++++ ...agentplugins_qoder_auth_membership_test.go | 63 ++++++++++ ...entplugins_qoder_original_metadata_test.go | 5 +- 3 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 repotests/agentplugins_qoder_auth_membership_contract_test.go create mode 100644 repotests/agentplugins_qoder_auth_membership_test.go diff --git a/repotests/agentplugins_qoder_auth_membership_contract_test.go b/repotests/agentplugins_qoder_auth_membership_contract_test.go new file mode 100644 index 000000000..679d0a779 --- /dev/null +++ b/repotests/agentplugins_qoder_auth_membership_contract_test.go @@ -0,0 +1,117 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "os" + "path/filepath" + "syscall" + "testing" +) + +func qoderMetadataUnknownMember(t *testing.T, root, name, kind string) string { + t.Helper() + path := filepath.Join(root, ".auth", name) + var err error + switch kind { + case "directory": + err = os.Mkdir(path, 0700) + case "symlink": + err = os.Symlink("machine_id", path) + case "fifo": + err = syscall.Mkfifo(path, 0600) + default: + err = os.WriteFile(path, []byte("unknown body must remain unread and unchanged"), 0000) + } + if err != nil { + t.Fatal(err) + } + return path +} + +func TestQoderOriginalMetadataRejectsUnknownAuthMemberBeforeBaseline(t *testing.T) { + for _, item := range []struct{ name, kind string }{ + {"user", "file"}, {"user.production", "file"}, {"unknown", "file"}, + {"unknown-directory", "directory"}, {"unknown-symlink", "symlink"}, {"unknown-fifo", "fifo"}, + } { + t.Run(item.name, func(t *testing.T) { + root := qoderMetadataFixture(t) + recipe, protected := qoderMetadataAuthor(t, root) + path := qoderMetadataUnknownMember(t, root, item.name, item.kind) + before, err := os.Lstat(path) + if err != nil { + t.Fatal(err) + } + // No whole-profile snapshot exists: the recipe itself must refuse. + if err := recipe.check(); err == nil { + t.Fatal("unknown auth member accepted before catalog baseline") + } + after, err := os.Lstat(path) + if err != nil || !projectNativeRuntimeStableStat(before.Sys(), after.Sys()) { + t.Fatal("refusal changed the unknown auth member") + } + for original, digest := range protected { + current, err := projectNativeRuntimeDigest(original) + if err != nil || current != digest { + t.Fatal("refusal changed authored metadata bytes") + } + } + }) + } +} + +func TestQoderOriginalMetadataInitialCaptureRejectsUnknownAuthMember(t *testing.T) { + root := qoderMetadataFixture(t) + qoderMetadataAuthor(t, root) + qoderMetadataUnknownMember(t, root, "user", "file") + profile, err := qoderLogOpenRoot(root) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := profile.Close(); err != nil { + t.Error(err) + } + }() + auth, err := qoderLogOpenAt(profile, ".auth", true) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := auth.Close(); err != nil { + t.Error(err) + } + }() + fresh := &qoderOriginalMetadata{root: root} + if err := fresh.capture(profile, auth); err == nil { + t.Fatal("initial capture accepted an unknown auth member") + } +} + +func TestQoderOriginalMetadataMembershipChecksUseFreshDirectoryOffset(t *testing.T) { + root := qoderMetadataFixture(t) + recipe, _ := qoderMetadataAuthor(t, root) + for range 3 { + for _, item := range qoderMetadataRecipe { + path := filepath.Join(root, item.name) + before, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if err := os.Chmod(path, item.mode); err != nil { + t.Fatal(err) + } + after, err := os.ReadFile(path) + if err != nil || !bytes.Equal(before, after) { + t.Fatal("ordinary metadata read or chmod changed content") + } + } + if err := os.Chmod(filepath.Join(root, ".auth"), 0700); err != nil { + t.Fatal(err) + } + if err := recipe.check(); err != nil { + t.Fatal("repeated check pinned timestamps or reused a directory offset") + } + } +} diff --git a/repotests/agentplugins_qoder_auth_membership_test.go b/repotests/agentplugins_qoder_auth_membership_test.go new file mode 100644 index 000000000..8a5d7b6ec --- /dev/null +++ b/repotests/agentplugins_qoder_auth_membership_test.go @@ -0,0 +1,63 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "errors" + "io" + "os" + + "golang.org/x/sys/unix" +) + +// Only names are enumerated. Unknown entries are never opened or read. +func qoderMetadataAuthNames(directory *os.File) error { + names, err := directory.Readdirnames(3) + if (err != nil && !errors.Is(err, io.EOF)) || len(names) != 2 { + return qoderMetadataRefused() + } + if !((names[0] == "machine_id" && names[1] == ".credential-transaction") || + (names[1] == "machine_id" && names[0] == ".credential-transaction")) { + return qoderMetadataRefused() + } + extra, err := directory.Readdirnames(1) + if len(extra) != 0 || !errors.Is(err, io.EOF) { + return qoderMetadataRefused() + } + return nil +} + +func qoderMetadataAuthLinked(profile, auth *os.File, expected os.FileInfo) bool { + held, err := auth.Stat() + var linked unix.Stat_t + return err == nil && projectNativeRuntimeStableStat(expected.Sys(), held.Sys()) && + unix.Fstatat(int(profile.Fd()), ".auth", &linked, unix.AT_SYMLINK_NOFOLLOW) == nil && + qoderMetadataLinked(expected, &linked) +} + +func qoderMetadataAuthMembers(profile, auth *os.File, expected os.FileInfo) (err error) { + before, err := auth.Stat() + if err != nil || !qoderMetadataSame(expected, before, true) || before.Mode() != os.ModeDir|0700 || !qoderMetadataAuthLinked(profile, auth, before) { + return qoderMetadataRefused() + } + // The held auth handle is O_PATH. Open a fresh readable descriptor relative + // to it so every check starts at offset zero without resolving a pathname. + fd, err := unix.Openat(int(auth.Fd()), ".", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return qoderMetadataRefused() + } + directory := os.NewFile(uintptr(fd), "qoder-held-auth-membership") + defer func() { err = errors.Join(err, directory.Close()) }() + opened, err := directory.Stat() + if err != nil || !projectNativeRuntimeStableStat(before.Sys(), opened.Sys()) { + return qoderMetadataRefused() + } + if err := qoderMetadataAuthNames(directory); err != nil { + return err + } + after, err := directory.Stat() + if err != nil || !projectNativeRuntimeStableStat(before.Sys(), after.Sys()) || !qoderMetadataAuthLinked(profile, auth, before) { + return qoderMetadataRefused() + } + return nil +} diff --git a/repotests/agentplugins_qoder_original_metadata_test.go b/repotests/agentplugins_qoder_original_metadata_test.go index b815beac5..96196f0af 100644 --- a/repotests/agentplugins_qoder_original_metadata_test.go +++ b/repotests/agentplugins_qoder_original_metadata_test.go @@ -173,6 +173,9 @@ func (r *qoderOriginalMetadata) capture(profile, auth *os.File) error { if err != nil || !qoderLogOwned(r.auth, true) || r.auth.Mode() != os.ModeDir|0700 { return qoderMetadataRefused() } + if err := qoderMetadataAuthMembers(profile, auth, r.auth); err != nil { + return err + } for i, item := range qoderMetadataRecipe { parent := auth if i == 0 { @@ -236,7 +239,7 @@ func (r *qoderOriginalMetadata) linked(profile, auth *os.File) (err error) { return qoderMetadataRefused() } } - return nil + return qoderMetadataAuthMembers(profile, auth, r.auth) } func qoderMetadataDirectoryLinked(info os.FileInfo, linked *unix.Stat_t) bool { From 83dd06d0bc3e932baf4f8de3d59333f167898e08 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 21:05:46 +0100 Subject: [PATCH 20/30] test(qoder): simplify exact member refusal predicate --- repotests/agentplugins_qoder_auth_membership_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repotests/agentplugins_qoder_auth_membership_test.go b/repotests/agentplugins_qoder_auth_membership_test.go index 8a5d7b6ec..7f1f503fc 100644 --- a/repotests/agentplugins_qoder_auth_membership_test.go +++ b/repotests/agentplugins_qoder_auth_membership_test.go @@ -16,8 +16,8 @@ func qoderMetadataAuthNames(directory *os.File) error { if (err != nil && !errors.Is(err, io.EOF)) || len(names) != 2 { return qoderMetadataRefused() } - if !((names[0] == "machine_id" && names[1] == ".credential-transaction") || - (names[1] == "machine_id" && names[0] == ".credential-transaction")) { + if (names[0] != "machine_id" || names[1] != ".credential-transaction") && + (names[1] != "machine_id" || names[0] != ".credential-transaction") { return qoderMetadataRefused() } extra, err := directory.Readdirnames(1) From 89321cbe5baf79d5837da4ad95040beabcd6cdbc Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 21:07:17 +0100 Subject: [PATCH 21/30] docs(qoder): record verified auth membership regression --- .../qoder-auth-membership-validation.md | 63 +++++++++++++++++++ .../remaining-native-qualification.md | 9 +++ 2 files changed, 72 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-auth-membership-validation.md diff --git a/docs/plans/project-install-e2e/qoder-auth-membership-validation.md b/docs/plans/project-install-e2e/qoder-auth-membership-validation.md new file mode 100644 index 000000000..d6aa616f8 --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-auth-membership-validation.md @@ -0,0 +1,63 @@ +# Qoder exact auth-membership correction: validation + +## Reviewed source and chronology + +The membership addendum was approved before its implementation by independent +GPT-6.1 Sol xhigh reviewers `kimi_current_review` (R1) and +`native_current_review` (R2), at exact plan SHA256 +`ff0d090da140c389e0690990ab6cd7440b8c1f2c92c008f2f28bb64fb0ff2b3f`. +The plan and review record were committed as `2b63d444f9cbb25be9fa23a475e80e50e0a5b22a`. + +The correction was committed as `b42ff698deb486b69b611b9d2adb6ead6accebb9`. +Both independent reviewers read the complete helper, contracts and wiring and +returned exact-source CODE GO. Actual lint then identified one staticcheck QF1001 +finding. Commit `83dd06d0bc3e932baf4f8de3d59333f167898e08` changes only the two +predicate lines using De Morgan's law. Both reviewers read that exact delta and +extended their CODE GO. Final code tree: +`c25f4a711379822b835b3e1e18f0bd6624cbcea4`. + +The original metadata author's complete `39f1267d` history and tests remain +preserved. This correction's plan approvals do not backdate reviews of that +original implementation. + +## Actual inert validation + +The new direct regression was run against the original helper through a Go +source overlay. It failed as expected for all six unknown auth siblings and for +initial capture, demonstrating the early-baseline gap. It ran in 0.013 seconds +of package test time. The old helper was never installed over the working source. + +On the final corrected source, the following gates actually passed: + +- `go test -p=1 -count=1 -timeout=120s ./repotests -run '^(TestQoder|TestProjectNativeCLI)'`: + PASS, 0.049 seconds of package test time. +- All enabled lint on the affected delta from `39f1267d`: zero issues. +- Unfiltered whole-repotests `revive,funlen,gocyclo,gocognit,dupl,depguard`: + zero issues. +- Formatting and whitespace checks: clean. + +Go 1.26.10 and golangci-lint 2.13.2 ran with offline dependency resolution. No +vendor executable, hosted environment simulation or native qualification ran +locally. The reviewers' static code approvals are separate from these root-run +tests. + +| Actual log | SHA256 | +| --- | --- | +| Original helper counterexample | `564c195467b4282a497b9c9a4aa0c39c01cae30f4f9d68c4a5f721642dac7d5d` | +| Final focused Go | `0df67033922854846ec5ae3c86b30b637afb41defd26348a039098ec250430c9` | +| Final affected lint | `e92606b0bf483111dff0a120c315ea165821348f31365020e2468a0059095c47` | +| Final unfiltered six-linter gate | `e92606b0bf483111dff0a120c315ea165821348f31365020e2468a0059095c47` | + +## Preserved evidence limits + +The fixture now rejects any direct `.auth` entry other than `machine_id` and +`.credential-transaction`, including before the first full-profile snapshot. +It enumerates bounded names through held directory authority and never reads +unknown bodies. Existing file ownership, byte, mode, inode, link, directory and +full-profile checks remain required. + +This is a fixture acceptance correction. All five qualification flags remain +false until genuine, current-source hosted evidence establishes applicable +claims. The earlier negative captures, their omissions and the absence of account +or model qualification remain unchanged. No new MCP support, VS Code work, merge +of an unqualified leaf, release or vendor-binary/source equivalence is claimed. diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index 1265c5500..4fd95dcdf 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1331,3 +1331,12 @@ with all five qualification flags false and eight omitted entries unknown. The npm/standalone equivalence caveat remains. Fresh genuine hosted evidence must establish the next boundary; any additional profile drift or genuine account refusal remains a failed observation. Root alone owns publication. + +## Qoder exact auth-membership correction (2026-10-27) + +Independent review found that an unknown auth sibling could enter the later +catalog baseline after setup. The [reviewed addendum](qoder-auth-membership-addendum-plan.md) +and [actual validation](qoder-auth-membership-validation.md) close that early +window while preserving the original metadata commits and complete-profile guard. +This source correction supplies no native qualification; fresh hosted evidence +and the existing lifecycle gates remain required. From 7389fc66b0fc7ffcff78e77900a2571e3c70a5f7 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 27 Oct 2026 22:05:19 +0100 Subject: [PATCH 22/30] docs(qoder): approve exact empty telemetry directory recipe --- ...empty-telemetry-directories-plan-review.md | 18 ++++ .../qoder-empty-telemetry-directories-plan.md | 93 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-empty-telemetry-directories-plan-review.md create mode 100644 docs/plans/project-install-e2e/qoder-empty-telemetry-directories-plan.md diff --git a/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-plan-review.md b/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-plan-review.md new file mode 100644 index 000000000..d1ee007fc --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-plan-review.md @@ -0,0 +1,18 @@ +# Qoder empty telemetry directories: preimplementation review + +Exact reviewed plan SHA256: `27a91ec4a8dcff76d08eabb2363701feeb20f73a4ce7a4c12e108db746d14256`. +The retained plan is the original frozen draft; both decisions below approve +that exact text before implementation. + +Independent GPT-6.1 Sol xhigh reviewers `native_finish` (R1) and +`kimi_cline_finish` (R2) returned GO. Both independently checked the pinned +official npm archive and bundle, the three corrected UTF-8 byte spans and +the three literal path hashes. They reviewed exclusive creation, exact +0700/current-owner held ancestry, closed membership from the first child, +within-check stability and no timestamp pin across commands. + +The current genuine 89321cbe capture remains failed/unqualified. Source supports +the three-directory fixture recipe, not shipping binary/source equivalence or +any unexported marker value. Four omitted log details remain unproved; the +complete profile, auth membership, original IDs/settings and logging guards +are unchanged. No vendor or public installer was run for either review. diff --git a/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-plan.md b/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-plan.md new file mode 100644 index 000000000..dbb6a201c --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-plan.md @@ -0,0 +1,93 @@ +# Qoder: independently authored empty telemetry directories + +DRAFT, not an implementation approval. Start from current published +89321cbe5baf79d5837da4ad95040beabcd6cdbc/tree +f75668e2162758f31327e6140e4ec4b355350813. Preserve the complete metadata and +exact-auth-membership corrections and their historical evidence. + +## New measured boundary and public source + +Genuine runtime40575301213/job122446553803 retains native12768990209 and +frontend12766634473. Root independently verified both API sizes/digests, closed +16/25 members, 24 frontend stream joins, exact committed source/tree and current +failed-only admission. Public add and the actual native catalog both exit0. +The catalog identifies the one expected enabled @flag installed plugin view. +The original anonymous metadata and exact .auth checks pass. The complete +original-profile guard still fails:12 to18 entries,6 added,1 changed,4 omitted. +All five/runtime/account/model flags remain false; no native session ran. + +All three exposed added directories have mode0700. Their recorded path digests +match exactly the following public-source literals/compositions: + +- tmp: cf10d3eb4b80f1fdd74306ab6e6152f1822b19451b959eba448ba2d0b2beb22b. +- tmp/telemetry: 9e364e7b404f8f44485dabd0a69b74fa7f7c92a8e2c6adac0650e765164cf7c7. +- tmp/telemetry/active-runs: 8aae50c2aa8f464951f142637714289606a7b6e289dce8c85e9257aaef9998c6. + +Pinned official npm1.1.78 archive SHA256c5b237d2fd84ceb44af5427f281a7f1bcadd8c888fbed49197b5ce2f37dada4f +contains bundle/qodercli.js SHA256ec7b2a2c1be206f525ca974c3b9455fbec9ce7411bc5dfdee55f1e82eb90df38. +The source's abnormal-exit marker defaults to join(globalProfile,tmp,telemetry, +active-runs), creates it recursively with mode0700, and removes its own marker +on completion. Source spans and exact literal joins are recorded in +/tmp/uap-qoder-893-root-audit/source-research.json, +SHA256beba66984e341b9b4d78207b85b228df1e678f7bcb316a51a6ec46f8a6b4ffe5. +The standalone binary/source correspondence remains unproven. No captured +private identifiers or file contents were read or inferred. + +Four log/latest details remain omitted. Source logger layout suggests a new +per-process run directory, qodercli.log, manifest.json and latest link, but their +actual omitted path/body details are unobserved. This slice grants NO logging +exception and cannot claim that the complete profile guard will pass afterward. + +## Exact original fixture recipe + +Before the first existing child in the genuine Qoder constructor, create only +these three new ordinary directories inside its original disposable .qoder. +Require every target initially absent; create exclusively through held no-follow +parent authority, set exact0700 only on newly created held descriptors, and +verify current UID/GID, ordinary-directory type and linked identities. Do not +reuse a preexisting directory, symlink, alias, foreign target or inherited tree. +No marker file, ID, account record, log, credential or trust value is authored. + +Retain a private recipe for the three initial device/inode/UID/GID/full modes. +Check it before and after every existing selected command, alongside the current +metadata/auth checks. Use fresh readable descriptors anchored to held directory +handles for bounded exact membership: tmp has only telemetry, telemetry has +only active-runs, and active-runs is empty. Read at most expected-count+1 names, +require exact unique names and EOF, and refuse read/close errors, unknown +children/types/links, parent substitution or ownership/mode drift. Do not open +unknown child bodies. Expected child directories retain their independently +held identity; the final linked checks must not accept a replacement chain. + +Within one check require stable held/linked metadata across enumeration. +Between commands do not pin directory access/change/modification timestamps: +the real source may create and remove its own temporary marker before returning. +The final exact empty-member check supplies the boundary. A leftover marker or +any other residual child remains a failure, never a new accepted baseline. + +Preserve the existing whole-profile before/after equality guard with no excluded +prefix, adopted map, rewritten baseline or logging allowance. Preserve exact +settings/anonymous-file bytes and modes and the closed .auth membership check. +No client argv/env/config behavior, telemetry switch, profile redirection, +provider/authentication path, acquisition/scanner/executable/source pin, +process/cleanup/deadline, raw schema, success admission or qualification changes. +No VS Code work or release. Any newly revealed profile difference remains an +actual failed observation for separate review. + +## Meaningful verification and publication + +Use ordinary inert files/directories to prove exclusive creation, exact modes +and full ancestry identities; required exact membership before any later profile +baseline; unknown child at each of the three levels; aliases/special entries; +early chmod and same-name parent replacement preserving all child inodes; and +changed original settings/ID/auth protections. Repeated valid checks and a +create-then-remove inert marker must pass while a retained marker refuses. +Keep all existing Qoder tests. No vendor, public installer or positive hosted +simulation runs locally. Do not claim the inert marker is native telemetry. + +Two independent plan GOs precede code. Use a new isolated exact-current-head +branch, independent exact-code review, focused Qoder/shared contracts and current +negative raw replay, actual all-enabled affected root lint and unchanged whole- +root six-linter gates. Coordinate the Go/lint slot and preserve every prior +capture. Root alone publishes a normal fast-forward after fresh remote/base +inspection; concurrent changes are preserved and reviewed, never overwritten. +The subsequent genuine capture establishes only its actual next boundary. From 9b2a946ff95960a09067def78abebb98941e4074 Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 22:18:44 +0100 Subject: [PATCH 23/30] test(qoder): retain exact empty telemetry directories --- ...plugins_project_native_cli_attempt_test.go | 10 + ...ins_qoder_empty_telemetry_contract_test.go | 205 +++++++++++++++++ ...agentplugins_qoder_empty_telemetry_test.go | 215 ++++++++++++++++++ 3 files changed, 430 insertions(+) create mode 100644 repotests/agentplugins_qoder_empty_telemetry_contract_test.go create mode 100644 repotests/agentplugins_qoder_empty_telemetry_test.go diff --git a/repotests/agentplugins_project_native_cli_attempt_test.go b/repotests/agentplugins_project_native_cli_attempt_test.go index 35cd880d4..b0679a1d3 100644 --- a/repotests/agentplugins_project_native_cli_attempt_test.go +++ b/repotests/agentplugins_project_native_cli_attempt_test.go @@ -95,6 +95,7 @@ type projectNativeCLIAttempt struct { evidence map[string]any binding domain.ClientBinding qoderMetadata *qoderOriginalMetadata + qoderTelemetry *qoderEmptyTelemetry } func (f *projectNativeCLIAttempt) keep(t *testing.T, name string, body []byte) { @@ -115,6 +116,11 @@ func (f *projectNativeCLIAttempt) check(t *testing.T) { t.Fatal(err) } } + if f.spec.client == "qoder" && f.qoderTelemetry != nil { + if err := f.qoderTelemetry.check(); err != nil { + t.Fatal(err) + } + } for _, inventory := range []map[string]string{f.immutable, f.protected} { for path, want := range inventory { got, err := projectNativeRuntimeDigest(path) @@ -344,6 +350,10 @@ func newProjectNativeCLIAttempt(t *testing.T, spec projectNativeCLISpec) *projec if err != nil { t.Fatal(err) } + f.qoderTelemetry, err = qoderAuthorEmptyTelemetry(f.ctx, filepath.Join(f.base.Home, ".qoder")) + if err != nil { + t.Fatal(err) + } } return f } diff --git a/repotests/agentplugins_qoder_empty_telemetry_contract_test.go b/repotests/agentplugins_qoder_empty_telemetry_contract_test.go new file mode 100644 index 000000000..0474f2287 --- /dev/null +++ b/repotests/agentplugins_qoder_empty_telemetry_contract_test.go @@ -0,0 +1,205 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "context" + "os" + "path/filepath" + "reflect" + "testing" + + "golang.org/x/sys/unix" +) + +func qoderTelemetryFixture(t *testing.T) (string, *qoderOriginalMetadata, *qoderEmptyTelemetry) { + t.Helper() + root := qoderMetadataFixture(t) + metadata, _ := qoderMetadataAuthor(t, root) + recipe, err := qoderAuthorEmptyTelemetry(context.Background(), root) + if err != nil { + t.Fatal(err) + } + return root, metadata, recipe +} + +func qoderTelemetryPath(root string, level int) string { + for _, name := range qoderTelemetryNames[:level+1] { + root = filepath.Join(root, name) + } + return root +} + +func TestQoderEmptyTelemetryAuthorsExactOriginalChainAndRetainsMetadata(t *testing.T) { + root, metadata, recipe := qoderTelemetryFixture(t) + for i := range qoderTelemetryNames { + info, err := os.Lstat(qoderTelemetryPath(root, i)) + if err != nil || !qoderLogOwned(info, true) || info.Mode() != os.ModeDir|0700 || !os.SameFile(recipe.directories[i], info) { + t.Fatal("authored directory mode/owner/identity differs") + } + } + before, err := projectNativeCLIQoderProfile(root) + if err != nil { + t.Fatal(err) + } + for range 3 { + if recipe.check() != nil || metadata.check() != nil { + t.Fatal("fresh membership check lost exact recipe or original metadata") + } + } + marker := filepath.Join(qoderTelemetryPath(root, 2), "inert-marker-never-native") + projectNativeRuntimeWrite(t, marker, []byte("inert private body; never exported"), 0600) + if recipe.check() == nil { + t.Fatal("leftover marker became a new accepted baseline") + } + if err := os.Remove(marker); err != nil { + t.Fatal(err) + } + if recipe.check() != nil || metadata.check() != nil { + t.Fatal("create/remove marker incorrectly pinned directory timestamps") + } + after, err := projectNativeCLIQoderProfile(root) + if err != nil || !reflect.DeepEqual(before, after) { + t.Fatal("empty directory recipe changed the full profile contract") + } + projectNativeRuntimeWrite(t, filepath.Join(root, "logs", "unknown.log"), []byte("unapproved log"), 0600) + after, err = projectNativeCLIQoderProfile(root) + if err != nil || reflect.DeepEqual(before, after) || recipe.check() != nil { + t.Fatal("telemetry recipe became a logging exception or whole-profile baseline") + } +} + +func TestQoderEmptyTelemetryRefusesEveryUnknownSiblingAndSpecialChild(t *testing.T) { + for level := range qoderTelemetryNames { + for _, kind := range []string{"file", "directory", "symlink", "fifo"} { + t.Run(qoderTelemetryNames[level]+"/"+kind, func(t *testing.T) { + root, _, recipe := qoderTelemetryFixture(t) + path := filepath.Join(qoderTelemetryPath(root, level), "private-unknown") + var err error + switch kind { + case "file": + err = os.WriteFile(path, []byte("PRIVATE-BODY-must-not-be-read"), 0000) + case "directory": + err = os.Mkdir(path, 0700) + case "symlink": + err = os.Symlink(filepath.Join(root, "settings.json"), path) + case "fifo": + err = unix.Mkfifo(path, 0600) + } + if err != nil { + t.Fatal(err) + } + if recipe.check() == nil { + t.Fatal("unknown child entered exact initial membership") + } + }) + } + } +} + +func TestQoderEmptyTelemetryRefusesModeAndCoherentParentReplacement(t *testing.T) { + for level := range qoderTelemetryNames { + for _, kind := range []string{"mode", "replacement", "alias"} { + t.Run(qoderTelemetryNames[level]+"/"+kind, func(t *testing.T) { + root, _, recipe := qoderTelemetryFixture(t) + path := qoderTelemetryPath(root, level) + retired := filepath.Join(filepath.Dir(root), "retained-original") + if kind == "mode" { + if err := os.Chmod(path, 0755); err != nil { + t.Fatal(err) + } + } else { + if err := os.Rename(path, retired); err != nil { + t.Fatal(err) + } + if kind == "alias" { + if err := os.Symlink(retired, path); err != nil { + t.Fatal(err) + } + } else { + if err := os.Mkdir(path, 0700); err != nil { + t.Fatal(err) + } + if level+1 < len(qoderTelemetryNames) { + name := qoderTelemetryNames[level+1] + if err := os.Rename(filepath.Join(retired, name), filepath.Join(path, name)); err != nil { + t.Fatal(err) + } + } + } + } + if recipe.check() == nil { + t.Fatal("mode/alias/parent substitution preserving children was accepted") + } + }) + } + } +} + +func TestQoderEmptyTelemetryRefusesExistingTargetsAndOriginalAncestorSubstitution(t *testing.T) { + for _, kind := range []string{"directory", "file", "symlink", "fifo"} { + root := qoderMetadataFixture(t) + path := filepath.Join(root, "tmp") + var err error + switch kind { + case "directory": + err = os.Mkdir(path, 0700) + case "file": + err = os.WriteFile(path, []byte("preexisting caller authority"), 0600) + case "symlink": + err = os.Symlink(t.TempDir(), path) + case "fifo": + err = unix.Mkfifo(path, 0600) + } + if err != nil { + t.Fatal(err) + } + before, err := os.Lstat(path) + if err != nil { + t.Fatal(err) + } + if got, err := qoderAuthorEmptyTelemetry(context.Background(), root); err == nil || got != nil { + t.Fatal("preexisting caller target was adopted") + } + after, err := os.Lstat(path) + if err != nil || !projectNativeRuntimeStableStat(before.Sys(), after.Sys()) { + t.Fatal("refusal changed the preexisting target") + } + } + parent := filepath.Join(t.TempDir(), "owned-parent") + root := filepath.Join(parent, ".qoder") + if err := os.MkdirAll(root, 0700); err != nil { + t.Fatal(err) + } + recipe, err := qoderAuthorEmptyTelemetry(context.Background(), root) + if err != nil { + t.Fatal(err) + } + if err := os.Rename(parent, parent+"-retired"); err != nil { + t.Fatal(err) + } + if err := os.Mkdir(parent, 0700); err != nil { + t.Fatal(err) + } + if err := os.Rename(filepath.Join(parent+"-retired", ".qoder"), root); err != nil { + t.Fatal(err) + } + if recipe.check() == nil { + t.Fatal("original ancestor replacement preserving the entire profile was accepted") + } +} + +func TestQoderEmptyTelemetryUsesOriginalContextAndCannotHideMetadataDrift(t *testing.T) { + root, metadata, recipe := qoderTelemetryFixture(t) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + recipe.ctx = ctx + if recipe.check() == nil { + t.Fatal("canceled original work context allowed a new telemetry check") + } + recipe.ctx = context.Background() + projectNativeRuntimeWrite(t, filepath.Join(root, ".auth", "private-extra"), []byte("unapproved auth sibling"), 0600) + if recipe.check() != nil || metadata.check() == nil { + t.Fatal("telemetry recipe hid the unchanged closed auth membership failure") + } +} diff --git a/repotests/agentplugins_qoder_empty_telemetry_test.go b/repotests/agentplugins_qoder_empty_telemetry_test.go new file mode 100644 index 000000000..2ae35343f --- /dev/null +++ b/repotests/agentplugins_qoder_empty_telemetry_test.go @@ -0,0 +1,215 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "context" + "errors" + "io" + "os" + "path/filepath" + "strings" + "syscall" + + "golang.org/x/sys/unix" +) + +var qoderTelemetryNames = [...]string{"tmp", "telemetry", "active-runs"} + +type qoderEmptyTelemetry struct { + ctx context.Context + root string + parents []os.FileInfo + profile os.FileInfo + directories [3]os.FileInfo +} + +// System ancestors are identity authorities, not inventories of ambient HOME. +func qoderTelemetryRoot(ctx context.Context, root string) (file *os.File, parents []os.FileInfo, err error) { + parts := strings.Split(strings.TrimPrefix(root, "/"), "/") + if ctx == nil || ctx.Err() != nil || !filepath.IsAbs(root) || filepath.Clean(root) != root || filepath.Base(root) != ".qoder" || len(root) > 4096 || len(parts) > 64 { + return nil, nil, qoderMetadataRefused() + } + fd, err := unix.Open("/", unix.O_PATH|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return nil, nil, qoderMetadataRefused() + } + file = os.NewFile(uintptr(fd), "qoder-telemetry-original-ancestor") + defer func() { + if err != nil && file != nil { + err = errors.Join(err, file.Close()) + file = nil + } + }() + for _, part := range parts { + info, statErr := file.Stat() + if ctx.Err() != nil || statErr != nil { + return file, nil, qoderMetadataRefused() + } + parents = append(parents, info) + next, openErr := qoderLogOpenAt(file, part, true) + closeErr := file.Close() + file = next + if openErr != nil || closeErr != nil { + return file, nil, qoderMetadataRefused() + } + } + return file, parents, nil +} + +func qoderTelemetrySameParents(before, after []os.FileInfo) bool { + if len(before) != len(after) { + return false + } + for i, old := range before { + now := after[i] + a, okA := old.Sys().(*syscall.Stat_t) + b, okB := now.Sys().(*syscall.Stat_t) + if !okA || !okB || !os.SameFile(old, now) || old.Mode() != now.Mode() || a.Uid != b.Uid || a.Gid != b.Gid { + return false + } + } + return true +} + +func qoderTelemetryCreate(parent *os.File, name string) (file *os.File, err error) { + if !qoderMetadataAbsent(parent, name) || unix.Mkdirat(int(parent.Fd()), name, 0700) != nil { + return nil, qoderMetadataRefused() + } + fd, err := unix.Openat(int(parent.Fd()), name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return nil, qoderMetadataRefused() + } + file = os.NewFile(uintptr(fd), "qoder-authored-empty-telemetry-directory") + if err := file.Chmod(0700); err != nil { + return nil, errors.Join(qoderMetadataRefused(), file.Close()) + } + return file, nil +} + +func qoderAuthorEmptyTelemetry(ctx context.Context, root string) (result *qoderEmptyTelemetry, err error) { + profile, parents, err := qoderTelemetryRoot(ctx, root) + if err != nil { + return nil, err + } + defer func() { err = errors.Join(err, profile.Close()) }() + info, err := profile.Stat() + if err != nil || !qoderLogOwned(info, true) || info.Mode() != os.ModeDir|0700 { + return nil, qoderMetadataRefused() + } + held := []*os.File{} + defer func() { + for i := len(held) - 1; i >= 0; i-- { + err = errors.Join(err, held[i].Close()) + } + }() + parent := profile + for _, name := range qoderTelemetryNames { + if ctx.Err() != nil { + return nil, qoderMetadataRefused() + } + child, createErr := qoderTelemetryCreate(parent, name) + if createErr != nil { + return nil, createErr + } + held, parent = append(held, child), child + } + result = &qoderEmptyTelemetry{ctx: ctx, root: root, parents: parents} + result.profile, err = profile.Stat() + if err != nil { + return nil, qoderMetadataRefused() + } + for i, directory := range held { + result.directories[i], err = directory.Stat() + if err != nil || !qoderLogOwned(result.directories[i], true) || result.directories[i].Mode() != os.ModeDir|0700 { + return nil, qoderMetadataRefused() + } + } + if err := result.check(); err != nil { + return nil, err + } + return result, nil +} + +// Enumerate only the fixed expected name, or exact emptiness. Unknown child +// bodies are never opened. A fresh descriptor starts every check at offset zero. +func qoderTelemetryMembers(directory *os.File, expected os.FileInfo, child string) (err error) { + fd, err := unix.Openat(int(directory.Fd()), ".", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return qoderMetadataRefused() + } + readable := os.NewFile(uintptr(fd), "qoder-held-empty-telemetry-membership") + defer func() { err = errors.Join(err, readable.Close()) }() + before, err := readable.Stat() + if err != nil || !projectNativeRuntimeStableStat(expected.Sys(), before.Sys()) { + return qoderMetadataRefused() + } + count := 0 + if child != "" { + count = 1 + } + names, err := readable.Readdirnames(count + 1) + if (err != nil && !errors.Is(err, io.EOF)) || len(names) != count || count == 1 && names[0] != child { + return qoderMetadataRefused() + } + extra, err := readable.Readdirnames(1) + after, statErr := readable.Stat() + if len(extra) != 0 || !errors.Is(err, io.EOF) || statErr != nil || !projectNativeRuntimeStableStat(expected.Sys(), after.Sys()) { + return qoderMetadataRefused() + } + return nil +} + +func (r *qoderEmptyTelemetry) check() (err error) { + profile, parents, err := qoderTelemetryRoot(r.ctx, r.root) + if err != nil { + return err + } + defer func() { err = errors.Join(err, profile.Close()) }() + info, err := profile.Stat() + if err != nil || !qoderMetadataSame(r.profile, info, true) || !qoderTelemetrySameParents(r.parents, parents) { + return qoderMetadataRefused() + } + edges := []qoderLogEdge{} + defer func() { + for i := len(edges) - 1; i >= 0; i-- { + err = errors.Join(err, edges[i].file.Close()) + } + }() + parent := profile + for i, name := range qoderTelemetryNames { + if r.ctx.Err() != nil { + return qoderMetadataRefused() + } + directory, openErr := qoderLogOpenAt(parent, name, true) + if openErr != nil { + return openErr + } + current, statErr := directory.Stat() + edges = append(edges, qoderLogEdge{parent: parent, file: directory, name: name, info: current}) + if statErr != nil || !qoderMetadataSame(r.directories[i], current, true) || current.Mode() != os.ModeDir|0700 { + return qoderMetadataRefused() + } + child := "" + if i+1 < len(qoderTelemetryNames) { + child = qoderTelemetryNames[i+1] + } + if err := qoderTelemetryMembers(directory, current, child); err != nil { + return err + } + parent = directory + } + if err := qoderLogCheckEdges(edges); err != nil { + return err + } + current, again, err := qoderTelemetryRoot(r.ctx, r.root) + if err != nil { + return err + } + defer func() { err = errors.Join(err, current.Close()) }() + linked, err := current.Stat() + if err != nil || !projectNativeRuntimeStableStat(info.Sys(), linked.Sys()) || !qoderTelemetrySameParents(parents, again) { + return qoderMetadataRefused() + } + return nil +} From 4f034eb374bb585f0bc409763e62f0058bebabed Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 22:19:03 +0100 Subject: [PATCH 24/30] docs(qoder): record reviewed directory fixture gates --- ...-empty-telemetry-directories-validation.md | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/plans/project-install-e2e/qoder-empty-telemetry-directories-validation.md diff --git a/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-validation.md b/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-validation.md new file mode 100644 index 000000000..973439f66 --- /dev/null +++ b/docs/plans/project-install-e2e/qoder-empty-telemetry-directories-validation.md @@ -0,0 +1,63 @@ +# Qoder empty telemetry directory validation + +This slice starts from published +`89321cbe5baf79d5837da4ad95040beabcd6cdbc` and implements only the +[independently reviewed directory recipe](qoder-empty-telemetry-directories-plan.md). +Native R1 and Kimi R2 approved exact plan SHA256 +`27a91ec4a8dcff76d08eabb2363701feeb20f73a4ce7a4c12e108db746d14256` +before the separate plan commit `7389fc66b0fc7ffcff78e77900a2571e3c70a5f7` +and implementation. Root authored the code; native independently reviewed all +three changed code files and gave CODE GO on tree +`f5918dd2910d8f3bb1cb8a4970c5eb76d61478d3`. + +The original constructor now exclusively creates `tmp/telemetry/active-runs` +under its original disposable `.qoder`, with exact mode0700 and held ancestry +identities. Every existing command retains the original context, profile, +anonymous metadata and closed auth checks and additionally checks the exact +three directory identities and closed membership. The final directory is empty. +No unknown child body is opened. A leftover marker, substituted parent, +permission drift or unknown child refuses. Directory timestamps are stable +within each check; they are not adopted as an across-command identity rule. +The complete existing profile equality and logging guard remain unchanged. +No logging exception or native account/permission/trust value was added. + +## Actual local verification + +- Focused inert `go test ./repotests -run '^(TestQoder|TestProjectNativeCLI)' + -count=1` passed, package0.112s and wall10.673s, on reviewed treef5918. +- Actual all-configured full-root affected lint against + `a412ba5e137fdcb7e004393229b323211ed746e1` initially found one spelling issue + in a new assertion message: `cancelled` instead of `canceled`. The failed + gate output is retained. Only that literal changed; native independently + confirmed the exact one-line delta and gave amended CODE GO on tree + `8df29edea8545365cd8964161e266ea982637c83`. +- Actual all-configured full-root affected lint on the amended tree reported + zero issues in31.631s. The unchanged whole-root + `revive,funlen,gocyclo,gocognit,dupl,depguard` gate reported zero issues + in1.625s. No lint exclusion or suppressed finding was introduced. +- Gofmt and staged whitespace checks passed. The sole post-test spelling + correction changes no executable predicate, fixture behavior or assertion. +- The genuine source893 native artifact12768990209 was replayed through the + current strict reader. Its inner ZIP SHA256 is + `764c0de8ac7c11399278f46ea684b2d0c2078cae55635994276af59d2403e0c7`. + All16 closed members remain accepted only as the original failed, + incomplete observation, with all five, account and model flags false. + Both runtime and shared research reader bytes are identical to source893. + +The implementation commit is +`9b2a946ff95960a09067def78abebb98941e4074`, with exact reviewed tree8df29. +Local receipts are retained as `/tmp/uap-qoder-empty-telemetry-focused-go.json`, +`/tmp/uap-qoder-empty-telemetry-lint-gates.json`, +`/tmp/uap-qoder-empty-telemetry-lint-gates-v2.json` and +`/tmp/uap-qoder-empty-telemetry-current-raw-replay.json`, with their corresponding +full command logs and hashes. This record adds no code after review. + +## Remaining genuine boundary + +The previous actual native catalog succeeded but the original full-profile +check failed on three new telemetry directories plus four omitted logging +details. This slice does not infer or authorize those omitted details. A new +genuine hosted capture must establish its actual next boundary. No vendor, +public installer or scanner executed locally; no hosted environment was +simulated. Native session, MCP operation, complete lifecycle, account and model +qualification remain unproved. This slice authorizes no merge or release. From 66d4ee9ce1f5106b117723b418c143f64338d200 Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 23:14:10 +0100 Subject: [PATCH 25/30] docs(qoder): freeze four-detail diagnostic 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 4fd95dcdf..ea1438e89 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1340,3 +1340,107 @@ and [actual validation](qoder-auth-membership-validation.md) close that early window while preserving the original metadata commits and complete-profile guard. This source correction supplies no native qualification; fresh hosted evidence and the existing lifecycle gates remain required. + + +# Qoder: complete the measured four-entry profile delta diagnostic + +PRE-CODE draft. Read AGENTS.md, owner scope and capability-preservation rules. +Obtain two independent GPT-6.1 Sol xhigh plan GOs before implementation. This is +diagnostic-only work on existing PR523, not permission for native log mutation, +profile relocation, a native retry at unchanged source, a release or VS Code. + +## Exact new genuine evidence + +Published source 4f034eb374bb585f0bc409763e62f0058bebabed, tree +3d04ce2878cf93239cfa2f9a2564dc2c6a931bc3. Actual runtime40585824651, +Qoder job122486483093, fails the original complete-profile equality guard after +4.42 seconds. Every recorded prefix command exits0 without a capture/context +error. Public add succeeds Prepared; plugins list exits0 in1149ms and selects +the one exact receipt-owned enabled flag-view plugin with one skill and no MCP. +No native prompt, later lifecycle or authentication/model result was reached. + +Native artifact12773802932 is4508B,16closed members, outer SHA256 +750064bd0a084ff22e5109e3e114f6d7ed7a21bbfe98d3cafea4f9afddc3a581; +frontend12774277343 has25members, outer +a8dd9a901886a9c3eb7f28a090d6aaa7a370ddc76e1898f063760c0dd4e28d3e. +API identities, ordinary ZIP membership/CRCs, clean exact source/tree/build, +all24frontend stream joins and unchanged exact-source failed admission pass. +Root semantic audit independently joins official archive/executable pins, +public source/package/binding/physical/receipt identities and native catalog. +Record /tmp/uap-qoder-4f-current-40585824651/root-semantic-audit.json, +SHA25636c431d8799a7ae513ef30bab98452afa3e9a3fb9283fb9b8003aae8da9e0cff. + +The complete delta now has15before/18after,3added/0removed/1changed. +Its area counts are exactly3logs and1log-latest; every other area is0. +The three retained details show changed latest symlink, added0755directory and +added0644file of994B. One entry is still explicitly omitted. Its name, kind, +mode and bytes remain unknown. The prior three telemetry directories no longer +appear as differences; that correction does not authorize any remaining change. + +Pinned npm1.1.78 source defines a candidate per-run directory, qodercli.log, +manifest.json and latest-link replacement before CLI argument parsing. It does +not identify the omitted native entry or prove npm/standalone implementation +equivalence. No quiet/telemetry setting is assumed to suppress these writes. + +## Proposed finite diagnostic amendment + +Change only qoderDeltaBound's maximum retained metadata details from3to4. +Preserve the existing complete-object2KiB JSON bound,4096-entry input bound, +strict inventory-record grammar, finite areas/kinds/operations, hashed keys, +hashed file/link records, and all numeric/mode constraints. Preserve the exact +existing other-file priority and deterministic area/hash ordering. The first +three selected details must remain identical; the fourth is the next detail +under that same ordering. No raw name, path, link target, contents or credential +is added and no filesystem read, enumeration or command is added. + +At exactly four valid differences the existing status becomes captured and +omitted_count becomes0; at five or more it remains entry-limit with exactly +total-minus4 omissions. The whole-object bound still refuses oversized output; +it is never increased or bypassed. This is a bounded formatter over the already +held unequal maps, not a new baseline, a permission decision or log classifier. + +All complete-profile, auth/metadata, empty telemetry, no-follow identity, +original HOME, immutable source, catalog, deadlines and process cleanup gates +remain byte-identical. The original t.Fatal after collecting this diagnostic +remains unconditional for unequal original profiles. All five/native/auth/model +flags stay false. Python admission, commands, workflow selection and source pins +are unchanged; historical failed raw archives must re-admit byte-identically. + +## Meaningful verification + +Update the existing independent expected metadata/count assertions only where +the intentional fourth retained detail changes them. Keep all prior checks, +priority cases, hostile records, actual-filesystem nonmutation, private-canary +checks and the fatal full-profile child control. State independent literal key +and content digests for the fourth selected fixture entries; do not replace +assertions with expectations computed through the formatter being tested. + +Exercise an actual inert filesystem with a latest-link replacement plus a new +log directory/two files: four details, exact counters, captured/zero omissions, +complete serialized object<=2048bytes, original maps/profile unchanged. Add a +fifth difference and verify exact one omission and unchanged prefix ordering. +Preserve zero/one/seven other-area cases with corrected exact fourth selection; +verify reverse insertion gives the same result. Extend the existing largest +valid file-record control to five entries, retaining four with truthful omission +and the unchanged complete-object limit. Unknown records remain refused. + +Run focused Qoder/shared inert Go contracts, existing runtime Python contracts, +actual full-root affected all-enabled and unfiltered six-linter gates, gofmt, +whitespace and unchanged original4f failed-envelope replay. No native/vendor, +scanner or public installer executable runs locally. Serialize Go/lint slots; +do not overlap snapshot-sensitive Go with other Python fixture mutations. + +## Review, source and publication + +After both plan GOs, recheck remote head and create an isolated worktree from +the agreed current4f. Commit this accepted amendment to the existing consolidated +remaining-native-qualification.md before code. Limit code to the Qoder formatter +and its affected/new inert contracts; do not merge unrelated leaf sources. +Two independent exact implementation reviews and executed gates follow. Root +alone publishes a normal expected-head fast-forward to existing PR523 and audits +the resulting genuine capture. Preserve concurrent work and all failed history. +This amendment grants no eventual log allowance or full-cycle qualification. + +## Accepted Qoder diagnostic amendment before implementation + +The exact draft SHA256 `49e8fdec9188273f924542bf13469e44c83393ade9bc5c392ad0d5ddb68bfe4a` received independent GPT-6.1 Sol xhigh plan GOs from `vscode_finish` (R1) and `native_finish` (R2) before any code edit. Both reviewers inspected the current formatter and exact failed capture; the original full-profile failure and all qualification limits remain required. From e06ec60865c37c41b6f37ebd101c7387d426ae3f Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 23:20:21 +0100 Subject: [PATCH 26/30] docs(qoder): correct pre-code preservation control claim --- .../remaining-native-qualification.md | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index ea1438e89..18b2b2cbd 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1411,8 +1411,11 @@ are unchanged; historical failed raw archives must re-admit byte-identically. Update the existing independent expected metadata/count assertions only where the intentional fourth retained detail changes them. Keep all prior checks, priority cases, hostile records, actual-filesystem nonmutation, private-canary -checks and the fatal full-profile child control. State independent literal key -and content digests for the fourth selected fixture entries; do not replace +checks and the original fatal full-profile comparison. The existing Qoder +controls exercise actual filesystem drift and unequal inventory maps; there is +no separate Qoder fatal-child test. Prove the fatal caller remains unchanged +with its complete file hash. State independent literal key and content digests +for the fourth selected fixture entries; do not replace assertions with expectations computed through the formatter being tested. Exercise an actual inert filesystem with a latest-link replacement plus a new @@ -1444,3 +1447,19 @@ This amendment grants no eventual log allowance or full-cycle qualification. ## Accepted Qoder diagnostic amendment before implementation The exact draft SHA256 `49e8fdec9188273f924542bf13469e44c83393ade9bc5c392ad0d5ddb68bfe4a` received independent GPT-6.1 Sol xhigh plan GOs from `vscode_finish` (R1) and `native_finish` (R2) before any code edit. Both reviewers inspected the current formatter and exact failed capture; the original full-profile failure and all qualification limits remain required. + +### Factual pre-code correction and renewed independent acceptance + +Before source edits, root corrected the reference to a separate fatal-child +control: Qoder has actual filesystem drift/unequal-map controls and an +unconditional fatal caller, not that separate Copilot test. The revised exact +plan SHA256 is `449e9c92195a9d3460a6a6ee7a9e76f6d94d3ccc9a7539922ed6e0c9027d21c8`. +Independent GPT-6.1 Sol xhigh reviewers `gemini_qoder_finish` and +`native_e2e_finish` both gave PLAN GO before implementation; receipt SHA256s +are `55cb807030414e335b55884c2187fc03f858748397546db2a6074f67eee4750e` +and `cd499270db43b6536fa61f197bd0bfabafee58d610fa0abc4f6c0f1f4c37c10e`. +Both checked the actual source and unchanged unconditional full-profile fatal +caller, whose complete file SHA256 is +`20ba3af567f4f0499220ff6ad7202993a4a4a292d6c41493f41cf174f804ba72`. +This factual correction changes no guard, authorized scope or intended tests. +The previous plan-only commit remains preserved in history. From 393bd1cd777eee598d48000f53c0235f34c09e08 Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 23:21:33 +0100 Subject: [PATCH 27/30] test(qoder): retain the fourth bounded profile delta detail --- ...tplugins_qoder_delta_file_priority_test.go | 7 +- ...entplugins_qoder_delta_four_detail_test.go | 79 +++++++++++++++++++ .../agentplugins_qoder_delta_priority_test.go | 19 +++-- .../agentplugins_qoder_profile_delta_test.go | 14 ++-- 4 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 repotests/agentplugins_qoder_delta_four_detail_test.go diff --git a/repotests/agentplugins_qoder_delta_file_priority_test.go b/repotests/agentplugins_qoder_delta_file_priority_test.go index afbfeeb62..8a3c017b4 100644 --- a/repotests/agentplugins_qoder_delta_file_priority_test.go +++ b/repotests/agentplugins_qoder_delta_file_priority_test.go @@ -14,6 +14,7 @@ var qoderFilePriorityExpected = []struct{ key, digest string }{ {"1c789d8c54f84d9a76bb9e9c466fd82edc93600e0ae1096beb1aa2caae60c045", "0bf9b4479f31c38fe151c1ee8166988124d602088321bb0568bad105c279342d"}, {"5b16aaa9c4be8b1466c2722c7fa908a2b7e41e6ea4c487f9a19cd536f1a655e4", "83e787826d4b24b98e62a63c91474209e44436757410c905e885fc36c1aad374"}, {"9c39a648892eec9283695b692d95e346f397274fb8d9e284c9a7eaf005d2fea0", "b012dc678157d20bc91a92eb1f4f2087a9d819be3d287674757833b62fd1d7fd"}, + {"a999891db345cf1910a7dd3194ccb1219501dbfbc262cdc5dd5c0448fe9d0874", "ab043a60050db6204df933f8fa0b12d03f920fb3dba9bee5bc519d261fcce57e"}, } func qoderFilePriorityAddDirectories(t *testing.T, root string) { @@ -60,8 +61,8 @@ func TestQoderProfileDeltaPrioritizesHeldOtherFilesOverDirectoryOnlyChanges(t *t unchanged := qoderDeltaUnchanged(t, root) delta := qoderDeltaContract(t, before, after) areas := map[string]int{"log-latest": 1, "logs": 3, "settings": 0, "plugin-cache": 0, "plugins": 0, "cache": 0, "other": 7} - if delta.Status != "entry-limit" || delta.BeforeEntries != len(before) || delta.AfterEntries != len(before)+10 || delta.Added != 10 || delta.Removed != 0 || delta.Changed != 1 || delta.OmittedCount != 8 || len(delta.Entries) != 3 || !reflect.DeepEqual(delta.Areas, areas) { - t.Fatal("file priority changed complete counts, areas or three-detail/eight-omission bounds") + if delta.Status != "entry-limit" || delta.BeforeEntries != len(before) || delta.AfterEntries != len(before)+10 || delta.Added != 10 || delta.Removed != 0 || delta.Changed != 1 || delta.OmittedCount != 7 || len(delta.Entries) != 4 || !reflect.DeepEqual(delta.Areas, areas) { + t.Fatal("file priority changed complete counts, areas or four-detail/seven-omission bounds") } size := int64(15) for index, want := range qoderFilePriorityExpected { @@ -127,7 +128,7 @@ func TestQoderProfileDeltaAllDirectoryOtherChangesKeepPreviousHashOrder(t *testi after := qoderDeltaSnapshot(t, root) unchanged := qoderDeltaUnchanged(t, root) delta := qoderDeltaContract(t, before, after) - if delta.Status != "entry-limit" || delta.Added != 5 || delta.Removed != 0 || delta.Changed != 0 || delta.Areas["other"] != 5 || delta.OmittedCount != 2 || len(delta.Entries) != 3 { + if delta.Status != "entry-limit" || delta.Added != 5 || delta.Removed != 0 || delta.Changed != 0 || delta.Areas["other"] != 5 || delta.OmittedCount != 1 || len(delta.Entries) != 4 { t.Fatal("directory-only priority changed previous counts or bounds") } for index, want := range qoderFilePriorityExpected { diff --git a/repotests/agentplugins_qoder_delta_four_detail_test.go b/repotests/agentplugins_qoder_delta_four_detail_test.go new file mode 100644 index 000000000..9f24a0c0b --- /dev/null +++ b/repotests/agentplugins_qoder_delta_four_detail_test.go @@ -0,0 +1,79 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "crypto/sha256" + "fmt" + "os" + "path/filepath" + "reflect" + "testing" +) + +func qoderFourDetailFixture(t *testing.T) (string, map[string]string) { + t.Helper() + root := qoderDeltaFixture(t) + before := qoderDeltaSnapshot(t, root) + directory := filepath.Join(root, "logs", "PRIVATE-KEY-new-run") + if err := os.Mkdir(directory, 0755); err != nil { + t.Fatal(err) + } + projectNativeRuntimeWrite(t, filepath.Join(directory, "qodercli.log"), []byte("PRIVATE-BODY-log\n"), 0644) + projectNativeRuntimeWrite(t, filepath.Join(directory, "manifest.json"), []byte("{\"PRIVATE-KEY\":\"PRIVATE-BODY\"}\n"), 0644) + link := filepath.Join(root, "logs", "latest") + if err := os.Remove(link); err != nil { + t.Fatal(err) + } + if err := os.Symlink("PRIVATE-KEY-new-run", link); err != nil { + t.Fatal(err) + } + return root, before +} + +func qoderFourDetailExpected(before, after map[string]string) []qoderDeltaEntry { + manifestSize, logSize := int64(31), int64(17) + return []qoderDeltaEntry{ + {KeySHA256: "22abff24b603cb9288fe82d7f92a98a22a4e3b648b8b0e07db5bc03da0901228", Area: "log-latest", Operation: "changed", + Before: qoderDeltaRecord{Kind: "symlink", RecordSHA256: fmt.Sprintf("%x", sha256.Sum256([]byte(before["logs/latest"])))}, + After: qoderDeltaRecord{Kind: "symlink", RecordSHA256: fmt.Sprintf("%x", sha256.Sum256([]byte(after["logs/latest"])))}}, + {KeySHA256: "1b370a214d3c23056984b0a326e20ea1bb58f6cc8403d87cf06e41eb31db1cc8", Area: "logs", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, + After: qoderDeltaRecord{Kind: "file", Mode: "644", Bytes: &manifestSize, ContentSHA256: "3ffa6538f18eb86fa0e9250ca108864f5393c1816851e9a3d758bf387104a751"}}, + {KeySHA256: "5e8c44b0532d3994adc6f7e8a3c67a68c9d8397812a0ab64583b728c58da4277", Area: "logs", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, + After: qoderDeltaRecord{Kind: "directory", Mode: "20000000755"}}, + {KeySHA256: "ee5ac080ef6087d938a7938b7c86dd310a8313384bee0e41a46cabb3f9fc0c3b", Area: "logs", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, + After: qoderDeltaRecord{Kind: "file", Mode: "644", Bytes: &logSize, ContentSHA256: "e2b9c8e8c303354e03149e46c6dafcd17303e3998b2026fb7aee9c6960136a4f"}}, + } +} + +func TestQoderProfileDeltaRetainsFourActualDetailsAndTruthfulFifthOmission(t *testing.T) { + for _, extra := range []int{0, 1} { + t.Run(fmt.Sprint(extra), func(t *testing.T) { + root, before := qoderFourDetailFixture(t) + if extra == 1 { + qoderDeltaMutate(t, root, "settings") + } + after := qoderDeltaSnapshot(t, root) + unchanged := qoderDeltaUnchanged(t, root) + delta := qoderDeltaContract(t, before, after) + status := "captured" + if extra == 1 { + status = "entry-limit" + } + areas := map[string]int{"log-latest": 1, "logs": 3, "settings": extra, "plugin-cache": 0, "plugins": 0, "cache": 0, "other": 0} + if delta.Status != status || delta.BeforeEntries != len(before) || delta.AfterEntries != len(before)+3 || delta.Added != 3 || delta.Removed != 0 || delta.Changed != 1+extra || delta.OmittedCount != extra || len(delta.Entries) != 4 || !reflect.DeepEqual(delta.Areas, areas) { + t.Fatal("four-detail diagnostic lost exact counts, bounds or omission") + } + for index, want := range qoderFourDetailExpected(before, after) { + if delta.Entries[index].KeySHA256 != want.KeySHA256 { + t.Fatal("four-detail diagnostic changed the independently expected prefix order") + } + qoderDeltaRequireDetail(t, delta, want) + } + qoderFilePriorityRequireUnchanged(t, root, before, after, delta, unchanged) + if reflect.DeepEqual(before, after) { + t.Fatal("four-detail diagnostic adopted the unequal original profile") + } + }) + } +} diff --git a/repotests/agentplugins_qoder_delta_priority_test.go b/repotests/agentplugins_qoder_delta_priority_test.go index 77829a5a2..378a9cc8a 100644 --- a/repotests/agentplugins_qoder_delta_priority_test.go +++ b/repotests/agentplugins_qoder_delta_priority_test.go @@ -49,18 +49,21 @@ func qoderDeltaPriorityExpected(before, after map[string]string, count int) []qo added("1cf52e636502d4fbfad917f2d94f3671b785a2d8b4d4177a7c8d23f0b2f60bad", "0bf9b4479f31c38fe151c1ee8166988124d602088321bb0568bad105c279342d"), added("4dabb223a442282b5ae1a0cf90be99ae89080e27501c03a2626d4f2c2f85d512", "6543378f10b203e977ea839d673fe1440461fd5c6184fb792dbfa4986988a883"), added("53e6490e39a55ba83e28aee3b11eb97ad9cd8b2c37899f1429ada97ac70d293d", "0f31bcd41780292ffd34d27412592d3b411a84a4cf63ff8be35c01fb6b4641dc"), + added("6797b1822823f37cf3df09000080f3eff8364b6a2d4e7a908a36c610528b144e", "2c22054a7009d10e53452da0dee842749730504c2b53c45e7a5cb6992724e831"), } } link := qoderDeltaEntry{KeySHA256: "22abff24b603cb9288fe82d7f92a98a22a4e3b648b8b0e07db5bc03da0901228", Area: "log-latest", Operation: "changed", Before: qoderDeltaRecord{Kind: "symlink", RecordSHA256: fmt.Sprintf("%x", sha256.Sum256([]byte(before["logs/latest"])))}, After: qoderDeltaRecord{Kind: "symlink", RecordSHA256: fmt.Sprintf("%x", sha256.Sum256([]byte(after["logs/latest"])))}} log := qoderDeltaEntry{KeySHA256: "85b711de57b6a2434a38cf3a2cb2b55ed13d60ca7ee98be2052a3022bf92d3cc", Area: "logs", Operation: "added", Before: qoderDeltaRecord{Kind: "absent"}, After: qoderDeltaRecord{Kind: "file", Mode: "600", Bytes: &logSize, ContentSHA256: "e2b9c8e8c303354e03149e46c6dafcd17303e3998b2026fb7aee9c6960136a4f"}} - if count == 1 { - return []qoderDeltaEntry{added("809103e20c841c150f13982def42e437f1b3c2d60e7c11550d9ec2aeacd02155", "83e787826d4b24b98e62a63c91474209e44436757410c905e885fc36c1aad374"), link, log} - } secondLog := log secondLog.KeySHA256 = "a19f11735bceecab78bb9f5261fca65713cc6f5219a632be852509e26f519a4b" - return []qoderDeltaEntry{link, log, secondLog} + if count == 1 { + return []qoderDeltaEntry{added("809103e20c841c150f13982def42e437f1b3c2d60e7c11550d9ec2aeacd02155", "83e787826d4b24b98e62a63c91474209e44436757410c905e885fc36c1aad374"), link, log, secondLog} + } + thirdLog := log + thirdLog.KeySHA256 = "b20a0545710e3fe8e085d613863a77d78dcafcdc3681b1dba27b4f29c3714661" + return []qoderDeltaEntry{link, log, secondLog, thirdLog} } func qoderDeltaReverseInsertion(source map[string]string) map[string]string { @@ -83,8 +86,12 @@ func TestQoderProfileDeltaPrioritizesActualOtherEntriesWithoutChangingCountsOrGu unchanged := qoderDeltaUnchanged(t, root) delta := qoderDeltaContract(t, before, after) areas := map[string]int{"log-latest": 1, "logs": 3, "settings": 0, "plugin-cache": 0, "plugins": 0, "cache": 0, "other": count} - if delta.Status != "entry-limit" || delta.BeforeEntries != len(before) || delta.AfterEntries != len(before)+count+3 || delta.Added != count+3 || delta.Changed != 1 || delta.Removed != 0 || delta.OmittedCount != count+1 || !reflect.DeepEqual(delta.Areas, areas) || len(delta.Entries) != 3 { - t.Fatal("priority changed exact whole-profile counts, three-detail bound or omissions") + status := "entry-limit" + if count == 0 { + status = "captured" + } + if delta.Status != status || delta.BeforeEntries != len(before) || delta.AfterEntries != len(before)+count+3 || delta.Added != count+3 || delta.Changed != 1 || delta.Removed != 0 || delta.OmittedCount != count || !reflect.DeepEqual(delta.Areas, areas) || len(delta.Entries) != 4 { + t.Fatal("priority changed exact whole-profile counts, four-detail bound or omissions") } for index, want := range qoderDeltaPriorityExpected(before, after, count) { if delta.Entries[index].KeySHA256 != want.KeySHA256 { diff --git a/repotests/agentplugins_qoder_profile_delta_test.go b/repotests/agentplugins_qoder_profile_delta_test.go index 13136deca..8da5507bd 100644 --- a/repotests/agentplugins_qoder_profile_delta_test.go +++ b/repotests/agentplugins_qoder_profile_delta_test.go @@ -88,9 +88,9 @@ func qoderDeltaBound(delta qoderProfileDelta) qoderProfileDelta { return qoderDeltaPriorityLess(a, b, delta.Areas["other"] > 0) }) delta.Status = "captured" - if len(delta.Entries) > 3 { - delta.OmittedCount, delta.Status = len(delta.Entries)-3, "entry-limit" - delta.Entries = delta.Entries[:3] + if len(delta.Entries) > 4 { + delta.OmittedCount, delta.Status = len(delta.Entries)-4, "entry-limit" + delta.Entries = delta.Entries[:4] } if raw, err := json.Marshal(delta); err != nil || len(raw) > 2<<10 { return qoderProfileDelta{Status: "diagnostic-bound"} @@ -361,18 +361,18 @@ func qoderDeltaUnchanged(t *testing.T, root string) func() { func TestQoderProfileDeltaBoundsDetailsAndRefusesMalformedHeldRecords(t *testing.T) { before, after := map[string]string{}, map[string]string{} mode := strconv.FormatUint(uint64(os.ModeSetuid|os.ModeSetgid|os.ModeSticky|0777), 8) - for _, name := range []string{"PRIVATE-KEY-one", "PRIVATE-KEY-two", "PRIVATE-KEY-three", "PRIVATE-KEY-four"} { + for _, name := range []string{"PRIVATE-KEY-one", "PRIVATE-KEY-two", "PRIVATE-KEY-three", "PRIVATE-KEY-four", "PRIVATE-KEY-five"} { before["cache/"+name] = "file:" + mode + ":67108864:" + strings.Repeat("a", 64) after["cache/"+name] = "file:" + mode + ":67108864:" + strings.Repeat("b", 64) } delta := qoderDeltaContract(t, before, after) - if delta.Status != "entry-limit" || delta.Changed != 4 || delta.Areas["cache"] != 4 || delta.OmittedCount != 1 || len(delta.Entries) != 3 { + if delta.Status != "entry-limit" || delta.Changed != 5 || delta.Areas["cache"] != 5 || delta.OmittedCount != 1 || len(delta.Entries) != 4 { t.Fatal("larger delta was silently truncated") } if again := qoderOriginalProfileDelta(before, after); !reflect.DeepEqual(delta, again) { t.Fatal("hashed bounded selection is not deterministic") } - wantDigests := []string{"53dc54f1a425947b2f6b8175aee8b2cc6cd4abe4a5c5a57162bf0f834419e346", "62b14fde114d9eb0efda38990c4afb861568275b5ccb6d929f123f9e0ee2c140", "8018caba2e34bf352146720b5528dfa3b229b0899a268f4e4035b3107f24c9c5"} + wantDigests := []string{"53dc54f1a425947b2f6b8175aee8b2cc6cd4abe4a5c5a57162bf0f834419e346", "62b14fde114d9eb0efda38990c4afb861568275b5ccb6d929f123f9e0ee2c140", "8018caba2e34bf352146720b5528dfa3b229b0899a268f4e4035b3107f24c9c5", "8d8f07ab09d35e8ee4436986ddbf96dc98c950c3bd9cbcda9d9e334ed943a6f4"} size := int64(64 << 20) for index, digest := range wantDigests { qoderDeltaRequireDetail(t, delta, qoderDeltaEntry{KeySHA256: digest, Area: "cache", Operation: "changed", Before: qoderDeltaRecord{Kind: "file", Mode: mode, Bytes: &size, ContentSHA256: strings.Repeat("a", 64)}, After: qoderDeltaRecord{Kind: "file", Mode: mode, Bytes: &size, ContentSHA256: strings.Repeat("b", 64)}}) @@ -382,7 +382,7 @@ func TestQoderProfileDeltaBoundsDetailsAndRefusesMalformedHeldRecords(t *testing } delta.Areas["cache"] = -1 delta.Entries[0].KeySHA256 = "PRIVATE-KEY" - if len(before) != 4 || len(after) != 4 || after["cache/PRIVATE-KEY-one"] != "file:"+mode+":67108864:"+strings.Repeat("b", 64) { + if len(before) != 5 || len(after) != 5 || after["cache/PRIVATE-KEY-one"] != "file:"+mode+":67108864:"+strings.Repeat("b", 64) { t.Fatal("returned diagnostic aliases an input inventory") } for _, record := range []string{"PRIVATE-BODY", "file:600:1:PRIVATE-BODY", "directory:600", "file:600:-1:" + strings.Repeat("a", 64), "file:600:67108865:" + strings.Repeat("a", 64), "symlink:PRIVATE-BODY"} { From 1050bbbe163abf4c60f1ea13565b72bc864c6e75 Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 27 Oct 2026 23:23:03 +0100 Subject: [PATCH 28/30] docs(qoder): record reviewed four-detail gates --- .../remaining-native-qualification.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index 18b2b2cbd..db62b4e34 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1463,3 +1463,28 @@ caller, whose complete file SHA256 is `20ba3af567f4f0499220ff6ad7202993a4a4a292d6c41493f41cf174f804ba72`. This factual correction changes no guard, authorized scope or intended tests. The previous plan-only commit remains preserved in history. + +### Four-detail implementation and executed validation + +Reviewed code `393bd1cd777eee598d48000f53c0235f34c09e08`, tree +`782cd158b1d579da3ad8f30c44c13042d91f0619`, received independent CODE GOs +from `gemini_qoder_finish` and `native_e2e_finish`, both GPT-6.1 Sol xhigh. +Receipt SHA256s are `c59a544e54b12a5339e138307bf7ac2e6940d4f06ab570683676f03a1032c3dc` +and `ca520b77c7eb95774f73fb8ef7233c8c5d31bd7234316162ab9a29ab3637d71c`. +Only the three retained-count literals change in the formatter. Strict parsing, +priority comparator, whole-object 2048-byte bound, runtime reader and complete +unconditional fatal caller remain unchanged. Independently stated fourth hashes +and actual filesystem four/five-difference controls preserve the previous prefix, +truthful omissions, privacy and held-map/profile nonmutation. Four maximum valid +changed-file details serialize to 1845 bytes. + +Executed on that exact code: focused Qoder/shared inert Go contracts PASS; full +root affected all-enabled lint against program base `a412ba5e137fdcb7e004393229b323211ed746e1` +and unfiltered six-linter gate both zero issues; existing runtime Python contracts +PASS. Full command outputs and their SHA256s are retained in the gate receipt. +Original failed4f archive re-admits with all16 members and summary byte-identical, +all five flags false. The native reader SHA256 remains +`f3403d72755fde23afe46aaafb709fe4a1f78be9691324c07e2bbfe3f125cdf6`. +No native client, vendor executable, public installer executable or scanner ran +locally. Fresh genuine hosted output is still required; this change grants no +log allowance, lifecycle qualification, authentication or model claim. From 25ea0f0d708504a914aeaf864206e98fdcfa1cc8 Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 29 Oct 2026 00:58:09 +0800 Subject: [PATCH 29/30] test(qoder): capture exact native log-run identity --- .../remaining-native-qualification.md | 49 ++++ ...plugins_project_native_cli_attempt_test.go | 4 +- ...qoder_log_run_observation_contract_test.go | 238 +++++++++++++++++ ...tplugins_qoder_log_run_observation_test.go | 246 ++++++++++++++++++ scripts/project_native_runtime.py | 53 ++++ scripts/test_project_native_runtime.py | 59 +++++ 6 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 repotests/agentplugins_qoder_log_run_observation_contract_test.go create mode 100644 repotests/agentplugins_qoder_log_run_observation_test.go diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index db62b4e34..b5d483358 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1488,3 +1488,52 @@ all five flags false. The native reader SHA256 remains No native client, vendor executable, public installer executable or scanner ran locally. Fresh genuine hosted output is still required; this change grants no log allowance, lifecycle qualification, authentication or model claim. + +## Qoder exact run-log projection before any ownership allowance (2026-10-28) + +Start at 1050bbbe163abf4c60f1ea13565b72bc864c6e75/tree 5e64b064acc40f52cf362ad43b59589dc829746a. +Run 40594441986/native 12777840418 measures exactly 3 added log entries and 1 changed latest; +all 4 details are retained, but added names and the new target/manifest remain unobserved. +Official npm 1.1.78 archive SHA256 c5b237d2fd84ceb44af5427f281a7f1bcadd8c888fbed49197b5ce2f37dada4f; +bundle/qodercli.js SHA256 ec7b2a2c1be206f525ca974c3b9455fbec9ce7411bc5dfdee55f1e82eb90df38. +Its bytes36498/39612/40361 define timestamp-random-PID runID, logs/runs/, +qodercli.log, manifest.json and latest replacement. This is publisher source guidance, +not proof of standalone equivalence. No supported off/init-suppression route was found. + +Add only a failed-capture projection at the existing unequal-profile diagnostic before +its unchanged unconditional fatal. Reuse the held no-follow latest/target mechanics; +require its record to match the already-held after inventory and a source-format +relative target runs/. Never export an arbitrary target, path or unknown name. +Inspect only the exact manifest.json below that held ordinary owned run directory, +at most 4096 bytes, single-link 0644, stable held/linked identity and exact inventory +byte-count/hash. Do not read log bodies or enumerate additional profile content. +Strictly parse the bounded source-defined manifest object; unknown/malformed shape +returns a fixed refusal status, never raw text. Validate nested argv/env/log-file +types privately. Emit only checked runID/relative target, manifest size/hash, numeric +manifest pid/ppid, safe start/version/platform/arch/role, and finite equality/closure +booleans against held A/selected argv/profile and the original before/after maps. +No raw argv/env/project_id/full manifest/log body or path outside owned fixtures. +Keep the complete serialized projection <=2048 bytes; refusal output is status-only. +Label PID provenance manifest-only: no admitted native/supervisor PID is claimed. +No runner/API, argv/env, config/auth/trust, acquisition/pin, deadline or cleanup change. + +Implement repotests/agentplugins_qoder_log_run_observation_test.go and _contract_test.go, +one hook in agentplugins_project_native_cli_attempt_test.go, and optional Qoder-only +complete-shape admission in scripts/project_native_runtime.py with matching contracts +in scripts/test_project_native_runtime.py. Preserve unmarked archives and other clients. +All qualification/runtime/auth/model flags stay false; no mutation allowance, adopted +baseline, new member/command, release or VS Code work. Inert checks cover exact source +layout/manifest joins, private canaries, unknown/duplicate/malformed/oversized data, +aliases/hardlinks/foreign authority, unstable/mismatched held identities and unchanged +maps/files/fatal guard. Two independent plan GOs precede code; exact code review, +focused Go/Python and affected/full lint gates precede root-only publication and a +fresh genuine hosted failed capture. Any ownership policy needs separate actual review. + +Implemented after root and independent Grok PLAN reviews; independent Grok and +root CODE reviews found no remaining issue. Focused Qoder/shared inert Go passed +in 0.379s; all 30 Python contracts passed, including the final valid-Grok baseline +control. Affected all-enabled lint and unfiltered six-linter gates report zero +issues. The original 1050 archive's 16 members and complete failed/all-false +result replay unchanged. Timestamp and unsafe-leaf refusal controls were corrected +without weakening admission. No vendor ran locally; fresh hosted observation is +required before any supported log-ownership allowance or lifecycle claim. diff --git a/repotests/agentplugins_project_native_cli_attempt_test.go b/repotests/agentplugins_project_native_cli_attempt_test.go index b0679a1d3..1e9bfdaba 100644 --- a/repotests/agentplugins_project_native_cli_attempt_test.go +++ b/repotests/agentplugins_project_native_cli_attempt_test.go @@ -825,11 +825,13 @@ func projectNativeCLIRuntimeAttempt(t *testing.T, client string) { f.evidence["qoder_original_log_link"] = map[string]any{"no_follow": true, "contained_ordinary_target": true, "record": record} } f.evidence["attempt_stage"] = "native-installed-catalog" - result := f.command(t, "native-catalog", f.base.Project, []string{f.client, "plugins", "list", "--json", "--plugin-dir", f.view}, 30*time.Second) + catalogArgv := []string{f.client, "plugins", "list", "--json", "--plugin-dir", f.view} + result := f.command(t, "native-catalog", f.base.Project, catalogArgv, 30*time.Second) after, err := projectNativeCLIQoderProfile(profile) if err != nil || !reflect.DeepEqual(after, before) { if err == nil { f.evidence["qoder_original_profile_change"] = qoderOriginalProfileDelta(before, after) + f.evidence["qoder_log_run_observation"] = qoderObserveLogRun(f.ctx, profile, f.base.Project, catalogArgv, before, after) } t.Fatal("Qoder catalog changed the complete original user profile/cache") } diff --git a/repotests/agentplugins_qoder_log_run_observation_contract_test.go b/repotests/agentplugins_qoder_log_run_observation_contract_test.go new file mode 100644 index 000000000..49048dda9 --- /dev/null +++ b/repotests/agentplugins_qoder_log_run_observation_contract_test.go @@ -0,0 +1,238 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + "reflect" + "strings" + "testing" +) + +const qoderInertRunID = "2026-10-28T15-30-45-123Z-abc123-p1234" + +func qoderRunFixture(t *testing.T, mutate func(map[string]any)) (string, string, []string, map[string]string, map[string]string) { + t.Helper() + root, link := qoderLogFixture(t, "run-private-target") + if err := os.Mkdir(filepath.Join(root, "logs", "runs"), 0755); err != nil { + t.Fatal(err) + } + projectNativeRuntimeWrite(t, filepath.Join(root, "settings.json"), []byte("ORIGINAL-PRIVATE-SETTINGS"), 0600) + before := qoderDeltaSnapshot(t, root) + cwd := filepath.Join(filepath.Dir(root), "owned-project") + argv := []string{filepath.Join(filepath.Dir(root), "owned-client"), "plugins", "list", "--json", "--plugin-dir", filepath.Join(cwd, "owned-view")} + manifest := map[string]any{"run_id": qoderInertRunID, "started_at": "2026-10-28T15:30:45.123Z", "pid": 1234, "ppid": 5678, + "process_role": "main", "cli_version": "1.1.78", "node_version": "v24.0.0", "platform": "linux", "arch": "x64", "cwd": cwd, + "project_id": "PRIVATE-PROJECT-ID", "argv": argv, "env_flags": map[string]any{"debug": false, "remote_worker": false, "ci": false}, + "log_files": map[string]any{"process": "qodercli.log"}} + if mutate != nil { + mutate(manifest) + } + body, err := json.Marshal(manifest) + if err != nil { + t.Fatal(err) + } + directory := filepath.Join(root, "logs", "runs", qoderInertRunID) + if err := os.Mkdir(directory, 0755); err != nil { + t.Fatal(err) + } + projectNativeRuntimeWrite(t, filepath.Join(directory, "manifest.json"), body, 0644) + projectNativeRuntimeWrite(t, filepath.Join(directory, "qodercli.log"), []byte("PRIVATE-LOG-BODY"), 0644) + if err := os.Remove(link); err != nil { + t.Fatal(err) + } + if err := os.Symlink("runs/"+qoderInertRunID, link); err != nil { + t.Fatal(err) + } + return root, cwd, argv, before, qoderDeltaSnapshot(t, root) +} + +func qoderRunObservation(t *testing.T, root, cwd string, argv []string, before, after map[string]string) qoderLogRunObservation { + t.Helper() + old, _ := json.Marshal(before) + current, _ := json.Marshal(after) + observation := qoderObserveLogRun(context.Background(), root, cwd, argv, before, after) + raw, err := json.Marshal(observation) + if err != nil || len(raw) > 2048 { + t.Fatal("run observation serialization bound") + } + for _, canary := range []string{"PRIVATE", root, cwd, argv[0], "env_flags", "project_id", "node_version", "argv\""} { + if bytes.Contains(raw, []byte(canary)) { + t.Fatal("run observation exposed private or unapproved data") + } + } + now, _ := json.Marshal(before) + stable, _ := json.Marshal(after) + if !bytes.Equal(old, now) || !bytes.Equal(current, stable) || !reflect.DeepEqual(qoderDeltaSnapshot(t, root), after) { + t.Fatal("run observation changed its maps or original profile") + } + return observation +} + +func TestQoderLogRunObservationJoinsHeldManifestWithoutAdoptingProfile(t *testing.T) { + root, cwd, argv, before, after := qoderRunFixture(t, nil) + observation := qoderRunObservation(t, root, cwd, argv, before, after) + body, err := os.ReadFile(filepath.Join(root, "logs", "runs", qoderInertRunID, "manifest.json")) + if err != nil { + t.Fatal(err) + } + digest := sha256.Sum256(body) + run := observation.Run + if observation.Status != "captured" || run == nil || run.RunID != qoderInertRunID || run.LatestTarget != "runs/"+qoderInertRunID || + run.ManifestBytes != len(body) || run.ManifestSHA256 != hex.EncodeToString(digest[:]) || run.PID != 1234 || run.PPID != 5678 || + run.PIDProvenance != "manifest-only" || !run.RunIDMatchesManifest || !run.CWDMatches || !run.ArgvMatches || !run.ExactAddedRunClosure || !run.PreexistingRecordsStable || reflect.DeepEqual(before, after) { + t.Fatal("source-defined projection lost a join or adopted the changed profile") + } +} + +func TestQoderLogRunObservationRetainsOnlyFinitePrivateMismatchFacts(t *testing.T) { + root, cwd, argv, before, after := qoderRunFixture(t, func(m map[string]any) { + m["cwd"], m["argv"], m["pid"] = "PRIVATE-CWD", []string{"PRIVATE-ARGV"}, 1235 + }) + run := qoderRunObservation(t, root, cwd, argv, before, after).Run + if run == nil || run.CWDMatches || run.ArgvMatches || run.RunIDMatchesManifest { + t.Fatal("private mismatch was concealed") + } + projectNativeRuntimeWrite(t, filepath.Join(root, "logs", "runs", qoderInertRunID, "PRIVATE-extra"), nil, 0600) + if err := os.WriteFile(filepath.Join(root, "settings.json"), []byte("CHANGED-PRIVATE-SETTINGS"), 0600); err != nil { + t.Fatal(err) + } + after = qoderDeltaSnapshot(t, root) + run = qoderRunObservation(t, root, cwd, argv, before, after).Run + if run == nil || run.ExactAddedRunClosure || run.PreexistingRecordsStable { + t.Fatal("new or changed foreign records were exempted") + } +} + +func TestQoderLogRunObservationRefusesUnknownAndUnsafeManifestShapes(t *testing.T) { + mutations := map[string]func(map[string]any){ + "unknown": func(m map[string]any) { m["PRIVATE-KEY"] = "PRIVATE-BODY" }, + "pid-type": func(m map[string]any) { m["pid"] = "1234" }, + "pid-range": func(m map[string]any) { m["pid"] = int64(1) << 32 }, + "start": func(m map[string]any) { m["started_at"] = "PRIVATE-TIME" }, + "run": func(m map[string]any) { m["run_id"] = "PRIVATE-RUN" }, + "role": func(m map[string]any) { m["process_role"] = "PRIVATE-ROLE" }, + "version": func(m map[string]any) { m["cli_version"] = "PRIVATE-VERSION" }, + "flags": func(m map[string]any) { m["env_flags"] = map[string]any{"PRIVATE-KEY": true} }, + "flag-type": func(m map[string]any) { m["env_flags"].(map[string]any)["debug"] = "PRIVATE-FLAG" }, + "logs": func(m map[string]any) { m["log_files"] = map[string]any{"process": "PRIVATE-LOG"} }, + "argv-type": func(m map[string]any) { m["argv"] = []any{true} }, + "oversized": func(m map[string]any) { m["project_id"] = strings.Repeat("PRIVATE", 1000) }, + } + for name, mutate := range mutations { + t.Run(name, func(t *testing.T) { + root, cwd, argv, before, after := qoderRunFixture(t, mutate) + observation := qoderRunObservation(t, root, cwd, argv, before, after) + if observation.Status != "manifest-refused" || observation.Run != nil { + t.Fatal("unknown/private manifest shape gained projection") + } + }) + } +} + +func TestQoderLogRunObservationSourceGrammarAndDuplicateRefusals(t *testing.T) { + for _, id := range []string{qoderInertRunID, "2026-10-28T15-30-45-123Z--p1", "2026-10-28T15-30-45-123+05-30-abc-p1234"} { + if _, _, ok := qoderLogRunParts(id); !ok { + t.Fatal("source run ID refused", id) + } + } + for _, id := range []string{strings.ReplaceAll(qoderInertRunID, "abc123", "abcdefg"), strings.ToUpper(qoderInertRunID), + strings.ReplaceAll(qoderInertRunID, "-28T", "-99T"), strings.ReplaceAll(qoderInertRunID, "-p1234", "-p01234"), + strings.ReplaceAll(qoderInertRunID, "30-45", "30-60"), strings.ReplaceAll(qoderInertRunID, "30-45", "30-61"), + strings.ReplaceAll(qoderInertRunID, "123Z", "123+24-00"), strings.ReplaceAll(qoderInertRunID, "123Z", "123+05-60"), "../PRIVATE", "PRIVATE"} { + if _, _, ok := qoderLogRunParts(id); ok { + t.Fatal("arbitrary source target admitted") + } + } + for _, raw := range []string{`{"pid":1,"pid":2}`, `[]`, `null`, `{"PRIVATE":"` + strings.Repeat("x", 4096) + `"}`} { + if _, ok := qoderLogRunManifest([]byte(raw), qoderInertRunID, "/owned", nil); ok { + t.Fatal("duplicate/malformed/unbounded manifest admitted") + } + } +} + +func TestQoderLogRunObservationRefusesAliasLinkAndHeldInventoryDrift(t *testing.T) { + for _, kind := range []string{"latest-record", "manifest-hash", "preexisting-run", "hardlink", "symlink", "run-alias", "held-directory"} { + t.Run(kind, func(t *testing.T) { + root, cwd, argv, before, after := qoderRunFixture(t, nil) + rel := filepath.Join("logs", "runs", qoderInertRunID, "manifest.json") + path := filepath.Join(root, rel) + want := "manifest-refused" + switch kind { + case "latest-record": + after["logs/latest"], want = "PRIVATE", "profile-refused" + case "manifest-hash": + after[rel] = "file:644:1:" + strings.Repeat("a", 64) + case "preexisting-run": + before[filepath.Dir(rel)], want = "directory:20000000755", "layout-refused" + case "hardlink": + if err := os.Link(path, filepath.Join(filepath.Dir(path), "PRIVATE-hardlink")); err != nil { + t.Fatal(err) + } + want = "profile-refused" + case "symlink": + if err := os.Rename(path, path+"-original"); err != nil { + t.Fatal(err) + } + if err := os.Symlink(path+"-original", path); err != nil { + t.Fatal(err) + } + want = "profile-refused" + default: + leaf, err := os.Lstat(filepath.Join(root, "logs", "latest")) + if err != nil { + t.Fatal(err) + } + link, err := qoderReadOriginalLogLink(root, leaf) + if err != nil { + t.Fatal(err) + } + dir := filepath.Dir(path) + if err := os.Rename(dir, dir+"-original"); err != nil { + t.Fatal(err) + } + if kind == "run-alias" { + if err := os.Symlink(dir+"-original", dir); err != nil { + t.Fatal(err) + } + } else { + if err := os.Mkdir(dir, 0755); err != nil { + t.Fatal(err) + } + for _, name := range []string{"manifest.json", "qodercli.log"} { + if err := os.Rename(filepath.Join(dir+"-original", name), filepath.Join(dir, name)); err != nil { + t.Fatal(err) + } + } + } + if _, err := qoderLogRunRead(root, link, after[rel]); err == nil { + t.Fatal("held directory replacement accepted") + } + want = "profile-refused" + } + if kind == "hardlink" || kind == "symlink" { + leaf, err := os.Lstat(filepath.Join(root, "logs", "latest")) + if err != nil { + t.Fatal(err) + } + link, err := qoderReadOriginalLogLink(root, leaf) + if err != nil { + t.Fatal(err) + } + if _, err := qoderLogRunRead(root, link, after[rel]); err == nil { + t.Fatal("unsafe manifest leaf accepted") + } + } + observation := qoderObserveLogRun(context.Background(), root, cwd, argv, before, after) + if observation.Status != want || observation.Run != nil { + t.Fatal("unsafe observation gained projection or wrong refusal stage", kind, observation.Status) + } + }) + } +} diff --git a/repotests/agentplugins_qoder_log_run_observation_test.go b/repotests/agentplugins_qoder_log_run_observation_test.go new file mode 100644 index 000000000..a7b25f998 --- /dev/null +++ b/repotests/agentplugins_qoder_log_run_observation_test.go @@ -0,0 +1,246 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "golang.org/x/sys/unix" +) + +type qoderLogRunObservation struct { + Status string `json:"status"` + Run *qoderLogRunData `json:"run,omitempty"` +} + +type qoderLogRunData struct { + RunID string `json:"run_id"` + LatestTarget string `json:"latest_target"` + ManifestBytes int `json:"manifest_bytes"` + ManifestSHA256 string `json:"manifest_sha256"` + PID int64 `json:"pid"` + PPID int64 `json:"ppid"` + StartedAt string `json:"started_at"` + CLIVersion string `json:"cli_version"` + Platform string `json:"platform"` + Arch string `json:"arch"` + ProcessRole string `json:"process_role"` + PIDProvenance string `json:"pid_provenance"` + RunIDMatchesManifest bool `json:"run_id_matches_manifest"` + CWDMatches bool `json:"cwd_matches"` + ArgvMatches bool `json:"argv_matches"` + ExactAddedRunClosure bool `json:"exact_added_run_closure"` + PreexistingRecordsStable bool `json:"preexisting_records_stable"` +} + +var qoderLogRunID = regexp.MustCompile(`^([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{3}(?:Z|[+-][0-9]{2}-[0-9]{2}))-([0-9a-z]{0,6})-p([1-9][0-9]{0,9})$`) + +const qoderLogTimeLayout = "2006-01-02T15:04:05.000Z07:00" + +func qoderLogTimestamp(text string) bool { + parsed, err := time.Parse(qoderLogTimeLayout, text) + return err == nil && parsed.Format(qoderLogTimeLayout) == text && (len(text) != 29 || text[24:26] < "24") +} + +func qoderLogRunParts(id string) (string, int64, bool) { + if len(id) > 64 { + return "", 0, false + } + parts := qoderLogRunID.FindStringSubmatch(id) + if len(parts) != 4 { + return "", 0, false + } + stamp := []byte(parts[1]) + stamp[13], stamp[16], stamp[19] = ':', ':', '.' + if len(stamp) == 29 { + stamp[26] = ':' + } + pid, pidErr := strconv.ParseInt(parts[3], 10, 32) + return string(stamp), pid, qoderLogTimestamp(string(stamp)) && pidErr == nil && pid > 0 +} + +func qoderLogRunObject(value any, keys string) (map[string]any, bool) { + object, ok := value.(map[string]any) + want := strings.Fields(keys) + if !ok || len(object) != len(want) { + return nil, false + } + for _, key := range want { + if _, present := object[key]; !present { + return nil, false + } + } + return object, true +} + +func qoderLogRunInteger(value any) (int64, bool) { + number, ok := value.(json.Number) + if !ok { + return 0, false + } + parsed, err := strconv.ParseInt(string(number), 10, 32) + return parsed, err == nil && parsed > 0 && strconv.FormatInt(parsed, 10) == string(number) +} + +func qoderLogRunManifest(body []byte, runID, cwd string, argv []string) (*qoderLogRunData, bool) { + if len(body) == 0 || len(body) > 4096 { + return nil, false + } + value, err := projectNativeRuntimeJSON(body) + object, ok := qoderLogRunObject(value, "run_id started_at pid ppid process_role cli_version node_version platform arch cwd project_id argv env_flags log_files") + if err != nil || !ok { + return nil, false + } + for _, key := range strings.Fields("run_id started_at process_role cli_version node_version platform arch cwd project_id") { + text, valid := object[key].(string) + if !valid || text == "" || len(text) > 4096 || strings.ContainsRune(text, 0) { + return nil, false + } + } + stamp, _, validID := qoderLogRunParts(object["run_id"].(string)) + pid, validPID := qoderLogRunInteger(object["pid"]) + ppid, validPPID := qoderLogRunInteger(object["ppid"]) + flags, validFlags := qoderLogRunObject(object["env_flags"], "debug remote_worker ci") + logs, validLogs := qoderLogRunObject(object["log_files"], "process") + if !validID || !qoderLogTimestamp(object["started_at"].(string)) || !validPID || !validPPID || !validFlags || !validLogs || + object["cli_version"] != "1.1.78" || object["platform"] != "linux" || object["arch"] != "x64" || object["process_role"] != "main" || logs["process"] != "qodercli.log" { + return nil, false + } + for _, flag := range flags { + if _, valid := flag.(bool); !valid { + return nil, false + } + } + arguments, valid := object["argv"].([]any) + if !valid || len(arguments) == 0 || len(arguments) > 32 { + return nil, false + } + actualArgv := make([]string, len(arguments)) + for index, argument := range arguments { + text, valid := argument.(string) + if !valid || len(text) > 4096 || strings.ContainsRune(text, 0) { + return nil, false + } + actualArgv[index] = text + } + _, runPID, _ := qoderLogRunParts(runID) + return &qoderLogRunData{RunID: runID, LatestTarget: "runs/" + runID, ManifestBytes: len(body), ManifestSHA256: qoderDeltaHash(string(body)), + PID: pid, PPID: ppid, StartedAt: object["started_at"].(string), CLIVersion: "1.1.78", Platform: "linux", Arch: "x64", ProcessRole: "main", + PIDProvenance: "manifest-only", RunIDMatchesManifest: object["run_id"] == runID && stamp == object["started_at"] && runPID == pid, + CWDMatches: object["cwd"] == cwd, ArgvMatches: reflect.DeepEqual(actualArgv, argv)}, true +} + +func qoderLogRunRead(root string, link qoderLogLinkSnapshot, expected string) (body []byte, err error) { + directory, err := qoderLogOpenRoot(filepath.Join(root, link.TargetRel)) + if err != nil { + return nil, err + } + defer func() { err = errors.Join(err, directory.Close()) }() + held, err := directory.Stat() + if err != nil || !projectNativeRuntimeStableStat(link.Target.Sys(), held.Sys()) { + return nil, fmt.Errorf("run directory identity") + } + fd, err := unix.Openat(int(directory.Fd()), "manifest.json", unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0) + if err != nil { + return nil, err + } + file := os.NewFile(uintptr(fd), "qoder-held-run-manifest") + defer func() { err = errors.Join(err, file.Close()) }() + before, err := file.Stat() + if err != nil || !qoderLogOwned(before, false) || before.Mode() != 0644 || before.Size() <= 0 || before.Size() > 4096 { + return nil, fmt.Errorf("manifest identity") + } + body, err = io.ReadAll(io.LimitReader(file, 4097)) + after, statErr := file.Stat() + if err != nil || statErr != nil || len(body) > 4096 || !projectNativeRuntimeStableStat(before.Sys(), after.Sys()) || expected != fmt.Sprintf("file:%o:%d:%s", before.Mode(), len(body), qoderDeltaHash(string(body))) { + return nil, fmt.Errorf("manifest held inventory") + } + linked, err := qoderLogOpenAt(directory, "manifest.json", false) + if err != nil { + return nil, err + } + current, statErr := linked.Stat() + closeErr := linked.Close() + if statErr != nil || closeErr != nil || !projectNativeRuntimeStableStat(before.Sys(), current.Sys()) { + return nil, fmt.Errorf("manifest linked identity") + } + return body, nil +} + +func qoderObserveLogRun(ctx context.Context, root, cwd string, argv []string, before, after map[string]string) qoderLogRunObservation { + refused := func(status string) qoderLogRunObservation { return qoderLogRunObservation{Status: status} } + if ctx == nil || ctx.Err() != nil { + return refused("context-refused") + } + if len(before) > 4096 || len(after) > 4096 { + return refused("profile-refused") + } + info, err := os.Lstat(filepath.Join(root, "logs", "latest")) + if err != nil { + return refused("profile-refused") + } + link, err := qoderReadOriginalLogLink(root, info) + if err != nil || link.Record != after["logs/latest"] || !link.Target.IsDir() || link.Target.Mode() != os.ModeDir|0755 { + return refused("profile-refused") + } + parts := strings.Split(link.TargetRel, "/") + if len(parts) != 3 || parts[0] != "logs" || parts[1] != "runs" { + return refused("layout-refused") + } + runID := parts[2] + if _, _, valid := qoderLogRunParts(runID); !valid { + return refused("layout-refused") + } + for key := range before { + if key == link.TargetRel || strings.HasPrefix(key, link.TargetRel+"/") { + return refused("layout-refused") + } + } + manifest := filepath.Join(link.TargetRel, "manifest.json") + body, err := qoderLogRunRead(root, link, after[manifest]) + if err != nil { + return refused("manifest-refused") + } + current, err := qoderReadOriginalLogLink(root, info) + if ctx.Err() != nil { + return refused("context-refused") + } + if err != nil || current.Record != link.Record || !projectNativeRuntimeStableStat(link.Profile.Sys(), current.Profile.Sys()) || !projectNativeRuntimeStableStat(link.Logs.Sys(), current.Logs.Sys()) { + return refused("profile-refused") + } + data, valid := qoderLogRunManifest(body, runID, cwd, argv) + if !valid { + return refused("manifest-refused") + } + data.PreexistingRecordsStable = true + for key, record := range before { + if key != "logs/latest" && after[key] != record { + data.PreexistingRecordsStable = false + } + } + added := map[string]bool{} + for key := range after { + if _, exists := before[key]; !exists { + added[key] = true + } + } + expected := map[string]bool{link.TargetRel: true, manifest: true, filepath.Join(link.TargetRel, "qodercli.log"): true} + log, validLog := qoderDeltaValue(after[filepath.Join(link.TargetRel, "qodercli.log")], true, false) + data.ExactAddedRunClosure = reflect.DeepEqual(added, expected) && after[link.TargetRel] == "directory:20000000755" && validLog && log.Kind == "file" && log.Mode == "644" + observation := qoderLogRunObservation{Status: "captured", Run: data} + if raw, err := json.Marshal(observation); err != nil || len(raw) > 2048 { + return refused("observation-bound") + } + return observation +} diff --git a/scripts/project_native_runtime.py b/scripts/project_native_runtime.py index 9d0ec9f0f..84e3197f2 100644 --- a/scripts/project_native_runtime.py +++ b/scripts/project_native_runtime.py @@ -223,6 +223,58 @@ def kimi_members(bodies, source): return result +def qoder_log_run_stamp(text): + if (type(text) is not str or text.endswith(("+00:00", "-00:00")) + or re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}(?:Z|[+-][0-9]{2}:[0-9]{2})", text) is None + or int(text[17:19]) > 59 or len(text) == 29 and (int(text[24:26]) > 23 or int(text[27:29]) > 59)): + raise ValueError("Qoder run timestamp shape refused") + time.strptime(text, "%Y-%m-%dT%H:%M:%S.%f%z") + + +def qoder_log_run_observation(result, client): + field = "qoder_log_run_observation" + if field not in result: + return + if client != "qoder": + raise ValueError("Qoder log projection cannot apply to another client") + observation = result[field] + if type(observation) is not dict or type(observation.get("status")) is not str: + raise ValueError("finite Qoder log projection required") + statuses = {"context-refused", "profile-refused", "layout-refused", "manifest-refused", "observation-bound"} + if observation["status"] in statuses: + if set(observation) != {"status"}: + raise ValueError("Qoder log refusal cannot retain private details") + return + if observation["status"] != "captured" or set(observation) != {"status", "run"}: + raise ValueError("unregistered Qoder log projection status") + run = observation["run"] + flags = {"run_id_matches_manifest", "cwd_matches", "argv_matches", "exact_added_run_closure", "preexisting_records_stable"} + fields = flags | {"run_id", "latest_target", "manifest_bytes", "manifest_sha256", "pid", "ppid", "started_at", + "cli_version", "platform", "arch", "process_role", "pid_provenance"} + if (type(run) is not dict or set(run) != fields or any(type(run[key]) is not bool for key in flags) + or run["cli_version"] != "1.1.78" or run["platform"] != "linux" or run["arch"] != "x64" + or run["process_role"] != "main" or run["pid_provenance"] != "manifest-only" + or type(run["manifest_bytes"]) is not int or not 1 <= run["manifest_bytes"] <= 4096 + or type(run["manifest_sha256"]) is not str or re.fullmatch(r"[0-9a-f]{64}", run["manifest_sha256"]) is None + or any(type(run[key]) is not int or not 0 < run[key] < 2 ** 31 for key in ("pid", "ppid")) + or type(run["run_id"]) is not str or type(run["latest_target"]) is not str + or run["latest_target"] != "runs/" + run["run_id"]): + raise ValueError("bounded Qoder log identity projection differs") + match = re.fullmatch(r"([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{3}(?:Z|[+-][0-9]{2}-[0-9]{2}))-([0-9a-z]{0,6})-p([1-9][0-9]{0,9})", run["run_id"]) + if match is None or int(match[3]) >= 2 ** 31: + raise ValueError("source-format Qoder run identity required") + stamp = match[1] + stamp = stamp[:13] + ":" + stamp[14:16] + ":" + stamp[17:19] + "." + stamp[20:] + if len(stamp) == 29: + stamp = stamp[:26] + ":" + stamp[27:] + qoder_log_run_stamp(stamp) + qoder_log_run_stamp(run["started_at"]) + if run["run_id_matches_manifest"] and (stamp != run["started_at"] or int(match[3]) != run["pid"]): + raise ValueError("Qoder manifest-only run identity join contradicts its projection") + if len(json.dumps(observation, separators=(",", ":"), ensure_ascii=False).encode("utf-8")) > 2048: + raise ValueError("Qoder log projection bound exceeded") + + def cli_attempt_members(bodies, source, client): if client not in CLI_ATTEMPTS: raise ValueError("unknown fixed native CLI attempt") @@ -258,6 +310,7 @@ def cli_attempt_members(bodies, source, client): if result.get("source_identity") != {"installer_base_commit": source["source_commit"], "installer_tree": source["source_tree"], "installer_source_state": "committed", "installer_patch_sha256": []}: raise ValueError("native CLI evidence source differs") + qoder_log_run_observation(result, client) return result diff --git a/scripts/test_project_native_runtime.py b/scripts/test_project_native_runtime.py index c4e00723b..2c421e853 100644 --- a/scripts/test_project_native_runtime.py +++ b/scripts/test_project_native_runtime.py @@ -129,6 +129,65 @@ def test_fixed_cli_attempts_cannot_turn_captured_output_into_success(self): with self.assertRaises(ValueError): runtime.cli_attempt_members({"runtime-attempt.json": json.dumps({**result, **change}).encode()}, self.source, client) + def qoder_log_observation(self): + run_id = "2026-10-28T15-30-45-123Z-abc123-p1234" + return {"status": "captured", "run": {"run_id": run_id, "latest_target": "runs/" + run_id, + "manifest_bytes": 991, "manifest_sha256": "a" * 64, "pid": 1234, "ppid": 5678, + "started_at": "2026-10-28T15:30:45.123Z", "cli_version": "1.1.78", "platform": "linux", "arch": "x64", + "process_role": "main", "pid_provenance": "manifest-only", "run_id_matches_manifest": True, + "cwd_matches": True, "argv_matches": True, "exact_added_run_closure": True, "preexisting_records_stable": True}} + + def test_qoder_log_projection_is_optional_finite_and_failed_only(self): + for client in runtime.CLI_ATTEMPTS: + original = {**self.cli_result(client), "native_inspection": {"enabled": client != "grok"}} + self.assertEqual(runtime.cli_attempt_members({"runtime-attempt.json": json.dumps(original).encode()}, self.source, client), original) + for status in ("captured", "context-refused", "profile-refused", "layout-refused", "manifest-refused", "observation-bound"): + projection = self.qoder_log_observation() if status == "captured" else {"status": status} + result = {**self.cli_result("qoder"), "qoder_log_run_observation": projection} + admitted = runtime.cli_attempt_members({"runtime-attempt.json": json.dumps(result).encode()}, self.source, "qoder") + self.assertEqual(admitted, result) + self.assertFalse(admitted["runtime_attempt_complete"]) + self.assertTrue(admitted["test_failed"]) + self.assertFalse(any(admitted["qualification"].values())) + if status != "captured": + with self.assertRaises(ValueError): + runtime.qoder_log_run_observation({"qoder_log_run_observation": {**projection, "PRIVATE": "PRIVATE"}}, "qoder") + for client in ("grok", "kiro", "windsurf"): + with self.assertRaises(ValueError): + runtime.qoder_log_run_observation({"qoder_log_run_observation": self.qoder_log_observation()}, client) + + def test_qoder_log_projection_refuses_unbounded_private_and_forged_provenance(self): + changes = {"latest_target": "../PRIVATE", "run_id": "PRIVATE", "manifest_bytes": 4097, + "manifest_sha256": "PRIVATE", "pid": True, "ppid": 0, "started_at": "PRIVATE", + "cli_version": "PRIVATE", "platform": "PRIVATE", "arch": "PRIVATE", "process_role": "PRIVATE", + "pid_provenance": "admitted-native-pid", "argv_matches": 1, "PRIVATE": "PRIVATE"} + for key, value in changes.items(): + projection = self.qoder_log_observation() + projection["run"][key] = value + with self.subTest(key=key), self.assertRaises(ValueError): + runtime.qoder_log_run_observation({"qoder_log_run_observation": projection}, "qoder") + for key in ("status", "run"): + projection = self.qoder_log_observation() + del projection[key] + with self.assertRaises(ValueError): + runtime.qoder_log_run_observation({"qoder_log_run_observation": projection}, "qoder") + for key, value in (("pid", 1235), ("started_at", "2026-10-28T15:30:45.124Z")): + projection = self.qoder_log_observation() + projection["run"][key] = value + with self.assertRaises(ValueError): + runtime.qoder_log_run_observation({"qoder_log_run_observation": projection}, "qoder") + projection["run"]["run_id_matches_manifest"] = False + runtime.qoder_log_run_observation({"qoder_log_run_observation": projection}, "qoder") + + def test_qoder_log_projection_calendar_and_offset_match_source_date(self): + for stamp in ("2026-10-28T15:30:60.123Z", "2026-10-28T15:30:61.123Z", "2026-02-30T15:30:45.123Z", + "2026-10-28T15:30:45.123+24:00", "2026-10-28T15:30:45.123+05:60", "2026-10-28T15:30:45.123+00:00"): + projection = self.qoder_log_observation() + run_id = stamp.replace(":", "-").replace(".", "-") + "-abc123-p1234" + projection["run"].update({"started_at": stamp, "run_id": run_id, "latest_target": "runs/" + run_id}) + with self.subTest(stamp=stamp), self.assertRaises(ValueError): + runtime.qoder_log_run_observation({"qoder_log_run_observation": projection}, "qoder") + def test_cli_member_table_rejects_partial_commands_provider_and_profiles(self): summary = {"runtime-attempt.json": json.dumps(self.cli_result("qoder")).encode()} for extra in ({"git-a.stdout.log": b""}, {"provider-request-1.json": b"{}"}, {"profile.json": b"{}"}): From e7b9e7ef831952edc1b18a650a1368113344791e Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 29 Oct 2026 03:01:32 +0800 Subject: [PATCH 30/30] test(qoder): complete supported project skills lifecycle --- .../agentplugins/project_qoder_e2e_test.go | 79 +++- .../remaining-native-qualification.md | 77 ++++ ...plugins_project_native_cli_attempt_test.go | 3 +- .../agentplugins_qoder_cycle_command_test.go | 226 +++++++++++ ...ns_qoder_cycle_completion_contract_test.go | 42 ++ ...gentplugins_qoder_cycle_completion_test.go | 82 ++++ .../agentplugins_qoder_cycle_fixture_test.go | 374 ++++++++++++++++++ ...agentplugins_qoder_cycle_lifecycle_test.go | 267 +++++++++++++ ...tplugins_qoder_cycle_logs_contract_test.go | 189 +++++++++ .../agentplugins_qoder_cycle_logs_test.go | 245 ++++++++++++ .../agentplugins_qoder_cycle_proof_test.go | 227 +++++++++++ ...ntplugins_qoder_cycle_public_proof_test.go | 121 ++++++ ...ugins_qoder_cycle_receipt_contract_test.go | 45 +++ .../agentplugins_qoder_cycle_receipt_test.go | 167 ++++++++ ...lugins_qoder_cycle_stager_contract_test.go | 169 ++++++++ scripts/project_native_qoder_cycle.py | 348 ++++++++++++++++ scripts/project_native_qoder_cycle_logs.py | 172 ++++++++ scripts/project_native_qoder_cycle_proof.py | 241 +++++++++++ scripts/project_native_runtime.py | 16 +- scripts/test_project_native_qoder_cycle.py | 247 ++++++++++++ ...est_project_native_qoder_cycle_fixtures.py | 212 ++++++++++ 21 files changed, 3542 insertions(+), 7 deletions(-) create mode 100644 repotests/agentplugins_qoder_cycle_command_test.go create mode 100644 repotests/agentplugins_qoder_cycle_completion_contract_test.go create mode 100644 repotests/agentplugins_qoder_cycle_completion_test.go create mode 100644 repotests/agentplugins_qoder_cycle_fixture_test.go create mode 100644 repotests/agentplugins_qoder_cycle_lifecycle_test.go create mode 100644 repotests/agentplugins_qoder_cycle_logs_contract_test.go create mode 100644 repotests/agentplugins_qoder_cycle_logs_test.go create mode 100644 repotests/agentplugins_qoder_cycle_proof_test.go create mode 100644 repotests/agentplugins_qoder_cycle_public_proof_test.go create mode 100644 repotests/agentplugins_qoder_cycle_receipt_contract_test.go create mode 100644 repotests/agentplugins_qoder_cycle_receipt_test.go create mode 100644 repotests/agentplugins_qoder_cycle_stager_contract_test.go create mode 100644 scripts/project_native_qoder_cycle.py create mode 100644 scripts/project_native_qoder_cycle_logs.py create mode 100644 scripts/project_native_qoder_cycle_proof.py create mode 100644 scripts/test_project_native_qoder_cycle.py create mode 100644 scripts/test_project_native_qoder_cycle_fixtures.py diff --git a/cli/cmd/agentplugins/project_qoder_e2e_test.go b/cli/cmd/agentplugins/project_qoder_e2e_test.go index 6e8aad245..c4e33b5ae 100644 --- a/cli/cmd/agentplugins/project_qoder_e2e_test.go +++ b/cli/cmd/agentplugins/project_qoder_e2e_test.go @@ -42,7 +42,7 @@ func configureProjectQoder(t *testing.T, app agentpluginscli.App, user string, r app.Installer = engine app.Detector = projectQoderDetector{user} app.Lifecycle.Activator = providers.Activator{Registry: app.ClientRegistry, Runner: runner} - app.Lifecycle.NativeObserver = providers.NativeIdentityObserver{Registry: app.ClientRegistry, Runner: runner} + app.Lifecycle.NativeObserver = providers.NativeIdentityObserver{Stager: app.Lifecycle.Stager, Registry: app.ClientRegistry, Runner: runner} compose := app.ForScope app.ForScope = func(scope domain.InstallScope, root string) (agentpluginscli.App, error) { selected, err := compose(scope, root) @@ -139,3 +139,80 @@ func projectQoderOwned(t *testing.T, j projectJourney, project, expected string) } return installation, b } + +// Exercise the same direct local, explicit-target CLI group route as the native +// cycle. No Qoder process is permitted; repair and retention use production APIs. +func TestProjectQoderDirectGroupRepairAndDefaultRemovalKeepExactAuthority(t *testing.T) { + transport := newProjectJourneyTransport(t, "git") + j := newProjectJourney(t, transport) + user := filepath.Join(j.home, ".qoder") + projectJourneyWrite(t, filepath.Join(user, "plugins", "installed_plugins_v2.json"), "original registry\n") + runner := &projectQoderNoNative{} + j.app = configureProjectQoder(t, j.app, user, runner) + beforeUser := securityFixtureTree(t, user) + for _, project := range []string{j.a, j.b} { + j.run(t, project, "add", transport.repo, "--target", "qoder", "--no-save") + } + installed, binding := projectQoderDirectOwned(t, j, projectJourneySkill) + beforeB := securityFixtureTree(t, j.b) + if len(installed.DataReceipts) != 0 || installed.DataRetained || binding.DataReceiptID != "" { + t.Fatal("skills-only install invented a data allocation") + } + stateBefore := projectJourneyRead(t, filepath.Join(j.a, ".uap", "state-v2.json")) + for _, args := range [][]string{{"add", transport.repo, "--target", "qoder", "--no-save"}, {"update", installed.DeclaredName, "--target", "qoder"}} { + output := j.run(t, j.a, args...) + if !strings.Contains(output, `"mutated":false`) || stateBefore != projectJourneyRead(t, filepath.Join(j.a, ".uap", "state-v2.json")) { + t.Fatal("direct unchanged group changed state", output) + } + } + output := j.run(t, j.a, "repair", installed.InstallationID, "--target", "qoder") + intact, current := projectQoderDirectOwned(t, j, projectJourneySkill) + if !strings.Contains(output, `"mutated":true`) || intact.InstallationID != installed.InstallationID || current.ClientBindingID != binding.ClientBindingID || intact.Source.TreeDigest != installed.Source.TreeDigest { + t.Fatal("explicit direct CLI group repair lost exact restaging authority", output) + } + projectJourneyWrite(t, filepath.Join(transport.repo, "skills", "docs", "SKILL.md"), projectJourneyAdvancedSkill) + j.run(t, j.a, "update", transport.repo, "--target", "qoder") + updated, current := projectQoderDirectOwned(t, j, projectJourneyAdvancedSkill) + if updated.Source.TreeDigest == installed.Source.TreeDigest || current.TargetLocator != binding.TargetLocator { + t.Fatal("same-version direct update lost source or physical authority") + } + // This inert CLI contract owns the complete selected package. The hosted + // cycle separately authorizes each leaf before nonrecursive fault removal. + if err := os.RemoveAll(current.TargetLocator); err != nil { + t.Fatal(err) + } + j.run(t, j.a, "repair", installed.InstallationID, "--target", "qoder") + repaired, _ := projectQoderDirectOwned(t, j, projectJourneyAdvancedSkill) + if repaired.Source.TreeDigest != updated.Source.TreeDigest { + t.Fatal("frozen local repair did not restore its exact recorded digest") + } + j.run(t, j.a, "remove", installed.DeclaredName, "--target", "qoder") + projectJourneyAbsent(t, current.TargetLocator) + state := j.state(t, j.a) + if len(state.Installations) != 1 || !state.Installations[0].DataRetained || len(state.Installations[0].Clients) != 0 || len(state.Installations[0].DataReceipts) != 1 { + t.Fatal("default skills-only removal did not retain its actual owner allocation") + } + if runner.calls != 0 || !reflect.DeepEqual(beforeUser, securityFixtureTree(t, user)) || !reflect.DeepEqual(beforeB, securityFixtureTree(t, j.b)) { + t.Fatal("direct A lifecycle invoked native or changed original user/B authority") + } +} + +func projectQoderDirectOwned(t *testing.T, j projectJourney, expected string) (domain.Installation, domain.ClientBinding) { + t.Helper() + state := j.state(t, j.a) + if len(state.Installations) != 1 || len(state.Installations[0].Clients) != 1 || len(state.EnvironmentReceipts) != 0 { + t.Fatal("direct --no-save route lost exact unsaved state") + } + installation := state.Installations[0] + for _, binding := range installation.Clients { + if binding.ClientID != "qoder" || binding.Scope != "project" || binding.Activation != domain.ActivationPrepared || binding.Verification != domain.VerificationInstalled || binding.NativeProfileRoot != filepath.Join(j.a, ".uap", "managed", "clients", "qoder") || len(binding.NativeObjects) != 1 || binding.NativeObjects[0].Path != binding.TargetLocator { + t.Fatal("direct Prepared receipt ownership differs") + } + if projectJourneyRead(t, filepath.Join(binding.TargetLocator, ".agentplugins-qoder-native", "skills", "docs", "SKILL.md")) != expected { + t.Fatal("direct Qoder native view does not match frozen source") + } + return installation, binding + } + t.Fatal("direct binding missing") + return domain.Installation{}, domain.ClientBinding{} +} diff --git a/docs/plans/project-install-e2e/remaining-native-qualification.md b/docs/plans/project-install-e2e/remaining-native-qualification.md index b5d483358..f11929067 100644 --- a/docs/plans/project-install-e2e/remaining-native-qualification.md +++ b/docs/plans/project-install-e2e/remaining-native-qualification.md @@ -1537,3 +1537,80 @@ issues. The original 1050 archive's 16 members and complete failed/all-false result replay unchanged. Timestamp and unsafe-leaf refusal controls were corrected without weakening admission. No vendor ran locally; fresh hosted observation is required before any supported log-ownership allowance or lifecycle claim. + +## Qoder complete supported skills catalog cycle — accepted precode amendment (2026-10-28) + +Root and an independent Sol reviewer read/rehashed and approved complete draft +SHA256 `16483ff2c375042cdfb39b813e35c2fffb26220b8dc366971ff1b231bb2412a1` +(17,922 bytes) before implementation; a third independent source review also +approved the same complete draft. Source is published `25ea0f0d708504a914aeaf864206e98fdcfa1cc8`, +tree `61331bbd744f447cc4cd86aabf742a2273d47e2a`. Two independent complete raw +audits of run `40731229834` joined API native artifact `12836830994` and frontend +`12836825965`, all16/25 members,24 stream receipts, source/tree/clean builds, +exact supported flag catalog and the finite log-run projection. The catalog +succeeded; the original profile guard correctly refused its four log changes. +All qualifications/auth/model remain false. This receipt grants no qualification. + +The next single bounded full loop has14 public/system commands and20 fresh +`qoder plugins list --json` catalogs in four closed members: summary, +`qoder-cycle-public.jsonl`, `qoder-cycle-catalog.jsonl`, `qoder-cycle-proof.jsonl`. +Only supported project lifecycle/restart-removal flags may qualify. Bindings +remain Prepared/installation_verified; MCP/auth/model/skill execution, automatic +cwd discovery and native import/cache replacement stay unclaimed. Preserve the +separate old authenticated-startup test/reader. No production adapter, native +mutation command, pin, workflow, runner API, provider or model change. + +Original source-authored schema2 User/foreignA/foreignB plugin records and their +manifests/skills are disabled once before children and immutable. Publisher +`getInstalledPluginRows` lists all three from every cwd with explicit projectPath +for A/B. The owned row is only the exact supported `--plugin-dir` receipt view; +never infer cwd hiding. After whole owned-package damage/removal, fresh queries +retain the SAME old view argument and require absence of its flag row plus the +complete original catalog. No empty replacement view or ignored stderr. + +Retain the original pre-native full profile map, held identities and logs-absent +precondition separately from a derived admitted telemetry ledger. Git/UAP calls +must preserve the full expected profile. Only explicit native version/catalog +calls may create one exact fresh source-defined run and its manifest/log plus +owned latest pointer; the first run may create only logs/runs parents as well. +No other new path, old-record change, alias/hardlink/foreign leaf or baseline +adoption is authorized. Reuse existing no-follow held root/link/manifest guards; +join exact modes/owners/membership/hashes, fresh runID, millisecond command-time +window, fixed version/role/platform and expected cwd. Manifest/argv/log/env stay +private; safe finite run projection, lengths/hashes/deltas and ledger joins only. +PID/PPID remain explicitly manifest-only, without runner/PID provenance claims. + +Exact publisher bundle1.1.78 SHA256 +`ec7b2a2c1be206f525ca974c3b9455fbec9ce7411bc5dfdee55f1e82eb90df38` +(34,562,870 bytes) defines logger39612–41200, Bun normalization `f9a`8516 and +`PGA/SJ`3529. Accept only exact held argv, or exactly one canonical bounded Linux +`/$bunfs/…` entrypoint at index1 with held executable and exact held argument +suffix; emit finite form without raw entrypoint. This is source guidance: +the full genuine run must confirm the shipping form or fail all-false. Registry +schema24599k, Boolean enabled-map24728830, resource collector24704176, +missing-directory loader24604992 and JSON renderer27075013 support the exact +catalog/control recipe; omit displayName in registry/manifest to avoid refresh. + +A/B have independent same-name local sources and stable derived identities. +A-two changes skill metadata/body at the same version; keep its applied source +frozen during whole exact-owned-package repair. Public grouped intact repair +restages/mutates, unlike the single SDK Repair route. Repeat add/unchanged update +are byte-stable no-ops. Preserve original User/B/foreign/caller authority. Verify +installed zero DataReceipts and actual default-remove retained owner-only data; +never invent native markers, adopt caller siblings or purge. Source/native view +and installed payload are text-only and their framing is independently recomputed. + +Keep original150/180 deadlines,30s per command plus existing5s exit grace, +1MiB stream/aggregate,16MiB total and80-member limits. Strict raw privacy before +base64 and after decoding, exact finite DTO/stage closure, old capture replay, +meaningful inert source/ownership/public API contracts and ordinary descendant +cleanup remain required. No review fatal/step barrier. Root plus independent +CODE reviews and focused Go/Python/affected/all-six gates precede root publication, +then one genuine hosted full-cycle run and two independent complete raw audits. + +The actual inert public CLI contract confirms the stage-specific DTOs: repeated +add is a singleton no-op with no operation/group ID; other successful lifecycle +commands are grouped. Repair/removal report source `local`; add/update report +`direct local source`. Repeat-add progress includes the transactional-apply line. +Go and the independent reader close those measured forms without accepting a +cross-stage union. Original profile and lifecycle authority remain unchanged. diff --git a/repotests/agentplugins_project_native_cli_attempt_test.go b/repotests/agentplugins_project_native_cli_attempt_test.go index 1e9bfdaba..cad97ae30 100644 --- a/repotests/agentplugins_project_native_cli_attempt_test.go +++ b/repotests/agentplugins_project_native_cli_attempt_test.go @@ -675,11 +675,12 @@ func (f *projectNativeCLIAttempt) finish(t *testing.T) { t.Error("original CLI attempt total deadline exceeded") } f.evidence["test_failed"], f.evidence["elapsed_ms"] = t.Failed(), time.Since(f.started).Milliseconds() - if f.base != nil { + if f.base != nil && f.evidence["capture_mode"] != qoderCycleMode { if body, err := projectNativeRuntimeRead(f.base.Events, 1<<20); err == nil { f.keep(t, "probe-events.jsonl", body) } } + qoderCycleFinalizeEvidence(f.evidence) raw, _ := json.MarshalIndent(f.evidence, "", " ") f.keep(t, "runtime-attempt.json", append(raw, '\n')) file, err := os.OpenFile(filepath.Join(f.output, "research.zip"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) diff --git a/repotests/agentplugins_qoder_cycle_command_test.go b/repotests/agentplugins_qoder_cycle_command_test.go new file mode 100644 index 000000000..e09688df1 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_command_test.go @@ -0,0 +1,226 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + "testing" + "time" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/adapters/process" + "github.com/777genius/plugin-kit-ai/install/integrationctl/ports" +) + +type qoderCycleCommand struct { + Argv []string `json:"argv"` + CWD string `json:"cwd"` + Exit int `json:"exit"` + Error bool `json:"error"` + Deadline bool `json:"deadline"` + Elapsed int64 `json:"elapsed_ms"` + Cleanup string `json:"cleanup"` +} + +type qoderCycleCommandRow struct { + Command qoderCycleCommand `json:"command"` + Started string `json:"started_at"` + Ended string `json:"ended_at"` + Stdout qoderCycleBytes `json:"stdout"` + Stderr qoderCycleBytes `json:"stderr"` + Log qoderCycleLogTransition `json:"log"` +} + +func (c *qoderSkillsCycle) run(t *testing.T, member, label, cwd string, argv []string, native bool) ports.CommandResult { + t.Helper() + c.check(t) + ctx, cancel := context.WithTimeout(c.f.ctx, 30*time.Second) + defer cancel() + kind := "public" + if member == qoderCycleAggregates[1] { + kind = "catalog" + } + c.f.evidence["cycle_stage"] = kind + ":" + label + start := time.Now().UTC() + result, err := (process.OS{}).RunWithDescendantExitGrace(ctx, ports.Command{Argv: argv, Env: c.f.env(), Dir: cwd, StdoutLimitBytes: 1 << 20}, 5*time.Second) + end := time.Now().UTC() + var log qoderCycleLogTransition + var logErr error + if native { + log, logErr = c.logs.native(cwd, argv, start, end) + } else { + log, logErr = c.logs.unchanged() + } + command := qoderCycleCommand{Argv: append([]string{}, argv...), CWD: cwd, Exit: result.ExitCode, Error: err != nil, Deadline: ctx.Err() != nil, Elapsed: end.Sub(start).Milliseconds(), Cleanup: "full descendant supervision; forced cleanup fails"} + c.append(t, member, label, qoderCycleCommandRow{Command: command, Started: start.Format(time.RFC3339Nano), Ended: end.Format(time.RFC3339Nano), Stdout: qoderCycleStream(t, result.Stdout), Stderr: qoderCycleStream(t, result.Stderr), Log: log}) + if member == qoderCycleAggregates[0] { + c.public = append(c.public, label) + } else { + c.catalogs = append(c.catalogs, label) + } + if err != nil || ctx.Err() != nil || end.Sub(start) > 35*time.Second || len(result.Stdout) > 1<<20 || len(result.Stderr) > 1<<20 || bytes.Contains(result.Stderr, []byte("stderr bytes omitted")) { + t.Fatal("Qoder cycle command/capture/ordinary cleanup failed") + } + if logErr != nil { + t.Fatal("Qoder strict original/log transition refused", logErr) + } + c.check(t) + return result +} + +func qoderCycleRole(label string) string { + if strings.HasSuffix(label, "-a") { + return "a" + } + if strings.HasSuffix(label, "-b") { + return "b" + } + return "outside" +} + +func (c *qoderSkillsCycle) publicArgs(label string) (string, []string) { + key := qoderCycleRole(label) + cwd := c.f.outside + if key != "outside" { + cwd = c.recipes[key].Project + } + if strings.HasPrefix(label, "git-") { + return cwd, []string{c.f.git, "-c", "core.hooksPath=/dev/null", "init", "--quiet"} + } + if label == "version" { + return cwd, []string{c.f.client, "--version"} + } + r := c.recipes[key] + verb := strings.Split(label, "-")[0] + switch label { + case "repeat-add-a": + verb = "add" + case "unchanged-update-a": + verb = "update" + case "intact-repair-a", "missing-repair-a": + verb = "repair" + case "repeat-remove-a": + verb = "remove" + } + selector := qoderCycleName + if verb == "add" || label == "update-a" { + selector = r.Source + } + if verb == "repair" { + selector = r.Installation.InstallationID + } + args := []string{c.f.installer, verb, selector, "--scope", "project", "--project-root", cwd, "--target", "qoder", "--format", "json"} + if verb == "add" { + args = append(args, "--no-save") + } + return cwd, args +} + +func (c *qoderSkillsCycle) publicCommand(t *testing.T, label string) ports.CommandResult { + t.Helper() + if len(c.public) >= len(qoderCyclePublic) || qoderCyclePublic[len(c.public)] != label { + t.Fatal("Qoder public one-shot order") + } + cwd, args := c.publicArgs(label) + if label == "version" && len(c.logs.runs) != 0 { + t.Fatal("Qoder initial version is not the first native logger") + } + result := c.run(t, qoderCycleAggregates[0], label, cwd, args, label == "version") + if label == "repeat-remove-a" { + if result.ExitCode != 1 || len(result.Stdout) != 0 || string(result.Stderr) != "agentplugins: plugin is not installed for target \"qoder\" in project scope; no target was changed\n" { + t.Fatal("Qoder exact repeated removal refusal differs") + } + } else if result.ExitCode != 0 { + t.Fatal("Qoder public child failed") + } + return result +} + +func qoderCycleHasFlag(stage string) bool { + switch stage { + case "empty-outside", "empty-a", "empty-b", "installed-outside", "updated-outside", "removed-outside", "final-outside": + return false + default: + return true + } +} + +func (c *qoderSkillsCycle) catalog(t *testing.T, stage string) { + t.Helper() + if len(c.catalogs) >= len(qoderCycleCatalog) || qoderCycleCatalog[len(c.catalogs)] != stage || len(c.logs.runs) != len(c.catalogs)+1 { + t.Fatal("Qoder fresh catalog/log order") + } + key := qoderCycleRole(stage) + cwd := c.f.outside + if key != "outside" { + cwd = c.recipes[key].Project + } + args := []string{c.f.client, "plugins", "list", "--json"} + var selected *qoderCycleRecipe + if qoderCycleHasFlag(stage) { + selected = c.recipes[key] + if selected == nil || selected.View == "" { + t.Fatal("Qoder exact current/former receipt view required") + } + args = append(args, "--plugin-dir", selected.View) + } + result := c.run(t, qoderCycleAggregates[1], stage, cwd, args, true) + if result.ExitCode != 0 || len(result.Stderr) != 0 { + t.Fatal("Qoder catalog native process/diagnostics") + } + if err := c.catalogJSON(result.Stdout, selected); err != nil { + t.Fatal(err) + } +} + +func (c *qoderSkillsCycle) catalogJSON(raw []byte, selected *qoderCycleRecipe) error { + value, err := projectNativeRuntimeJSON(raw) + rows, ok := value.([]any) + if err != nil || !ok { + return fmt.Errorf("Qoder whole catalog array required") + } + expected := []any{} + if selected != nil && !selected.Removed && !selected.Missing { + expected = append(expected, map[string]any{"id": qoderCycleName + "@flag", "name": qoderCycleName, "source": qoderCycleName + "@inline", "version": "1.0.0", "scope": "flag", "enabled": true, "canDisable": false, "installPath": selected.View, "description": qoderCycleDescription, "resources": qoderCycleResources(qoderCycleName, fmt.Sprintf("Disposable Qoder %s %s %s", selected.Key, selected.Revision, selected.Nonce))}) + } + for _, key := range []string{"a", "b", "user"} { + name := qoderCycleControlName(key) + parent := c.f.base.Home + switch key { + case "a": + parent = c.f.base.Project + case "b": + parent = c.f.other + } + description := "Original Qoder " + key + " " + c.f.base.Nonce + row := map[string]any{"id": name + "@local", "name": name, "source": name + "@local", "version": "0.9.0", "scope": "user", "enabled": false, "canDisable": true, "installPath": parent + "/.qoder/plugins/" + name, "description": description, "resources": qoderCycleResources(name, description)} + if key != "user" { + row["scope"], row["projectPath"] = "project", parent + } + expected = append(expected, row) + } + if !reflect.DeepEqual(rows, expected) { + return fmt.Errorf("Qoder exact original/flag catalog resource identity differs") + } + return nil +} + +func qoderCycleResources(name, description string) map[string]any { + return map[string]any{"skills": []any{map[string]any{"name": name, "description": description}}, "agents": []any{}, "mcpServers": []any{}, "commands": []any{}, "hooks": []any{}} +} + +func qoderCyclePublicJSON(raw []byte, verb string, mutated bool) error { + if err := clineCyclePublicJSON(raw, mutated); err != nil { + return err + } + value, err := projectNativeRuntimeJSON(raw) + row, ok := value.(map[string]any) + if err != nil || !ok || projectNativeRuntimeKeys(row, "schema_version command result data", "") != nil || row["schema_version"] != json.Number("1") || row["command"] != verb || row["result"] != "success" { + return fmt.Errorf("Qoder exact public success envelope") + } + return nil +} diff --git a/repotests/agentplugins_qoder_cycle_completion_contract_test.go b/repotests/agentplugins_qoder_cycle_completion_contract_test.go new file mode 100644 index 000000000..98de942d5 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_completion_contract_test.go @@ -0,0 +1,42 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "encoding/json" + "testing" +) + +func TestQoderCycleLateSharedFinalizationFailureCannotSerializeQualification(t *testing.T) { + for _, mode := range []string{qoderCycleMode, "legacy-attempt"} { + evidence := map[string]any{"capture_mode": mode, "runtime_attempt_complete": true, "test_failed": false, + "qualification": map[string]bool{"project-lifecycle": true, "native-restart-removal": true, "mcp-initialize": false, "mcp-tools-list": false, "mcp-tools-call": false}} + qoderCycleFinalizeEvidence(evidence) + if evidence["runtime_attempt_complete"] != true { + t.Fatal("successful candidate changed before late guard") + } + // The existing shared 180-second guard records failure after c.finish + // may have provisionally qualified. Exercise its exact serialization hook. + evidence["test_failed"], evidence["elapsed_ms"] = true, int64(180001) + qoderCycleFinalizeEvidence(evidence) + raw, err := json.Marshal(evidence) + if err != nil { + t.Fatal(err) + } + value, err := projectNativeRuntimeJSON(raw) + if err != nil { + t.Fatal(err) + } + row := value.(map[string]any) + if row["runtime_attempt_complete"] != (mode != qoderCycleMode) { + t.Fatal("late failure completion or legacy behavior changed") + } + if mode == qoderCycleMode { + for _, flag := range row["qualification"].(map[string]any) { + if flag != false { + t.Fatal("late failed summary retained a qualification flag") + } + } + } + } +} diff --git a/repotests/agentplugins_qoder_cycle_completion_test.go b/repotests/agentplugins_qoder_cycle_completion_test.go new file mode 100644 index 000000000..9cdbf7322 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_completion_test.go @@ -0,0 +1,82 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "fmt" + "reflect" + "testing" + "time" +) + +var qoderCycleProofStages = []string{"fixture", "source-a-a-one", "caller-a", "source-b-b-one", "caller-b", "original-user", "original-a", "original-b", "original-registry", "original-profile", "add-a", "add-b", "repeat-add-a", "unchanged-update-a", "intact-repair-a", "before-source-revision-a", "source-a-a-two", "update-a", "before-missing-a", "missing-a", "missing-repair-a", "remove-a", "repeat-remove-a", "remove-b", "log-ledger"} + +// Run at the existing serializer boundary, after its own late deadline guard. +// Other capture modes retain their existing finalization behavior. +func qoderCycleFinalizeEvidence(evidence map[string]any) { + if evidence["capture_mode"] != qoderCycleMode || evidence["test_failed"] != true { + return + } + evidence["runtime_attempt_complete"] = false + evidence["qualification"] = map[string]bool{"project-lifecycle": false, "native-restart-removal": false, "mcp-initialize": false, "mcp-tools-list": false, "mcp-tools-call": false} +} + +func (c *qoderSkillsCycle) closure() error { + for index, stages := range [][]string{qoderCyclePublic, qoderCycleCatalog, qoderCycleProofStages} { + raw, ok := c.aggregates[qoderCycleAggregates[index]] + if !ok || len(raw) > 1<<20 || !bytes.HasSuffix(raw, []byte{'\n'}) { + return fmt.Errorf("Qoder full aggregate closure") + } + lines := bytes.Split(bytes.TrimSuffix(raw, []byte{'\n'}), []byte{'\n'}) + if len(lines) != len(stages) { + return fmt.Errorf("Qoder full aggregate count") + } + for i, line := range lines { + value, err := projectNativeRuntimeJSON(line) + row, ok := value.(map[string]any) + if err != nil || !ok || projectNativeRuntimeKeys(row, "stage value", "") != nil || row["stage"] != stages[i] { + return fmt.Errorf("Qoder full aggregate ordered stage differs") + } + } + } + return nil +} + +func (c *qoderSkillsCycle) finish(t *testing.T) { + t.Helper() + f := c.f + f.evidence["runtime_attempt_complete"] = false + flags := map[string]bool{"project-lifecycle": false, "native-restart-removal": false, "mcp-initialize": false, "mcp-tools-list": false, "mcp-tools-call": false} + f.evidence["qualification"] = flags + f.evidence["cycle_observed_complete"] = c.complete + f.evidence["cycle_public_stages"], f.evidence["cycle_catalog_stages"] = append([]string{}, c.public...), append([]string{}, c.catalogs...) + runs := 0 + if c.logs != nil { + runs = len(c.logs.runs) + } + f.evidence["cycle_log_runs"] = runs + if err := c.authority(); err != nil { + t.Error(err) + } + for _, name := range qoderCycleAggregates { + if raw, ok := c.aggregates[name]; ok { + if qoderCyclePrivate(raw) { + t.Error("Qoder private/unbounded aggregate refused") + continue + } + f.keep(t, name, raw) + } + } + closed := c.closure() == nil + correct := f.evidence["capture_mode"] == qoderCycleMode && f.evidence["cycle_stage"] == "complete-cycle" && f.evidence["vendor_authentication_qualified"] == false && f.evidence["real_model_qualified"] == false && reflect.DeepEqual(c.public, qoderCyclePublic) && reflect.DeepEqual(c.catalogs, qoderCycleCatalog) && runs == 21 + complete := c.complete && !t.Failed() && f.ctx.Err() == nil && correct && closed && len(f.files) == 3 && time.Since(f.started) < 180*time.Second + if c.complete && !complete { + t.Error("Qoder full final deadline/ordinary cleanup/source/capture gate failed") + } + if complete { + f.evidence["runtime_attempt_complete"] = true + flags["project-lifecycle"], flags["native-restart-removal"] = true, true + } + f.finish(t) +} diff --git a/repotests/agentplugins_qoder_cycle_fixture_test.go b/repotests/agentplugins_qoder_cycle_fixture_test.go new file mode 100644 index 000000000..ce286e460 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_fixture_test.go @@ -0,0 +1,374 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "syscall" + "testing" + "unicode/utf8" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/packagedigest" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" +) + +const qoderCycleMode = "qoder-complete-skills-cycle" +const qoderCycleName = "uap-qoder-cycle" +const qoderCycleDescription = "Disposable Qoder project cycle" + +var qoderCyclePublic = []string{"git-a", "git-b", "git-outside", "version", "add-a", "add-b", "repeat-add-a", "unchanged-update-a", "intact-repair-a", "update-a", "missing-repair-a", "remove-a", "repeat-remove-a", "remove-b"} +var qoderCycleCatalog = []string{"empty-outside", "empty-a", "empty-b", "installed-a", "installed-b", "installed-outside", "stable-a", "updated-a", "updated-b", "updated-outside", "damaged-a", "repaired-a", "removed-a", "remaining-b", "removed-outside", "repeat-removed-a", "removed-b", "final-a", "final-b", "final-outside"} +var qoderCycleAggregates = []string{"qoder-cycle-public.jsonl", "qoder-cycle-catalog.jsonl", "qoder-cycle-proof.jsonl"} + +type qoderCycleBytes struct { + Bytes int `json:"bytes"` + SHA256 string `json:"sha256"` + Body []byte `json:"body"` +} + +type qoderCycleRecipe struct { + Key, Project, Source, Revision, Nonce, Digest string + Authored map[string][]byte + Installation domain.Installation + Binding domain.ClientBinding + Active, View string + Removed, Missing bool +} + +type qoderSkillsCycle struct { + f *projectNativeCLIAttempt + recipes map[string]*qoderCycleRecipe + aggregates map[string][]byte + original map[string]os.FileInfo + trees map[string]map[string]string + profileRecipe map[string]string + groups map[string]bool + logs *qoderCycleLogs + public, catalogs []string + complete bool + mutable string +} + +func newQoderSkillsCycle(f *projectNativeCLIAttempt) *qoderSkillsCycle { + f.name = qoderCycleName + f.evidence["capture_mode"] = qoderCycleMode + f.evidence["cycle_stage"] = "prepare" + return &qoderSkillsCycle{f: f, recipes: map[string]*qoderCycleRecipe{}, aggregates: map[string][]byte{}, original: map[string]os.FileInfo{}, trees: map[string]map[string]string{}, profileRecipe: map[string]string{".": "directory:20000000700"}, groups: map[string]bool{}, public: []string{}, catalogs: []string{}} +} + +func qoderCyclePrivate(raw []byte) bool { + marker := []byte("uap-qoder-private-") + if len(raw) > 1<<20 || !utf8.Valid(raw) || bytes.Contains(raw, marker) { + return true + } + budget := clineCyclePrivacyBudget{text: 4 * len(raw), nodes: 16384} + return clineCycleCredentialJSON(raw, marker, 0, &budget) +} + +func qoderCycleStream(t *testing.T, raw []byte) qoderCycleBytes { + t.Helper() + if qoderCyclePrivate(raw) { + t.Fatal("Qoder cycle private/unbounded stream refused before capture") + } + return qoderCycleBytes{Bytes: len(raw), SHA256: qoderDeltaHash(string(raw)), Body: append([]byte{}, raw...)} +} + +func (c *qoderSkillsCycle) append(t *testing.T, member, stage string, value any) { + t.Helper() + known := false + for _, name := range qoderCycleAggregates { + if member == name { + known = true + } + } + if !known || stage == "" { + t.Fatal("Qoder cycle unregistered aggregate/stage") + } + raw, err := json.Marshal(map[string]any{"stage": stage, "value": value}) + if err != nil { + t.Fatal(err) + } + raw = append(raw, '\n') + total := len(raw) + for _, body := range c.aggregates { + total += len(body) + } + if len(c.aggregates[member])+len(raw) > 1<<20 || total > 16<<20 { + t.Fatal("Qoder cycle aggregate bound") + } + c.aggregates[member] = append(c.aggregates[member], raw...) +} + +func (c *qoderSkillsCycle) statePath(r *qoderCycleRecipe) string { + return filepath.Join(r.Project, ".uap", "state-v2.json") +} + +func (c *qoderSkillsCycle) hold(t *testing.T, path string) { + t.Helper() + if err := projectNativeRuntimeParents(path); err != nil { + t.Fatal(err) + } + info, err := os.Lstat(path) + if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() && !info.Mode().IsRegular() { + t.Fatal("Qoder original ordinary identity required") + } + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok || stat.Uid != uint32(os.Getuid()) || stat.Gid != uint32(os.Getgid()) || info.Mode().IsRegular() && stat.Nlink != 1 { + t.Fatal("Qoder original owner/link identity") + } + c.original[path] = info +} + +func (c *qoderSkillsCycle) check(t *testing.T) { + t.Helper() + if c.f.ctx.Err() != nil { + t.Fatal("Qoder original work deadline exhausted") + } + if err := c.authority(); err != nil { + t.Fatal(err) + } +} + +func (c *qoderSkillsCycle) authority() error { + if c.f.qoderMetadata != nil { + if err := c.f.qoderMetadata.check(); err != nil { + return err + } + } + if c.f.qoderTelemetry != nil { + if err := c.f.qoderTelemetry.check(); err != nil { + return err + } + } + for _, inventory := range []map[string]string{c.f.immutable, c.f.protected} { + for path, want := range inventory { + got, err := projectNativeRuntimeDigest(path) + if err != nil || got != want { + return fmt.Errorf("Qoder final immutable/original protected identity changed") + } + } + } + if c.f.bin != "" { + path := filepath.Join(c.f.bin, "qoder") + info, err := os.Lstat(path) + target, linkErr := os.Readlink(path) + if err != nil || linkErr != nil || info.Mode()&os.ModeSymlink == 0 || target != c.f.client { + return fmt.Errorf("Qoder admitted executable alias changed") + } + } + if c.f.base != nil { + if err := c.mutableAuthority(); err != nil { + return err + } + } + if c.logs != nil { + if err := c.logs.check(); err != nil { + return err + } + } + for path, before := range c.original { + if err := projectNativeRuntimeParents(path); err != nil { + return err + } + after, err := os.Lstat(path) + if err != nil || !os.SameFile(before, after) || before.Mode() != after.Mode() { + return fmt.Errorf("Qoder original identity/mode changed") + } + a, b := before.Sys().(*syscall.Stat_t), after.Sys().(*syscall.Stat_t) + if a.Uid != b.Uid || a.Gid != b.Gid || before.Mode().IsRegular() && b.Nlink != 1 { + return fmt.Errorf("Qoder original owner/link changed") + } + } + for root, want := range c.trees { + actual, err := clineAttemptTree(root) + if err != nil || !reflect.DeepEqual(actual, want) { + return fmt.Errorf("Qoder original/unaffected tree changed") + } + } + return nil +} + +func (c *qoderSkillsCycle) prepare(t *testing.T) { + t.Helper() + f := c.f + layout := map[string]string{"root": f.base.Root, "home": f.base.Home, "a": f.base.Project, "b": f.other, "outside": f.outside, "installer": f.installer, "client": f.client, "git": f.git} + f.evidence["nonce"], f.evidence["layout"] = f.base.Nonce, layout + c.append(t, "qoder-cycle-proof.jsonl", "fixture", map[string]any{"nonce": f.base.Nonce, "layout": layout, "owner_uid": os.Getuid(), "owner_gid": os.Getgid()}) + for _, key := range []string{"a", "b"} { + project := f.base.Project + if key == "b" { + project = f.other + } + r := &qoderCycleRecipe{Key: key, Project: project, Source: filepath.Join(f.base.Root, "cycle-source-"+key), Revision: key + "-one", Nonce: qoderDeltaHash(f.base.Nonce + key)[:32]} + c.recipes[key] = r + c.author(t, r, false) + path := filepath.Join(project, ".uap", "plugin-data", "caller-owned", "keep.txt") + body := []byte("CALLER_" + key + "_" + f.base.Nonce + "\n") + projectNativeRuntimeWrite(t, path, body, 0600) + c.hold(t, path) + c.hold(t, filepath.Dir(path)) + f.protected[path] = qoderDeltaHash(string(body)) + inventory, err := clineAttemptTree(filepath.Dir(path)) + if err != nil { + t.Fatal(err) + } + c.trees[filepath.Dir(path)] = inventory + c.append(t, "qoder-cycle-proof.jsonl", "caller-"+key, map[string]any{"path": path, "body": qoderCycleStream(t, body), "files": inventory}) + } + for i, item := range qoderMetadataRecipe { + c.profileRecipe[item.name] = fmt.Sprintf("file:%o:%d:%s", item.mode, item.size, f.qoderMetadata.files[i].digest) + } + for _, path := range []string{".auth", "tmp", "tmp/telemetry", "tmp/telemetry/active-runs"} { + c.profileRecipe[path] = "directory:20000000700" + } + c.controls(t) + logs, err := newQoderCycleLogs(f.ctx, filepath.Join(f.base.Home, ".qoder"), c.profileRecipe) + if err != nil { + t.Fatal(err) + } + c.logs = logs + c.append(t, "qoder-cycle-proof.jsonl", "original-profile", map[string]any{"logs_absent": true, "inventory": logs.original}) + for relative := range logs.original { + c.hold(t, filepath.Join(logs.root, relative)) + } + c.check(t) +} + +func (c *qoderSkillsCycle) author(t *testing.T, r *qoderCycleRecipe, replace bool) { + t.Helper() + authored := qoderCycleAuthored(r.Key, r.Revision, r.Nonce) + for relative, body := range authored { + path := filepath.Join(r.Source, filepath.FromSlash(relative)) + if replace { + if _, err := projectNativeRuntimeRead(path, 1<<20); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, body, 0600); err != nil { + t.Fatal(err) + } + } else { + projectNativeRuntimeWrite(t, path, body, 0600) + } + } + r.Authored = authored + entries, err := clineAttemptPayload(r.Source) + if err != nil { + t.Fatal(err) + } + r.Digest, err = packagedigest.DigestCaptured(c.f.ctx, entries) + if err != nil { + t.Fatal(err) + } + inventory, err := clineAttemptTree(r.Source) + if err != nil { + t.Fatal(err) + } + c.trees[r.Source] = inventory + c.append(t, "qoder-cycle-proof.jsonl", "source-"+r.Key+"-"+r.Revision, map[string]any{"source": r.Source, "digest": r.Digest, "files": inventory}) +} + +func qoderCycleAuthored(key, revision, nonce string) map[string][]byte { + manifest, _ := json.Marshal(map[string]any{"$schema": domain.PluginSchemaV1, "name": qoderCycleName, "version": "1.0.0", "description": qoderCycleDescription}) + skill := []byte(fmt.Sprintf("---\nname: %s\ndescription: Disposable Qoder %s %s %s\n---\nUAP_QODER_%s_%s\n", qoderCycleName, key, revision, nonce, revision, nonce)) + return map[string][]byte{"plugin.json": manifest, "skills/" + qoderCycleName + "/SKILL.md": skill} +} + +func qoderCycleControlName(key string) string { + if key == "user" { + return "uap-qoder-original-user" + } + return "uap-qoder-foreign-" + key +} + +func (c *qoderSkillsCycle) controls(t *testing.T) { + t.Helper() + f := c.f + registry := map[string]any{} + enabled := map[string]bool{} + for _, key := range []string{"user", "a", "b"} { + name := qoderCycleControlName(key) + parent := f.base.Home + switch key { + case "a": + parent = f.base.Project + case "b": + parent = f.other + } + root := filepath.Join(parent, ".qoder", "plugins", name) + description := "Original Qoder " + key + " " + f.base.Nonce + manifest, _ := json.Marshal(map[string]any{"name": name, "version": "0.9.0", "description": description, "skills": "./skills/"}) + skill := []byte(fmt.Sprintf("---\nname: %s\ndescription: %s\n---\nUAP_QODER_ORIGINAL_%s_%s\n", name, description, strings.ToUpper(key), f.base.Nonce)) + for relative, body := range map[string][]byte{".qoder-plugin/plugin.json": manifest, "skills/" + name + "/SKILL.md": skill} { + path := filepath.Join(root, relative) + projectNativeRuntimeWrite(t, path, body, 0600) + if key == "user" { + c.profileFile(t, path, body) + } + } + c.hold(t, filepath.Dir(root)) + c.hold(t, filepath.Dir(filepath.Dir(root))) + if err := filepath.WalkDir(root, func(path string, _ os.DirEntry, err error) error { + if err != nil { + return err + } + c.hold(t, path) + return nil + }); err != nil { + t.Fatal(err) + } + inventory, err := clineAttemptTree(root) + if err != nil { + t.Fatal(err) + } + c.trees[root] = inventory + entry := map[string]any{"scope": "user", "installPath": root, "version": "0.9.0"} + if key != "user" { + entry["scope"], entry["projectPath"] = "project", parent + } + registry[name+"@local"] = []any{entry} + enabled[name+"@local"] = false + c.append(t, "qoder-cycle-proof.jsonl", "original-"+key, map[string]any{"key": key, "path": root, "manifest": qoderCycleStream(t, manifest), "skill": qoderCycleStream(t, skill), "files": inventory, "entry": entry}) + } + profile := filepath.Join(f.base.Home, ".qoder") + settings := filepath.Join(profile, "settings.json") + f.check(t) + body, _ := json.Marshal(map[string]any{"general": map[string]any{"enableAutoUpdate": false}, "enabledPlugins": enabled}) + if err := os.WriteFile(settings, body, 0600); err != nil { + t.Fatal(err) + } + f.protected[settings] = qoderDeltaHash(string(body)) + c.profileFile(t, settings, body) + body, _ = json.Marshal(map[string]any{"version": 2, "plugins": registry}) + path := filepath.Join(profile, "plugins", "installed_plugins_v2.json") + projectNativeRuntimeWrite(t, path, body, 0600) + f.protected[path] = qoderDeltaHash(string(body)) + c.profileFile(t, path, body) + c.append(t, "qoder-cycle-proof.jsonl", "original-registry", map[string]any{"path": path, "registry": qoderCycleStream(t, body), "settings_path": settings, "settings": qoderCycleStream(t, mustQoderCycleRead(t, settings))}) +} + +func mustQoderCycleRead(t *testing.T, path string) []byte { + t.Helper() + raw, err := projectNativeRuntimeRead(path, 1<<20) + if err != nil { + t.Fatal(err) + } + return raw +} + +func (c *qoderSkillsCycle) profileFile(t *testing.T, path string, body []byte) { + t.Helper() + relative, err := filepath.Rel(filepath.Join(c.f.base.Home, ".qoder"), path) + if err != nil || relative == "." || strings.HasPrefix(relative, "..") { + t.Fatal("Qoder authored profile recipe escaped") + } + c.profileRecipe[relative] = fmt.Sprintf("file:600:%d:%s", len(body), qoderDeltaHash(string(body))) + for parent := filepath.Dir(relative); parent != "."; parent = filepath.Dir(parent) { + c.profileRecipe[parent] = "directory:20000000700" + } +} diff --git a/repotests/agentplugins_qoder_cycle_lifecycle_test.go b/repotests/agentplugins_qoder_cycle_lifecycle_test.go new file mode 100644 index 000000000..616b804e7 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_lifecycle_test.go @@ -0,0 +1,267 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "reflect" + "sort" + "strings" + "syscall" + "testing" +) + +func (c *qoderSkillsCycle) suspend(r *qoderCycleRecipe) { + delete(c.f.protected, c.statePath(r)) + if r.Active != "" { + delete(c.trees, r.Active) + } +} + +func (c *qoderSkillsCycle) apply(t *testing.T, label, key string, mutated bool) { + t.Helper() + r := c.recipes[key] + c.check(t) + var before []byte + if !mutated { + before = mustQoderCycleRead(t, c.statePath(r)) + } + c.suspend(r) + c.mutable = key + if label == "missing-repair-a" { + r.Missing = false + } + result := c.publicCommand(t, label) + if label != "repeat-remove-a" { + verb := strings.Split(label, "-")[0] + switch label { + case "repeat-add-a": + verb = "add" + case "unchanged-update-a": + verb = "update" + case "intact-repair-a", "missing-repair-a": + verb = "repair" + } + if err := qoderCyclePublicJSON(result.Stdout, verb, mutated); err != nil { + t.Fatal(err) + } + if string(result.Stderr) != qoderCycleProgress(label) { + t.Fatal("Qoder public progress differs from exact source contract") + } + } + if !mutated && !bytes.Equal(before, mustQoderCycleRead(t, c.statePath(r))) { + t.Fatal("Qoder repeated/no-op public operation changed state bytes") + } + if strings.HasPrefix(label, "remove-") { + r.Removed = true + } + c.proof(t, r, label) + if label != "repeat-remove-a" { + _, args := c.publicArgs(label) + group, err := qoderCyclePublicProof(result.Stdout, label, args[1], mutated, r) + if err != nil || label != "repeat-add-a" && c.groups[group] { + t.Fatal("Qoder public result differs from exact current proof or repeats a group", err) + } + if group != "" { + c.groups[group] = true + } + } + c.mutable = "" + c.check(t) +} + +func qoderCycleProgress(label string) string { + switch label { + case "add-a", "add-b": + return "Resolving and validating one Agent Plugin package for every selected target...\nInstalling " + qoderCycleName + " for 1 selected clients...\n" + case "repeat-add-a": + return "Resolving and validating one Agent Plugin package for every selected target...\nApplying transactional client package...\n" + case "unchanged-update-a", "update-a": + return "Resolving and validating one updated Agent Plugin package for every selected target...\n" + case "intact-repair-a", "missing-repair-a": + return "Resolving and validating each unique exact installed package revision once...\n" + default: + return "" + } +} + +func (c *qoderSkillsCycle) reviseA(t *testing.T) { + t.Helper() + r := c.recipes["a"] + c.proof(t, r, "before-source-revision-a") + delete(c.trees, r.Source) + r.Revision = "a-two" + c.author(t, r, true) + c.check(t) +} + +func (c *qoderSkillsCycle) missingA(t *testing.T) { + t.Helper() + r := c.recipes["a"] + c.proof(t, r, "before-missing-a") + c.check(t) + c.removeOwnedA(t) +} + +func (c *qoderSkillsCycle) removeOwnedA(t *testing.T) { + t.Helper() + r := c.recipes["a"] + if r.Revision != "a-two" || r.Removed || r.Missing || len(r.Binding.NativeObjects) != 1 || r.Binding.NativeObjects[0].Path != r.Active { + t.Fatal("Qoder controlled fault requires exact whole owned frozen A-two package") + } + before, err := clineAttemptTree(r.Active) + if err != nil || !reflect.DeepEqual(before, c.trees[r.Active]) { + t.Fatal("Qoder fault differs from the last independently owned payload", err) + } + held := map[string]os.FileInfo{} + paths := make([]string, 0, len(before)) + for relative := range before { + path := filepath.Join(r.Active, relative) + if err := projectNativeRuntimeParents(path); err != nil { + t.Fatal(err) + } + info, err := os.Lstat(path) + if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() && !info.Mode().IsRegular() { + t.Fatal("Qoder fault requires ordinary exact owned membership") + } + stat := info.Sys().(*syscall.Stat_t) + if stat.Uid != uint32(os.Getuid()) || stat.Gid != uint32(os.Getgid()) || info.Mode().IsRegular() && stat.Nlink != 1 { + t.Fatal("Qoder fault ownership/link differs") + } + if info.Mode().IsRegular() { + body := mustQoderCycleRead(t, path) + current, err := os.Lstat(path) + if err != nil || !projectNativeRuntimeStableStat(info.Sys(), current.Sys()) || before[relative] != fmt.Sprintf("file:%o:%s", info.Mode(), qoderDeltaHash(string(body))) { + t.Fatal("Qoder fault file bytes/identity changed before authorization") + } + } + held[relative] = info + paths = append(paths, relative) + } + sort.Slice(paths, func(i, j int) bool { + return len(strings.Split(paths[i], string(filepath.Separator))) > len(strings.Split(paths[j], string(filepath.Separator))) || len(strings.Split(paths[i], string(filepath.Separator))) == len(strings.Split(paths[j], string(filepath.Separator))) && paths[i] > paths[j] + }) + // The top '.' directory is removed only after every known child; no recursive + // remover can consume an unexpected foreign leaf introduced during the fault. + delete(c.trees, r.Active) + for _, relative := range paths { + if relative == "." { + continue + } + path := filepath.Join(r.Active, relative) + now, err := os.Lstat(path) + if err != nil || !os.SameFile(held[relative], now) || now.Mode() != held[relative].Mode() || now.Mode().IsRegular() && !projectNativeRuntimeStableStat(held[relative].Sys(), now.Sys()) { + t.Fatal("Qoder held fault identity changed") + } + if err := projectNativeRuntimeParents(path); err != nil { + t.Fatal(err) + } + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + } + current, err := os.Lstat(r.Active) + if err != nil || !os.SameFile(held["."], current) || !current.IsDir() { + t.Fatal("Qoder whole owned root changed before final nonrecursive removal") + } + if err := os.Remove(r.Active); err != nil { + t.Fatal(err) + } + if err := qoderCycleAbsent(r.Active); err != nil { + t.Fatal(err) + } + r.Missing = true + c.append(t, qoderCycleAggregates[2], "missing-a", map[string]any{"path": r.Active, "source_digest": r.Digest, "object": r.Binding.NativeObjects[0], "before": before, "absent": true}) + c.check(t) +} + +func TestAgentpluginsQoderNativeProjectCompleteSkillsCycle(t *testing.T) { + spec, err := projectNativeCLISpecification("qoder") + if err != nil { + t.Fatal(err) + } + if os.Getenv(spec.optin) != "1" { + t.Skip("opt-in genuine hosted Qoder supported skills catalog cycle") + } + f := newProjectNativeCLIAttempt(t, spec) + c := newQoderSkillsCycle(f) + defer c.finish(t) + c.prepare(t) + for _, label := range []string{"git-a", "git-b", "git-outside"} { + result := c.publicCommand(t, label) + if len(result.Stdout) != 0 || len(result.Stderr) != 0 { + t.Fatal("Qoder real Git setup diagnostics") + } + } + result := c.publicCommand(t, "version") + if string(result.Stdout) != "1.1.78\n" || len(result.Stderr) != 0 { + t.Fatal("Qoder exact pinned native version") + } + for _, stage := range []string{"empty-outside", "empty-a", "empty-b"} { + c.catalog(t, stage) + } + c.apply(t, "add-a", "a", true) + c.catalog(t, "installed-a") + c.apply(t, "add-b", "b", true) + c.catalog(t, "installed-b") + c.catalog(t, "installed-outside") + c.apply(t, "repeat-add-a", "a", false) + c.apply(t, "unchanged-update-a", "a", false) + c.apply(t, "intact-repair-a", "a", true) + c.catalog(t, "stable-a") + c.reviseA(t) + c.apply(t, "update-a", "a", true) + for _, stage := range []string{"updated-a", "updated-b", "updated-outside"} { + c.catalog(t, stage) + } + c.missingA(t) + c.catalog(t, "damaged-a") + c.apply(t, "missing-repair-a", "a", true) + c.catalog(t, "repaired-a") + c.apply(t, "remove-a", "a", true) + for _, stage := range []string{"removed-a", "remaining-b", "removed-outside"} { + c.catalog(t, stage) + } + c.apply(t, "repeat-remove-a", "a", false) + c.catalog(t, "repeat-removed-a") + c.apply(t, "remove-b", "b", true) + for _, stage := range []string{"removed-b", "final-a", "final-b", "final-outside"} { + c.catalog(t, stage) + } + if !reflect.DeepEqual(c.public, qoderCyclePublic) || !reflect.DeepEqual(c.catalogs, qoderCycleCatalog) || len(c.logs.runs) != 21 { + t.Fatal("Qoder full public/catalog/log-run closure") + } + c.check(t) + c.append(t, qoderCycleAggregates[2], "log-ledger", map[string]any{"original_sha256": qoderCycleInventoryHash(c.logs.original), "expected_sha256": qoderCycleInventoryHash(c.logs.expected), "runs": append([]string{}, c.logs.runs...)}) + c.complete = true + c.f.evidence["cycle_stage"] = "complete-cycle" +} + +func (c *qoderSkillsCycle) mutableAuthority() error { + for key, r := range c.recipes { + if key == c.mutable { + continue + } + if r.Installation.InstallationID == "" { + if err := qoderCycleAbsent(c.statePath(r)); err != nil { + return fmt.Errorf("Qoder uninstalled other project state appeared") + } + continue + } + if r.Removed || r.Missing { + if err := qoderCycleAbsent(r.Active); err != nil { + return err + } + } + if !r.Removed { + physical := qoderCycleName + "-" + qoderDeltaHash(r.Installation.InstallationID)[:12] + if err := qoderCycleAbsent(filepath.Join(r.Project, ".uap", "plugin-data", physical)); err != nil { + return fmt.Errorf("Qoder installed skills-only data appeared") + } + } + } + return qoderCycleAbsent(c.f.base.Events) +} diff --git a/repotests/agentplugins_qoder_cycle_logs_contract_test.go b/repotests/agentplugins_qoder_cycle_logs_contract_test.go new file mode 100644 index 000000000..904d9d80d --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_logs_contract_test.go @@ -0,0 +1,189 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "testing" + "time" +) + +func TestQoderCycleArgvAcceptsOnlyExactHeldOrOneSourceEmbeddedEntrypoint(t *testing.T) { + expected := make([]string, 0, 7) + expected = append(expected, "/held/qodercli", "plugins", "list", "--json", "--plugin-dir", "/held/view") + cases := []struct { + args []string + form string + }{ + {append([]string{}, expected...), "direct"}, + {append([]string{expected[0], "/$bunfs/root/qodercli.js"}, expected[1:]...), "compiled-embedded"}, + {nil, "refused"}, + {append([]string{"/foreign/qodercli", "/$bunfs/root/qodercli.js"}, expected[1:]...), "refused"}, + {append([]string{expected[0], "/private/build/entry.js"}, expected[1:]...), "refused"}, + {append([]string{expected[0], "/$bunfs/root/../entry.js"}, expected[1:]...), "refused"}, + {append([]string{expected[0], "/$bunfs/root/entry\n.js"}, expected[1:]...), "refused"}, + {append([]string{expected[0], "/$bunfs/root/entry.js", "foreign"}, expected[1:]...), "refused"}, + } + for i, tc := range cases { + if got := qoderCycleArgvForm(tc.args, expected); got != tc.form { + t.Fatal(i, got, tc.form) + } + } + if qoderCycleArgvForm(nil, nil) != "refused" || qoderCycleArgvForm(expected, append(expected, "foreign")) != "refused" { + t.Fatal("absent or changed argument authority") + } +} + +func qoderCycleLogInert(t *testing.T) (*qoderCycleLogs, []string) { + t.Helper() + root := filepath.Join(t.TempDir(), ".qoder") + if err := os.Mkdir(root, 0700); err != nil { + t.Fatal(err) + } + recipe := map[string]string{".": "directory:20000000700"} + logs, err := newQoderCycleLogs(context.Background(), root, recipe) + if err != nil { + t.Fatal(err) + } + recipe["PRIVATE_FOREIGN"] = "unknown" + if len(logs.original) != 1 || len(logs.expected) != 1 { + t.Fatal("original recipe aliased caller state") + } + return logs, []string{"/held/qodercli", "plugins", "list", "--json"} +} + +func qoderCycleLogAuthor(t *testing.T, l *qoderCycleLogs, args []string, number int) (time.Time, time.Time, string) { + t.Helper() + stamp := time.Date(2026, 10, 28, 12, 34, number, 789000000, time.UTC) + id := fmt.Sprintf("2026-10-28T12-34-%02d-789Z-a1b2c3-p%d", number, 12345+number) + root := filepath.Join(l.root, "logs", "runs", id) + if err := os.MkdirAll(root, 0755); err != nil { + t.Fatal(err) + } + body, _ := json.Marshal(map[string]any{"run_id": id, "started_at": stamp.Format(qoderLogTimeLayout), "pid": 12345 + number, "ppid": 12, "process_role": "main", "cli_version": "1.1.78", "node_version": "v24.0.0", "platform": "linux", "arch": "x64", "cwd": l.root, "project_id": "uap-qoder-private-project", "argv": args, "env_flags": map[string]any{"debug": false, "remote_worker": false, "ci": true}, "log_files": map[string]any{"process": "qodercli.log"}}) + projectNativeRuntimeWrite(t, filepath.Join(root, "manifest.json"), body, 0644) + projectNativeRuntimeWrite(t, filepath.Join(root, "qodercli.log"), []byte("uap-qoder-private-log-body\n"), 0644) + latest := filepath.Join(l.root, "logs", "latest") + if number > 1 { + if err := os.Remove(latest); err != nil { + t.Fatal(err) + } + } + if err := os.Symlink("runs/"+id, latest); err != nil { + t.Fatal(err) + } + return stamp.Add(-time.Millisecond), stamp.Add(time.Millisecond), id +} + +func TestQoderCycleLogLedgerDerivesFirstAndAccumulatedRunsWithoutBaselineAdoption(t *testing.T) { + l, expected := qoderCycleLogInert(t) + args := append([]string{expected[0], "/$bunfs/root/qodercli.js"}, expected[1:]...) + before, _ := json.Marshal(l.original) + for number := 1; number <= 2; number++ { + start, end, _ := qoderCycleLogAuthor(t, l, args, number) + transition, err := l.native(l.root, expected, start, end) + if err != nil { + t.Fatal(err) + } + if transition.Kind != "one-run" || transition.Form != "compiled-embedded" || transition.Run == nil || transition.Run.PIDProvenance != "manifest-only" || !transition.Run.ExactAddedRunClosure || !transition.Run.PreexistingRecordsStable || len(l.runs) != number { + t.Fatal("source-defined run transition differs") + } + raw, _ := json.Marshal(transition) + if bytes.Contains(raw, []byte("uap-qoder-private-")) || bytes.Contains(raw, []byte("$bunfs")) || len(raw) > 16<<10 { + t.Fatal("log/argv/private data escaped bounded projection") + } + if err := l.check(); err != nil { + t.Fatal(err) + } + } + after, _ := json.Marshal(l.original) + if !bytes.Equal(before, after) || len(l.expected) != 10 { + t.Fatal("original map renewed or accumulated closure differs") + } + path := filepath.Join(l.root, "logs", "runs", l.runs[0], "qodercli.log") + if err := os.WriteFile(path, []byte("changed private bytes"), 0644); err != nil { + t.Fatal(err) + } + if l.check() == nil { + t.Fatal("old admitted task run was renewed") + } +} + +func TestQoderCycleLogLedgerRefusesForeignOldAliasAndUnknownNativeChanges(t *testing.T) { + for _, fault := range []string{"extra", "cwd", "argv", "time", "manifest-link", "latest-alias", "mode"} { + t.Run(fault, func(t *testing.T) { + l, expected := qoderCycleLogInert(t) + args := append([]string{expected[0], "/$bunfs/root/qodercli.js"}, expected[1:]...) + start, end, id := qoderCycleLogAuthor(t, l, args, 1) + cwd := l.root + switch fault { + case "extra": + projectNativeRuntimeWrite(t, filepath.Join(l.root, "PRIVATE_FOREIGN"), []byte("private"), 0600) + case "cwd": + cwd = "/foreign" + case "argv": + expected = append(expected, "foreign") + case "time": + start = start.Add(time.Second) + end = end.Add(time.Second) + case "manifest-link": + path := filepath.Join(l.root, "logs", "runs", id, "manifest.json") + if err := os.Rename(path, path+".original"); err != nil { + t.Fatal(err) + } + if err := os.Symlink(path+".original", path); err != nil { + t.Fatal(err) + } + case "latest-alias": + path := filepath.Join(l.root, "logs", "latest") + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + if err := os.Symlink("runs/../runs/"+id, path); err != nil { + t.Fatal(err) + } + case "mode": + if err := os.Chmod(filepath.Join(l.root, "logs", "runs", id, "qodercli.log"), 0600); err != nil { + t.Fatal(err) + } + } + old := map[string]string{} + for key, value := range l.expected { + old[key] = value + } + if _, err := l.native(cwd, expected, start, end); err == nil { + t.Fatal("foreign/unmeasured log transition accepted") + } + if !reflect.DeepEqual(old, l.expected) || len(l.runs) != 0 { + t.Fatal("refused native inventory became owned") + } + }) + } + l, _ := qoderCycleLogInert(t) + projectNativeRuntimeWrite(t, filepath.Join(l.root, "PRIVATE"), []byte("unchanged-only must refuse"), 0600) + if _, err := l.unchanged(); err == nil { + t.Fatal("public call may not acquire hypothetical logger allowance") + } + if _, err := newQoderCycleLogs(context.Background(), l.root, map[string]string{".": "directory:20000000700"}); err == nil { + t.Fatal("initial foreign profile record adopted") + } +} + +func TestQoderCyclePrivacyChecksRawBeforeBase64AndEscapedStrings(t *testing.T) { + for _, raw := range [][]byte{[]byte("uap-qoder-private-canary"), []byte(`{"safe":"uap-qoder-\u0070rivate-canary"}`), {0xff}, bytes.Repeat([]byte{'x'}, (1<<20)+1)} { + if !qoderCyclePrivate(raw) { + t.Fatal("private/raw boundary widened") + } + } + for _, raw := range [][]byte{nil, []byte("ordinary fixture\n"), []byte(`{"safe":"fixture"}`)} { + if qoderCyclePrivate(raw) { + t.Fatal("safe raw refused") + } + } +} diff --git a/repotests/agentplugins_qoder_cycle_logs_test.go b/repotests/agentplugins_qoder_cycle_logs_test.go new file mode 100644 index 000000000..6da772f27 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_logs_test.go @@ -0,0 +1,245 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "syscall" + "time" +) + +type qoderCycleLogs struct { + ctx context.Context + root string + original, expected map[string]string + held map[string]os.FileInfo + runs []string +} + +type qoderCycleLogTransition struct { + Kind string `json:"kind"` + Before string `json:"before_sha256"` + After string `json:"after_sha256"` + Form string `json:"argv_form,omitempty"` + LatestRecord string `json:"latest_record,omitempty"` + Run *qoderLogRunData `json:"run,omitempty"` + Delta *qoderProfileDelta `json:"delta,omitempty"` + LogBytes int64 `json:"log_bytes,omitempty"` + LogSHA256 string `json:"log_sha256,omitempty"` +} + +func qoderCycleInventoryHash(value map[string]string) string { + raw, _ := json.Marshal(value) + return qoderDeltaHash(string(raw)) +} + +func newQoderCycleLogs(ctx context.Context, root string, recipe map[string]string) (*qoderCycleLogs, error) { + actual, err := projectNativeCLIQoderProfile(root) + if err != nil || ctx == nil || ctx.Err() != nil || !reflect.DeepEqual(actual, recipe) { + return nil, fmt.Errorf("Qoder original profile differs from pre-authored closed recipe") + } + if _, err := os.Lstat(filepath.Join(root, "logs")); !os.IsNotExist(err) { + return nil, fmt.Errorf("Qoder logs must be absent before any native child") + } + l := &qoderCycleLogs{ctx: ctx, root: root, original: map[string]string{}, expected: map[string]string{}, held: map[string]os.FileInfo{}, runs: []string{}} + for path, record := range recipe { + l.original[path], l.expected[path] = record, record + } + return l, nil +} + +func (l *qoderCycleLogs) check() error { + if l.ctx.Err() != nil { + return fmt.Errorf("Qoder log authority deadline") + } + actual, err := projectNativeCLIQoderProfile(l.root) + if err != nil || !reflect.DeepEqual(actual, l.expected) { + return fmt.Errorf("Qoder original/admitted log inventory changed") + } + for path, before := range l.held { + after, err := os.Lstat(filepath.Join(l.root, path)) + if err != nil || !os.SameFile(before, after) || before.Mode() != after.Mode() { + return fmt.Errorf("Qoder admitted old log identity changed") + } + a, b := before.Sys().(*syscall.Stat_t), after.Sys().(*syscall.Stat_t) + if a.Uid != b.Uid || a.Gid != b.Gid || before.Mode().IsRegular() && b.Nlink != 1 { + return fmt.Errorf("Qoder admitted old log ownership changed") + } + } + return nil +} + +// The embedded path stays private; the normalized argument authority is exact. +func qoderCycleArgvForm(actual, expected []string) string { + if len(expected) == 0 { + return "refused" + } + if reflect.DeepEqual(actual, expected) { + return "direct" + } + if len(actual) != len(expected)+1 || actual[0] != expected[0] || !reflect.DeepEqual(actual[2:], expected[1:]) { + return "refused" + } + embedded := actual[1] + if len(embedded) > 4096 || !strings.HasPrefix(embedded, "/$bunfs/") || !filepath.IsAbs(embedded) || filepath.Clean(embedded) != embedded { + return "refused" + } + for _, char := range embedded { + if char < 32 || char == 127 { + return "refused" + } + } + return "compiled-embedded" +} + +func (l *qoderCycleLogs) unchanged() (qoderCycleLogTransition, error) { + before := qoderCycleInventoryHash(l.expected) + if err := l.check(); err != nil { + return qoderCycleLogTransition{Kind: "refused", Before: before}, err + } + return qoderCycleLogTransition{Kind: "unchanged", Before: before, After: before}, nil +} + +func (l *qoderCycleLogs) native(cwd string, argv []string, start, end time.Time) (qoderCycleLogTransition, error) { + transition := qoderCycleLogTransition{Kind: "refused", Before: qoderCycleInventoryHash(l.expected)} + actual, err := projectNativeCLIQoderProfile(l.root) + if err != nil || l.ctx.Err() != nil { + return transition, fmt.Errorf("Qoder native complete log inventory refused") + } + info, err := os.Lstat(filepath.Join(l.root, "logs", "latest")) + if err != nil { + return transition, err + } + link, err := qoderReadOriginalLogLink(l.root, info) + if err != nil || link.Record != actual["logs/latest"] || link.Target.Mode() != os.ModeDir|0755 { + return transition, fmt.Errorf("Qoder latest does not join the held source-defined run") + } + parts := strings.Split(link.TargetRel, "/") + if len(parts) != 3 || parts[0] != "logs" || parts[1] != "runs" { + return transition, fmt.Errorf("Qoder source run path") + } + runID := parts[2] + for _, old := range l.runs { + if old == runID { + return transition, fmt.Errorf("Qoder log run identity reused") + } + } + for path := range l.expected { + if path == link.TargetRel || strings.HasPrefix(path, link.TargetRel+"/") { + return transition, fmt.Errorf("Qoder source run prefix preexists") + } + } + manifest := filepath.Join(link.TargetRel, "manifest.json") + body, err := qoderLogRunRead(l.root, link, actual[manifest]) + if err != nil { + return transition, err + } + data, valid := qoderLogRunManifest(body, runID, cwd, argv) + if !valid || !data.RunIDMatchesManifest || !data.CWDMatches { + return transition, fmt.Errorf("Qoder complete manifest source/cwd/run identity") + } + stamp, err := time.Parse(qoderLogTimeLayout, data.StartedAt) + if err != nil || end.Before(start) || end.Sub(start) > 35*time.Second || stamp.Before(start.Truncate(time.Millisecond)) || stamp.After(end) { + return transition, fmt.Errorf("Qoder run timestamp does not belong to this command interval") + } + value, _ := projectNativeRuntimeJSON(body) + packet := value.(map[string]any)["argv"].([]any) + args := make([]string, len(packet)) + for i, arg := range packet { + args[i] = arg.(string) + } + transition.Form = qoderCycleArgvForm(args, argv) + transition.Run = data + if transition.Form == "refused" { + return transition, fmt.Errorf("Qoder manifest has an unmeasured private argv form") + } + logPath := filepath.Join(link.TargetRel, "qodercli.log") + log, valid := qoderDeltaValue(actual[logPath], true, false) + if !valid || log.Kind != "file" || log.Mode != "644" || log.Bytes == nil || *log.Bytes <= 0 { + return transition, fmt.Errorf("Qoder source log ordinary mode") + } + if err := qoderCycleLogLeaf(l.root, logPath, actual[logPath]); err != nil { + return transition, err + } + expected := map[string]string{} + for path, record := range l.expected { + expected[path] = record + } + first := len(l.runs) == 0 + if first { + expected["logs"], expected["logs/runs"] = "directory:20000000755", "directory:20000000755" + } + expected[link.TargetRel] = "directory:20000000755" + expected[manifest] = fmt.Sprintf("file:644:%d:%s", data.ManifestBytes, data.ManifestSHA256) + expected[logPath] = actual[logPath] + expected["logs/latest"] = link.Record + if !reflect.DeepEqual(actual, expected) { + return transition, fmt.Errorf("Qoder native changed a foreign/preexisting/unrecorded profile entry") + } + again, err := qoderReadOriginalLogLink(l.root, info) + if err != nil || again.Record != link.Record || !projectNativeRuntimeStableStat(link.Profile.Sys(), again.Profile.Sys()) || !projectNativeRuntimeStableStat(link.Logs.Sys(), again.Logs.Sys()) { + return transition, fmt.Errorf("Qoder held log link/profile changed during classification") + } + delta := qoderOriginalProfileDelta(l.expected, actual) + data.ExactAddedRunClosure, data.PreexistingRecordsStable = true, true + transition.Kind, transition.After, transition.Delta = "one-run", qoderCycleInventoryHash(expected), &delta + transition.LogBytes, transition.LogSHA256 = *log.Bytes, log.ContentSHA256 + transition.LatestRecord = link.Record + // Only this independently validated recipe is added, never an observed map. + l.expected = expected + l.runs = append(l.runs, runID) + held := []string{link.TargetRel, manifest, logPath} + if first { + held = append(held, "logs", "logs/runs") + } + for _, path := range held { + record, err := os.Lstat(filepath.Join(l.root, path)) + if err != nil { + return transition, err + } + l.held[path] = record + } + if err := l.check(); err != nil { + return transition, err + } + return transition, nil +} + +func qoderCycleLogLeaf(root, relative, expected string) (err error) { + directory, err := qoderLogOpenRoot(filepath.Join(root, filepath.Dir(relative))) + if err != nil { + return err + } + defer func() { err = errors.Join(err, directory.Close()) }() + file, err := qoderLogOpenAt(directory, filepath.Base(relative), false) + if err != nil { + return err + } + defer func() { err = errors.Join(err, file.Close()) }() + info, err := file.Stat() + if err != nil || !qoderLogOwned(info, false) || info.Mode() != 0644 { + return fmt.Errorf("Qoder log held ordinary ownership") + } + raw, err := projectNativeRuntimeRead(filepath.Join(root, relative), 64<<20) + after, statErr := file.Stat() + if err != nil || statErr != nil || !projectNativeRuntimeStableStat(info.Sys(), after.Sys()) || expected != fmt.Sprintf("file:644:%d:%s", len(raw), qoderDeltaHash(string(raw))) { + return fmt.Errorf("Qoder log leaf does not join the held inventory") + } + linked, err := qoderLogOpenAt(directory, filepath.Base(relative), false) + if err != nil { + return err + } + defer func() { err = errors.Join(err, linked.Close()) }() + current, err := linked.Stat() + if err != nil || !projectNativeRuntimeStableStat(info.Sys(), current.Sys()) { + return fmt.Errorf("Qoder log linked identity changed") + } + return nil +} diff --git a/repotests/agentplugins_qoder_cycle_proof_test.go b/repotests/agentplugins_qoder_cycle_proof_test.go new file mode 100644 index 000000000..38d9b0268 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_proof_test.go @@ -0,0 +1,227 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "regexp" + "strings" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" +) + +var qoderCycleID = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`) + +type qoderCycleDelivery struct { + Active string `json:"active"` + View string `json:"view"` + Files map[string]string `json:"files"` + Manifest *qoderCycleBytes `json:"manifest"` + Skill *qoderCycleBytes `json:"skill"` +} + +type qoderCycleData struct { + Allocated bool `json:"allocated"` + Path string `json:"path"` + Receipt *domain.DataReceipt `json:"receipt"` + Owner *qoderCycleBytes `json:"owner"` + Files map[string]string `json:"files"` +} + +func qoderCycleAbsent(path string) error { + if !filepath.IsAbs(path) || filepath.Clean(path) != path { + return fmt.Errorf("Qoder absence path is noncanonical") + } + if _, err := os.Lstat(path); !os.IsNotExist(err) { + return fmt.Errorf("Qoder exact owned absence required") + } + for parent, depth := filepath.Dir(path), 0; ; parent, depth = filepath.Dir(parent), depth+1 { + if depth > 128 { + return fmt.Errorf("Qoder absent ancestor bound") + } + info, err := os.Lstat(parent) + if err != nil && !os.IsNotExist(err) { + return err + } + if err == nil && (!info.IsDir() || info.Mode()&os.ModeSymlink != 0) { + return fmt.Errorf("Qoder absent path has alias/non-directory parent") + } + if filepath.Dir(parent) == parent { + return nil + } + } +} + +func qoderCycleExpectedSource(i domain.Installation, r *qoderCycleRecipe) error { + source := domain.SourceBinding{SourceBindingID: "src_" + qoderDeltaHash(r.Source + "\x00\x00")[:24], RequestedSource: r.Source, CanonicalSource: r.Source, TreeDigest: r.Digest} + pkg := domain.PackageBinding{LoaderKind: domain.LoaderKindAgentPlugins, FormatID: domain.FormatIDAgentPluginsV1, SchemaURI: domain.PluginSchemaV1, DeclaredName: qoderCycleName, Version: "1.0.0", ManifestDigest: "sha256:" + qoderDeltaHash(string(r.Authored["plugin.json"])), Inventory: domain.ComponentInventory{Skills: []string{qoderCycleName}}} + want := domain.Installation{InstallationID: i.InstallationID, DeclaredName: qoderCycleName, Source: source, Package: pkg, OriginMode: domain.OriginModeDirect, OperationGroupID: i.OperationGroupID, DataReceipts: i.DataReceipts, DataRetained: i.DataRetained, Clients: i.Clients, CreatedAt: i.CreatedAt, UpdatedAt: i.UpdatedAt} + if !qoderCycleID.MatchString(i.InstallationID) || !qoderCycleOperation.MatchString(i.OperationGroupID) || !qoderCycleTimes(i.CreatedAt, i.UpdatedAt) || !reflect.DeepEqual(i, want) || r.Installation.CreatedAt != "" && i.CreatedAt != r.Installation.CreatedAt { + return fmt.Errorf("Qoder exact independently authored installation/source/package differs") + } + return nil +} + +func (c *qoderSkillsCycle) binding(t *testing.T, r *qoderCycleRecipe, i domain.Installation) { + t.Helper() + if len(i.Clients) != 1 || i.DataRetained || len(i.DataReceipts) != 0 { + t.Fatal("Qoder skills-only exact installed client/no-data state required") + } + physical := qoderCycleName + "-" + qoderDeltaHash(i.InstallationID)[:12] + base := filepath.Join(r.Project, ".uap", "managed", "clients", "qoder") + active := filepath.Join(base, physical) + id := "client_" + qoderDeltaHash(strings.Join([]string{i.InstallationID, "qoder", "project", active}, "\x00"))[:24] + b, ok := i.Clients[id] + revision := &domain.ClientPackageRevision{Version: "1.0.0", TreeDigest: r.Digest, ManifestDigest: "sha256:" + qoderDeltaHash(string(r.Authored["plugin.json"]))} + want := domain.ClientBinding{ClientBindingID: id, ClientID: "qoder", Scope: "project", TargetLocator: active, PhysicalArtifact: physical, NativeProfileRoot: base, Materialization: domain.MaterializationMaterialized, Activation: domain.ActivationPrepared, Verification: domain.VerificationInstalled, Authentication: domain.AuthenticationNotChecked, Policy: domain.PolicyAllowed, AffectedSurfaces: []string{"qoder"}, PackageRevision: revision, NativeObjects: b.NativeObjects, Receipts: b.Receipts, UpdatedAt: b.UpdatedAt} + if !ok || !reflect.DeepEqual(b, want) || !qoderCycleTimes(i.CreatedAt, b.UpdatedAt) || len(b.NativeObjects) != 1 { + t.Fatal("Qoder derived Prepared binding differs") + } + if r.Binding.ClientBindingID != "" && (r.Binding.ClientBindingID != id || r.Binding.PhysicalArtifact != physical || r.Binding.TargetLocator != active) { + t.Fatal("Qoder lifecycle changed stable binding/physical authority") + } + for key, other := range c.recipes { + if key != r.Key && other.Installation.InstallationID != "" && (other.Installation.InstallationID == i.InstallationID || other.Binding.ClientBindingID == id || other.Binding.PhysicalArtifact == physical) { + t.Fatal("Qoder same-name projects share logical/physical authority") + } + } + r.Binding, r.Active, r.View = b, active, filepath.Join(active, ".agentplugins-qoder-native") +} + +func (c *qoderSkillsCycle) proof(t *testing.T, r *qoderCycleRecipe, stage string) { + t.Helper() + raw := mustQoderCycleRead(t, c.statePath(r)) + state, err := clineCycleState(raw) + if err != nil || qoderCycleRawState(raw) != nil || len(state.Installations) != 1 { + t.Fatal("Qoder exact single current-schema state required", err) + } + i := state.Installations[0] + if err := qoderCycleExpectedSource(i, r); err != nil { + t.Fatal(err) + } + if r.Installation.InstallationID != "" && i.InstallationID != r.Installation.InstallationID { + t.Fatal("Qoder installation identity changed") + } + prior := r.Binding + if r.Removed { + if len(i.Clients) != 0 || !i.DataRetained { + t.Fatal("Qoder default removal did not retain data-only installation") + } + } else { + c.binding(t, r, i) + } + if err := c.receipts(r, i, prior, state.TransactionReceipts, stage); err != nil { + t.Fatal(err) + } + delivery := c.delivery(t, r) + data := c.data(t, r, i) + r.Installation = i + c.append(t, qoderCycleAggregates[2], stage, map[string]any{"scope": r.Key, "revision": r.Revision, "removed": r.Removed, "source_digest": r.Digest, "state": qoderCycleStream(t, raw), "binding": r.Binding, "delivery": delivery, "data": data}) + c.f.protected[c.statePath(r)] = qoderDeltaHash(string(raw)) + c.check(t) +} + +func qoderCycleNativeManifest() []byte { + raw, _ := json.MarshalIndent(map[string]any{"name": qoderCycleName, "version": "1.0.0", "description": qoderCycleDescription, "skills": "./skills/"}, "", " ") + return append(raw, '\n') +} + +func (c *qoderSkillsCycle) delivery(t *testing.T, r *qoderCycleRecipe) qoderCycleDelivery { + t.Helper() + out := qoderCycleDelivery{Active: r.Active, View: r.View} + if r.Removed || r.Missing { + if err := qoderCycleAbsent(r.Active); err != nil { + t.Fatal(err) + } + return out + } + files := map[string][]byte{} + for name, body := range r.Authored { + files[name] = body + } + manifest := qoderCycleNativeManifest() + skill := r.Authored["skills/"+qoderCycleName+"/SKILL.md"] + files[".agentplugins-qoder-native/.qoder-plugin/plugin.json"] = manifest + files[".agentplugins-qoder-native/skills/"+qoderCycleName+"/SKILL.md"] = skill + want := map[string]string{".": "directory:20000000755"} + for relative, body := range files { + want[relative] = "file:644:" + qoderDeltaHash(string(body)) + for parent := filepath.Dir(relative); parent != "."; parent = filepath.Dir(parent) { + want[parent] = "directory:20000000755" + } + actual := mustQoderCycleRead(t, filepath.Join(r.Active, relative)) + if !bytes.Equal(actual, body) { + t.Fatal("Qoder exact authored/projected package leaf differs") + } + } + actual, err := clineAttemptTree(r.Active) + if err != nil || !reflect.DeepEqual(actual, want) { + t.Fatal("Qoder full ordinary selected payload membership/mode differs", err) + } + digest, err := projectNativeCLIPackageDigest(c.f.ctx, r.Active) + if err != nil { + t.Fatal(err) + } + expected := domain.NativeObjectOwnership{ObjectID: "package:qoder:" + r.Binding.PhysicalArtifact, Kind: "managed_package_directory", LogicalName: qoderCycleName, Path: r.Active, ManagedDigest: digest, ProtectionClass: "managed"} + if !reflect.DeepEqual(r.Binding.NativeObjects, []domain.NativeObjectOwnership{expected}) { + t.Fatal("Qoder sole package receipt does not join exact projected text payload") + } + out.Files = actual + manifestStream, skillStream := qoderCycleStream(t, manifest), qoderCycleStream(t, skill) + out.Manifest, out.Skill = &manifestStream, &skillStream + c.trees[r.Active] = actual + return out +} + +func (c *qoderSkillsCycle) data(t *testing.T, r *qoderCycleRecipe, i domain.Installation) qoderCycleData { + t.Helper() + physical := qoderCycleName + "-" + qoderDeltaHash(i.InstallationID)[:12] + path := filepath.Join(r.Project, ".uap", "plugin-data", physical) + out := qoderCycleData{Path: path} + if !r.Removed { + if len(i.DataReceipts) != 0 || r.Binding.DataReceiptID != "" || qoderCycleAbsent(path) != nil { + t.Fatal("Qoder skills-only installed package acquired unproved data") + } + return out + } + id := "data_" + qoderDeltaHash(strings.Join([]string{i.InstallationID, physical, "project"}, "\x00"))[:24] + receipt := i.DataReceipts[id] + if len(i.DataReceipts) != 1 || receipt.DataReceiptID != id || receipt.PhysicalBackend != physical || receipt.Scope != "project" || receipt.Locator != path || receipt.State != domain.DataReceiptOwned || !qoderCycleTimes(receipt.CreatedAt, receipt.UpdatedAt) || receipt.OwnershipDigest != "sha256:"+qoderDeltaHash("agentplugins-plugin-data-v1\x00"+id+"\x00"+path) { + t.Fatal("Qoder actual default retained data authority differs") + } + if len(r.Installation.DataReceipts) != 0 && !reflect.DeepEqual(i.DataReceipts, r.Installation.DataReceipts) { + t.Fatal("Qoder retained receipt changed") + } + ownerPath := filepath.Join(path, ".agentplugins-data-owner.json") + owner := mustQoderCycleRead(t, ownerPath) + if _, err := projectNativeRuntimeJSON(owner); err != nil { + t.Fatal(err) + } + var actual domain.DataReceipt + decoder := json.NewDecoder(bytes.NewReader(owner)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&actual); err != nil || !reflect.DeepEqual(actual, receipt) { + t.Fatal("Qoder actual owner marker differs from retained receipt", err) + } + want := map[string]string{".": "directory:20000000700", ".agentplugins-data-owner.json": "file:600:" + qoderDeltaHash(string(owner))} + inventory, err := clineAttemptTree(path) + if err != nil || !reflect.DeepEqual(inventory, want) { + t.Fatal("Qoder retained data contains unexpected membership/mode/bytes", err) + } + stream := qoderCycleStream(t, owner) + out.Allocated, out.Receipt, out.Owner, out.Files = true, &receipt, &stream, inventory + c.trees[path] = inventory + c.f.protected[ownerPath] = qoderDeltaHash(string(owner)) + for _, held := range []string{path, ownerPath} { + if _, exists := c.original[held]; !exists { + c.hold(t, held) + } + } + return out +} diff --git a/repotests/agentplugins_qoder_cycle_public_proof_test.go b/repotests/agentplugins_qoder_cycle_public_proof_test.go new file mode 100644 index 000000000..bd9ce2f73 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_public_proof_test.go @@ -0,0 +1,121 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "encoding/json" + "fmt" + "reflect" +) + +func qoderCyclePublicProof(raw []byte, stage, verb string, changed bool, r *qoderCycleRecipe) (string, error) { + value, err := projectNativeRuntimeJSON(raw) + row, ok := value.(map[string]any) + if err != nil || !ok || projectNativeRuntimeKeys(row, "schema_version command result data", "") != nil || row["schema_version"] != json.Number("1") || row["command"] != verb || row["result"] != "success" { + return "", fmt.Errorf("Qoder public proof envelope differs") + } + data, ok := row["data"].(map[string]any) + if ok && stage == "repeat-add-a" { + if changed || projectNativeRuntimeKeys(data, "plugin version source tree_digest manifest_digest dry_run result", "security next_action") != nil { + return "", fmt.Errorf("Qoder exact singleton repeat-add fields differ") + } + return "", qoderCyclePublicOutput(data, r, false, "direct local source") + } + if !ok || projectNativeRuntimeKeys(data, "batch status succeeded failed plugin dry_run targets", "operation_id action_required version source revision tree_digest manifest_digest security environment plugin_data_preserved data_retained retained_data retained_data_action") != nil { + return "", fmt.Errorf("Qoder public group fields differ") + } + status := "completed" + if verb == "remove" { + status = "data_retained" + } + for field, expected := range map[string]any{"batch": true, "status": status, "succeeded": json.Number("1"), "failed": json.Number("0"), "plugin": qoderCycleName, "dry_run": false} { + if !reflect.DeepEqual(data[field], expected) { + return "", fmt.Errorf("Qoder public group outcome differs") + } + } + targets, ok := data["targets"].([]any) + if !ok || len(targets) != 1 { + return "", fmt.Errorf("Qoder public single target required") + } + target, ok := targets[0].(map[string]any) + required := "target status output" + if verb == "update" { + required += " selected" + } + if !ok || projectNativeRuntimeKeys(target, required, "next_action") != nil || target["target"] != "qoder" || target["status"] != "external_completed" || verb == "update" && target["selected"] != true { + return "", fmt.Errorf("Qoder public selected target differs") + } + output, ok := target["output"].(map[string]any) + if !ok || projectNativeRuntimeKeys(output, "plugin version source tree_digest manifest_digest dry_run result", "operation_id revision security next_action") != nil { + return "", fmt.Errorf("Qoder public target output fields differ") + } + i := r.Installation + source := "direct local source" + if verb == "repair" || verb == "remove" { + source = "local" + } + if err := qoderCyclePublicOutput(output, r, changed, source); err != nil { + return "", err + } + group, ok := data["operation_id"].(string) + if !ok || !qoderCycleOperation.MatchString(group) || output["operation_id"] != group || changed && group != i.OperationGroupID { + return "", fmt.Errorf("Qoder public operation/group differs from current proof") + } + if err := qoderCyclePublicIdentities(data, qoderCyclePublicIdentityFields(r, changed), changed); err != nil { + return "", err + } + if verb == "remove" { + var retained any + for _, receipt := range i.DataReceipts { + retained = []any{map[string]any{"data_receipt_id": receipt.DataReceiptID, "physical_backend_id": receipt.PhysicalBackend, "scope": receipt.Scope, "state": string(receipt.State)}} + } + if data["plugin_data_preserved"] != true || data["data_retained"] != true || !reflect.DeepEqual(data["retained_data"], retained) { + return "", fmt.Errorf("Qoder public retained data differs from actual owner allocation") + } + } + return group, nil +} + +func qoderCyclePublicOutput(output map[string]any, r *qoderCycleRecipe, changed bool, source string) error { + for field, expected := range map[string]any{"plugin": qoderCycleName, "version": "1.0.0", "source": source, "tree_digest": r.Digest, "manifest_digest": r.Installation.Package.ManifestDigest, "dry_run": false} { + if !reflect.DeepEqual(output[field], expected) { + return fmt.Errorf("Qoder public source/revision differs from current proof") + } + } + result, ok := output["result"].(map[string]any) + if !ok || result["installation_id"] != r.Installation.InstallationID || result["requires_confirmation"] != false || result["mutated"] != changed { + return fmt.Errorf("Qoder public mutation differs from current proof") + } + return qoderCyclePublicIdentities(output, qoderCyclePublicIdentityFields(r, changed), changed) +} + +func qoderCyclePublicIdentityFields(r *qoderCycleRecipe, changed bool) map[string]any { + i, b := r.Installation, r.Binding + return map[string]any{"tree_digest": r.Digest, "manifest_digest": i.Package.ManifestDigest, "installation_id": i.InstallationID, "client_binding_id": b.ClientBindingID, "physical_artifact_id": b.PhysicalArtifact, "target_locator": b.TargetLocator, "client_id": "qoder", "target": "qoder", "scope": "project", "mutated": changed} +} + +func qoderCyclePublicIdentities(value any, checks map[string]any, changed bool) error { + switch item := value.(type) { + case map[string]any: + for field, child := range item { + if expected, exists := checks[field]; exists && !reflect.DeepEqual(child, expected) { + return fmt.Errorf("Qoder recursive public identity differs from current proof") + } + if field == "no_change" { + if flag, ok := child.(bool); !ok || changed && flag { + return fmt.Errorf("Qoder public no-change claim contradicts mutation") + } + } + if err := qoderCyclePublicIdentities(child, checks, changed); err != nil { + return err + } + } + case []any: + for _, child := range item { + if err := qoderCyclePublicIdentities(child, checks, changed); err != nil { + return err + } + } + } + return nil +} diff --git a/repotests/agentplugins_qoder_cycle_receipt_contract_test.go b/repotests/agentplugins_qoder_cycle_receipt_contract_test.go new file mode 100644 index 000000000..60a0b8127 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_receipt_contract_test.go @@ -0,0 +1,45 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "path/filepath" + "testing" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" +) + +func TestQoderCycleReceiptRefusesForeignAuthorityPathsAndDigests(t *testing.T) { + c := qoderCycleInert(t) + qoderCycleStageInert(t, c, "a") + b := c.recipes["a"].Binding + group := "op-0123456789abcdef0123456789abcdef" + operation := group + "-001" + suffix := qoderDeltaHash(operation)[:16] + digest := b.NativeObjects[0].ManagedDigest + valid := domain.MutationReceipt{OperationID: operation, OperationGroupID: group, Sequence: 1, MutationType: "directory_swap", ClientBindingID: b.ClientBindingID, ActivePath: b.TargetLocator, StagingPath: filepath.Join(b.NativeProfileRoot, ".agentplugins-staging-"+suffix), BackupPath: filepath.Join(b.NativeProfileRoot, ".agentplugins-backup-"+suffix), AfterDigest: digest, Phase: "committed"} + if err := qoderCycleReceipt(valid, b, 1, "directory_swap", "", digest); err != nil { + t.Fatal(err) + } + for _, mutate := range []func(*domain.MutationReceipt){ + func(r *domain.MutationReceipt) { r.ActivePath = c.f.other }, + func(r *domain.MutationReceipt) { r.StagingPath = filepath.Join(c.f.base.Home, "foreign") }, + func(r *domain.MutationReceipt) { r.BackupPath += "-other" }, + func(r *domain.MutationReceipt) { r.DataReceiptID = "foreign" }, + func(r *domain.MutationReceipt) { + r.ProfileOwners = []domain.PhysicalProfileOwner{{Namespace: "foreign"}} + }, + func(r *domain.MutationReceipt) { r.DirectoryProof = []byte(`{}`) }, + func(r *domain.MutationReceipt) { r.EnvironmentAuthority = &domain.EnvironmentAuthority{} }, + func(r *domain.MutationReceipt) { r.BeforeDigest = digest }, + func(r *domain.MutationReceipt) { r.AfterDigest = "sha256:foreign" }, + func(r *domain.MutationReceipt) { r.Sequence++ }, + func(r *domain.MutationReceipt) { r.OperationID = group + "-002" }, + } { + wrong := valid + mutate(&wrong) + if qoderCycleReceipt(wrong, b, 1, "directory_swap", "", digest) == nil { + t.Fatal("foreign/extra receipt authority was admitted") + } + } +} diff --git a/repotests/agentplugins_qoder_cycle_receipt_test.go b/repotests/agentplugins_qoder_cycle_receipt_test.go new file mode 100644 index 000000000..f1c4869b4 --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_receipt_test.go @@ -0,0 +1,167 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "context" + "fmt" + "path/filepath" + "reflect" + "regexp" + "time" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/packagedigest" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" +) + +var qoderCycleOperation = regexp.MustCompile(`^op-[0-9a-f]{32}$`) + +func qoderCycleRawState(raw []byte) error { + value, err := projectNativeRuntimeJSON(raw) + packet, ok := value.(map[string]any) + if err != nil || !ok || projectNativeRuntimeKeys(packet, "schema_version installations", "transaction_receipts") != nil { + return fmt.Errorf("Qoder closed state fields differ") + } + rows, ok := packet["installations"].([]any) + if !ok || len(rows) != 1 { + return fmt.Errorf("Qoder closed installation count") + } + i, ok := rows[0].(map[string]any) + if !ok || projectNativeRuntimeKeys(i, "installation_id declared_name source package clients origin_mode operation_group_id created_at updated_at", "data_receipts data_retained") != nil { + return fmt.Errorf("Qoder closed installation authority fields differ") + } + for field, keys := range map[string]string{"source": "source_binding_id requested_source canonical_source resolved_revision tree_digest", "package": "loader_kind format_id schema_uri declared_name version manifest_digest inventory"} { + object, ok := i[field].(map[string]any) + if !ok || projectNativeRuntimeKeys(object, keys, "") != nil { + return fmt.Errorf("Qoder exact source/package field closure differs") + } + } + pkg := i["package"].(map[string]any) + inventory, ok := pkg["inventory"].(map[string]any) + if !ok || projectNativeRuntimeKeys(inventory, "mcp_present mcp_enabled skills", "") != nil { + return fmt.Errorf("Qoder skills-only inventory field closure differs") + } + bindings, ok := i["clients"].(map[string]any) + if !ok { + return fmt.Errorf("Qoder exact clients object required") + } + for _, value := range bindings { + b, ok := value.(map[string]any) + if !ok || projectNativeRuntimeKeys(b, "client_binding_id client_id scope target_locator physical_artifact_id materialization activation authentication policy verification package_revision affected_surfaces native_profile_root native_objects receipts updated_at", "") != nil { + return fmt.Errorf("Qoder closed binding authority fields differ") + } + if err := qoderCycleRawReceipts(b["receipts"]); err != nil { + return err + } + } + if rows, exists := packet["transaction_receipts"]; exists { + return qoderCycleRawReceipts(rows) + } + return nil +} + +func qoderCycleRawReceipts(value any) error { + rows, ok := value.([]any) + if !ok { + return fmt.Errorf("Qoder receipt array required") + } + for _, value := range rows { + row, ok := value.(map[string]any) + if !ok || projectNativeRuntimeKeys(row, "operation_id operation_group_id sequence mutation_type client_binding_id active_path backup_path phase", "staging_path before_digest after_digest") != nil { + return fmt.Errorf("Qoder closed receipt authority fields differ") + } + } + return nil +} + +func qoderCycleTimes(created, updated string) bool { + a, first := time.Parse(time.RFC3339Nano, created) + b, second := time.Parse(time.RFC3339Nano, updated) + return first == nil && second == nil && !b.Before(a) +} + +func qoderCycleExpectedDigest(ctx context.Context, key, revision, nonce string) (string, error) { + files := qoderCycleAuthored(key, revision, nonce) + files[".agentplugins-qoder-native/.qoder-plugin/plugin.json"] = qoderCycleNativeManifest() + files[".agentplugins-qoder-native/skills/"+qoderCycleName+"/SKILL.md"] = files["skills/"+qoderCycleName+"/SKILL.md"] + dirs := map[string]bool{} + for path := range files { + for parent := filepath.Dir(path); parent != "."; parent = filepath.Dir(parent) { + dirs[parent] = true + } + } + entries := make([]packagedigest.CapturedEntry, 0, len(files)+len(dirs)) + for path, body := range files { + entries = append(entries, packagedigest.CapturedEntry{Path: path, Kind: "file", Content: body}) + } + for path := range dirs { + entries = append(entries, packagedigest.CapturedEntry{Path: path, Kind: "directory"}) + } + return projectNativeRuntimeInstalledDigest(ctx, entries) +} + +func qoderCycleReceipt(row domain.MutationReceipt, b domain.ClientBinding, sequence int, kind, before, after string) error { + if !qoderCycleOperation.MatchString(row.OperationGroupID) || row.OperationID != row.OperationGroupID+"-001" { + return fmt.Errorf("Qoder exact receipt operation/group differs") + } + suffix := qoderDeltaHash(row.OperationID)[:16] + want := domain.MutationReceipt{OperationID: row.OperationID, OperationGroupID: row.OperationGroupID, Sequence: sequence, MutationType: kind, ClientBindingID: b.ClientBindingID, ActivePath: b.TargetLocator, BackupPath: filepath.Join(b.NativeProfileRoot, ".agentplugins-backup-"+suffix), BeforeDigest: before, AfterDigest: after, Phase: "committed"} + if kind == "directory_swap" { + want.StagingPath = filepath.Join(b.NativeProfileRoot, ".agentplugins-staging-"+suffix) + } + if !reflect.DeepEqual(row, want) { + return fmt.Errorf("Qoder exact receipt authority/path/sequence/digest differs") + } + return nil +} + +func (c *qoderSkillsCycle) receipts(r *qoderCycleRecipe, i domain.Installation, prior domain.ClientBinding, rows []domain.MutationReceipt, stage string) error { + count := 1 + if r.Key == "a" { + switch stage { + case "intact-repair-a", "before-source-revision-a": + count = 2 + case "update-a", "before-missing-a": + count = 3 + case "missing-repair-a", "remove-a", "repeat-remove-a": + count = 4 + } + } + b := r.Binding + if len(b.Receipts) != count || len(prior.Receipts) > count || len(prior.Receipts) > 0 && !reflect.DeepEqual(prior.Receipts, b.Receipts[:len(prior.Receipts)]) { + return fmt.Errorf("Qoder exact stage/prior receipt history differs") + } + one, err := qoderCycleExpectedDigest(c.f.ctx, r.Key, r.Key+"-one", r.Nonce) + if err != nil { + return err + } + two := one + if count > 2 { + two, err = qoderCycleExpectedDigest(c.f.ctx, r.Key, "a-two", r.Nonce) + if err != nil { + return err + } + } + before, after := []string{"", one, one, ""}, []string{one, one, two, two} + seen := map[string]bool{} + for index, row := range b.Receipts { + if seen[row.OperationID] || qoderCycleReceipt(row, b, index+1, "directory_swap", before[index], after[index]) != nil { + return fmt.Errorf("Qoder exact ordered swap history differs") + } + seen[row.OperationID] = true + } + if !r.Removed { + if len(rows) != 0 || i.OperationGroupID != b.Receipts[count-1].OperationGroupID { + return fmt.Errorf("Qoder active group/state transaction differs") + } + return nil + } + if len(rows) != 1 || rows[0].OperationGroupID != i.OperationGroupID || seen[rows[0].OperationID] { + return fmt.Errorf("Qoder exact sole removal transaction differs") + } + digest, err := qoderCycleExpectedDigest(c.f.ctx, r.Key, r.Revision, r.Nonce) + if err != nil { + return err + } + return qoderCycleReceipt(rows[0], b, count+1, "directory_remove", digest, "") +} diff --git a/repotests/agentplugins_qoder_cycle_stager_contract_test.go b/repotests/agentplugins_qoder_cycle_stager_contract_test.go new file mode 100644 index 000000000..52fe8d03f --- /dev/null +++ b/repotests/agentplugins_qoder_cycle_stager_contract_test.go @@ -0,0 +1,169 @@ +//go:build linux + +package pluginkitairepo_test + +import ( + "context" + "os" + "path/filepath" + "reflect" + "testing" + "time" + + "github.com/777genius/plugin-kit-ai/install/integrationctl/adapters/pathpolicy" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/loader" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/packagedigest" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/specregistry" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/adapters/statev2" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/clients/qoder" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/domain" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/planner" + "github.com/777genius/plugin-kit-ai/install/integrationctl/agentplugins/providers" +) + +// Only inert files and actual production planner/stager/adapter APIs. This +// helper never initializes hosted admission, runs an executable or qualifies. +func qoderCycleInert(t *testing.T) *qoderSkillsCycle { + t.Helper() + root := t.TempDir() + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + t.Cleanup(cancel) + spec, err := projectNativeCLISpecification("qoder") + if err != nil { + t.Fatal(err) + } + f := &projectNativeCLIAttempt{spec: spec, started: time.Now(), ctx: ctx, cancel: cancel, files: map[string][]byte{}, immutable: map[string]string{}, protected: map[string]string{}, used: map[string]bool{}, evidence: map[string]any{}, client: filepath.Join(root, "qodercli"), installer: filepath.Join(root, "installer"), git: filepath.Join(root, "git"), bin: filepath.Join(root, "native-bin")} + f.base = &nativeFixture{Root: root, Home: filepath.Join(root, "home"), Project: filepath.Join(root, "project"), Probe: filepath.Join(root, "probe"), Events: filepath.Join(root, "events.jsonl"), Nonce: "0123456789abcdef0123456789abcdef"} + f.other, f.outside = filepath.Join(root, "project-b"), filepath.Join(root, "outside") + for _, path := range []string{f.base.Home, filepath.Join(f.base.Project, ".git"), filepath.Join(f.other, ".git"), filepath.Join(f.outside, ".git"), f.bin} { + if err := os.MkdirAll(path, 0700); err != nil { + t.Fatal(err) + } + } + for _, path := range []string{f.client, f.installer, f.git, f.base.Probe} { + projectNativeRuntimeWrite(t, path, []byte("inert bytes, never executed\n"), 0700) + f.immutable[path] = nativeSHA(t, path) + } + if err := os.Symlink(f.client, filepath.Join(f.bin, "qoder")); err != nil { + t.Fatal(err) + } + settings := filepath.Join(f.base.Home, ".qoder", "settings.json") + _, body := projectNativeCLIUpdater("qoder") + projectNativeRuntimeWrite(t, settings, body, 0600) + f.protected[settings] = nativeSHA(t, settings) + f.qoderMetadata, err = qoderAuthorOriginalMetadata(filepath.Dir(settings), f.protected) + if err != nil { + t.Fatal(err) + } + f.qoderTelemetry, err = qoderAuthorEmptyTelemetry(ctx, filepath.Dir(settings)) + if err != nil { + t.Fatal(err) + } + c := newQoderSkillsCycle(f) + c.prepare(t) + return c +} + +func qoderCycleStageInert(t *testing.T, c *qoderSkillsCycle, key string) { + t.Helper() + r := c.recipes[key] + snapshot, err := (packagedigest.Builder{TempRoot: c.f.base.Root}).Snapshot(c.f.ctx, r.Source, domain.SourceIdentity{RequestedSource: r.Source, CanonicalSource: r.Source}) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := packagedigest.Remove(snapshot); err != nil { + t.Error(err) + } + }() + spec, err := specregistry.New() + if err != nil { + t.Fatal(err) + } + envelope, err := (loader.Loader{Registry: spec}).LoadSnapshot(c.f.ctx, snapshot) + if err != nil { + t.Fatal(err) + } + registry, err := clients.NewRegistry(qoder.New()) + if err != nil { + t.Fatal(err) + } + client := domain.DetectedClient{ClientID: domain.ClientQoder, ConfigRoot: filepath.Join(c.f.base.Home, ".qoder"), ExecutablePath: c.f.client, Version: "1.1.78", Status: domain.DetectionDetected} + planning := planner.Planner{ProjectRoot: r.Project, ManagedRoot: filepath.Join(r.Project, ".uap", "managed"), Paths: pathpolicy.Policy{}, Registry: registry} + iid := "01234567-89ab-4cde-8fab-0123456789ab" + if key == "b" { + iid = "01234567-89ab-4cde-8fab-0123456789ac" + } + physical := qoderCycleName + "-" + qoderDeltaHash(iid)[:12] + plan, err := planning.Plan(c.f.ctx, domain.PlanRequest{Envelope: envelope, Client: client, Scope: domain.ScopeProject, ProjectRoot: r.Project, PhysicalArtifactID: physical}) + if err != nil || plan.Status != domain.PlanReady { + t.Fatal("actual inert Qoder planner", err) + } + stager := providers.Stager{Registry: registry, Paths: pathpolicy.Policy{}} + delivery, err := stager.Stage(c.f.ctx, envelope, plan, "inert-qoder-cycle", domain.CompatibilityHints{}) + if err != nil { + t.Fatal(err) + } + if err := os.Rename(delivery.StagingPath, delivery.ActivePath); err != nil { + t.Fatal(err) + } + request := domain.ActivationRequest{Client: client, Plan: plan, Delivery: delivery, DeclaredName: qoderCycleName, BackendExecutable: c.f.client} + out, err := qoder.New().Activate(c.f.ctx, clients.Env{}, request) + if err != nil || out.Activation != domain.ActivationPrepared || out.Verification != domain.VerificationInstalled { + t.Fatal("actual inert Qoder adapter", err) + } + stamp := "2026-10-28T00:00:00Z" + b := domain.ClientBinding{ClientBindingID: domain.ComputeClientBindingID(iid, "qoder", "project", plan.ActivePath), ClientID: "qoder", Scope: "project", TargetLocator: plan.ActivePath, PhysicalArtifact: physical, Materialization: domain.MaterializationMaterialized, Activation: out.Activation, Authentication: domain.AuthenticationNotChecked, Policy: domain.PolicyAllowed, Verification: out.Verification, PackageRevision: &domain.ClientPackageRevision{Version: "1.0.0", TreeDigest: envelope.TreeDigest, ManifestDigest: envelope.ManifestDigest}, AffectedSurfaces: []string{"qoder"}, NativeProfileRoot: plan.NativeRegistryRoot, NativeObjects: delivery.NativeObjects, UpdatedAt: stamp} + i := domain.Installation{InstallationID: iid, DeclaredName: qoderCycleName, Source: domain.SourceBinding{SourceBindingID: domain.ComputeSourceBindingID(envelope.Source), RequestedSource: r.Source, CanonicalSource: r.Source, TreeDigest: envelope.TreeDigest}, Package: domain.PackageBinding{LoaderKind: envelope.LoaderKind, FormatID: envelope.FormatID, SchemaURI: envelope.SchemaURI, DeclaredName: envelope.Manifest.Name, Version: envelope.Manifest.Version, ManifestDigest: envelope.ManifestDigest, Inventory: envelope.Inventory}, OriginMode: domain.OriginModeDirect, OperationGroupID: "op-00000000000000000000000000000000", Clients: map[string]domain.ClientBinding{b.ClientBindingID: b}, CreatedAt: stamp, UpdatedAt: stamp} + c.mutable = key + if err := (statev2.Store{Path: c.statePath(r), ProjectRoot: r.Project}).Save(domain.StateFileV2{SchemaVersion: domain.StateSchemaVersion, Installations: []domain.Installation{i}}); err != nil { + t.Fatal(err) + } + stored, err := clineCycleState(mustQoderCycleRead(t, c.statePath(r))) + if err != nil || len(stored.Installations) != 1 { + t.Fatal("actual inert stored state differs", err) + } + i = stored.Installations[0] + if err := qoderCycleExpectedSource(i, r); err != nil { + t.Fatal(err) + } + c.binding(t, r, i) + c.delivery(t, r) + digest, err := qoderCycleExpectedDigest(c.f.ctx, r.Key, r.Revision, r.Nonce) + if err != nil || r.Binding.NativeObjects[0].ManagedDigest != digest { + t.Fatal("independently framed authored payload differs from actual stager digest", err) + } + c.data(t, r, i) + r.Installation = i + c.f.protected[c.statePath(r)] = nativeSHA(t, c.statePath(r)) + c.mutable = "" + c.check(t) +} + +func TestQoderCycleOwnedProofJoinsActualProductionStagerAndPreparedAdapter(t *testing.T) { + c := qoderCycleInert(t) + for _, key := range []string{"a", "b"} { + qoderCycleStageInert(t, c, key) + } + if c.recipes["a"].Binding.PhysicalArtifact == c.recipes["b"].Binding.PhysicalArtifact || c.recipes["a"].Binding.ClientBindingID == c.recipes["b"].Binding.ClientBindingID { + t.Fatal("same-name projects share physical authority") + } + before := c.recipes["b"].Installation + a := c.recipes["a"] + delete(c.trees, a.Source) + a.Revision = "a-two" + c.author(t, a, true) + delete(c.trees, a.Active) + delete(c.f.protected, c.statePath(a)) + // Only this helper's previously owned package is removed in the inert setup; + // the separately tested production fault itself uses nonrecursive removals. + if err := os.RemoveAll(a.Active); err != nil { + t.Fatal(err) + } + qoderCycleStageInert(t, c, "a") + c.removeOwnedA(t) + if !reflect.DeepEqual(before, c.recipes["b"].Installation) { + t.Fatal("A exact whole-package fault changed B") + } +} diff --git a/scripts/project_native_qoder_cycle.py b/scripts/project_native_qoder_cycle.py new file mode 100644 index 000000000..5e1e9ca57 --- /dev/null +++ b/scripts/project_native_qoder_cycle.py @@ -0,0 +1,348 @@ +"""Closed Qoder skills-cycle capture; no filesystem access or child processes.""" +import base64 +from datetime import datetime +import hashlib +import json +from pathlib import PurePosixPath +import re + +MODE = "qoder-complete-skills-cycle" +NAME = "uap-qoder-cycle" +DESCRIPTION = "Disposable Qoder project cycle" +PUBLIC = tuple("git-a git-b git-outside version add-a add-b repeat-add-a unchanged-update-a intact-repair-a update-a missing-repair-a remove-a repeat-remove-a remove-b".split()) +CATALOG = tuple("empty-outside empty-a empty-b installed-a installed-b installed-outside stable-a updated-a updated-b updated-outside damaged-a repaired-a removed-a remaining-b removed-outside repeat-removed-a removed-b final-a final-b final-outside".split()) +PROOF = tuple("fixture source-a-a-one caller-a source-b-b-one caller-b original-user original-a original-b original-registry original-profile add-a add-b repeat-add-a unchanged-update-a intact-repair-a before-source-revision-a source-a-a-two update-a before-missing-a missing-a missing-repair-a remove-a repeat-remove-a remove-b log-ledger".split()) +ORDER = tuple(("public" if stage[0] == "P" else "catalog", stage[1:]) for stage in "Pgit-a Pgit-b Pgit-outside Pversion Cempty-outside Cempty-a Cempty-b Padd-a Cinstalled-a Padd-b Cinstalled-b Cinstalled-outside Prepeat-add-a Punchanged-update-a Pintact-repair-a Cstable-a Pupdate-a Cupdated-a Cupdated-b Cupdated-outside Cdamaged-a Pmissing-repair-a Crepaired-a Premove-a Cremoved-a Cremaining-b Cremoved-outside Prepeat-remove-a Crepeat-removed-a Premove-b Cremoved-b Cfinal-a Cfinal-b Cfinal-outside".split()) +MEMBERS = ("qoder-cycle-public.jsonl", "qoder-cycle-catalog.jsonl", "qoder-cycle-proof.jsonl") +FLAGS = tuple("project-lifecycle native-restart-removal mcp-initialize mcp-tools-list mcp-tools-call".split()) +CLEANUP = "full descendant supervision; forced cleanup fails" +HEX = re.compile(r"[0-9a-f]{64}\Z") +SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json" + + +def require(condition): + if not condition: + raise ValueError("Qoder complete skills-cycle evidence join differs") + + +def obj(value, required, optional=""): + required, optional = set(required.split()), set(optional.split()) + require(type(value) is dict and required <= set(value) <= required | optional) + return value + + +def same(left, right): + if type(left) is not type(right): + return False + if type(left) is dict: + return set(left) == set(right) and all(same(left[key], value) for key, value in right.items()) + if type(left) is list: + return len(left) == len(right) and all(same(a, b) for a, b in zip(left, right)) + return left == right + + +def natural(value, maximum): + require(type(value) is int and 0 <= value <= maximum) + return value + + +def digest(value): + require(type(value) is str and HEX.fullmatch(value)) + return value + + +def load(raw): + from project_native_runtime import strict_json + return strict_json(raw) + + +def private(raw): + from project_native_runtime import cline_cycle_private + return cline_cycle_private(raw, marker=b"uap-qoder-private-") + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def compact(value): + return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode() + + +def canonical(value): + require(type(value) is str and value.startswith("/") and not value.startswith("//") and value != "/" + and str(PurePosixPath(value)) == value and ".." not in PurePosixPath(value).parts and "\0" not in value) + return value + + +def instant(value): + require(type(value) is str) + match = re.fullmatch(r"(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d)(?:\.(\d{1,9}))?(Z|[+-]\d\d:\d\d)", value) + require(match is not None) + base, fraction, offset = match.groups() + moment = datetime.fromisoformat(base + ("+00:00" if offset == "Z" else offset)) + return int(moment.timestamp()) * 10**9 + int((fraction or "").ljust(9, "0")) + + +def stream(value): + obj(value, "bytes sha256 body") + natural(value["bytes"], 1 << 20) + digest(value["sha256"]) + require(type(value["body"]) is str and len(value["body"]) <= ((1 << 20) + 2) // 3 * 4) + raw = base64.b64decode(value["body"], validate=True) + require(len(raw) == value["bytes"] and sha(raw) == value["sha256"] + and base64.b64encode(raw).decode() == value["body"] and not private(raw)) + return raw + + +def embedded(value): + if type(value) is dict: + if {"body", "sha256"} <= set(value): + stream(value) + else: + for item in value.values(): + embedded(item) + elif type(value) is list: + for item in value: + embedded(item) + + +def records(raw, order): + require(type(raw) is bytes and len(raw) <= 1 << 20 and (not raw or raw.endswith(b"\n")) and not private(raw)) + result = [obj(load(line), "stage value") for line in raw.splitlines()] + require(len(result) <= len(order)) + for index, row in enumerate(result): + require(row["stage"] == order[index] and type(row["value"]) is dict) + embedded(row["value"]) + return result + + +def timed(value, complete=False): + obj(value, "command started_at ended_at stdout stderr log") + command = obj(value["command"], "argv cwd exit error deadline elapsed_ms cleanup") + require(type(command["argv"]) is list and 1 <= len(command["argv"]) <= 64 + and all(type(arg) is str and len(arg) <= 4096 and "\0" not in arg for arg in command["argv"])) + canonical(command["cwd"]) + require(type(command["exit"]) is int and -1 <= command["exit"] <= 255 + and type(command["error"]) is bool and type(command["deadline"]) is bool and command["cleanup"] == CLEANUP) + start, end = instant(value["started_at"]), instant(value["ended_at"]) + require(value["started_at"].endswith("Z") and value["ended_at"].endswith("Z")) + maximum = 35000 if complete else 65000 + natural(command["elapsed_ms"], maximum) + require(0 <= end - start <= maximum * 10**6 and command["elapsed_ms"] == (end - start) // 10**6) + if complete: + require(command["error"] is False and command["deadline"] is False) + return command, stream(value["stdout"]), stream(value["stderr"]), start, end + + +def role(stage): + return "a" if stage.endswith("-a") else "b" if stage.endswith("-b") else "outside" + + +def public_args(stage, layout, proofs): + key = role(stage) + cwd = layout[key] + if stage.startswith("git-"): + return cwd, [layout["git"], "-c", "core.hooksPath=/dev/null", "init", "--quiet"] + if stage == "version": + return cwd, [layout["client"], "--version"] + verb = "repair" if "repair" in stage else stage.removeprefix("repeat-").removeprefix("unchanged-").split("-")[0] + selector = layout["root"] + "/cycle-source-" + key if verb == "add" or stage == "update-a" else proofs[stage]["installation"]["installation_id"] if verb == "repair" else NAME + return cwd, [layout["installer"], verb, selector, "--scope", "project", "--project-root", cwd, "--target", "qoder", "--format", "json"] + (["--no-save"] if verb == "add" else []) + + +def progress(stage): + if stage in ("add-a", "add-b"): + return f"Resolving and validating one Agent Plugin package for every selected target...\nInstalling {NAME} for 1 selected clients...\n".encode() + if stage == "repeat-add-a": + return b"Resolving and validating one Agent Plugin package for every selected target...\nApplying transactional client package...\n" + if stage in ("unchanged-update-a", "update-a"): + return b"Resolving and validating one updated Agent Plugin package for every selected target...\n" + if "repair" in stage: + return b"Resolving and validating each unique exact installed package revision once...\n" + return b"" + + +def public_result(raw, stage, verb, proof): + row = obj(load(raw), "schema_version command result data") + require(type(row["schema_version"]) is int and row["schema_version"] == 1 and row["command"] == verb and row["result"] == "success") + single = stage == "repeat-add-a" + data = row["data"] + if single: + output = obj(data, "plugin version source tree_digest manifest_digest dry_run result", "security next_action") + else: + obj(data, "batch status succeeded failed plugin dry_run targets", "operation_id action_required version source revision tree_digest manifest_digest security environment plugin_data_preserved data_retained retained_data retained_data_action") + expected = dict(batch=True, status="data_retained" if verb == "remove" else "completed", succeeded=1, failed=0, plugin=NAME, dry_run=False) + require(all(same(data[field], value) for field, value in expected.items()) and type(data["targets"]) is list and len(data["targets"]) == 1) + target = obj(data["targets"][0], "target status output" + (" selected" if verb == "update" else ""), "next_action") + require(target["target"] == "qoder" and target["status"] == "external_completed" and (verb != "update" or target["selected"] is True)) + output = obj(target["output"], "plugin version source tree_digest manifest_digest dry_run result", "operation_id revision security next_action") + ins, bound = proof["installation"], proof["binding"] + expected = dict(plugin=NAME, version="1.0.0", source="local" if verb in ("repair", "remove") else "direct local source", tree_digest=proof["source_digest"], manifest_digest=ins["package"]["manifest_digest"], dry_run=False) + require(all(same(output[field], value) for field, value in expected.items())) + changed = stage not in ("repeat-add-a", "unchanged-update-a") + result = output["result"] + require(type(result) is dict and result.get("installation_id") == ins["installation_id"] and result.get("requires_confirmation") is False and result.get("mutated") is changed) + operation = None if single else data.get("operation_id") + if single: + require(result.get("no_change") is True) + else: + require(type(operation) is str and re.fullmatch(r"op-[0-9a-f]{32}", operation) and output.get("operation_id") == operation) + if changed: + require(operation == ins["operation_group_id"]) + checks = dict(tree_digest=proof["source_digest"], manifest_digest=ins["package"]["manifest_digest"], installation_id=ins["installation_id"], client_binding_id=bound["client_binding_id"], + physical_artifact_id=bound["physical_artifact_id"], target_locator=bound["target_locator"], client_id="qoder", target="qoder", scope="project") + def visit(value): + if type(value) is dict: + for field, item in value.items(): + if field in checks: + require(same(item, checks[field])) + if field == "mutated": + require(item is changed) + if field == "no_change": + require(type(item) is bool and (not changed or item is False)) + visit(item) + elif type(value) is list: + for item in value: + visit(item) + visit(data) + if verb == "remove": + require(data.get("plugin_data_preserved") is True and data.get("data_retained") is True) + retained = proof["data"]["receipt"] + require(same(data.get("retained_data"), [{key: retained[key] for key in ("data_receipt_id", "physical_backend_id", "scope", "state")}])) + return operation + + +def has_flag(stage): + return stage not in ("empty-outside", "empty-a", "empty-b", "installed-outside", "updated-outside", "removed-outside", "final-outside") + + +def catalog(raw, stage, layout, base, proofs): + from project_native_qoder_cycle_proof import control, nonce + key = role(stage) + cwd, argv = layout[key], [layout["client"], "plugins", "list", "--json"] + expected = [] + if has_flag(stage): + view = proofs["add-" + key]["view"] + argv += ["--plugin-dir", view] + if stage in ("installed-a", "installed-b", "stable-a", "updated-a", "updated-b", "repaired-a", "remaining-b"): + revision = "a-two" if stage in ("updated-a", "repaired-a") else key + "-one" + expected.append(dict(id=NAME + "@flag", name=NAME, source=NAME + "@inline", version="1.0.0", scope="flag", enabled=True, canDisable=False, + installPath=view, description=DESCRIPTION, resources=resources(NAME, f"Disposable Qoder {key} {revision} {nonce(base, key)}"))) + for who in ("a", "b", "user"): + name, path, description, _, _, entry = control(layout, base, who) + expected.append(dict(id=name + "@local", name=name, source=name + "@local", enabled=False, canDisable=True, description=description, resources=resources(name, description), **entry)) + require(same(load(raw), expected)) + return cwd, argv + + +def resources(name, description): + return dict(skills=[dict(name=name, description=description)], agents=[], mcpServers=[], commands=[], hooks=[]) + + +def pins(summary, layout): + require(same(summary["acquisition"], {"client": "qoder", "acquisition": {"sha256": "5e3213d2d4256c86c5ad2c8bf73fd392f92ba80d0d38f822806542805a48a8ea", "pin_verified": True, + "members": [{"Name": "qodercli", "Kind": "file", "Bytes": 182982112, "sha256": "0da0976e13fe00bc28f725d6fe7c2f0fe9fa9a8c37eeb2ee4e80ad4c0fc58953"}]}, "node": None, + "runtime_files": [{"path": layout["client"], "sha256": "0da0976e13fe00bc28f725d6fe7c2f0fe9fa9a8c37eeb2ee4e80ad4c0fc58953"}]})) + require(same(summary["scanner"], {"archive_sha256": "2b3d176db752433b904a4b42375543ff398f4841d22e48f7d4f23ded925b72da", "binary_sha256": "b6723451f155eb49e84acb34d1f06de929bddfcff6238cd4e38cdc6519396fba", "platform": "linux-amd64", "route": "prepopulated supported scanner cache; ordinary CLI security assessment", "source_tag": "v0.1.3", "version": "0.1.3"})) + + +def proof_framing(stage, value): + if stage == "fixture": + obj(value, "nonce layout owner_uid owner_gid") + elif stage.startswith("source-"): + obj(value, "source digest files") + elif stage.startswith("caller-"): + obj(value, "path body files") + elif stage in ("original-user", "original-a", "original-b"): + obj(value, "key path manifest skill files entry") + elif stage == "original-registry": + obj(value, "path registry settings_path settings") + elif stage == "original-profile": + obj(value, "logs_absent inventory") + elif stage == "missing-a": + obj(value, "path source_digest object before absent") + elif stage == "log-ledger": + obj(value, "original_sha256 expected_sha256 runs") + else: + obj(value, "scope revision removed source_digest state binding delivery data") + obj(value["delivery"], "active view files manifest skill") + obj(value["data"], "allocated path receipt owner files") + + +def complete(captured, summary): + from project_native_qoder_cycle_proof import validate + from project_native_qoder_cycle_logs import transition + layout, base, uid, gid, original, proofs, ledger = validate(captured[2], summary) + require(summary["fixture_authored_updater_config"] == layout["home"] + "/.qoder/settings.json") + pins(summary, layout) + public, catalogs = ({row["stage"]: row["value"] for row in rows} for rows in captured[:2]) + expected, runs, groups, last, elapsed = dict(original), [], set(), None, 0 + for kind, stage in ORDER: + value = (public if kind == "public" else catalogs)[stage] + command, out, err, start, end = timed(value, True) + require(last is None or last <= start) + last, elapsed = end, elapsed + command["elapsed_ms"] + if kind == "public": + cwd, argv = public_args(stage, layout, proofs) + require(command["exit"] == (1 if stage == "repeat-remove-a" else 0)) + if stage.startswith("git-"): + require(out == err == b"") + elif stage == "version": + require(out == b"1.1.78\n" and err == b"") + elif stage == "repeat-remove-a": + require(out == b"" and err == b'agentplugins: plugin is not installed for target "qoder" in project scope; no target was changed\n') + else: + group = public_result(out, stage, argv[1], proofs[stage]) + require(err == progress(stage)) + if group is not None: + require(group not in groups) + groups.add(group) + else: + require(command["exit"] == 0 and err == b"") + cwd, argv = catalog(out, stage, layout, base, proofs) + require(command["argv"] == argv and command["cwd"] == cwd) + expected = transition(value["log"], expected, runs, uid, gid, start, end, kind == "catalog" or stage == "version") + obj(ledger, "original_sha256 expected_sha256 runs") + require(len(runs) == summary["cycle_log_runs"] == 21 and same(ledger, dict(original_sha256=sha(compact(original)), expected_sha256=sha(compact(expected)), runs=runs))) + require(elapsed <= summary["elapsed_ms"]) + + +def members(bodies, source): + try: + return admit(bodies, source) + except (KeyError, TypeError, AttributeError, IndexError, UnicodeError, RecursionError, OverflowError) as error: + raise ValueError("Qoder complete skills-cycle structure refused") from error + + +def admit(bodies, source): + require("runtime-attempt.json" in bodies and set(bodies) <= {"runtime-attempt.json", *MEMBERS} + and all(type(raw) is bytes and len(raw) <= 1 << 20 and not private(raw) for raw in bodies.values()) and sum(map(len, bodies.values())) <= 16 << 20) + summary = obj(load(bodies["runtime-attempt.json"]), "client version acquisition scanner source_identity activation vendor_authentication_qualified real_model_qualified capture_mode nonce layout fixture_authored_updater_config cycle_stage cycle_public_stages cycle_catalog_stages cycle_log_runs cycle_observed_complete runtime_attempt_complete test_failed elapsed_ms qualification") + require(summary["client"] == "qoder" and summary["version"] == "1.1.78" and summary["capture_mode"] == MODE and summary["activation"] == "prepared" + and summary["vendor_authentication_qualified"] is False and summary["real_model_qualified"] is False) + require(all(type(summary[field]) is bool for field in ("runtime_attempt_complete", "test_failed", "cycle_observed_complete"))) + capture_limit = 195000 if summary["test_failed"] and not summary["runtime_attempt_complete"] else 180000 + natural(summary["elapsed_ms"], capture_limit) + natural(summary["cycle_log_runs"], 21) + expected_source = dict(installer_base_commit=source["source_commit"], installer_tree=source["source_tree"], installer_source_state="committed", installer_patch_sha256=[]) + require(same(summary["source_identity"], expected_source)) + flag = summary["runtime_attempt_complete"] + require(same(summary["qualification"], {key: flag and key in FLAGS[:2] for key in FLAGS})) + captured = [records(bodies.get(name, b""), order) for name, order in zip(MEMBERS, (PUBLIC, CATALOG, PROOF))] + require(same(summary["cycle_public_stages"], [row["stage"] for row in captured[0]]) and same(summary["cycle_catalog_stages"], [row["stage"] for row in captured[1]])) + require(summary["cycle_stage"] in {"prepare", "complete-cycle"} | {kind + ":" + stage for kind, stage in ORDER}) + from project_native_qoder_cycle_logs import framing + for rows in captured[:2]: + for row in rows: + timed(row["value"]) + framing(row["value"]["log"]) + for row in captured[2]: + proof_framing(row["stage"], row["value"]) + if not flag: + require(summary["test_failed"] is True) + return summary + require(set(bodies) == {"runtime-attempt.json", *MEMBERS} and summary["test_failed"] is False and summary["cycle_observed_complete"] is True and summary["cycle_stage"] == "complete-cycle" + and summary["elapsed_ms"] < 150000 and all(len(rows) == len(order) for rows, order in zip(captured, (PUBLIC, CATALOG, PROOF)))) + complete(captured, summary) + return summary diff --git a/scripts/project_native_qoder_cycle_logs.py b/scripts/project_native_qoder_cycle_logs.py new file mode 100644 index 000000000..6fa464103 --- /dev/null +++ b/scripts/project_native_qoder_cycle_logs.py @@ -0,0 +1,172 @@ +"""Independent closed Qoder original-profile and one-run log overlay joins.""" +import re + +from project_native_qoder_cycle import compact, digest, instant, natural, obj, require, same, sha + +RUN = re.compile(r"(\d{4}-\d\d-\d\dT)(\d\d)-(\d\d)-(\d\d)-(\d{3})(Z|[+-]\d\d-\d\d)-([0-9a-z]{0,6})-p([1-9][0-9]{0,9})\Z") +RUN_FIELDS = "run_id latest_target manifest_bytes manifest_sha256 pid ppid started_at cli_version platform arch process_role pid_provenance run_id_matches_manifest cwd_matches argv_matches exact_added_run_closure preexisting_records_stable" + + +def integer(text, base=10, signed=False): + require(type(text) is str and re.fullmatch(r"-?(?:0|[1-9][0-9]*)" if base == 10 else r"[1-7][0-7]*", text)) + value = int(text, base) + require((str(value) if base == 10 else format(value, "o")) == text) + require(-(1 << 63) <= value < 1 << 63 if signed else 0 <= value < 1 << 64) + return value + + +def run(value, form, start, end): + obj(value, RUN_FIELDS) + text = value["run_id"] + require(type(text) is str and len(text) <= 64) + match = RUN.fullmatch(text) + require(match is not None) + day, hour, minute, second, millis, zone, _, process = match.groups() + if zone != "Z": + require(int(zone[1:3]) < 24 and int(zone[4:]) < 60 and (int(zone[1:3]) != 0 or int(zone[4:]) != 0)) + timestamp = day + hour + ":" + minute + ":" + second + "." + millis + (zone if zone == "Z" else zone[:3] + ":" + zone[4:]) + require(value["started_at"] == timestamp and start // 10**6 * 10**6 <= instant(timestamp) <= end) + require(type(value["pid"]) is int and value["pid"] == int(process) and 0 < value["pid"] < 1 << 31) + natural(value["ppid"], (1 << 31) - 1) + require(value["ppid"] > 0) + natural(value["manifest_bytes"], 4096) + require(value["manifest_bytes"] > 0) + digest(value["manifest_sha256"]) + expected = {"latest_target": "runs/" + text, "cli_version": "1.1.78", "platform": "linux", "arch": "x64", "process_role": "main", "pid_provenance": "manifest-only", + "run_id_matches_manifest": True, "cwd_matches": True, "argv_matches": form == "direct", "exact_added_run_closure": True, "preexisting_records_stable": True} + require(all(same(value[key], wanted) for key, wanted in expected.items())) + return text + + +def latest(record, identity, uid, gid): + require(type(record) is str and len(record) <= 1024) + parts = record.split(":") + require(len(parts) == 26 and parts[0] == "symlink" and parts[12] == "target") + for first, mode in ((1, 0o120777), (13, 0o40755)): + fields = [integer(parts[first + i], 8 if i == 2 else 10, i in (7, 9)) for i in range(11)] + require(fields[1] > 0 and fields[2] == mode and fields[3] > 0 and fields[4] == uid and fields[5] == gid) + require(fields[6] < 1 << 63 and fields[8] < 10**9 and fields[10] < 10**9) + if first == 1: + require(fields[3] == 1) + target = ("runs/" + identity).encode() + require(integer(parts[7]) == integer(parts[24]) == len(target) and digest(parts[25]) == sha(target)) + return record + + +def projected_value(record): + if record is None: + return {"kind": "absent"} + if record.startswith("symlink:"): + return {"kind": "symlink", "record_sha256": sha(record.encode())} + parts = record.split(":") + if len(parts) == 2: + return {"kind": "directory", "mode": parts[1]} + require(len(parts) == 4 and parts[0] == "file") + return {"kind": "file", "mode": parts[1], "bytes": integer(parts[2]), "content_sha256": digest(parts[3])} + + +def delta(before, after): + result = dict(status="captured", before_entries=len(before), after_entries=len(after), added=0, removed=0, changed=0, + areas={key: 0 for key in ("log-latest", "logs", "settings", "plugin-cache", "plugins", "cache", "other")}, omitted_count=0, entries=[]) + for key in set(before) | set(after): + if before.get(key) == after.get(key): + continue + require(key == "logs/latest" or key == "logs" or key.startswith("logs/")) + operation = "added" if key not in before else "removed" if key not in after else "changed" + area = "log-latest" if key == "logs/latest" else "logs" + result[operation] += 1 + result["areas"][area] += 1 + result["entries"].append(dict(key_sha256=sha(key.encode()), area=area, operation=operation, + before=projected_value(before.get(key)), after=projected_value(after.get(key)))) + result["entries"].sort(key=lambda entry: (entry["area"] != "log-latest", entry["key_sha256"])) + if len(result["entries"]) > 4: + result["status"], result["omitted_count"] = "entry-limit", len(result["entries"]) - 4 + result["entries"] = result["entries"][:4] + require(len(compact(result)) <= 2048) + return result + + +def framing(value): + obj(value, "kind before_sha256 after_sha256", "argv_form latest_record run delta log_bytes log_sha256") + digest(value["before_sha256"]) + require(value["kind"] in ("unchanged", "one-run", "refused")) + if "run" in value: + packet = obj(value["run"], RUN_FIELDS) + require(type(packet["run_id"]) is str and len(packet["run_id"]) <= 64 and RUN.fullmatch(packet["run_id"])) + require(packet["latest_target"] == "runs/" + packet["run_id"]) + natural(packet["manifest_bytes"], 4096) + require(packet["manifest_bytes"] > 0) + digest(packet["manifest_sha256"]) + for field in ("pid", "ppid"): + natural(packet[field], (1 << 31) - 1) + require(packet[field] > 0) + instant(packet["started_at"]) + require(all(type(packet[field]) is bool for field in ("run_id_matches_manifest", "cwd_matches", "argv_matches", "exact_added_run_closure", "preexisting_records_stable"))) + require(all(packet[field] == wanted for field, wanted in dict(cli_version="1.1.78", platform="linux", arch="x64", process_role="main", pid_provenance="manifest-only").items())) + if value["kind"] == "refused": + require(value["after_sha256"] == "" and set(value) <= set("kind before_sha256 after_sha256 argv_form run".split())) + if "argv_form" in value: + require(value["argv_form"] in ("direct", "compiled-embedded", "refused")) + return + digest(value["after_sha256"]) + if value["kind"] == "unchanged": + require(set(value) == {"kind", "before_sha256", "after_sha256"} and value["before_sha256"] == value["after_sha256"]) + else: + require(set(value) == set("kind before_sha256 after_sha256 argv_form latest_record run delta log_bytes log_sha256".split())) + require(value["argv_form"] in ("direct", "compiled-embedded")) + natural(value["log_bytes"], 64 << 20) + require(value["log_bytes"] > 0) + digest(value["log_sha256"]) + require(type(value["latest_record"]) is str and len(value["latest_record"]) <= 1024 and type(value["delta"]) is dict and len(compact(value["delta"])) <= 2048) + parts = value["latest_record"].split(":") + require(len(parts) == 26) + uid, gid = integer(parts[5]), integer(parts[6]) + natural(uid, (1 << 32) - 1) + natural(gid, (1 << 32) - 1) + latest(value["latest_record"], value["run"]["run_id"], uid, gid) + packet = obj(value["delta"], "status before_entries after_entries added removed changed areas omitted_count entries") + require(packet["status"] in ("captured", "entry-limit")) + for field in ("before_entries", "after_entries", "added", "removed", "changed", "omitted_count"): + natural(packet[field], 4096) + obj(packet["areas"], "log-latest logs settings plugin-cache plugins cache other") + for count in packet["areas"].values(): + natural(count, 4096) + require(type(packet["entries"]) is list and len(packet["entries"]) <= 4) + for entry in packet["entries"]: + obj(entry, "key_sha256 area operation before after") + digest(entry["key_sha256"]) + require(entry["area"] in packet["areas"] and entry["operation"] in ("added", "removed", "changed")) + for record in (entry["before"], entry["after"]): + require(type(record) is dict and record.get("kind") in ("absent", "symlink", "directory", "file")) + fields = {"absent": "kind", "symlink": "kind record_sha256", "directory": "kind mode", "file": "kind mode bytes content_sha256"} + obj(record, fields[record["kind"]]) + if record["kind"] == "symlink": digest(record["record_sha256"]) + if record["kind"] in ("directory", "file"): integer(record["mode"], 8) + if record["kind"] == "file": + natural(record["bytes"], 64 << 20) + digest(record["content_sha256"]) + + +def transition(value, expected, runs, uid, gid, start, end, native): + framing(value) + before = sha(compact(expected)) + require(value["before_sha256"] == before) + if not native: + require(value["kind"] == "unchanged" and value["after_sha256"] == before) + return expected + require(value["kind"] == "one-run") + identity = run(value["run"], value["argv_form"], start, end) + require(identity not in runs) + path = "logs/runs/" + identity + require(not any(key == path or key.startswith(path + "/") for key in expected)) + after = dict(expected) + if not runs: + require(not any(key == "logs" or key.startswith("logs/") for key in expected)) + after.update({"logs": "directory:20000000755", "logs/runs": "directory:20000000755"}) + after[path] = "directory:20000000755" + after[path + "/manifest.json"] = "file:644:" + str(value["run"]["manifest_bytes"]) + ":" + value["run"]["manifest_sha256"] + after[path + "/qodercli.log"] = "file:644:" + str(value["log_bytes"]) + ":" + value["log_sha256"] + after["logs/latest"] = latest(value["latest_record"], identity, uid, gid) + require(value["after_sha256"] == sha(compact(after)) and same(value["delta"], delta(expected, after))) + runs.append(identity) + return after diff --git a/scripts/project_native_qoder_cycle_proof.py b/scripts/project_native_qoder_cycle_proof.py new file mode 100644 index 000000000..98681a191 --- /dev/null +++ b/scripts/project_native_qoder_cycle_proof.py @@ -0,0 +1,241 @@ +"""Text-only source, selected package, original controls and retention proofs.""" +from pathlib import PurePosixPath +import re + +from project_native_qoder_cycle import DESCRIPTION, NAME, PROOF, SCHEMA, canonical, compact, digest, instant, natural, obj, require, same, sha, stream + +DIR = "directory:20000000" +STATE_STAGES = tuple("add-a add-b repeat-add-a unchanged-update-a intact-repair-a before-source-revision-a update-a before-missing-a missing-repair-a remove-a repeat-remove-a remove-b".split()) +UUID = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\Z") + + +def nonce(base, key): + return sha((base + key).encode())[:32] + + +def authored(base, key, revision): + n = nonce(base, key) + return {"plugin.json": compact({"$schema": SCHEMA, "name": NAME, "version": "1.0.0", "description": DESCRIPTION}), + "skills/" + NAME + "/SKILL.md": f"---\nname: {NAME}\ndescription: Disposable Qoder {key} {revision} {n}\n---\nUAP_QODER_{revision}_{n}\n".encode()} + + +def projected(base, key, revision): + import json + files = authored(base, key, revision) + files[".agentplugins-qoder-native/.qoder-plugin/plugin.json"] = (json.dumps({"name": NAME, "version": "1.0.0", "description": DESCRIPTION, "skills": "./skills/"}, sort_keys=True, indent=2) + "\n").encode() + files[".agentplugins-qoder-native/skills/" + NAME + "/SKILL.md"] = files["skills/" + NAME + "/SKILL.md"] + return files + + +def tree(files, mode="600", dirs="700"): + result = {".": DIR + dirs} + for path, body in files.items(): + result[path] = "file:" + mode + ":" + sha(body) + for parent in PurePosixPath(path).parents: + if str(parent) != ".": + result.setdefault(str(parent), DIR + dirs) + return result + + +def tree_digest(files, source=False): + directories = {str(p) for path in files for p in PurePosixPath(path).parents} - {"."} + out = bytearray() + def frame(raw): + out.extend(len(raw).to_bytes(8, "big")) + out.extend(raw) + if source: + frame(b"agentplugins.package-tree\0sha256\0v1") + for path in sorted(set(files) | directories): + is_file, body = path in files, files.get(path, b"") + if source: + for value in ("entry", path, "file" if is_file else "directory", "100644" if is_file else "040000", ""): + frame(value.encode()) + out.extend(len(body).to_bytes(8, "big")) + else: + out.extend(f'{"file" if is_file else "dir"}\0{path}\0false\0{len(body)}\0'.encode()) + out.extend(body) + return "sha256:" + sha(out) + + +def fixture(value, summary): + obj(value, "nonce layout owner_uid owner_gid") + base, layout = value["nonce"], obj(value["layout"], "root home a b outside installer client git") + require(type(base) is str and re.fullmatch(r"[0-9a-f]{32}", base)) + for path in layout.values(): + canonical(path) + require(len(set(layout.values())) == len(layout) and same(summary["layout"], layout) and summary["nonce"] == base) + require(all(layout[key] == layout["root"] + "/" + suffix for key, suffix in (("home", "home"), ("a", "project"), ("b", "project-b"), ("outside", "outside")))) + natural(value["owner_uid"], (1 << 32) - 1) + natural(value["owner_gid"], (1 << 32) - 1) + return layout, base, value["owner_uid"], value["owner_gid"] + + +def control(layout, base, key): + name = "uap-qoder-original-user" if key == "user" else "uap-qoder-foreign-" + key + parent = layout["home"] if key == "user" else layout[key] + path, description = parent + "/.qoder/plugins/" + name, "Original Qoder " + key + " " + base + manifest = compact({"name": name, "version": "0.9.0", "description": description, "skills": "./skills/"}) + body = f"---\nname: {name}\ndescription: {description}\n---\nUAP_QODER_ORIGINAL_{key.upper()}_{base}\n".encode() + entry = dict(scope="user" if key == "user" else "project", installPath=path, version="0.9.0") + if key != "user": + entry["projectPath"] = parent + return name, path, description, manifest, body, entry + + +def originals(proofs, layout, base): + profile = {".": "directory:20000000700", ".auth": "directory:20000000700"} + registry, enabled = {}, {} + for key in ("user", "a", "b"): + row = obj(proofs["original-" + key], "key path manifest skill files entry") + name, path, _, manifest, body, entry = control(layout, base, key) + files = {".qoder-plugin/plugin.json": manifest, "skills/" + name + "/SKILL.md": body} + require(row["key"] == key and row["path"] == path and stream(row["manifest"]) == manifest + and stream(row["skill"]) == body and same(row["entry"], entry) and same(row["files"], tree(files))) + registry[name + "@local"], enabled[name + "@local"] = [entry], False + if key == "user": + for relative, value in tree(files).items(): + profile["plugins/" + name + ("/" + relative if relative != "." else "")] = value.replace("file:600:", "file:600:" + str(len(files[relative])) + ":") if relative in files else value + row = obj(proofs["original-registry"], "path registry settings_path settings") + root = layout["home"] + "/.qoder" + raw_registry = compact({"version": 2, "plugins": registry}) + settings = compact({"general": {"enableAutoUpdate": False}, "enabledPlugins": enabled}) + require(row["path"] == root + "/plugins/installed_plugins_v2.json" and row["settings_path"] == root + "/settings.json" + and stream(row["registry"]) == raw_registry and stream(row["settings"]) == settings) + profile.update({"settings.json": "file:600:" + str(len(settings)) + ":" + sha(settings), + "plugins": "directory:20000000700", "plugins/installed_plugins_v2.json": "file:600:" + str(len(raw_registry)) + ":" + sha(raw_registry), + ".auth/.credential-transaction": "file:600:0:" + sha(b"")}) + for path in ("tmp", "tmp/telemetry", "tmp/telemetry/active-runs"): + profile[path] = "directory:20000000700" + row = obj(proofs["original-profile"], "logs_absent inventory") + require(row["logs_absent"] is True and type(row["inventory"]) is dict) + actual = row["inventory"] + for path, mode in (("installation_id", "644"), (".auth/machine_id", "600")): + # These private fresh UUID bytes are held/validated by Go, never exported. + value = actual.get(path) + require(type(value) is str and re.fullmatch("file:" + mode + r":36:[0-9a-f]{64}", value)) + profile[path] = value + require(same(actual, profile)) + return profile + + +def receipt(row, bound, sequence, kind, before, after): + require(type(row) is dict and type(row.get("operation_group_id")) is str and re.fullmatch(r"op-[0-9a-f]{32}", row["operation_group_id"])) + operation = row["operation_group_id"] + "-001" + suffix = sha(operation.encode())[:16] + parent = str(PurePosixPath(bound["target_locator"]).parent) + expected = dict(operation_id=operation, operation_group_id=row["operation_group_id"], sequence=sequence, mutation_type=kind, + client_binding_id=bound["client_binding_id"], active_path=bound["target_locator"], backup_path=parent + "/.agentplugins-backup-" + suffix, phase="committed") + if kind == "directory_swap": + expected["staging_path"] = parent + "/.agentplugins-staging-" + suffix + if before: + expected["before_digest"] = before + if after: + expected["after_digest"] = after + require(same(row, expected)) + + +def state(row, stage, key, revision, layout, base, prior): + from project_native_qoder_cycle import load + obj(row, "scope revision removed source_digest state binding delivery data") + removed = stage in ("remove-a", "repeat-remove-a", "remove-b") + require(row["scope"] == key and row["revision"] == revision and row["removed"] is removed) + source_files = authored(base, key, revision) + source_path, source_digest = layout["root"] + "/cycle-source-" + key, tree_digest(source_files, True) + require(row["source_digest"] == source_digest) + document = obj(load(stream(row["state"])), "schema_version installations", "transaction_receipts") + require(type(document["schema_version"]) is int and document["schema_version"] == 5 and type(document["installations"]) is list and len(document["installations"]) == 1) + ins = obj(document["installations"][0], "installation_id declared_name source package clients origin_mode operation_group_id created_at updated_at", "data_receipts data_retained") + iid = ins["installation_id"] + require(type(iid) is str and UUID.fullmatch(iid) and instant(ins["created_at"]) <= instant(ins["updated_at"])) + require(type(ins["operation_group_id"]) is str and re.fullmatch(r"op-[0-9a-f]{32}", ins["operation_group_id"])) + manifest_digest = "sha256:" + sha(source_files["plugin.json"]) + require(ins["declared_name"] == NAME and ins["origin_mode"] == "direct" + and same(ins["source"], dict(source_binding_id="src_" + sha((source_path + "\0\0").encode())[:24], requested_source=source_path, canonical_source=source_path, resolved_revision="", tree_digest=source_digest)) + and same(ins["package"], dict(loader_kind="agent_plugins", format_id="agent-plugins/1.0.0", schema_uri=SCHEMA, declared_name=NAME, version="1.0.0", manifest_digest=manifest_digest, inventory=dict(mcp_present=False, mcp_enabled=False, skills=[NAME])))) + physical = NAME + "-" + sha(iid.encode())[:12] + root = layout[key] + "/.uap/managed/clients/qoder" + active, view = root + "/" + physical, root + "/" + physical + "/.agentplugins-qoder-native" + bid = "client_" + sha("\0".join((iid, "qoder", "project", active)).encode())[:24] + b = obj(row["binding"], "client_binding_id client_id scope target_locator physical_artifact_id materialization activation authentication policy verification package_revision affected_surfaces native_profile_root native_objects receipts updated_at") + expected = dict(client_binding_id=bid, client_id="qoder", scope="project", target_locator=active, physical_artifact_id=physical, + materialization="materialized", activation="prepared", authentication="not_checked", policy="allowed", verification="installation_verified", affected_surfaces=["qoder"], native_profile_root=root, + package_revision=dict(version="1.0.0", tree_digest=source_digest, manifest_digest=manifest_digest)) + require(all(same(b[field], value) for field, value in expected.items())) + instant(b["updated_at"]) + payload = projected(base, key, revision) + installed_digest = tree_digest(payload) + require(same(b["native_objects"], [dict(object_id="package:qoder:" + physical, kind="managed_package_directory", logical_name=NAME, path=active, managed_digest=installed_digest, protection_class="managed")])) + require(same(ins["clients"], {} if removed else {bid: b}) and ins.get("data_retained", False) is removed) + count = 1 if key == "b" else 2 if stage in ("intact-repair-a", "before-source-revision-a") else 3 if stage in ("update-a", "before-missing-a") else 4 if stage in ("missing-repair-a", "remove-a", "repeat-remove-a") else 1 + require(type(b["receipts"]) is list and len(b["receipts"]) == count) + one, two = tree_digest(projected(base, key, key + "-one")), tree_digest(projected(base, key, "a-two")) + before, after, seen = ("", one, one, ""), (one, one, two, two), set() + for index, value in enumerate(b["receipts"]): + receipt(value, b, index + 1, "directory_swap", before[index], after[index]) + require(value["operation_id"] not in seen) + seen.add(value["operation_id"]) + if prior: + require(ins["installation_id"] == prior["installation"]["installation_id"] and same(b["receipts"][:len(prior["binding"]["receipts"])], prior["binding"]["receipts"])) + transactions = document.get("transaction_receipts", []) + require(type(transactions) is list and len(transactions) == int(removed)) + if removed: + receipt(transactions[0], b, count + 1, "directory_remove", installed_digest, "") + require(transactions[0]["operation_group_id"] == ins["operation_group_id"] and transactions[0]["operation_id"] not in seen) + else: + require(ins["operation_group_id"] == b["receipts"][-1]["operation_group_id"]) + delivery = obj(row["delivery"], "active view files manifest skill") + require(delivery["active"] == active and delivery["view"] == view) + if removed: + require(all(delivery[field] is None for field in ("files", "manifest", "skill"))) + else: + require(same(delivery["files"], tree(payload, "644", "755")) and stream(delivery["manifest"]) == payload[".agentplugins-qoder-native/.qoder-plugin/plugin.json"] + and stream(delivery["skill"]) == source_files["skills/" + NAME + "/SKILL.md"]) + data(row["data"], ins, b, removed, layout[key], physical) + return {**row, "installation": ins, "active": active, "view": view, "physical": physical} + + +def data(value, ins, bound, removed, project, physical): + from project_native_qoder_cycle import load + obj(value, "allocated path receipt owner files") + path = project + "/.uap/plugin-data/" + physical + require(value["allocated"] is removed and value["path"] == path) + if not removed: + require(ins.get("data_receipts", {}) == {} and all(value[field] is None for field in ("receipt", "owner", "files"))) + return + did = "data_" + sha("\0".join((ins["installation_id"], physical, "project")).encode())[:24] + r = obj(value["receipt"], "data_receipt_id physical_backend_id scope locator ownership_digest state created_at updated_at") + expected = dict(data_receipt_id=did, physical_backend_id=physical, scope="project", locator=path, ownership_digest="sha256:" + sha(("agentplugins-plugin-data-v1\0" + did + "\0" + path).encode()), state="owned") + require(all(same(r[field], wanted) for field, wanted in expected.items()) and instant(r["created_at"]) <= instant(r["updated_at"])) + require(same(ins.get("data_receipts"), {did: r}) and bound.get("data_receipt_id", "") == "") + owner = stream(value["owner"]) + require(same(load(owner), r) and same(value["files"], tree({".agentplugins-data-owner.json": owner}))) + + +def validate(rows, summary): + require([row["stage"] for row in rows] == list(PROOF)) + proofs = {row["stage"]: row["value"] for row in rows} + layout, base, uid, gid = fixture(proofs["fixture"], summary) + original = originals(proofs, layout, base) + for key, revision in (("a", "a-one"), ("b", "b-one"), ("a", "a-two")): + value = obj(proofs["source-" + key + "-" + revision], "source digest files") + files = authored(base, key, revision) + require(value["source"] == layout["root"] + "/cycle-source-" + key and value["digest"] == tree_digest(files, True) and same(value["files"], tree(files))) + for key in ("a", "b"): + value = obj(proofs["caller-" + key], "path body files") + body = ("CALLER_" + key + "_" + base + "\n").encode() + require(value["path"] == layout[key] + "/.uap/plugin-data/caller-owned/keep.txt" and stream(value["body"]) == body and same(value["files"], tree({"keep.txt": body}))) + current, selected = {}, {} + for stage in STATE_STAGES: + key = "b" if stage.endswith("-b") else "a" + revision = "b-one" if key == "b" else "a-two" if STATE_STAGES.index(stage) >= STATE_STAGES.index("update-a") else "a-one" + value = state(proofs[stage], stage, key, revision, layout, base, current.get(key)) + if stage in ("repeat-add-a", "unchanged-update-a", "before-source-revision-a", "before-missing-a", "repeat-remove-a"): + require(same(proofs[stage]["state"], current[key]["state"]) and same(proofs[stage]["delivery"], current[key]["delivery"]) + and same(proofs[stage]["data"], current[key]["data"])) + current[key], selected[stage] = value, value + require(len({value["installation"]["installation_id"] for value in current.values()}) == len({value["binding"]["client_binding_id"] for value in current.values()}) == len({value["physical"] for value in current.values()}) == 2) + missing = obj(proofs["missing-a"], "path source_digest object before absent") + before = selected["before-missing-a"] + require(missing["absent"] is True and missing["path"] == before["active"] and missing["source_digest"] == before["source_digest"] + and same(missing["object"], before["binding"]["native_objects"][0]) and same(missing["before"], before["delivery"]["files"])) + return layout, base, uid, gid, original, selected, proofs["log-ledger"] diff --git a/scripts/project_native_runtime.py b/scripts/project_native_runtime.py index 84e3197f2..f601742aa 100644 --- a/scripts/project_native_runtime.py +++ b/scripts/project_native_runtime.py @@ -44,8 +44,8 @@ "file": "repotests/agentplugins_kimi_project_native_attempt_test.go", "optin": "AGENTPLUGINS_KIMI_PROJECT_RUNTIME_ATTEMPT", "seconds": 180, "phase": "installed-single-tool-attempt"}, - "qoder": {"test": "TestAgentpluginsQoderNativeProjectRuntimeAttempt", - "file": "repotests/agentplugins_qoder_project_native_attempt_test.go", + "qoder": {"test": "TestAgentpluginsQoderNativeProjectCompleteSkillsCycle", + "file": "repotests/agentplugins_qoder_cycle_lifecycle_test.go", "optin": "AGENTPLUGINS_QODER_PROJECT_RUNTIME_ATTEMPT", "seconds": 180, "phase": "runtime-attempt"}, "grok": {"test": "TestAgentpluginsGrokNativeProjectRuntimeAttempt", "file": "repotests/agentplugins_grok_project_native_attempt_test.go", @@ -278,6 +278,12 @@ def qoder_log_run_observation(result, client): def cli_attempt_members(bodies, source, client): if client not in CLI_ATTEMPTS: raise ValueError("unknown fixed native CLI attempt") + summary = strict_json(bodies.get("runtime-attempt.json", b"{}")) + if isinstance(summary, dict) and "capture_mode" in summary: + if client != "qoder" or summary["capture_mode"] != "qoder-complete-skills-cycle": + raise ValueError("unknown fixed native CLI capture mode") + from project_native_qoder_cycle import members + return members(bodies, source) allowed = {"runtime-attempt.json", "probe-events.jsonl"} commands = CLI_COMMANDS + (("native-catalog",) if client == "qoder" else ()) for label in commands: @@ -345,8 +351,8 @@ def reject_constant(_): return True -def cline_cycle_private(body): - if len(body) > 1 << 20 or b"uap-cline-private-" in body: +def cline_cycle_private(body, marker=b"uap-cline-private-"): + if len(body) > 1 << 20 or marker in body: return True budget = [4 * len(body), 16384] def encoded(raw, depth): @@ -397,7 +403,7 @@ def visit(value, depth, document_depth=0): raw = value.encode("utf-8", errors="strict") except UnicodeError: return True - if b"uap-cline-private-" in raw: + if marker in raw: return True return encoded(raw, depth + 1) if type(value) is dict: diff --git a/scripts/test_project_native_qoder_cycle.py b/scripts/test_project_native_qoder_cycle.py new file mode 100644 index 000000000..89aeb92b7 --- /dev/null +++ b/scripts/test_project_native_qoder_cycle.py @@ -0,0 +1,247 @@ +"""Inert parser controls; these never execute or qualify the native client.""" +import base64 +import copy +import json +import unittest + +import project_native_qoder_cycle as cycle +import project_native_qoder_cycle_logs as logs +import project_native_qoder_cycle_proof as proof +import project_native_runtime as runtime +from test_project_native_qoder_cycle_fixtures import LAYOUT, NONCE, SOURCE, container, raw, stream + + +def aggregate(bodies, index, change): + name = cycle.MEMBERS[index] + rows = [json.loads(line) for line in bodies[name].splitlines()] + change(rows) + bodies[name] = b"".join(raw(row) + b"\n" for row in rows) + + +def summary_change(bodies, **change): + value = json.loads(bodies["runtime-attempt.json"]) + value.update(change) + bodies["runtime-attempt.json"] = raw(value) + return value + + +class QoderCycleContracts(unittest.TestCase): + def test_digest_framing_matches_independent_actual_production_staging(self): + # Literal hashes from the actual production planner/stager/Prepared + # adapter export 8da61784...; its absent public receipts are not invented. + base = "0123456789abcdef0123456789abcdef" + expected = { + "a": ("sha256:dfd0babe6a02cd29f1427e44c4b12a7515dfaac0f646380102dab35f65931f62", "sha256:9f7dbd302ca44e1ecb60cb7ddb94330877191378393ef8a3454e87b1dd965418"), + "b": ("sha256:eb4fea6357e08d9ccad93947938bce06cb4d10f664b907d2d5696abaf0d9b769", "sha256:b2470dadb2c9402e7885e9e7eb1507d645cc0765c336736c5a9032af34096391"), + } + manifest = b'{\n "description": "Disposable Qoder project cycle",\n "name": "uap-qoder-cycle",\n "skills": "./skills/",\n "version": "1.0.0"\n}\n' + for key, (source, installed) in expected.items(): + self.assertEqual(proof.tree_digest(proof.authored(base, key, key + "-one"), True), source) + projected = proof.projected(base, key, key + "-one") + self.assertEqual(projected[".agentplugins-qoder-native/.qoder-plugin/plugin.json"], manifest) + self.assertEqual(proof.tree_digest(projected), installed) + + def test_authored_complete_container_closes_only_supported_two_flags(self): + bodies = container() + result = runtime.cli_attempt_members(bodies, SOURCE, "qoder") + self.assertEqual(len(bodies), 4) + self.assertEqual((len(result["cycle_public_stages"]), len(result["cycle_catalog_stages"]), result["cycle_log_runs"]), (14, 20, 21)) + self.assertEqual({key for key, value in result["qualification"].items() if value}, set(cycle.FLAGS[:2])) + self.assertFalse(result["vendor_authentication_qualified"]) + self.assertFalse(result["real_model_qualified"]) + self.assertEqual([len(cycle.records(bodies[name], order)) for name, order in zip(cycle.MEMBERS, (cycle.PUBLIC, cycle.CATALOG, cycle.PROOF))], [14, 20, 25]) + + def test_failed_prefix_keeps_unknown_native_body_all_false_and_missing_empty_aggregates(self): + bodies = container() + public = [json.loads(line) for line in bodies[cycle.MEMBERS[0]].splitlines()][:4] + catalog = [json.loads(bodies[cycle.MEMBERS[1]].splitlines()[0])] + catalog[0]["value"]["stdout"] = stream(b'{"unknownShippingShape":true}') + proofs = [json.loads(line) for line in bodies[cycle.MEMBERS[2]].splitlines()][:10] + for name, rows in zip(cycle.MEMBERS, (public, catalog, proofs)): + bodies[name] = b"".join(raw(row) + b"\n" for row in rows) + expected = summary_change(bodies, runtime_attempt_complete=False, test_failed=True, cycle_observed_complete=False, + cycle_stage="catalog:empty-outside", cycle_public_stages=list(cycle.PUBLIC[:4]), cycle_catalog_stages=["empty-outside"], cycle_log_runs=2, + qualification={key: False for key in cycle.FLAGS}) + self.assertEqual(cycle.members(bodies, SOURCE), expected) + self.assertEqual(cycle.stream(catalog[0]["value"]["stdout"]), b'{"unknownShippingShape":true}') + for name in cycle.MEMBERS[:2]: bodies.pop(name) + expected = summary_change(bodies, cycle_stage="prepare", cycle_public_stages=[], cycle_catalog_stages=[], cycle_log_runs=0) + self.assertEqual(cycle.members(bodies, SOURCE), expected) + + def test_late_failure_keeps_existing_cleanup_envelope_without_promoting_success(self): + bodies = container() + expected = summary_change(bodies, elapsed_ms=180001, runtime_attempt_complete=False, test_failed=True, + qualification={key: False for key in cycle.FLAGS}) + self.assertEqual(cycle.members(bodies, SOURCE), expected) + self.assertFalse(any(expected["qualification"].values())) + summary_change(bodies, elapsed_ms=195001) + with self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + bodies = container() + summary_change(bodies, elapsed_ms=150000) + with self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + + def test_summary_members_stages_types_flags_and_deadline_refuse(self): + for fault in ("mode", "client", "source", "auth", "model", "mcp", "bool-int", "late", "unknown", "updater", "log-count", "missing", "extra", "skipped", "reordered", "oversize", "duplicate-json"): + bodies = container() + value = json.loads(bodies["runtime-attempt.json"]) + if fault == "mode": value["capture_mode"] = "unknown" + elif fault == "client": value["client"] = "grok" + elif fault == "source": value["source_identity"]["installer_tree"] = "0" * 40 + elif fault == "auth": value["vendor_authentication_qualified"] = True + elif fault == "model": value["real_model_qualified"] = True + elif fault == "mcp": value["qualification"]["mcp-tools-list"] = True + elif fault == "bool-int": value["qualification"]["mcp-tools-call"] = 0 + elif fault == "late": value["elapsed_ms"] = 150001 + elif fault == "unknown": value["unapproved"] = False + elif fault == "updater": value["fixture_authored_updater_config"] = "/foreign/settings.json" + elif fault == "log-count": value["cycle_log_runs"] = 20 + elif fault == "missing": bodies.pop(cycle.MEMBERS[2]) + elif fault == "extra": bodies["unexpected.log"] = b"unknown" + elif fault == "oversize": bodies[cycle.MEMBERS[1]] = b"x" * ((1 << 20) + 1) + elif fault == "skipped": aggregate(bodies, 1, lambda rows: rows.pop(1)) + elif fault == "reordered": aggregate(bodies, 0, lambda rows: rows.reverse()) + bodies["runtime-attempt.json"] = raw(value) + if fault == "duplicate-json": bodies["runtime-attempt.json"] = raw(value).replace(b'"client":', b'"client":"qoder","client":', 1) + with self.subTest(fault=fault), self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + + def test_source_state_payload_absence_retention_and_noop_bytes_refuse(self): + for fault in ("source", "caller", "foreign", "registry", "profile-extra", "anonymous-mode", "binding", "view", "digest", "data-allocated", "owner", "missing-path", "missing-receipt", "noop-bytes", "noop-retained-bytes", "state-extra", "receipt"): + bodies = container() + def change(rows): + p = {row["stage"]: row["value"] for row in rows} + if fault == "source": p["source-a-a-two"]["digest"] = "sha256:" + "0" * 64 + elif fault == "caller": p["caller-a"]["body"] = stream(b"foreign") + elif fault == "foreign": p["original-user"]["path"] = "/foreign/plugin" + elif fault == "registry": p["original-registry"]["settings"] = stream(b"{}") + elif fault == "profile-extra": p["original-profile"]["inventory"]["unknown"] = "directory:20000000700" + elif fault == "anonymous-mode": p["original-profile"]["inventory"]["installation_id"] = "file:600:36:" + "0" * 64 + elif fault == "binding": p["update-a"]["binding"]["scope"] = "user" + elif fault == "view": p["update-a"]["delivery"]["view"] = "/foreign/view" + elif fault == "digest": p["add-a"]["binding"]["native_objects"][0]["managed_digest"] = "sha256:" + "0" * 64 + elif fault == "data-allocated": p["add-b"]["data"]["allocated"] = True + elif fault == "owner": p["remove-a"]["data"]["owner"] = stream(b"{}") + elif fault == "missing-path": p["missing-a"]["path"] += "/skills" + elif fault == "missing-receipt": p["missing-a"]["object"]["path"] = "/foreign" + elif fault == "noop-bytes": p["repeat-add-a"]["state"] = stream(base64.b64decode(p["repeat-add-a"]["state"]["body"]) + b" ") + elif fault == "noop-retained-bytes": + data = p["repeat-remove-a"]["data"] + owner = base64.b64decode(data["owner"]["body"]) + b" " + data["owner"] = stream(owner) + data["files"][".agentplugins-data-owner.json"] = "file:600:" + cycle.sha(owner) + elif fault == "state-extra": + value = json.loads(base64.b64decode(p["add-a"]["state"]["body"])) + value["environment_receipts"] = [dict(unapproved=True)] + p["add-a"]["state"] = stream(raw(value)) + else: p["intact-repair-a"]["binding"]["receipts"][-1]["sequence"] = True + aggregate(bodies, 2, change) + with self.subTest(fault=fault), self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + + def test_log_overlay_held_stat_manifest_time_identity_and_chain_refuse(self): + for fault in ("uid", "gid", "mode", "hardlink", "noncanonical", "target-hash", "target-length", "timestamp", "run-pid", "provenance", "argv", "manifest-hash", "log-empty", "before", "after", "delta-count", "delta-path", "extra", "overlap", "ledger"): + bodies = container() + def change(rows): + value = rows[0]["value"] + log = value["log"] + if fault in ("uid", "gid", "mode", "hardlink", "noncanonical", "target-hash", "target-length"): + pieces = log["latest_record"].split(":") + index, replacement = {"uid": (5, "1001"), "gid": (18, "1001"), "mode": (15, "40777"), "hardlink": (4, "2"), "noncanonical": (1, "01"), "target-hash": (25, "0" * 64), "target-length": (24, "1")}[fault] + pieces[index] = replacement + log["latest_record"] = ":".join(pieces) + elif fault == "timestamp": log["run"]["started_at"] = "2026-10-27T00:00:00.000Z" + elif fault == "run-pid": log["run"]["pid"] += 1 + elif fault == "provenance": log["run"]["pid_provenance"] = "supervisor" + elif fault == "argv": log["run"]["argv_matches"] = not log["run"]["argv_matches"] + elif fault == "manifest-hash": log["run"]["manifest_sha256"] = "0" * 64 + elif fault == "log-empty": log["log_bytes"] = 0 + elif fault == "before": log["before_sha256"] = "0" * 64 + elif fault == "after": log["after_sha256"] = "0" * 64 + elif fault == "delta-count": log["delta"]["changed"] += 1 + elif fault == "delta-path": log["delta"]["entries"][0]["key_sha256"] = "0" * 64 + elif fault == "extra": log["adopted"] = True + elif fault == "overlap": value["started_at"] = "2026-10-27T00:00:00Z" + if fault == "ledger": aggregate(bodies, 2, lambda rows: rows[-1]["value"].update(expected_sha256="0" * 64)) + else: aggregate(bodies, 1, change) + with self.subTest(fault=fault), self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + + def test_whole_catalog_disabled_controls_scope_and_same_removed_view_refuse(self): + for fault in ("foreign-missing", "foreign-enabled", "project-path", "resources", "stale-owned", "removed-argv", "extra"): + bodies = container() + def change(rows): + row = rows[0] if fault not in ("stale-owned", "removed-argv") else next(row for row in rows if row["stage"] == "removed-a") + value = row["value"] + actual = json.loads(base64.b64decode(value["stdout"]["body"])) + if fault == "foreign-missing": actual.pop() + elif fault == "foreign-enabled": actual[0]["enabled"] = True + elif fault == "project-path": actual[0]["projectPath"] = LAYOUT["b"] + elif fault == "resources": actual[0]["resources"]["mcpServers"] = [dict(name="foreign")] + elif fault == "stale-owned": actual.insert(0, dict(name=cycle.NAME)) + elif fault == "removed-argv": value["command"]["argv"][-1] = "/substitute-empty-view" + else: actual[0]["displayName"] = "unknown" + value["stdout"] = stream(raw(actual)) + aggregate(bodies, 1, change) + with self.subTest(fault=fault), self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + + def test_public_scope_source_mutation_and_operation_joins_refuse(self): + for fault in ("scope", "source", "mutated", "dry-run", "operation", "selected", "retention"): + bodies = container() + def change(rows): + stage = "update-a" if fault == "selected" else "remove-a" if fault == "retention" else "add-a" + value = next(row["value"] for row in rows if row["stage"] == stage) + actual = json.loads(base64.b64decode(value["stdout"]["body"])) + target = actual["data"]["targets"][0] + if fault == "scope": target["output"]["result"]["scope"] = "user" + elif fault == "source": target["output"]["tree_digest"] = "sha256:" + "0" * 64 + elif fault == "mutated": target["output"]["result"]["mutated"] = False + elif fault == "dry-run": actual["data"]["dry_run"] = True + elif fault == "operation": target["output"]["operation_id"] = "op-" + "0" * 32 + elif fault == "selected": target["selected"] = False + else: actual["data"]["retained_data"] = [] + value["stdout"] = stream(raw(actual)) + aggregate(bodies, 0, change) + with self.subTest(fault=fault), self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + + def test_measured_single_noop_and_grouped_lifecycle_sources_do_not_mix(self): + for fault in ("grouped-repeat", "single-add", "single-operation", "single-nochange", "repeat-source", "repair-source", "remove-source"): + bodies = container() + def change(rows): + stage = "add-a" if fault == "single-add" else "intact-repair-a" if fault == "repair-source" else "remove-a" if fault == "remove-source" else "repeat-add-a" + value = next(row["value"] for row in rows if row["stage"] == stage) + actual = json.loads(base64.b64decode(value["stdout"]["body"])) + if fault == "grouped-repeat": + output = actual["data"] + operation = "op-" + "0" * 32 + output["operation_id"] = operation + actual["data"] = dict(operation_id=operation, batch=True, status="completed", succeeded=1, failed=0, plugin=cycle.NAME, dry_run=False, + targets=[dict(target="qoder", status="external_completed", output=output)]) + elif fault == "single-add": actual["data"] = actual["data"]["targets"][0]["output"] + elif fault == "single-operation": actual["data"]["operation_id"] = "op-" + "0" * 32 + elif fault == "single-nochange": actual["data"]["result"]["no_change"] = False + elif fault == "repeat-source": actual["data"]["source"] = "local" + else: actual["data"]["targets"][0]["output"]["source"] = "direct local source" + value["stdout"] = stream(raw(actual)) + aggregate(bodies, 0, change) + with self.subTest(fault=fault), self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + + def test_raw_encoded_privacy_canonical_base64_and_duplicate_keys_refuse(self): + for body in (b"uap-qoder-private-inert", b'{"key":"\\u0075ap-qoder-private-inert"}', b'{}\n{"key":"\\u0075ap-qoder-private-inert"}\n'): + with self.assertRaises(ValueError): cycle.stream(stream(body)) + bodies = container() + aggregate(bodies, 2, lambda rows: rows[2]["value"].update(body=stream(body))) + with self.assertRaises(ValueError): cycle.members(bodies, SOURCE) + for field, value in (("bytes", True), ("body", "Zh=="), ("sha256", "0" * 64), ("extra", False)): + with self.subTest(field=field), self.assertRaises(ValueError): cycle.stream({**stream(b"f"), field: value}) + + def test_exact_selector_mode_dispatch_and_cline_default_marker_preserved(self): + case = runtime.CASES["qoder"] + self.assertEqual((case["test"], case["file"], case["seconds"], case["optin"]), ("TestAgentpluginsQoderNativeProjectCompleteSkillsCycle", "repotests/agentplugins_qoder_cycle_lifecycle_test.go", 180, "AGENTPLUGINS_QODER_PROJECT_RUNTIME_ATTEMPT")) + with self.assertRaises(ValueError): runtime.cli_attempt_members(container(), SOURCE, "grok") + bodies = container() + summary_change(bodies, capture_mode="unknown") + with self.assertRaises(ValueError): runtime.cli_attempt_members(bodies, SOURCE, "qoder") + self.assertTrue(runtime.cline_cycle_private(b"uap-cline-private-inert")) + self.assertFalse(runtime.cline_cycle_private(b"uap-qoder-private-inert")) + self.assertTrue(runtime.cline_cycle_private(b"uap-qoder-private-inert", marker=b"uap-qoder-private-")) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test_project_native_qoder_cycle_fixtures.py b/scripts/test_project_native_qoder_cycle_fixtures.py new file mode 100644 index 000000000..1b04cee02 --- /dev/null +++ b/scripts/test_project_native_qoder_cycle_fixtures.py @@ -0,0 +1,212 @@ +"""Authored finite parser inputs, never genuine hosted qualification evidence.""" +import base64 +import copy +from datetime import datetime, timedelta, timezone +import json + +import project_native_qoder_cycle as cycle +import project_native_qoder_cycle_logs as logs +import project_native_qoder_cycle_proof as proof + +SOURCE = dict(source_commit="a" * 40, source_tree="b" * 40) +ROOT, NONCE = "/inert/qoder-cycle", "1" * 32 +LAYOUT = dict(root=ROOT, home=ROOT + "/home", a=ROOT + "/project", b=ROOT + "/project-b", outside=ROOT + "/outside", installer="/held/installer", client="/held/qodercli", git="/held/git") + + +def raw(value): + return json.dumps(value, sort_keys=True, separators=(",", ":")).encode() + + +def stream(body): + return dict(bytes=len(body), sha256=cycle.sha(body), body=base64.b64encode(body).decode()) + + +def mutation(bound, group, sequence, kind, before="", after=""): + operation = group + "-001" + suffix, parent = cycle.sha(operation.encode())[:16], str(cycle.PurePosixPath(bound["target_locator"]).parent) + result = dict(operation_id=operation, operation_group_id=group, sequence=sequence, mutation_type=kind, client_binding_id=bound["client_binding_id"], active_path=bound["target_locator"], backup_path=parent + "/.agentplugins-backup-" + suffix, phase="committed") + if kind == "directory_swap": result["staging_path"] = parent + "/.agentplugins-staging-" + suffix + if before: result["before_digest"] = before + if after: result["after_digest"] = after + return result + + +def state_rows(): + result, current = {}, {} + for stage in proof.STATE_STAGES: + key = "b" if stage.endswith("-b") else "a" + if stage in ("repeat-add-a", "unchanged-update-a", "before-source-revision-a", "before-missing-a", "repeat-remove-a"): + result[stage] = copy.deepcopy(current[key]) + continue + revision = "b-one" if key == "b" else "a-two" if stage in ("update-a", "missing-repair-a", "remove-a") else "a-one" + files = proof.authored(NONCE, key, revision) + iid = key * 8 + "-1111-4111-8111-" + key * 12 + physical = cycle.NAME + "-" + cycle.sha(iid.encode())[:12] + native_base = LAYOUT[key] + "/.uap/managed/clients/qoder" + active = native_base + "/" + physical + bid = "client_" + cycle.sha("\0".join((iid, "qoder", "project", active)).encode())[:24] + group, stamp = "op-" + cycle.sha(stage.encode())[:32], "2026-10-28T00:00:00Z" + source = ROOT + "/cycle-source-" + key + source_digest, manifest_digest = proof.tree_digest(files, True), "sha256:" + cycle.sha(files["plugin.json"]) + binding = dict(client_binding_id=bid, client_id="qoder", scope="project", target_locator=active, physical_artifact_id=physical, materialization="materialized", activation="prepared", authentication="not_checked", policy="allowed", verification="installation_verified", affected_surfaces=["qoder"], native_profile_root=native_base, + package_revision=dict(version="1.0.0", tree_digest=source_digest, manifest_digest=manifest_digest), updated_at=stamp, + native_objects=[dict(object_id="package:qoder:" + physical, kind="managed_package_directory", logical_name=cycle.NAME, path=active, managed_digest=proof.tree_digest(proof.projected(NONCE, key, revision)), protection_class="managed")]) + removed = stage in ("remove-a", "remove-b") + prior = current.get(key) + if removed: + binding = copy.deepcopy(prior["binding"]) + else: + binding["receipts"] = copy.deepcopy(prior["binding"]["receipts"]) if prior else [] + before = prior["binding"]["native_objects"][0]["managed_digest"] if prior and stage != "missing-repair-a" else "" + binding["receipts"].append(mutation(binding, group, len(binding["receipts"]) + 1, "directory_swap", before, binding["native_objects"][0]["managed_digest"])) + installation = dict(installation_id=iid, declared_name=cycle.NAME, origin_mode="direct", operation_group_id=group, created_at=stamp, updated_at=stamp, + source=dict(source_binding_id="src_" + cycle.sha((source + "\0\0").encode())[:24], requested_source=source, canonical_source=source, resolved_revision="", tree_digest=source_digest), + package=dict(loader_kind="agent_plugins", format_id="agent-plugins/1.0.0", schema_uri=cycle.SCHEMA, declared_name=cycle.NAME, version="1.0.0", manifest_digest=manifest_digest, inventory=dict(mcp_present=False, mcp_enabled=False, skills=[cycle.NAME])), clients={} if removed else {bid: binding}) + payload = proof.projected(NONCE, key, revision) + delivery = dict(active=active, view=active + "/.agentplugins-qoder-native", files=proof.tree(payload, "644", "755"), manifest=stream(payload[".agentplugins-qoder-native/.qoder-plugin/plugin.json"]), skill=stream(files["skills/" + cycle.NAME + "/SKILL.md"])) + data_path = LAYOUT[key] + "/.uap/plugin-data/" + physical + data = dict(allocated=False, path=data_path, receipt=None, owner=None, files=None) + document = dict(schema_version=5, installations=[installation]) + if removed: + did = "data_" + cycle.sha("\0".join((iid, physical, "project")).encode())[:24] + receipt = dict(data_receipt_id=did, physical_backend_id=physical, scope="project", locator=data_path, state="owned", ownership_digest="sha256:" + cycle.sha(("agentplugins-plugin-data-v1\0" + did + "\0" + data_path).encode()), created_at=stamp, updated_at=stamp) + owner = raw(receipt) + b"\n" + installation.update(data_retained=True, data_receipts={did: receipt}) + data.update(allocated=True, receipt=receipt, owner=stream(owner), files=proof.tree({".agentplugins-data-owner.json": owner})) + delivery.update(files=None, manifest=None, skill=None) + document["transaction_receipts"] = [mutation(binding, group, len(binding["receipts"]) + 1, "directory_remove", binding["native_objects"][0]["managed_digest"])] + result[stage] = dict(scope=key, revision=revision, removed=removed, source_digest=source_digest, state=stream(raw(document)), binding=binding, delivery=delivery, data=data) + current[key] = result[stage] + return result + + +def proof_rows(): + rows = state_rows() + rows["fixture"] = dict(nonce=NONCE, layout=LAYOUT, owner_uid=1000, owner_gid=1000) + for key, revision in (("a", "a-one"), ("b", "b-one"), ("a", "a-two")): + files = proof.authored(NONCE, key, revision) + rows["source-" + key + "-" + revision] = dict(source=ROOT + "/cycle-source-" + key, digest=proof.tree_digest(files, True), files=proof.tree(files)) + for key in ("a", "b"): + body = ("CALLER_" + key + "_" + NONCE + "\n").encode() + rows["caller-" + key] = dict(path=LAYOUT[key] + "/.uap/plugin-data/caller-owned/keep.txt", body=stream(body), files=proof.tree({"keep.txt": body})) + registry, enabled, profile = {}, {}, {".": "directory:20000000700", ".auth": "directory:20000000700"} + for key in ("user", "a", "b"): + name, path, _, manifest, body, entry = proof.control(LAYOUT, NONCE, key) + files = {".qoder-plugin/plugin.json": manifest, "skills/" + name + "/SKILL.md": body} + rows["original-" + key] = dict(key=key, path=path, manifest=stream(manifest), skill=stream(body), files=proof.tree(files), entry=entry) + registry[name + "@local"], enabled[name + "@local"] = [entry], False + if key == "user": + for relative, value in proof.tree(files).items(): + profile["plugins/" + name + ("/" + relative if relative != "." else "")] = ("file:600:" + str(len(files[relative])) + ":" + cycle.sha(files[relative])) if relative in files else value + reg = raw(dict(version=2, plugins=registry)) + settings = raw(dict(general=dict(enableAutoUpdate=False), enabledPlugins=enabled)) + root = LAYOUT["home"] + "/.qoder" + rows["original-registry"] = dict(path=root + "/plugins/installed_plugins_v2.json", registry=stream(reg), settings_path=root + "/settings.json", settings=stream(settings)) + profile.update({"settings.json": "file:600:" + str(len(settings)) + ":" + cycle.sha(settings), "plugins": "directory:20000000700", "plugins/installed_plugins_v2.json": "file:600:" + str(len(reg)) + ":" + cycle.sha(reg), "installation_id": "file:644:36:" + cycle.sha(b"anonymous installation UUID fixture"), ".auth/machine_id": "file:600:36:" + cycle.sha(b"anonymous machine UUID fixture"), ".auth/.credential-transaction": "file:600:0:" + cycle.sha(b"")}) + for path in ("tmp", "tmp/telemetry", "tmp/telemetry/active-runs"): + profile[path] = "directory:20000000700" + rows["original-profile"] = dict(logs_absent=True, inventory=profile) + before = rows["before-missing-a"] + rows["missing-a"] = dict(path=before["delivery"]["active"], source_digest=before["source_digest"], object=before["binding"]["native_objects"][0], before=before["delivery"]["files"], absent=True) + return rows + + +def public_output(stage, selected): + verb = "repair" if "repair" in stage else stage.removeprefix("repeat-").removeprefix("unchanged-").split("-")[0] + ins = cycle.load(cycle.stream(selected["state"]))["installations"][0] + group = "op-" + cycle.sha(stage.encode())[:32] + output = dict(operation_id=group, plugin=cycle.NAME, version="1.0.0", source="local" if verb in ("repair", "remove") else "direct local source", tree_digest=selected["source_digest"], manifest_digest=ins["package"]["manifest_digest"], dry_run=False, + result=dict(installation_id=ins["installation_id"], requires_confirmation=False, mutated=stage not in ("repeat-add-a", "unchanged-update-a"))) + if stage == "repeat-add-a": + del output["operation_id"] + output["result"]["no_change"] = True + return raw(dict(schema_version=1, command="add", result="success", data=output)) + target = dict(target="qoder", status="external_completed", output=output) + if verb == "update": target["selected"] = True + data = dict(batch=True, status="data_retained" if verb == "remove" else "completed", succeeded=1, failed=0, plugin=cycle.NAME, dry_run=False, targets=[target], operation_id=group) + if verb == "remove": + receipt = selected["data"]["receipt"] + data.update(data_retained=True, plugin_data_preserved=True, retained_data=[{key: receipt[key] for key in ("data_receipt_id", "physical_backend_id", "scope", "state")}]) + return raw(dict(schema_version=1, command=verb, result="success", data=data)) + + +def native_catalog(stage, rows): + key = cycle.role(stage) + expected = [] + if cycle.has_flag(stage) and stage in ("installed-a", "installed-b", "stable-a", "updated-a", "updated-b", "repaired-a", "remaining-b"): + revision = "a-two" if stage in ("updated-a", "repaired-a") else key + "-one" + expected.append(dict(id=cycle.NAME + "@flag", name=cycle.NAME, source=cycle.NAME + "@inline", version="1.0.0", scope="flag", enabled=True, canDisable=False, installPath=rows["add-" + key]["delivery"]["view"], description=cycle.DESCRIPTION, + resources=dict(skills=[dict(name=cycle.NAME, description=f"Disposable Qoder {key} {revision} {proof.nonce(NONCE, key)}")], agents=[], mcpServers=[], commands=[], hooks=[]))) + for who in ("a", "b", "user"): + name, _, description, _, _, entry = proof.control(LAYOUT, NONCE, who) + expected.append(dict(id=name + "@local", name=name, source=name + "@local", enabled=False, canDisable=True, description=description, + resources=dict(skills=[dict(name=name, description=description)], agents=[], mcpServers=[], commands=[], hooks=[]), **entry)) + return raw(expected) + + +def log_record(expected, identifiers, when): + index = len(identifiers) + pid, text = 1000 + index, when.isoformat(timespec="milliseconds").replace("+00:00", "Z") + identity = when.strftime("%Y-%m-%dT%H-%M-%S-") + f"{when.microsecond // 1000:03d}Z-{index:x}-p{pid}" + target = "runs/" + identity + seconds = int(when.timestamp()) + def metadata(inode, mode, links, size): + return f"1:{inode}:{mode:o}:{links}:1000:1000:{size}:{seconds}:0:{seconds}:0" + latest = "symlink:" + metadata(2000 + index, 0o120777, 1, len(target)) + ":target:" + metadata(3000 + index, 0o40755, 2, 4096) + ":" + str(len(target)) + ":" + cycle.sha(target.encode()) + form = "direct" if index % 2 else "compiled-embedded" + run = dict(run_id=identity, latest_target=target, manifest_bytes=200 + index, manifest_sha256=cycle.sha(("manifest" + str(index)).encode()), pid=pid, ppid=999, started_at=text, + cli_version="1.1.78", platform="linux", arch="x64", process_role="main", pid_provenance="manifest-only", run_id_matches_manifest=True, cwd_matches=True, + argv_matches=form == "direct", exact_added_run_closure=True, preexisting_records_stable=True) + after = dict(expected) + if not identifiers: + after.update({"logs": "directory:20000000755", "logs/runs": "directory:20000000755"}) + path = "logs/runs/" + identity + logbytes, loghash = 123 + index, cycle.sha(("log" + str(index)).encode()) + after.update({path: "directory:20000000755", path + "/manifest.json": "file:644:" + str(run["manifest_bytes"]) + ":" + run["manifest_sha256"], + path + "/qodercli.log": "file:644:" + str(logbytes) + ":" + loghash, "logs/latest": latest}) + transition = dict(kind="one-run", before_sha256=cycle.sha(raw(expected)), after_sha256=cycle.sha(raw(after)), argv_form=form, latest_record=latest, run=run, delta=logs.delta(expected, after), log_bytes=logbytes, log_sha256=loghash) + identifiers.append(identity) + return transition, after + + +def container(): + """Full authored parser container, not native observation or release proof.""" + rows = proof_rows() + selected = {stage: {**row, "view": row["delivery"]["view"], "installation": json.loads(base64.b64decode(row["state"]["body"]))["installations"][0]} for stage, row in rows.items() if stage in proof.STATE_STAGES} + expected = dict(rows["original-profile"]["inventory"]) + original_hash, identifiers, public, catalogs = cycle.sha(raw(expected)), [], [], [] + origin = datetime(2026, 10, 28, 0, 0, 0, tzinfo=timezone.utc) + for index, (kind, stage) in enumerate(cycle.ORDER): + started = origin + timedelta(milliseconds=100 * index) + ended = started + timedelta(milliseconds=1) + if kind == "public": + cwd, argv = cycle.public_args(stage, LAYOUT, selected) + code, out, err = 0, b"", b"" + if stage == "version": out = b"1.1.78\n" + elif stage == "repeat-remove-a": code, err = 1, b'agentplugins: plugin is not installed for target "qoder" in project scope; no target was changed\n' + elif not stage.startswith("git-"): out, err = public_output(stage, rows[stage]), cycle.progress(stage) + else: + key = cycle.role(stage) + cwd, argv = LAYOUT[key], [LAYOUT["client"], "plugins", "list", "--json"] + if cycle.has_flag(stage): argv += ["--plugin-dir", rows["add-" + key]["delivery"]["view"]] + code, out, err = 0, native_catalog(stage, rows), b"" + if kind == "catalog" or stage == "version": + transition, expected = log_record(expected, identifiers, started) + else: + transition = dict(kind="unchanged", before_sha256=cycle.sha(raw(expected)), after_sha256=cycle.sha(raw(expected))) + value = dict(command=dict(argv=argv, cwd=cwd, exit=code, error=False, deadline=False, elapsed_ms=1, cleanup=cycle.CLEANUP), + started_at=started.isoformat(timespec="microseconds").replace("+00:00", "Z"), ended_at=ended.isoformat(timespec="microseconds").replace("+00:00", "Z"), stdout=stream(out), stderr=stream(err), log=transition) + (public if kind == "public" else catalogs).append(dict(stage=stage, value=value)) + rows["log-ledger"] = dict(original_sha256=original_hash, expected_sha256=cycle.sha(raw(expected)), runs=identifiers) + summary = dict(client="qoder", version="1.1.78", capture_mode=cycle.MODE, activation="prepared", vendor_authentication_qualified=False, real_model_qualified=False, + nonce=NONCE, layout=LAYOUT, fixture_authored_updater_config=LAYOUT["home"] + "/.qoder/settings.json", cycle_stage="complete-cycle", cycle_public_stages=list(cycle.PUBLIC), cycle_catalog_stages=list(cycle.CATALOG), cycle_log_runs=21, + cycle_observed_complete=True, runtime_attempt_complete=True, test_failed=False, elapsed_ms=5000, qualification={key: key in cycle.FLAGS[:2] for key in cycle.FLAGS}, + source_identity=dict(installer_base_commit=SOURCE["source_commit"], installer_tree=SOURCE["source_tree"], installer_source_state="committed", installer_patch_sha256=[]), + acquisition={"client": "qoder", "acquisition": {"sha256": "5e3213d2d4256c86c5ad2c8bf73fd392f92ba80d0d38f822806542805a48a8ea", "pin_verified": True, + "members": [{"Name": "qodercli", "Kind": "file", "Bytes": 182982112, "sha256": "0da0976e13fe00bc28f725d6fe7c2f0fe9fa9a8c37eeb2ee4e80ad4c0fc58953"}]}, "node": None, + "runtime_files": [{"path": LAYOUT["client"], "sha256": "0da0976e13fe00bc28f725d6fe7c2f0fe9fa9a8c37eeb2ee4e80ad4c0fc58953"}]}, + scanner={"archive_sha256": "2b3d176db752433b904a4b42375543ff398f4841d22e48f7d4f23ded925b72da", "binary_sha256": "b6723451f155eb49e84acb34d1f06de929bddfcff6238cd4e38cdc6519396fba", "platform": "linux-amd64", "route": "prepopulated supported scanner cache; ordinary CLI security assessment", "source_tag": "v0.1.3", "version": "0.1.3"}) + aggregates = public, catalogs, [dict(stage=stage, value=rows[stage]) for stage in cycle.PROOF] + result = {name: b"".join(raw(row) + b"\n" for row in captured) for name, captured in zip(cycle.MEMBERS, aggregates)} + result["runtime-attempt.json"] = raw(summary) + return result