@pi-in-go/pigpen-a2a 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CREDITS.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +97 -0
  4. package/extensions/a2a/auth.go +120 -0
  5. package/extensions/a2a/auth_test.go +133 -0
  6. package/extensions/a2a/bench_test.go +70 -0
  7. package/extensions/a2a/binary_test.go +225 -0
  8. package/extensions/a2a/cancelqueued_test.go +142 -0
  9. package/extensions/a2a/card_cache_test.go +42 -0
  10. package/extensions/a2a/client.go +499 -0
  11. package/extensions/a2a/client_test.go +402 -0
  12. package/extensions/a2a/config.go +282 -0
  13. package/extensions/a2a/config_test.go +194 -0
  14. package/extensions/a2a/e2e_test.go +311 -0
  15. package/extensions/a2a/executor.go +318 -0
  16. package/extensions/a2a/extension.go +382 -0
  17. package/extensions/a2a/extension_test.go +396 -0
  18. package/extensions/a2a/fakehost_test.go +548 -0
  19. package/extensions/a2a/fakepig_test.go +212 -0
  20. package/extensions/a2a/gaps_test.go +59 -0
  21. package/extensions/a2a/go.mod +15 -0
  22. package/extensions/a2a/go.sum +14 -0
  23. package/extensions/a2a/interop_test.go +163 -0
  24. package/extensions/a2a/procattr_other.go +25 -0
  25. package/extensions/a2a/procattr_windows.go +26 -0
  26. package/extensions/a2a/resubscribe_test.go +113 -0
  27. package/extensions/a2a/review_test.go +293 -0
  28. package/extensions/a2a/server.go +267 -0
  29. package/extensions/a2a/server_test.go +792 -0
  30. package/extensions/a2a/survivors_test.go +302 -0
  31. package/extensions/a2a/worker.go +388 -0
  32. package/extensions/a2a/worker_test.go +257 -0
  33. package/package.json +41 -0
  34. package/port/PORT.md +126 -0
  35. package/port/a2a-go-LICENSE +201 -0
  36. package/port/golden/flag-without-auth-refused.jsonl +5 -0
  37. package/port/golden/listener-off.jsonl +4 -0
  38. package/port/golden/send-missing-message.jsonl +18 -0
  39. package/port/golden/send-without-remotes.jsonl +18 -0
  40. package/port/golden/task-unknown-action.jsonl +18 -0
  41. package/port/golden/tools-visible-to-model.jsonl +11 -0
  42. package/port/interop/kagent/main.go +73 -0
  43. package/port/mutation-run.txt +222 -0
  44. package/port/mutations.json +656 -0
  45. package/port/red.txt +105 -0
  46. package/port/scenarios/flag-without-auth-refused.json +8 -0
  47. package/port/scenarios/listener-off.json +7 -0
  48. package/port/scenarios/send-missing-message.json +11 -0
  49. package/port/scenarios/send-without-remotes.json +11 -0
  50. package/port/scenarios/task-unknown-action.json +11 -0
  51. package/port/scenarios/tools-visible-to-model.json +8 -0
  52. package/provenance.json +18 -0
@@ -0,0 +1,257 @@
1
+ package a2aext
2
+
3
+ import (
4
+ "context"
5
+ "path/filepath"
6
+ "regexp"
7
+ "runtime"
8
+ "strings"
9
+ "testing"
10
+ "time"
11
+ )
12
+
13
+ var alice = Principal{Name: "alice", Tenant: "team-a"}
14
+
15
+ func newTestWorker(t *testing.T) (*ProcessWorker, string, string) {
16
+ t.Helper()
17
+ cfg, logPath, getenv := fakeWorkerConfig(t)
18
+ state := t.TempDir()
19
+ w, err := NewProcessWorker(cfg, state, getenv)
20
+ if err != nil {
21
+ t.Fatal(err)
22
+ }
23
+ return w, logPath, state
24
+ }
25
+
26
+ func TestSessionIDIsStableScopedAndSafe(t *testing.T) {
27
+ hex := regexp.MustCompile(`^[0-9a-f]{32,64}$`)
28
+ a := SessionID(alice, "ctx-1")
29
+ if !hex.MatchString(a) {
30
+ t.Fatalf("session id %q must be lowercase hex", a)
31
+ }
32
+ if a != SessionID(alice, "ctx-1") {
33
+ t.Fatal("the same context must map to the same session")
34
+ }
35
+ if a == SessionID(alice, "ctx-2") {
36
+ t.Fatal("different contexts must not share a session")
37
+ }
38
+ if a == SessionID(Principal{Name: "mallory", Tenant: "team-b"}, "ctx-1") {
39
+ t.Fatal("the same context id under another tenant must not share a session")
40
+ }
41
+ if id := SessionID(alice, "../../etc/passwd"); !hex.MatchString(id) {
42
+ t.Fatalf("a hostile context id must not reach the file name: %q", id)
43
+ }
44
+ }
45
+
46
+ func TestWorkerPassesFlagsSessionAndCwd(t *testing.T) {
47
+ w, logPath, state := newTestWorker(t)
48
+ res, err := w.Run(context.Background(), Turn{Principal: alice, ContextID: "c1", TaskID: "t1", Prompt: "hello there"}, func(Update) {})
49
+ if err != nil {
50
+ t.Fatal(err)
51
+ }
52
+ if res.Text != "echo: hello there" || res.Failure != "" {
53
+ t.Fatalf("result %+v", res)
54
+ }
55
+ l := readFakeLog(t, logPath)
56
+ for _, want := range []string{"--mode", "rpc", "--no-extensions", "--no-skills", "--no-context-files", "--no-prompt-templates", "--offline"} {
57
+ if !contains(l.Args, want) {
58
+ t.Errorf("argv lacks %s: %v", want, l.Args)
59
+ }
60
+ }
61
+ if contains(l.Args, "--no-session") {
62
+ t.Error("contexts continue across tasks, so the session must be kept")
63
+ }
64
+ if got := argValue(l.Args, "--tools"); got != "read,grep,find,ls" {
65
+ t.Errorf("--tools = %q", got)
66
+ }
67
+ if got := argValue(l.Args, "--session-id"); got != SessionID(alice, "c1") {
68
+ t.Errorf("--session-id = %q", got)
69
+ }
70
+ dir := argValue(l.Args, "--session-dir")
71
+ if !strings.HasPrefix(dir, state) || dir == state {
72
+ t.Errorf("--session-dir %q must be a per-principal directory under %q", dir, state)
73
+ }
74
+ if other := w.sessionDir(Principal{Name: "bob"}); other == dir {
75
+ t.Error("principals must not share a session directory")
76
+ }
77
+ if l.Prompt != "hello there" {
78
+ t.Errorf("prompt = %q", l.Prompt)
79
+ }
80
+ // The child reports the directory the OS resolved (macOS's TMPDIR is a symlink: /var -> /private/var).
81
+ if got, want := realPath(t, l.Cwd), realPath(t, w.cfg.Cwd); got != want {
82
+ t.Errorf("cwd = %q (%q), want %q (%q)", l.Cwd, got, w.cfg.Cwd, want)
83
+ }
84
+ }
85
+
86
+ // realPath is p with symlinks resolved, so two spellings of one directory compare equal.
87
+ func realPath(t *testing.T, p string) string {
88
+ t.Helper()
89
+ r, err := filepath.EvalSymlinks(p)
90
+ if err != nil {
91
+ t.Fatal(err)
92
+ }
93
+ return r
94
+ }
95
+
96
+ func TestWorkerModelAndExtraArgs(t *testing.T) {
97
+ cfg, logPath, getenv := fakeWorkerConfig(t)
98
+ cfg.Provider, cfg.Model, cfg.Args = "p", "m", []string{"--thinking", "off"}
99
+ w, err := NewProcessWorker(cfg, t.TempDir(), getenv)
100
+ if err != nil {
101
+ t.Fatal(err)
102
+ }
103
+ if _, err := w.Run(context.Background(), Turn{Principal: alice, ContextID: "c", Prompt: "x y"}, func(Update) {}); err != nil {
104
+ t.Fatal(err)
105
+ }
106
+ l := readFakeLog(t, logPath)
107
+ if argValue(l.Args, "--provider") != "p" || argValue(l.Args, "--model") != "m" || argValue(l.Args, "--thinking") != "off" {
108
+ t.Fatalf("argv %v", l.Args)
109
+ }
110
+ }
111
+
112
+ func TestWorkerEnvironmentIsAllowlisted(t *testing.T) {
113
+ w, logPath, _ := newTestWorker(t)
114
+ if _, err := w.Run(context.Background(), Turn{Principal: alice, ContextID: "c", Prompt: "ab"}, func(Update) {}); err != nil {
115
+ t.Fatal(err)
116
+ }
117
+ env := readFakeLog(t, logPath).Env
118
+ if env["PIG_A2A_WORKER"] != "1" {
119
+ t.Error("the child must be marked as a worker so it never opens a listener")
120
+ }
121
+ if env["OPENAI_API_KEY"] != "sk-passthrough" || env["PIG_HOME"] != "/pig/home" || env["PIG_CODING_AGENT_DIR"] != "/pig/agent" {
122
+ t.Errorf("configured passthrough and PiG's config directories must reach the child: %v", env)
123
+ }
124
+ for _, leak := range []string{"TOKEN_A", "SOMETHING_SECRET"} {
125
+ if _, ok := env[leak]; ok {
126
+ t.Errorf("%s leaked into the worker environment", leak)
127
+ }
128
+ }
129
+ }
130
+
131
+ func TestWorkerStreamsUpdatesAndFinalText(t *testing.T) {
132
+ w, _, _ := newTestWorker(t)
133
+ var text strings.Builder
134
+ var tools []string
135
+ res, err := w.Run(context.Background(), Turn{Principal: alice, ContextID: "c", Prompt: "TOOL please"}, func(u Update) {
136
+ text.WriteString(u.Text)
137
+ if u.Tool != "" {
138
+ tools = append(tools, u.Tool)
139
+ }
140
+ })
141
+ if err != nil {
142
+ t.Fatal(err)
143
+ }
144
+ if res.Text != "done" || text.String() != "done" || strings.Join(tools, ",") != "read" {
145
+ t.Fatalf("res %+v streamed %q tools %v", res, text.String(), tools)
146
+ }
147
+ }
148
+
149
+ func TestWorkerStreamedTextEqualsFinalTextWithoutDuplication(t *testing.T) {
150
+ w, _, _ := newTestWorker(t)
151
+ var text strings.Builder
152
+ res, err := w.Run(context.Background(), Turn{Principal: alice, ContextID: "c", Prompt: "abcdef"}, func(u Update) { text.WriteString(u.Text) })
153
+ if err != nil {
154
+ t.Fatal(err)
155
+ }
156
+ if text.String() != res.Text {
157
+ t.Fatalf("streamed %q, final %q", text.String(), res.Text)
158
+ }
159
+ }
160
+
161
+ func TestWorkerModelFailureIsAFailureWithoutProviderDetail(t *testing.T) {
162
+ w, _, _ := newTestWorker(t)
163
+ res, err := w.Run(context.Background(), Turn{Principal: alice, ContextID: "c", Prompt: "FAIL now"}, func(Update) {})
164
+ if err != nil {
165
+ t.Fatal(err)
166
+ }
167
+ if res.Failure == "" {
168
+ t.Fatal("a model error must fail the task")
169
+ }
170
+ if strings.Contains(res.Failure, "sk-123") {
171
+ t.Fatalf("provider error text can carry secrets; it must not reach the A2A peer: %q", res.Failure)
172
+ }
173
+ }
174
+
175
+ func TestWorkerCancelAbortsThenReaps(t *testing.T) {
176
+ if runtime.GOOS == "windows" {
177
+ t.Skip("process liveness probe is POSIX")
178
+ }
179
+ w, logPath, _ := newTestWorker(t)
180
+ ctx, cancel := context.WithCancel(context.Background())
181
+ done := make(chan error, 1)
182
+ go func() {
183
+ _, err := w.Run(ctx, Turn{Principal: alice, ContextID: "c", Prompt: "SLOW task"}, func(Update) {})
184
+ done <- err
185
+ }()
186
+ l := waitFakeLog(t, logPath, func(l fakeLog) bool { return l.Started })
187
+ cancel()
188
+ select {
189
+ case err := <-done:
190
+ if err == nil {
191
+ t.Fatal("cancellation must surface as an error")
192
+ }
193
+ case <-time.After(10 * time.Second):
194
+ t.Fatal("Run did not return after cancellation")
195
+ }
196
+ if l2 := readFakeLog(t, logPath); !l2.Aborted {
197
+ t.Error("the worker must send the RPC abort before killing anything")
198
+ }
199
+ if processAlive(l.PID) {
200
+ t.Error("the child process must be reaped")
201
+ }
202
+ }
203
+
204
+ func TestWorkerKillsAChildThatIgnoresAbort(t *testing.T) {
205
+ if runtime.GOOS == "windows" {
206
+ t.Skip("process liveness probe is POSIX")
207
+ }
208
+ w, logPath, _ := newTestWorker(t)
209
+ ctx, cancel := context.WithCancel(context.Background())
210
+ done := make(chan error, 1)
211
+ go func() {
212
+ _, err := w.Run(ctx, Turn{Principal: alice, ContextID: "c", Prompt: "IGNORE_ABORT"}, func(Update) {})
213
+ done <- err
214
+ }()
215
+ l := waitFakeLog(t, logPath, func(l fakeLog) bool { return l.Started })
216
+ cancel()
217
+ select {
218
+ case <-done:
219
+ case <-time.After(15 * time.Second):
220
+ t.Fatal("a child that ignores abort must be killed after the grace period")
221
+ }
222
+ deadline := time.Now().Add(5 * time.Second)
223
+ for processAlive(l.PID) && time.Now().Before(deadline) {
224
+ time.Sleep(20 * time.Millisecond)
225
+ }
226
+ if processAlive(l.PID) {
227
+ t.Fatal("child survived")
228
+ }
229
+ }
230
+
231
+ func TestWorkerCommandThatCannotStartIsReported(t *testing.T) {
232
+ cfg, _, getenv := fakeWorkerConfig(t)
233
+ cfg.Command = "/nonexistent/pig"
234
+ w, err := NewProcessWorker(cfg, t.TempDir(), getenv)
235
+ if err != nil {
236
+ return // refusing at construction is fine too
237
+ }
238
+ if _, err := w.Run(context.Background(), Turn{Principal: alice, ContextID: "c", Prompt: "x"}, func(Update) {}); err == nil {
239
+ t.Fatal("want an error")
240
+ }
241
+ }
242
+
243
+ func TestWorkerRefusesEmptyPrompt(t *testing.T) {
244
+ w, _, _ := newTestWorker(t)
245
+ if _, err := w.Run(context.Background(), Turn{Principal: alice, ContextID: "c", Prompt: " "}, func(Update) {}); err == nil {
246
+ t.Fatal("an empty prompt is not a task")
247
+ }
248
+ }
249
+
250
+ func contains(list []string, s string) bool {
251
+ for _, x := range list {
252
+ if x == s {
253
+ return true
254
+ }
255
+ }
256
+ return false
257
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@pi-in-go/pigpen-a2a",
3
+ "version": "0.1.0",
4
+ "description": "A2A (Agent2Agent) for PiG: serve PiG tasks to A2A agents and call remote A2A agents, on the upstream Go SDK a2a-go. The listener is off unless configured.",
5
+ "keywords": [
6
+ "pig-package",
7
+ "a2a",
8
+ "agent2agent",
9
+ "protocol",
10
+ "kagent",
11
+ "extension",
12
+ "pigpen"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "Michael Kinsy",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/MichaelKinsy/pigpen.git",
19
+ "directory": "components/a2a"
20
+ },
21
+ "homepage": "https://github.com/MichaelKinsy/pigpen/tree/main/components/a2a#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/MichaelKinsy/pigpen/issues"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "files": [
29
+ "extensions",
30
+ "port",
31
+ "README.md",
32
+ "LICENSE",
33
+ "CREDITS.md",
34
+ "provenance.json"
35
+ ],
36
+ "pi": {
37
+ "extensions": [
38
+ "extensions/a2a"
39
+ ]
40
+ }
41
+ }
package/port/PORT.md ADDED
@@ -0,0 +1,126 @@
1
+ # a2a: port record
2
+
3
+ **Kind of port (Skill, "What kind of port is this?"): 3, new code with no original.** a2a is an *original adapter* on the upstream
4
+ Go SDK [a2a-go](https://github.com/a2aproject/a2a-go) v2.6.0 (commit `ebf17c56ef7e63c72883a45454a538bbc0df66b8`, Apache-2.0). There
5
+ is no TypeScript or Pi original to twin, so there is **no Pi oracle and no equivalence claim**. The golden traces in `golden/` are
6
+ **self-recorded** regression baselines (lane `pig-go-self`): the port was recorded from itself and reviewed by hand, and `pigeq check`
7
+ labels them that way. What replaces the oracle for protocol facts is a2a-go's own contract plus an independent A2A peer (below).
8
+
9
+ ## Identities
10
+
11
+ | Item | Value |
12
+ |---|---|
13
+ | Package | `components/a2a` (module `github.com/MichaelKinsy/pigpen/a2a`, Go extension `a2a`), Piglet `piglets/a2a` |
14
+ | Upstream SDK | `github.com/a2aproject/a2a-go/v2` **v2.6.0**, A2A protocol **1.0** (JSON-RPC binding), Apache-2.0, `port/a2a-go-LICENSE` |
15
+ | Host | PiG 0.3.0 (commit `63c6ba456`), upstream Pi 0.87.1, go1.27.1 |
16
+ | Interop peer | kagent upstream `main` at `e4516302` (its Go ADK server package `go/adk/pkg/a2a/server`, which pins a2a-go v2.6.0) |
17
+ | Gap check | `pig docs list` / `pig docs show built-in-extensions` in 0.3.0: PiG ships no A2A. The older port map lists A2A remote sessions as "not ported (spec 482)". `pigeq gaps --go`: PASS (`port-gaps`), public SDK only, no process-global calls |
18
+
19
+ ## Gap check against the SDK surface
20
+
21
+ Everything the extension uses is fully provided by the 0.3.0 Go SDK: `sdk.New`, `RegisterTool`, `RegisterCommand`, `RegisterFlag`,
22
+ `OnEvent("session_start"|"session_shutdown")`, `Context.Notify`, `sdk.NewToolError`. No `PARTIAL` or `MISSING` (`pigeq gaps --go`).
23
+ Fused-extension rule found in `pig docs show extensions`: a fused extension shares PiG's process, so a2a discards the HTTP server's and
24
+ a2a-go's logs (`ErrorLog` and slog to `io.Discard`, `RecentLogs` ring instead) rather than writing to PiG's terminal.
25
+
26
+ ## Contract
27
+
28
+ Every behaviour is a row; each has a test and, where a mutation applies, a named mutant that the tests kill (`mutations.json`).
29
+
30
+ | # | Contract | Tests | Mutants |
31
+ |---|---|---|---|
32
+ | C01 | Listener is off unless configured; installing opens no port | `TestListenerIsOffUnlessConfigured`, `TestNoConfigurationOpensNoListener`, scenario `listener-off` | `listener-on-by-default` |
33
+ | C02 | Config: `a2a.json` in the agent dir, `PIG_A2A_CONFIG`, env `PIG_A2A_LISTEN`, flag `--a2a-listen` (flag > env > file); strict JSON | `TestConfigFileEnablesListener`, `TestPrecedenceFlagOverEnvOverFile`, `TestConfigPathFromEnv`, `TestConfigIsReadFromTheAgentDirectory`, `TestFlagOverridesTheConfigurationFile` | precedence, agent-dir, flag mutants |
34
+ | C03 | Secrets are named by env var, never inline; never echoed | `TestInlineSecretsAreRejected`, `TestTokenEnvMustBeSetAndIsNotEchoed`, `TestRemoteAuthFailureDoesNotEchoTheToken` | inline-secret, echo mutants |
35
+ | C04 | A listener needs authentication; `insecureNoAuth` only on loopback; not together with tokens; tokens ≥16 chars, unique names and values | `TestListenerNeedsAuthentication`, `TestInsecureNoAuthOnlyOnLoopback`, `TestTokenConfigValidation`, `TestTwoVariablesWithOneValueAreRejected`, `TestInsecureNoAuthWithTokensIsAmbiguous`, scenario `flag-without-auth-refused` | auth/loopback/duplicate mutants |
36
+ | C05 | Bearer tokens compared by digest, constant time; token in a query string ignored; 401 + `WWW-Authenticate: Bearer`; agent card public | `TestAuthenticateBearer`, `TestAuthenticateRejects`, `TestTokenInQueryStringIsIgnored`, `TestWrapRejectsWith401AndChallenge`, `TestWrapPassesPrincipalAndPublicPaths`, `TestUnauthenticatedRequestIsRejectedBeforeTheWorker` | compare, query, 401 mutants |
37
+ | C06 | Protocol pinned to A2A 1.0: `A2A-Version: 1.0` required (an absent header, which the specification reads as 0.3, is refused), other versions refused, repeated `1.0, 1.0` accepted (kagent's client), `1.0, 0.3` refused | `TestProtocolVersionIsPinned`, `TestMixedVersionHeaderIsRefused`, `TestAgentCardIsPublicAndPinnedToProtocol10` | version mutants |
38
+ | C07 | Agent card: name, Bearer scheme, streaming true, push false, one 1.0 JSON-RPC interface, external URL | `TestAgentCardIsPublicAndPinnedToProtocol10`, `TestAgentCardAdvertisesExternalURL` | card mutants |
39
+ | C08 | Task identity: one message is one task on one worker turn; result is a completed task with the reply as artifact | `TestSendMessageRunsATurnAndReturnsACompletedTask`, `TestE2E_TaskRunsRealPiGWithAToolCall` | `worker-keeps-extensions`, `worker-not-marked`, `tool-updates-dropped` |
40
+ | C09 | Context identity: a `contextId` is a PiG session; the same id continues it, across tasks | `TestSameContextIDContinuesTheSameWorkerContext`, `TestE2E_ContextContinuesAcrossTasksAndTenantsAreIsolated`, `TestSessionIDIsStableScopedAndSafe` | session-id mutants |
41
+ | C10 | Tenant isolation: contexts, tasks and sessions are scoped to the caller's principal; another caller cannot read, attach to or subscribe to a task | `TestTenantsDoNotShareContextsOrTasks`, `TestBobCannotAttachToAliceTaskWithAMessage`, `TestSubscribeToALiveTaskDeliversTheRest`, `TestPrincipalKeysIsolate`, `TestExecutorRefusesAForgedUser` | `principal-not-checked`, key mutants |
42
+ | C11 | A request's tenant must match the token's | `TestRequestTenantMustMatchTheTokensTenant` | `tenant-not-enforced` |
43
+ | C12 | Cancellation aborts the worker (RPC abort, grace, SIGTERM, SIGKILL of the process group), ends `canceled`, reaps the child | `TestCancelTaskAbortsTheWorkerAndEndsCanceled`, `TestWorkerCancelAbortsThenReaps`, `TestWorkerKillsAChildThatIgnoresAbort`, `TestWorkerAsksForTermBeforeKill`, `TestWorkerEscalatesToKillWhenTermIsIgnored`, `TestE2E_CancelAbortsRealPiG` | `no-terminate`, `no-kill-after-grace`, `abort-not-sent`, `cancel-does-not-stop-worker` |
44
+ | C13 | Streaming: artifact chunks arrive before completion; streamed text equals the final text | `TestStreamingDeliversArtifactChunksBeforeCompletion`, `TestWorkerStreamsUpdatesAndFinalText…` | stream mutants |
45
+ | C14 | Failures end `failed` without provider detail sent to the peer | `TestWorkerFailureBecomesAFailedTask`, `TestWorkerErrorDetailIsNotSentToThePeer`, `TestModelFailureMessageIsExactlyGeneric`, `TestWorkerModelFailureIsAFailureWithoutProviderDetail` | `model-error-text-copied` |
46
+ | C15 | Limits: concurrent tasks, one task at a time per context, task timeout, prompt 256 KiB, body 1 MiB | `TestConcurrencyLimitQueuesTasks`, `TestTasksInOneContextRunOneAtATime`, `TestTaskTimeoutFailsTheTask`, `TestOversizedPromptIsRefusedBelowTheBodyLimit`, `TestBodyLimitAppliesEvenWithASmallPrompt`, `TestOversizedRequestBodyIsRefused` | limit mutants |
47
+ | C16 | Only text parts; empty message refused; attachments refused, not dropped | `TestNonTextPartsAreRejected`, `TestEmptyMessageIsRejected`, `TestTextPlusFilePartIsRefusedNotTrimmed` | `file-parts-dropped` |
48
+ | C17 | Worker has no tools by default (`--no-tools`; PiG's file tools take absolute paths, so even `read` reaches every file the account can), configured tools are the allowlist, no extensions, no context files, own session dir, allowlisted env; the worker never opens a listener | `TestWorkerDefaultsHaveNoTools`, `TestReview_WorkerHasNoToolsUnlessConfigured`, `TestE2E_DefaultWorkerCannotReadFilesOutsideItsWorkspace`, `TestWorkerPassesFlagsSessionAndCwd`, `TestWorkerEnvironmentIsAllowlisted`, `TestWorkerProcessDoesNotOpenAListener`, `TestWorkerChildNeverListens` | tool/env mutants |
49
+ | C18 | A turn that needs interactive input or a rejected prompt fails, never hangs | `TestWorkerFailsATurnThatNeedsInteractiveInput`, `TestWorkerReportsARejectedPromptWithoutTheReason` | `ui-request-hangs`, `rejected-prompt-ignored` |
50
+ | C27 | A cancel that arrives before the worker starts (queued behind the concurrency limit, or behind another task of the same context) ends the task `canceled`, never starts a worker, and leaves the task ahead untouched | `TestCancelBeforeTheWorkerStartsBehindTheConcurrencyLimit`, `TestCancelBeforeTheWorkerStartsBehindTheSameContext` | `queued-cancel-ignored-at-slot`, `queued-cancel-ignored-at-context-lock` |
51
+ | C28 | An `insecureNoAuth` listener answers only a loopback `Host` (DNS rebinding); a token listener serves any `Host` (proxies) | `TestReview_InsecureLoopbackListenerRefusesAForeignHostHeader`, `TestReview_TokenListenerServesAnyHostHeader` | `insecure-host-unchecked` |
52
+ | C29 | The per-context lock is per principal (two tenants with one `contextId` do not queue behind each other); a process a turn left behind is killed | `TestReview_SameContextIDInTwoTenantsRunsConcurrently`, `TestReview_WorkerKillsWhatATurnLeftBehind` | `context-lock-shared-across-tenants`, `left-behind-child-survives` |
53
+ | C19 | Shutdown cancels running tasks, waits, and closes the listener; a second start and a busy port are errors; TLS load failure is an error | `TestShutdownCancelsRunningTasksAndStopsListening`, `TestStartFailsOnAnAddressInUse`, `TestSecondStartIsAnError`, `TestShutdownClosesThePortEvenRightAfterStart`, `TestTLSListener`, `TestUnreadableTLSKeyPairFailsStart` | lifecycle mutants |
54
+ | C20 | Client: named remotes, card resolution, direct endpoint (`skipCard`), pinned 1.0 JSON-RPC interface only, `A2A-Version: 1.0` sent | `TestCardResolvesAndSummarises`, `TestSkipCardUsesTheEndpointDirectly`, `TestCardWithoutAProtocol10InterfaceIsRefused`, `TestSendReusesContextAndSendsProtocolVersion` | client mutants |
55
+ | C21 | Client credentials go only to the configured origin, scheme and host (transport check and card pinning, two independent defences; https is never downgraded to http); redirects are not followed | `TestReview_CredentialsAreNotDowngradedToPlainHTTP`, `TestCredentialsAreNotSentToAHostTheCardNamed`, `TestCredentialTransportRefusesAnotherHost`, `TestRemoteRedirectsAreNotFollowed`, `TestInterop_KagentCardHostIsNotTrustedWithCredentials` | `transport-host-not-checked`, `transport-scheme-not-checked`, `card-scheme-downgrade-accepted`, `redirects-followed-with-credentials` |
56
+ | C22 | Client: cancelling the call (or its timeout) cancels the remote task; states failed/input-required reported; terminal task reported | `TestCancellingTheCallCancelsTheRemoteTask`, `TestSendHonoursTheConfiguredTimeout`, `TestFailedAndInputRequiredStates`, `TestSummaryOfAnInputRequiredTaskIsNotTerminal`, `TestSendToATerminalTaskStillReportsItsState`, `TestGetAndCancelTask` | client-cancel mutants |
57
+ | C23 | Tools `a2a_agents`, `a2a_send`, `a2a_task`, command `/a2a`; argument errors name the argument; no remotes says how to configure | `TestRegistersToolsCommandAndLifecycleHandlers`, `TestToolsRejectBadArguments`, `TestToolsWithoutRemotesSayWhy`, `TestSendToolPassesTheContextIDThrough`, `TestStatusListsRemotes`, scenarios `tools-visible-to-model`, `send-missing-message`, `send-without-remotes`, `task-unknown-action` | tool mutants |
58
+ | C24 | Lifecycle: new/resume/fork keep the listener, other reasons stop it; reload re-reads the configuration; invalid config is reported, not fatal | `TestSessionSwitchKeepsTheListenerReloadRestartsIt`, `TestReloadAppliesChangedConfiguration`, `TestToolAfterShutdownUsesTheRewrittenConfiguration`, `TestInvalidConfigurationIsReportedNotFatal`, `TestListenFailureIsReportedToTheUser` | lifecycle mutants |
59
+ | C26 | The Piglet Binary hosts the listener itself: configured, it serves the card publicly, refuses unauthenticated calls, runs a real worker (the same Binary) on a local model, isolates tenants; unconfigured or flag-only it stays off | `TestBinary_*` (3, `PIG_A2A_BINARY`) | (external) |
60
+ | C25 | Interop with kagent's A2A server package (as a remote for PiG) and a kagent-shaped client (a2a-go v2, repeated version header) against PiG's server | `TestInterop_*` (4), `scripts/interop-kagent.mjs` | (external) |
61
+
62
+ ## Named gaps (skipped tests, `gaps_test.go`)
63
+
64
+ Each is a `t.Skip` with a reason; none is silent. 13 gaps: `TestGap_GRPCTransportBinding`, `TestGap_RESTTransportBinding`, `TestGap_PushNotifications`,
65
+ `TestGap_ProtocolV03Compatibility`, `TestGap_ExtendedAgentCard`, `TestGap_PersistentTaskStore`, `TestGap_InputRequiredFromPiG`,
66
+ `TestGap_FileAndDataParts`, `TestGap_OAuth2AndMutualTLSAuthentication`, `TestGap_SignedAgentCards`, `TestGap_SubscribeToTaskAfterRestart`,
67
+ `TestGap_ClientStreamingProgressToTheModel`, `TestGap_ClientResubscribeTool`. Conditional skips (not gaps), each with a named reason: three kagent tests unless `KAGENT_A2A_ECHO` is set (`scripts/interop-kagent.mjs`), four e2e tests unless `PIG_A2A_E2E_BIN` is set, four Binary tests unless `PIG_A2A_BINARY` is set.
68
+
69
+ ## Deviations from the Skill (recorded, not hidden)
70
+
71
+ - **Red-green.** RED commit `16ddb5c` (74 tests fail on a signature stub; `red.txt`), then GREEN. Two RED tests were fixed in GREEN because the *test double* was wrong, not
72
+ the expectation: the fake remote lacked an authenticated user for a2a-go's task store, and one cancel test raced the stream's first event (it now waits for it). Tests were added
73
+ after the first mutation run (survivors below); they only add assertions.
74
+ - **No twins.** There is no upstream suite; a2a-go's conformance behaviour is exercised through its own client against the server, and the fake remote (an independent
75
+ `a2asrv` server) against the client.
76
+ - **Golden traces** are self-recorded (`pig-go-self`), six scenarios, hand reviewed.
77
+
78
+ ## Host findings (for the porter and PiG)
79
+
80
+ - The fake-host template's `Command` sent the wrong request shape (fixed in the porter branch); a tool's own argument check runs, since the host does not validate schemas in the fake.
81
+ - A stale `go.mod` passes workspace tests but fails PiG's packed build (missing `go.sum` entries): always `go mod tidy` with the full source, then build a Binary.
82
+ - kagent's ADK server hard-codes its readiness port `:8081`; kagent's client repeats the `A2A-Version` header (`1.0, 1.0`); a2a-go's in-memory task store lists tasks only for an authenticated user.
83
+ - **a2a-go v2.6.0 defect (reported here, not fixed upstream):** `SubscribeToTask` on a *live* task attaches to the task's event queue without an ownership check
84
+ (`a2asrv/handler.go`, `execManager.Resubscribe` before `taskStore.Get`), so any authenticated caller who knows a task id can read another tenant's live stream. `GetTask`,
85
+ `CancelTask` and `ListTasks` are masked. The port owns the task store and checks ownership in its call interceptor (`guard`); `TestSubscribeToALiveTaskDeliversTheRest`
86
+ and mutant `subscribe-ownership-unchecked` pin it. Found only by writing the roadmap's "stream resubscription" item with a second principal.
87
+ - a2a-go's client ends the event stream with an error on cancel, so a "cancel after clean end" branch would be dead code (removed).
88
+ - `go.work` written by `pigeq` uses `go 1.26`, which is below `go 1.26.0`; needed a local fix.
89
+ - PiG 0.3.0's `read`, `grep`, `find` and `ls` resolve absolute paths and `~` outside the working directory
90
+ (`internal/codingagent/tools/path_utils.go` `resolveToCwd`), so a tool allowlist is not a file-access boundary. The worker
91
+ therefore defaults to `--no-tools`; a PiG option that confines file tools to the working directory would let a served
92
+ worker have read tools safely.
93
+
94
+ ## What PiG 0.4.0 (Pi 0.99.1) will change
95
+
96
+ Not built or run against 0.4.0. Expected: the SDK module path and `sdk.New` surface are the public API this port already uses, so a rebuild is the test; the worker's RPC
97
+ protocol (`--mode rpc` JSONL: `prompt`, `abort`, `message_update`, `agent_end`, `extension_ui_request`) and flags (`--session-dir`, `--session-id`, `--tools`, `--no-*`) are read by
98
+ `worker.go` and are the surface to re-check. The kagent interop does not depend on a PiG version (it speaks A2A only).
99
+
100
+ ## Proof results
101
+
102
+ Host: PiG 0.3.0+0.87.1 built from `63c6ba456`; Piglet Binary `piglets/a2a` built with `pig piglet build --format binary` from the staged tree
103
+ (58.9 MB, fused, a2a-go linked; the Binary is not committed). Lane build: sha256 `17eff7afd824d1b5…`; review rebuild after the fixes: sha256 `8e0f00bd44bbdab7…`.
104
+
105
+ | Layer | Result |
106
+ |---|---|
107
+ | Unit tests, `go test -race -count=3` | 122 pass, 24 skip (13 named gaps, 11 conditional), 0 fail (review re-run; the lane's run was 116 pass, 22 skip); also `GOMAXPROCS=4 taskset -c 0-3 -race -count=24`: pass (a first 24-run found a real race: `Server.Shutdown` could return before the Serve goroutine had closed the port; fixed, pinned by `TestShutdownClosesThePortEvenRightAfterStart`); `-count=8` under 6 CPU burners: pass |
108
+ | Real PiG worker end to end (`PIG_A2A_E2E_BIN`) | 4 pass with `pig` 0.3.0 and with the Binary: tool call (tools named), context continuity, tenant isolation, cancel reaps the child, and a worker configured only by `a2a.json` cannot read a file outside its workspace (this last test failed on the lane's code: the default `read` tool returned the file to the peer) |
109
+ | The Binary hosts the listener (`PIG_A2A_BINARY`) | 4 pass: configured (card public, 401, task on a real worker that is the same Binary, the tools named in `a2a.json` and no others, tenant isolation), off without configuration, flag without tokens refused, and its default worker cannot read outside its workspace (fails against the lane's Binary, passes against the review's) |
110
+ | kagent interop (`KAGENT_A2A_ECHO`, `scripts/interop-kagent.mjs`, kagent upstream `e4516302`) | 4 pass: kagent's own A2A server package as a remote for PiG (send, stream, cancel; credentials not sent to the card's host), a kagent-shaped client (a2a-go v2, repeated version header) against PiG's server. **No live kagent cluster**: `docker` and `kubectl` exist here, `kind` does not |
111
+ | Host scenarios, `pigeq check` (self-recorded, `pig-go-self`) | 6 pass + `port-gaps` + `exec-coverage` |
112
+ | Mutation (`mutation-run.txt`) | **109 mutants, 109 killed, 0 survived** (review re-run with an exact find/replace runner; the review replaced `default-tools-not-read-only` with `worker-gets-pig-default-tools` and added six: `insecure-host-unchecked`, `transport-scheme-not-checked`, `card-scheme-downgrade-accepted`, `context-lock-shared-across-tenants` and `left-behind-child-survives`, the last two surviving the lane's tests). Lane's run, `pigeq mutate --unit-only --jobs 4`: 104/104. First run: 65 killed of 102; after adding tests (`survivors_test.go` and additions) and fixing 6 mutants that did not compile: 96/100; the last 5 (plus one new mutant for the SubscribeToTask defect) killed. One mutant judged equivalent and dropped (`cancel-does-not-wait`: a2a-go's cancel already waits for the executor). Two dropped as dead code (`cancel-after-stream-end-missed`) or equivalent (`shutdown-leaves-workers`) |
113
+ | `go vet` | linux, windows, darwin |
114
+ | Repository | `npm run generate`/`check`, `npm test` (62), `npm run validate`, `npm run test:go-ports -- -race -count=3`: run against the porter-driver's scripts (this branch keeps the shared scripts at base, so merge the porter-driver branch first), all pass. `pig install components/a2a/extensions/a2a --validate-only`: packable |
115
+
116
+ ### Mutants (109)
117
+
118
+ `listener-starts-without-config`, `worker-child-may-listen`, `flag-not-highest-precedence`, `env-not-over-file`, `unknown-keys-and-inline-secrets-accepted`, `listener-without-tokens-allowed`, `insecure-no-auth-off-loopback`, `insecure-and-tokens-both`, `short-tokens-allowed`, `duplicate-token-values-allowed`, `duplicate-token-names-allowed`, `tenant-not-validated`, `missing-token-env-allowed`, `listen-not-host-port`, `worker-gets-pig-default-tools`, `remote-url-scheme-unchecked`, `remote-name-unchecked`, `basic-scheme-accepted`, `extra-fields-accepted`, `token-comparison-inverted`, `insecure-flag-ignored`, `tenant-key-uses-token-name`, `key-namespaces-collide`, `everything-public`, `no-bearer-challenge`, `empty-authenticator-allowed`, `file-parts-dropped`, `empty-prompt-accepted`, `prompt-size-unbounded`, `timeout-never-fires`, `worker-error-detail-leaks`, `context-not-serialised`, `concurrency-limit-ignored`, `cancel-does-not-stop-worker`, `failure-reason-dropped`, `principal-not-checked`, `version-not-enforced`, `absent-version-accepted`, `minor-versions-accepted`, `mixed-version-values-accepted`, `tenant-not-enforced`, `user-not-attached`, `body-size-unbounded`, `card-requires-auth`, `card-drops-streaming`, `card-claims-push`, `card-ignores-external-url`, `card-without-security-requirement`, `plain-http-on-tls`, `second-start-allowed`, `worker-keeps-extensions`, `worker-not-marked`, `worker-inherits-secrets`, `passenv-ignored`, `session-id-ignores-principal`, `session-id-ignores-context`, `session-dir-shared`, `worker-cwd-not-set`, `tools-allowlist-dropped`, `session-not-kept`, `abort-not-sent`, `no-kill-after-grace`, `no-terminate`, `model-error-text-copied`, `model-error-not-a-failure`, `streamed-and-final-text-duplicated`, `empty-prompt-sent`, `tool-updates-dropped`, `ui-request-hangs`, `rejected-prompt-ignored`, `any-protocol-version-accepted`, `credentials-follow-the-card`, `transport-host-not-checked`, `redirects-followed-with-credentials`, `bearer-not-sent`, `missing-bearer-env-tolerated`, `custom-headers-dropped`, `remote-not-cancelled-on-abort`, `timeout-ignored`, `unknown-agent-unnamed`, `names-unsorted`, `empty-message-sent`, `input-required-called-terminal`, `failure-text-hidden`, `auth-error-shows-token-context`, `switch-drops-listener`, `shutdown-keeps-listener`, `config-error-silent`, `start-failure-silent`, `flag-not-read`, `agent-dir-ignored`, `stop-does-not-reload-config`, `status-hides-remotes`, `tool-required-not-enforced`, `tool-empty-message-allowed`, `tool-errors-not-errors`, `unfinished-task-not-flagged`, `task-action-unchecked`, `send-drops-context`, `tls-load-failure-ignored`, `subscribe-ownership-unchecked`, `queued-cancel-ignored-at-slot`, `queued-cancel-ignored-at-context-lock`, `shutdown-does-not-close-the-port`, `insecure-host-unchecked`, `transport-scheme-not-checked`, `card-scheme-downgrade-accepted`, `context-lock-shared-across-tenants`, `left-behind-child-survives`
119
+
120
+
121
+ ## Re-verified on PiG 0.4.1 (porter-verify)
122
+
123
+ The golden traces were recorded again from the port itself (`--self`, a regression baseline), with PiG 0.4.1 content (`5f948f86a`, `pig --version`
124
+ `0.3.1+1.0.1`) and normalizer v3, because Pi 1.0.x changed the trace format: a `prompt` response now carries
125
+ `data.disposition` (Pi 0.99.0, #9098). An event-by-event diff against the previous traces shows no other
126
+ difference, and `pigeq check` passes on the Go port. Details: `docs/plan/progress/porter-verify.md`.
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,5 @@
1
+ {"kind":"header","scenario":"flag-without-auth-refused","lane":"pig-go-self","host":"pig 0.3.1+1.0.1","extension":"sha256:766f7013020ffc915c35c324d2997a21f7274842e4e3a5c598801682e171d7fd","normalizer":"v3"}
2
+ {"step":"00-startup","ch":"ui","data":{"message":"a2a: a listener needs at least one entry in tokens (or insecureNoAuth on a loopback address)","method":"notify","notifyType":"error"}}
3
+ {"step":"01-status","ch":"ui","data":{"message":"a2a: listener off (a2a: a listener needs at least one entry in tokens (or insecureNoAuth on a loopback address))","method":"notify","notifyType":"info"}}
4
+ {"step":"01-status","ch":"response","data":{"command":"prompt","data":{"disposition":"handled"},"success":true}}
5
+ {"step":"99-shutdown","ch":"exit","data":{"code":0}}
@@ -0,0 +1,4 @@
1
+ {"kind":"header","scenario":"listener-off","lane":"pig-go-self","host":"pig 0.3.1+1.0.1","extension":"sha256:766f7013020ffc915c35c324d2997a21f7274842e4e3a5c598801682e171d7fd","normalizer":"v3"}
2
+ {"step":"01-status","ch":"ui","data":{"message":"a2a: listener off (set listen in a2a.json, PIG_A2A_LISTEN or --a2a-listen to serve tasks)","method":"notify","notifyType":"info"}}
3
+ {"step":"01-status","ch":"response","data":{"command":"prompt","data":{"disposition":"handled"},"success":true}}
4
+ {"step":"99-shutdown","ch":"exit","data":{"code":0}}