diff --git a/BUILD-LOG.md b/BUILD-LOG.md index cba839b6..ce33c152 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -1898,3 +1898,7 @@ Independent R1 SUPPORTED the Gate 7 reconciliation; Jason answered yes to all th ## 2026-09-10 — registry plan fully resolved ms-grill-me rounds resolved gates 2/3/4/8/9/10 and the per-execution selection model; PI-REFRESH-ROCKO-1 established the 0.85.1 noninteractive refresh path read-only; owner Q15(a) selected pi auth check; GATE7-FINAL-FILBERT-1 APPROVED and corroborated the findings against the pinned 0.84.4 install. Committed 224147ec and 86e009dd (with intervening registration commits), pushed, remote identities verified. All ten gates carry owner rulings. Implementation awaits a separately chartered increment; pi pin moves to 0.85.1 within that increment. + +## 2026-09-10 — #1499 increment 1 implemented (pre-review) + +packages/mosaic added: provider/account/profile/selection/harness schemas with strict unknown-field and ID validation, allowInsecureTransport rule (plain-http custom endpoints only), fork-pin field, harness id==executable rule (gate 1), registry tree loader with cross-reference integrity, read-only validate/list CLI with secret-free projections. 15 package tests pass. Pi pin moved 0.84.4 -> 0.85.1 (package.json, lockfile regenerated, three fixture scripts updated). Suites after pin: config/auth/conductor/release green; task suite shows exactly the one pre-existing failure also present at stashed baseline (no agent identity on headless run); one earlier mock-adapter failure did not recur on rerun and is recorded as flaky-unreproduced. Implementation awaits independent review before commit/push consideration per charter; no materializer/refresh/credential paths exist in this code. diff --git a/package-lock.json b/package-lock.json index 07d09169..24441634 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,25 @@ { "name": "mosaic-stack-dev-test", - "version": "0.1.0", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mosaic-stack-dev-test", - "version": "0.1.0", + "version": "0.0.3", "license": "UNLICENSED", "dependencies": { - "@earendil-works/pi-coding-agent": "0.84.4" + "@earendil-works/pi-coding-agent": "0.85.1" } }, "node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.123.0.tgz", + "integrity": "sha512-Y9oX9mPNGZClHQOFqrWRk43Srcu/UHuPq3rfxxOq7JgW0gi+lJA2MAOK4Ul3k/+AUrwRWFJvd0tK3oC0Pw25dw==", "license": "MIT", "dependencies": { - "json-schema-to-ts": "^3.1.1" + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" }, "bin": { "anthropic-ai-sdk": "bin/cli" @@ -107,9 +108,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.977.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.9.tgz", - "integrity": "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==", + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.0.tgz", + "integrity": "sha512-2yX9LUmxPklVjSGTb8dfnWRJSiFQ3TeH2nn7G1mdKHTfnabzF0+gfrS8rYfLWmZrQ8A3mEcxMJjRc51dL5KWaA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.974.5", @@ -126,12 +127,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.70.tgz", - "integrity": "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==", + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.71.tgz", + "integrity": "sha512-JN+JHruYZw3GUZB8YGAlDk4wTDPOEAEEdEzj5nS0xodWR4smzHsN7PnK2j6IeOsDIj2aqua5DSbhXl9Gtf90FQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -142,12 +143,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.72", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.72.tgz", - "integrity": "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==", + "version": "3.972.73", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.73.tgz", + "integrity": "sha512-uyYYnJOnlis8uQzaYGPd7N1JoioCoNpXgnkXYixsWJXHXgXyYi8WXJSDfofxJeWfQIGWLe2Nwyq60Uc7MZdVOg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", @@ -160,13 +161,13 @@ } }, "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.0.tgz", - "integrity": "sha512-0mq1pHadfyXCYCqm2cNpbjNIT+fbaUpNxewZb/YNr2L0IrEVMOb8gM/Fl4K6XvHCW3uSNDFwPl/+iKm0bx9jYg==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@smithy/types": "^4.18.0", "tslib": "^2.6.2" }, "engines": { @@ -174,19 +175,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.15.tgz", - "integrity": "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==", + "version": "3.973.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.16.tgz", + "integrity": "sha512-i++ly+0Uxa+u3ebSSyr0S/3CFhFJDxCXT3+Zj+mW2bXenEx5bKGCdTIKFu39SgXBNhWDjex/8cXUx9MUTMCrTw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/credential-provider-env": "^3.972.70", - "@aws-sdk/credential-provider-http": "^3.972.72", - "@aws-sdk/credential-provider-login": "^3.972.77", - "@aws-sdk/credential-provider-process": "^3.972.70", - "@aws-sdk/credential-provider-sso": "^3.973.14", - "@aws-sdk/credential-provider-web-identity": "^3.972.76", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/credential-provider-env": "^3.972.71", + "@aws-sdk/credential-provider-http": "^3.972.73", + "@aws-sdk/credential-provider-login": "^3.972.78", + "@aws-sdk/credential-provider-process": "^3.972.71", + "@aws-sdk/credential-provider-sso": "^3.973.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.77", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", @@ -198,13 +199,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.77", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.77.tgz", - "integrity": "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==", + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.78.tgz", + "integrity": "sha512-eUtswnXu0+Ii9ieRK+0L7aPFV3Z/dnW2VntJzjBP9xs8s+8p5nBNuymIXtXwZ+5r5+XJP3e32nMkuZ/r0HozEA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -215,17 +216,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.82", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.82.tgz", - "integrity": "sha512-znDkEOGXB8W3kG1LJUKP3foBZY/9qLM0eil/DxWXSp37XsdsRLQHE/d/OaCGGVgKpA6znR38h/+INk8do1FjiA==", + "version": "3.972.83", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.83.tgz", + "integrity": "sha512-jdso7ejzfRnatxMUZK4S/U6KbaDPCvfIV4XL+IQAPFDBt5rj5Fq595euqlK8Le4lNCMFR9oUpt+1l0aMgaayOQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.70", - "@aws-sdk/credential-provider-http": "^3.972.72", - "@aws-sdk/credential-provider-ini": "^3.973.15", - "@aws-sdk/credential-provider-process": "^3.972.70", - "@aws-sdk/credential-provider-sso": "^3.973.14", - "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/credential-provider-env": "^3.972.71", + "@aws-sdk/credential-provider-http": "^3.972.73", + "@aws-sdk/credential-provider-ini": "^3.973.16", + "@aws-sdk/credential-provider-process": "^3.972.71", + "@aws-sdk/credential-provider-sso": "^3.973.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.77", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", @@ -237,12 +238,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.70.tgz", - "integrity": "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==", + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.71.tgz", + "integrity": "sha512-lYmXJa4gvq4xN1lrT5NiP5vIYYKcGWAdj8y+8o6dlcateB5eF3Dn8DtmjjHKfMBrTPAMr2pebIiX/UOj8c1/UA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -253,14 +254,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.14.tgz", - "integrity": "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==", + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.15.tgz", + "integrity": "sha512-6Jhcf4v0pSFdjk1EW2kvzuEBKD+UZ2uNcHUIglKKLndD20YhvkL2kdmDOV5/j4mYuWWwe/a1FQ1aomU86/Cg5Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", - "@aws-sdk/token-providers": "3.1116.0", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/token-providers": "3.1129.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -271,13 +272,13 @@ } }, "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1116.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1116.0.tgz", - "integrity": "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==", + "version": "3.1129.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1129.0.tgz", + "integrity": "sha512-Sbl3rpzQdsG4ZK2zh0JWUYyZPKKorJlVOddA2T0DVbKJFrsW8J6wgnslxxUH04+WaBMr4A1HzJZvZX0xUvkniA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -288,13 +289,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.76", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.76.tgz", - "integrity": "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==", + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.77.tgz", + "integrity": "sha512-uylIQSUWpfLuH2LovxEEfwzJGM/SabLOfLMg6YXu/E8jJEKUdpdILCVCQCdFvHyu/7dLJOHPMfrSwduxO56NkQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -335,12 +336,12 @@ } }, "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.52", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.52.tgz", - "integrity": "sha512-vsPPM+nMbKJlUCFU+eoGZbdxdxDIAX9LbpjSXaR5Ufpmqgp8TdYQnoExhLu4T3umW/JIIPny1ydbhWidZZYokQ==", + "version": "3.972.53", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.53.tgz", + "integrity": "sha512-bIrDaMENQmYRBHntOiOheqkiw5+fhKW4Lqb+mS1uqF0VwvdWI22fW2HFgWrng66CmYd+4k8ePlpj38sEfTuMLQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", @@ -353,12 +354,12 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.44", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.44.tgz", - "integrity": "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==", + "version": "3.997.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.45.tgz", + "integrity": "sha512-mooq9Q+jLa18VoM7HouczmslZU60iiB0aKc/Ztnq/luIL1ud0z4DnYprLR/ZO1gp331S9tJctM1HZr7u6YKBXQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/signature-v4-multi-region": "^3.996.46", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", @@ -372,13 +373,13 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.0.tgz", - "integrity": "sha512-0mq1pHadfyXCYCqm2cNpbjNIT+fbaUpNxewZb/YNr2L0IrEVMOb8gM/Fl4K6XvHCW3uSNDFwPl/+iKm0bx9jYg==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@smithy/types": "^4.18.0", "tslib": "^2.6.2" }, "engines": { @@ -473,14 +474,27 @@ "node": ">=6.9.0" } }, - "node_modules/@earendil-works/pi-agent-core": { - "version": "0.84.4", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.84.4.tgz", - "integrity": "sha512-HyUnjaOXj6oN/6SNcr8A1J/ElRQA50FtIE0XUTSKAQVqmdlb9qdojOyUQwF/jULE5+yOEtGuVgi/N1RnBiNG+g==", + "node_modules/@earendil-works/chord": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@earendil-works/chord/-/chord-0.85.1.tgz", + "integrity": "sha512-VDlkEC3dhCzQ5fcyH1OhG19dq+6jCn+rqc/iXFivwDYGR5anwo2RCiXij9PpHhqNR5GuhhE+Er69Zi1Sn4eY6w==", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "^0.84.4", - "@earendil-works/pi-telemetry": "^0.84.4", + "esbuild": "0.28.1" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-agent-core": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.85.1.tgz", + "integrity": "sha512-hIXIP3eAWueAYiAl8aMvWCvvZ8Q5gT3Dip5bE5uJyIGh4+YlWRjtMLI4BaeoXoSs93zndjue61u1B/vhefLnuA==", + "license": "MIT", + "dependencies": { + "@earendil-works/chord": "^0.85.1", + "@earendil-works/pi-ai": "^0.85.1", + "@earendil-works/pi-telemetry": "^0.85.1", "diff": "8.0.4", "ignore": "7.0.5", "typebox": "1.3.7", @@ -491,14 +505,14 @@ } }, "node_modules/@earendil-works/pi-ai": { - "version": "0.84.4", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.84.4.tgz", - "integrity": "sha512-AClAZxf5+c4RRu44NJPS6wyQy+Nmq+Mzyyrdvm4ZVMNuixelO02RZX4G4Aq1F145Yzp43wnM5S+hLlSI7ypfVw==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.85.1.tgz", + "integrity": "sha512-+VgVIJDkDO2efYJKEEqvPTH4zmnIaXdAppGbO+vKFA9qy5PdhFiAenuFAkU+oiCSfOC4dMHDyrjdQeL4ZoC5CQ==", "license": "MIT", "dependencies": { - "@anthropic-ai/sdk": "0.91.1", + "@anthropic-ai/sdk": "0.123.0", "@aws-sdk/client-bedrock-runtime": "3.1048.0", - "@earendil-works/pi-telemetry": "^0.84.4", + "@earendil-works/pi-telemetry": "^0.85.1", "@google/genai": "1.52.0", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", @@ -514,29 +528,16 @@ "node": ">=22.19.0" } }, - "node_modules/@earendil-works/pi-client": { - "version": "0.84.4", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-client/-/pi-client-0.84.4.tgz", - "integrity": "sha512-q398WY/3ZQHTizk7IKxApzqFV0xt4yM9LkSkwyqeLK5Bj5RwRjOWxESt26z4LgNp4O+8hqhqFPf/8fj4H5rE4A==", - "license": "MIT", - "dependencies": { - "@earendil-works/pi-protocol": "^0.84.4" - }, - "engines": { - "node": ">=22.19.0" - } - }, "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.84.4", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.84.4.tgz", - "integrity": "sha512-jmOlrqUmvhh/siNWFRXjYLJzhKFIHNsAQaysRwzQPQFnPAaV/vhqHsLH/MBsIISA1Rjj7WTUFR3nJrpXoLx39w==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.85.1.tgz", + "integrity": "sha512-FGRN+OHbWaefBPGaTggAdLjrIHW+s2PzLyglz/5dfLzb9of7uuXMXYC0fJIeZTw+shS32o2cuQ9jF7YSDuL/oQ==", "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "^0.84.4", - "@earendil-works/pi-ai": "^0.84.4", - "@earendil-works/pi-client": "^0.84.4", - "@earendil-works/pi-protocol": "^0.84.4", - "@earendil-works/pi-tui": "^0.84.4", + "@earendil-works/chord": "^0.85.1", + "@earendil-works/pi-agent-core": "^0.85.1", + "@earendil-works/pi-ai": "^0.85.1", + "@earendil-works/pi-tui": "^0.85.1", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", @@ -563,31 +564,19 @@ "@mariozechner/clipboard": "0.3.9" } }, - "node_modules/@earendil-works/pi-protocol": { - "version": "0.84.4", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-protocol/-/pi-protocol-0.84.4.tgz", - "integrity": "sha512-acyE9ozxkMiWiz/xyWpU0O9vwnYv0hyG889Vniv6Sg9c9zfsX+8MePnDNphBacY2Fvm1rxdsGmiVDSZl9yuDFA==", - "license": "MIT", - "dependencies": { - "typebox": "1.3.7" - }, - "engines": { - "node": ">=22.19.0" - } - }, "node_modules/@earendil-works/pi-telemetry": { - "version": "0.84.4", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.84.4.tgz", - "integrity": "sha512-8e2CuxM+ht+hedQXTZmi5JVl6/xDK9RpSDL2+MbITevKYQhMZ/z6lJOTFgox3HQyGxO8mOZEtYGVeQNaD4OzqA==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.85.1.tgz", + "integrity": "sha512-Bg/YN6kA7Swja/NQxka8xFdecb4E/auIEGF2G5A25EaQXhRnPj300/7/KpgsDDMYUzHTDAv4RyUxaQPJKW81Rw==", "license": "MIT", "engines": { "node": ">=22.19.0" } }, "node_modules/@earendil-works/pi-tui": { - "version": "0.84.4", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.84.4.tgz", - "integrity": "sha512-nPUnwDkLtupPXnZQYrCwPFcuTydCDqTY6ZbFqhsL4S4kVq0AT418kPa/6uXwtaCD+MjBNBltb7ScTYX65yeE1w==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.85.1.tgz", + "integrity": "sha512-OIzw9efInmO4WOBnD4TxcTdBjmzvYJpzslkgoUro946nEGoYWg5rwv1p4fDt3/JvMx9QybryUCUwlm7j8Dreig==", "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", @@ -597,6 +586,422 @@ "node": ">=22.19.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@google/genai": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", @@ -906,13 +1311,13 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.7.2.tgz", - "integrity": "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.33.2", - "@smithy/types": "^4.17.2", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", "tslib": "^2.6.2" }, "engines": { @@ -960,9 +1365,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.17.2.tgz", - "integrity": "sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.18.0.tgz", + "integrity": "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -997,13 +1402,19 @@ "node": ">=14.0.0" } }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@types/node": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", - "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", + "version": "22.20.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.2.tgz", + "integrity": "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==", "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/retry": { @@ -1153,12 +1564,59 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -1632,6 +2090,16 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/standardwebhooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", + "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", @@ -1660,9 +2128,9 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, "node_modules/web-streams-polyfill": { diff --git a/package.json b/package.json index f5673c09..3c0e8096 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,6 @@ "description": "Minimal Mosaic Stack container proof of concept: one Pi agent, four local contract files, one real model request returning MOSAIC_HELLO_OK.", "license": "UNLICENSED", "dependencies": { - "@earendil-works/pi-coding-agent": "0.84.4" + "@earendil-works/pi-coding-agent": "0.85.1" } } diff --git a/packages/mosaic/package.json b/packages/mosaic/package.json new file mode 100644 index 00000000..f92fe8d0 --- /dev/null +++ b/packages/mosaic/package.json @@ -0,0 +1,19 @@ +{ + "name": "@mosaic/registry", + "version": "0.1.0", + "private": true, + "description": "Mosaic auth/provider/harness registry schemas and validation (increment 1: schemas, validation, read-only CLI).", + "license": "UNLICENSED", + "type": "module", + "engines": { "node": ">=24" }, + "bin": { "mosaic-registry": "src/cli/main.mjs" }, + "exports": { + ".": "./src/index.mjs", + "./ids.mjs": "./src/ids.mjs", + "./records.mjs": "./src/records.mjs", + "./registry.mjs": "./src/registry.mjs" + }, + "scripts": { + "test": "node --test tests/" + } +} diff --git a/packages/mosaic/src/cli/main.mjs b/packages/mosaic/src/cli/main.mjs new file mode 100644 index 00000000..0bf51666 --- /dev/null +++ b/packages/mosaic/src/cli/main.mjs @@ -0,0 +1,43 @@ +import { loadRegistry } from "../registry.mjs"; + +// Read-only registry CLI: validate and list. No mutation commands (increment 1). + +function usage() { + process.stdout.write(`usage: mosaic-registry --registry-root +commands: + validate validate the registry tree; exit 0 valid, 1 invalid, 2 usage + list list providers, accounts, profiles (no credential fields ever) +`); +} + +async function main(argv) { + const command = argv[0]; + const rootIndex = argv.indexOf("--registry-root"); + const root = rootIndex !== -1 ? argv[rootIndex + 1] : undefined; + if ((command !== "validate" && command !== "list") || !root) { + usage(); + return 2; + } + const { entries, errors } = await loadRegistry(root); + if (command === "validate") { + if (errors.length > 0) { + for (const err of errors) process.stderr.write(`${err.path}: ${err.code}${err.detail ? `: ${err.detail}` : ""}\n`); + process.stderr.write(`${errors.length} validation error(s)\n`); + return 1; + } + process.stdout.write("valid\n"); + return 0; + } + // list: strictly non-secret projection. + const providers = Object.keys(entries.providers).sort().map((id) => ({ id, kind: entries.providers[id].kind })); + const accounts = Object.keys(entries.accounts).sort().map((ref) => ({ + ref, + name: entries.accounts[ref].name, + type: entries.accounts[ref].type, + })); + const profiles = Object.keys(entries.profiles).sort().map((id) => ({ id })); + process.stdout.write(`${JSON.stringify({ providers, accounts, profiles }, null, 2)}\n`); + return errors.length > 0 ? 1 : 0; +} + +process.exitCode = await main(process.argv.slice(2)); diff --git a/packages/mosaic/src/index.mjs b/packages/mosaic/src/index.mjs new file mode 100644 index 00000000..3b1f0b4a --- /dev/null +++ b/packages/mosaic/src/index.mjs @@ -0,0 +1,4 @@ +import { validateProvider, validateAccount, validateSettingsProfile, validateSeatSelection, validateHarnessManifest, ValidationError } from "./records.mjs"; +import { loadRegistry } from "./registry.mjs"; + +export { validateProvider, validateAccount, validateSettingsProfile, validateSeatSelection, validateHarnessManifest, ValidationError, loadRegistry }; diff --git a/packages/mosaic/src/records.mjs b/packages/mosaic/src/records.mjs new file mode 100644 index 00000000..aec3516a --- /dev/null +++ b/packages/mosaic/src/records.mjs @@ -0,0 +1,184 @@ +// Registry record validation for the Mosaic auth/provider/harness domain. +// Increment 1 per docs/plans/2026-09-10_m20-increment1-charter.md: pure +// schema/reference validation, no I/O, no secrets, no materialization. + +const ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/; + +export class ValidationError extends Error { + constructor(path, code, detail) { + super(`${path}: ${code}${detail ? `: ${detail}` : ""}`); + this.name = "ValidationError"; + this.path = path; + this.code = code; + this.detail = detail ?? ""; + } +} + +const fail = (errors, path, code, detail) => errors.push(new ValidationError(path, code, detail)); + +function isPlainObject(value) { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +// Rejects unknown fields: every record lists its allowed keys and a type map. +function checkShape(errors, path, value, allowed, requireAll) { + if (!isPlainObject(value)) { + fail(errors, path, "not-an-object"); + return; + } + for (const key of Object.keys(value)) { + if (!(key in allowed)) fail(errors, `${path}.${key}`, "unknown-field"); + } + for (const [key, type] of Object.entries(allowed)) { + if (!(key in value)) { + if (requireAll.includes(key)) fail(errors, `${path}.${key}`, "missing-field"); + continue; + } + const v = value[key]; + if (type === "id") { + if (typeof v !== "string" || !ID_PATTERN.test(v)) fail(errors, `${path}.${key}`, "invalid-id"); + } else if (type === "string") { + if (typeof v !== "string" || v.length === 0) fail(errors, `${path}.${key}`, "invalid-string"); + } else if (type === "accountRef[]") { + if (!Array.isArray(v) || v.length === 0 || v.some((e) => typeof e !== "string" || !/^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,63}$/.test(e))) { + fail(errors, `${path}.${key}`, "invalid-account-ref-list"); + } + } else if (type === "string[]") { + if (!Array.isArray(v) || v.length === 0 || v.some((e) => typeof e !== "string" || !ID_PATTERN.test(e))) { + fail(errors, `${path}.${key}`, "invalid-id-list"); + } + } else if (type === "record") { + if (!isPlainObject(v)) fail(errors, `${path}.${key}`, "not-an-object"); + } else if (type === "timestamp") { + if (typeof v !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/.test(v)) { + fail(errors, `${path}.${key}`, "invalid-timestamp"); + } + } else if (type === "boolean") { + if (typeof v !== "boolean") fail(errors, `${path}.${key}`, "invalid-boolean"); + } + } +} + +export function validateProvider(record) { + const errors = []; + checkShape(errors, "provider", record, { + providerVersion: "id", + id: "id", + kind: "string", + harnesses: "record", + credentialTypes: "string[]", + allowInsecureTransport: "boolean", + }, ["providerVersion", "id", "kind", "harnesses", "credentialTypes"]); + if (errors.length === 0) { + if (record.kind !== "native" && record.kind !== "custom-endpoint") { + fail(errors, "provider.kind", "unsupported-kind", record.kind); + } + for (const [harness, cfg] of Object.entries(record.harnesses)) { + if (!ID_PATTERN.test(harness)) fail(errors, `provider.harnesses.${harness}`, "invalid-id"); + if (!isPlainObject(cfg)) fail(errors, `provider.harnesses.${harness}`, "not-an-object"); + } + const harnessCfg = record.harnesses.pi; + if (record.kind === "custom-endpoint" && isPlainObject(harnessCfg)) { + const url = harnessCfg.baseUrl; + if (typeof url !== "string" || !/^https?:\/\//.test(url)) { + fail(errors, "provider.harnesses.pi.baseUrl", "invalid-url"); + } else if (url.startsWith("http://") && record.allowInsecureTransport !== true) { + fail(errors, "provider.allowInsecureTransport", "insecure-transport-not-allowed"); + } + if (!Array.isArray(harnessCfg.models) || harnessCfg.models.length === 0) { + fail(errors, "provider.harnesses.pi.models", "invalid-model-list"); + } + } else if (record.kind === "native" && isPlainObject(harnessCfg) && harnessCfg.baseUrl === undefined && Array.isArray(harnessCfg.models) === false) { + // native: pi entry is providerId only; models/bases come from provider catalog + } + if (record.kind === "native" && record.allowInsecureTransport === true) { + fail(errors, "provider.allowInsecureTransport", "insecure-transport-unsupported-for-native"); + } + } + return errors; +} + +export function validateAccount(record, providerId) { + const errors = []; + checkShape(errors, "account", record, { + accountVersion: "id", + id: "id", + name: "string", + provider: "id", + type: "string", + createdAt: "timestamp", + }, ["accountVersion", "id", "name", "provider", "type", "createdAt"]); + if (errors.length === 0) { + if (providerId !== undefined && record.provider !== providerId) { + fail(errors, "account.provider", "provider-path-mismatch", `${record.provider} vs ${providerId}`); + } + if (record.type !== "oauth" && record.type !== "api_key" && record.type !== "none") { + fail(errors, "account.type", "unsupported-credential-type", record.type); + } + } + return errors; +} + +export function validateSettingsProfile(record) { + const errors = []; + checkShape(errors, "profile", record, { + settingsVersion: "id", + id: "id", + allowedAccounts: "accountRef[]", + providers: "string[]", + defaultAccounts: "record", + models: "record", + }, ["settingsVersion", "id", "allowedAccounts"]); + if (errors.length === 0) { + for (const [provider, account] of Object.entries(record.defaultAccounts ?? {})) { + if (!ID_PATTERN.test(provider)) fail(errors, `profile.defaultAccounts.${provider}`, "invalid-id"); + if (typeof account !== "string" || !/^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,63}$/.test(account)) { + fail(errors, `profile.defaultAccounts.${provider}`, "invalid-account-ref"); + } + } + } + return errors; +} + +export function validateSeatSelection(record) { + const errors = []; + checkShape(errors, "selection", record, { + selectionVersion: "id", + profile: "id", + accounts: "record", + updatedAt: "timestamp", + // Fork pin (gate 6): present only when this selection was pinned at fork time. + pinnedFromSession: "string", + }, ["selectionVersion", "profile"]); + if (errors.length === 0) { + for (const [provider, account] of Object.entries(record.accounts ?? {})) { + if (!ID_PATTERN.test(provider)) fail(errors, `selection.accounts.${provider}`, "invalid-id"); + if (typeof account !== "string" || !/^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,63}$/.test(account)) { + fail(errors, `selection.accounts.${provider}`, "invalid-account-ref"); + } + } + } + return errors; +} + +export function validateHarnessManifest(record) { + const errors = []; + checkShape(errors, "harness", record, { + harnessVersion: "id", + id: "id", + executable: "string", + adapter: "string", + compatibleRange: "string", + executionMode: "string", + materializers: "string[]", + }, ["harnessVersion", "id", "executable", "adapter", "compatibleRange", "executionMode", "materializers"]); + if (errors.length === 0) { + if (record.id !== record.executable) { + fail(errors, "harness.executable", "id-executable-mismatch", "gate 1: canonical IDs are executable names"); + } + if (record.executionMode !== "container" && record.executionMode !== "host") { + fail(errors, "harness.executionMode", "unsupported-mode", record.executionMode); + } + } + return errors; +} diff --git a/packages/mosaic/src/registry.mjs b/packages/mosaic/src/registry.mjs new file mode 100644 index 00000000..1d16aaab --- /dev/null +++ b/packages/mosaic/src/registry.mjs @@ -0,0 +1,106 @@ +// Registry tree loading and cross-record reference validation. +// Reads a supplied registry root; never writes, never touches credentials. + +import { readFile, readdir, stat } from "node:fs/promises"; +import { join } from "node:path"; +import { validateProvider, validateAccount, validateSettingsProfile, validateSeatSelection, validateHarnessManifest } from "./records.mjs"; + +export async function loadRegistry(root) { + const entries = { providers: {}, accounts: {}, profiles: {}, selections: {}, harnesses: {} }; + const errors = []; + + const providersDir = join(root, "auth", "providers"); + for (const file of await safeList(providersDir, errors)) { + if (!file.endsWith(".json")) continue; + const id = file.slice(0, -5); + const record = await readJson(join(providersDir, file), errors); + if (record === undefined) continue; + errors.push(...validateProvider(record)); + if (record.id !== id) errors.push(new (await import("./records.mjs")).ValidationError(`providers/${file}`, "id-path-mismatch", `${record.id} vs ${id}`)); + if (entries.providers[id]) errors.push(new (await import("./records.mjs")).ValidationError(`providers/${file}`, "duplicate-id", id)); + entries.providers[id] = record; + } + + const accountsDir = join(root, "auth", "accounts"); + for (const provider of await safeList(accountsDir, errors)) { + for (const accountDir of await safeList(join(accountsDir, provider), errors)) { + // account.json sits in a per-account directory; credential.json is never read. + const record = await readJson(join(accountsDir, provider, accountDir, "account.json"), errors); + if (record === undefined) continue; + errors.push(...validateAccount(record, provider)); + const accountRef = `${provider}/${accountDir}`; + if (record.id !== accountDir) errors.push(new (await import("./records.mjs")).ValidationError(`accounts/${provider}/${accountDir}`, "id-path-mismatch", `${record.id} vs ${accountDir}`)); + if (entries.accounts[accountRef]) errors.push(new (await import("./records.mjs")).ValidationError(`accounts/${provider}/${accountDir}`, "duplicate-id", accountRef)); + entries.accounts[accountRef] = record; + } + } + + const settingsDir = join(root, "auth", "settings"); + for (const file of await safeList(settingsDir, errors)) { + if (!file.endsWith(".json")) continue; + const record = await readJson(join(settingsDir, file), errors); + if (record === undefined) continue; + errors.push(...validateSettingsProfile(record)); + if (record.id !== file.slice(0, -5)) errors.push(new (await import("./records.mjs")).ValidationError(`settings/${file}`, "id-path-mismatch")); + entries.profiles[file.slice(0, -5)] = record; + } + + const harnessesDir = join(root, "harnesses"); + for (const file of await safeList(harnessesDir, errors)) { + if (!file.endsWith(".json")) continue; + const record = await readJson(join(harnessesDir, file), errors); + if (record === undefined) continue; + errors.push(...validateHarnessManifest(record)); + entries.harnesses[file.slice(0, -5)] = record; + } + + // Cross-record reference integrity. + for (const [ref, account] of Object.entries(entries.accounts)) { + if (!entries.providers[account.provider]) { + errors.push(new (await import("./records.mjs")).ValidationError(`accounts/${ref}`, "missing-provider", account.provider)); + } + } + for (const [pid, profile] of Object.entries(entries.profiles)) { + for (const ref of profile.allowedAccounts ?? []) { + if (!entries.accounts[ref]) errors.push(new (await import("./records.mjs")).ValidationError(`profiles/${pid}`, "missing-account", ref)); + } + for (const provider of profile.providers ?? []) { + if (!entries.providers[provider]) errors.push(new (await import("./records.mjs")).ValidationError(`profiles/${pid}`, "missing-provider", provider)); + } + for (const [provider, ref] of Object.entries(profile.defaultAccounts ?? {})) { + if (!entries.accounts[ref]) errors.push(new (await import("./records.mjs")).ValidationError(`profiles/${pid}`, "missing-default-account", ref)); + else if (!ref.startsWith(`${provider}/`)) errors.push(new (await import("./records.mjs")).ValidationError(`profiles/${pid}`, "default-account-provider-mismatch", ref)); + } + } + + return { entries, errors }; +} + +async function safeList(dir, errors) { + try { + const s = await stat(dir); + if (!s.isDirectory()) throw new Error("not-a-directory"); + return await readdir(dir); + } catch { + return []; + } +} + +async function readJson(path, errors) { + try { + return JSON.parse(await readFile(path, "utf8")); + } catch (err) { + errors.push(new ValidationErrorCompat(path, "invalid-json", err.message)); + return undefined; + } +} + +class ValidationErrorCompat extends Error { + constructor(path, code, detail) { + super(`${path}: ${code}: ${detail}`); + this.name = "ValidationError"; + this.path = path; + this.code = code; + this.detail = detail; + } +} diff --git a/packages/mosaic/tests/fixtures/valid/auth/accounts/openai-codex/homelab-openai/account.json b/packages/mosaic/tests/fixtures/valid/auth/accounts/openai-codex/homelab-openai/account.json new file mode 100644 index 00000000..6a7bbd59 --- /dev/null +++ b/packages/mosaic/tests/fixtures/valid/auth/accounts/openai-codex/homelab-openai/account.json @@ -0,0 +1 @@ +{"accountVersion":"1","id":"homelab-openai","name":"Homelab OpenAI","provider":"openai-codex","type":"oauth","createdAt":"2026-09-10T00:00:00Z"} diff --git a/packages/mosaic/tests/fixtures/valid/auth/providers/ollama-remote.json b/packages/mosaic/tests/fixtures/valid/auth/providers/ollama-remote.json new file mode 100644 index 00000000..a057b728 --- /dev/null +++ b/packages/mosaic/tests/fixtures/valid/auth/providers/ollama-remote.json @@ -0,0 +1 @@ +{"providerVersion":"1","id":"ollama-remote","kind":"custom-endpoint","allowInsecureTransport":true,"harnesses":{"pi":{"api":"openai-completions","baseUrl":"http://ollama.internal:11434/v1","models":["qwen2.5-coder:7b"]}},"credentialTypes":["none"]} diff --git a/packages/mosaic/tests/fixtures/valid/auth/providers/openai-codex.json b/packages/mosaic/tests/fixtures/valid/auth/providers/openai-codex.json new file mode 100644 index 00000000..e3b0e596 --- /dev/null +++ b/packages/mosaic/tests/fixtures/valid/auth/providers/openai-codex.json @@ -0,0 +1 @@ +{"providerVersion":"1","id":"openai-codex","kind":"native","harnesses":{"pi":{"providerId":"openai-codex"}},"credentialTypes":["oauth","api_key"]} diff --git a/packages/mosaic/tests/fixtures/valid/auth/settings/research-default.json b/packages/mosaic/tests/fixtures/valid/auth/settings/research-default.json new file mode 100644 index 00000000..4e4c0c78 --- /dev/null +++ b/packages/mosaic/tests/fixtures/valid/auth/settings/research-default.json @@ -0,0 +1 @@ +{"settingsVersion":"1","id":"research-default","allowedAccounts":["openai-codex/homelab-openai"],"providers":["openai-codex","ollama-remote"],"defaultAccounts":{"openai-codex":"openai-codex/homelab-openai"},"models":{"ollama-remote":["qwen2.5-coder:7b"]}} diff --git a/packages/mosaic/tests/fixtures/valid/harnesses/pi.json b/packages/mosaic/tests/fixtures/valid/harnesses/pi.json new file mode 100644 index 00000000..b03dd7f0 --- /dev/null +++ b/packages/mosaic/tests/fixtures/valid/harnesses/pi.json @@ -0,0 +1 @@ +{"harnessVersion":"1","id":"pi","executable":"pi","adapter":"pi","compatibleRange":">=0.85.1 <0.86.0","executionMode":"container","materializers":["auth-json","models-json"]} diff --git a/packages/mosaic/tests/registry.test.mjs b/packages/mosaic/tests/registry.test.mjs new file mode 100644 index 00000000..ee545edf --- /dev/null +++ b/packages/mosaic/tests/registry.test.mjs @@ -0,0 +1,149 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, dirname, resolve } from "node:path"; +import { spawnSync } from "node:child_process"; +import { validateProvider, validateAccount, validateSettingsProfile, validateSeatSelection, validateHarnessManifest } from "../src/records.mjs"; + +const pkgRoot = resolve(import.meta.dirname, ".."); +const cli = join(pkgRoot, "src", "cli", "main.mjs"); +const validFixture = join(pkgRoot, "tests", "fixtures", "valid"); + +function makeRoot() { + const root = mkdtempSync(join(tmpdir(), "mosaic-registry-test-")); + return root; +} + +function writeTree(root, files) { + for (const [name, content] of Object.entries(files)) { + const path = join(root, name); + mkdirSync(dirname(path), { recursive: true }); + writeFileSync(path, typeof content === "string" ? content : JSON.stringify(content)); + } +} + +function runCli(root, command = "validate") { + return spawnSync(process.execPath, [cli, command, "--registry-root", root], { encoding: "utf8", timeout: 15000 }); +} + +const validTree = () => ({ + "auth/providers/openai-codex.json": { providerVersion: "1", id: "openai-codex", kind: "native", harnesses: { pi: { providerId: "openai-codex" } }, credentialTypes: ["oauth", "api_key"] }, + "auth/accounts/openai-codex/homelab/account.json": { accountVersion: "1", id: "homelab", name: "Homelab", provider: "openai-codex", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }, + "auth/settings/research.json": { settingsVersion: "1", id: "research", allowedAccounts: ["openai-codex/homelab"], providers: ["openai-codex"], defaultAccounts: { "openai-codex": "openai-codex/homelab" } }, + "harnesses/pi.json": { harnessVersion: "1", id: "pi", executable: "pi", adapter: "pi", compatibleRange: ">=0.85.1 <0.86.0", executionMode: "container", materializers: ["auth-json"] }, +}); + +test("valid fixture tree validates and lists without secrets", () => { + const r = runCli(validFixture); + assert.equal(r.status, 0, r.stderr); + assert.match(r.stdout, /^valid\n$/); + const l = runCli(validFixture, "list"); + assert.equal(l.status, 0, l.stderr); + const listed = JSON.parse(l.stdout); + assert.equal(listed.providers.length, 2); + assert.equal(listed.accounts.length, 1); + assert.equal(listed.profiles.length, 1); + assert.ok(!JSON.stringify(listed).includes("credential")); + assert.ok(!JSON.stringify(listed).includes("token")); +}); + +test("unknown-field refuses", () => { + const errors = validateProvider({ providerVersion: "1", id: "p", kind: "native", harnesses: {}, credentialTypes: ["none"], extra: 1 }); + assert.equal(errors[0].code, "unknown-field"); +}); + +test("invalid-id refuses uppercase and traversal shapes", () => { + assert.ok(validateProvider({ providerVersion: "1", id: "BadID", kind: "native", harnesses: {}, credentialTypes: ["none"] }).some((e) => e.code === "invalid-id")); + assert.ok(validateAccount({ accountVersion: "1", id: "../escape", name: "x", provider: "p", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }).some((e) => e.code === "invalid-id")); +}); + +test("plain-http baseUrl requires allowInsecureTransport", () => { + const base = { providerVersion: "1", id: "o", kind: "custom-endpoint", harnesses: { pi: { api: "openai-completions", baseUrl: "http://h:11434/v1", models: ["m"] } }, credentialTypes: ["none"] }; + assert.ok(validateProvider(base).some((e) => e.code === "insecure-transport-not-allowed")); + assert.equal(validateProvider({ ...base, allowInsecureTransport: true }).length, 0); + const https = JSON.parse(JSON.stringify(base)); + https.harnesses.pi.baseUrl = "https://h/v1"; + assert.equal(validateProvider(https).length, 0); +}); + +test("native provider rejects allowInsecureTransport", () => { + assert.ok(validateProvider({ providerVersion: "1", id: "p", kind: "native", harnesses: {}, credentialTypes: ["none"], allowInsecureTransport: true }).some((e) => e.code === "insecure-transport-unsupported-for-native")); +}); + +test("unsupported credential type and kind refuse", () => { + assert.ok(validateAccount({ accountVersion: "1", id: "a", name: "x", provider: "p", type: "basic", createdAt: "2026-09-10T00:00:00Z" }).some((e) => e.code === "unsupported-credential-type")); + assert.ok(validateProvider({ providerVersion: "1", id: "p", kind: "weird", harnesses: {}, credentialTypes: ["none"] }).some((e) => e.code === "unsupported-kind")); +}); + +test("account provider-path mismatch refuses", () => { + assert.ok(validateAccount({ accountVersion: "1", id: "a", name: "x", provider: "other", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }, "right").some((e) => e.code === "provider-path-mismatch")); +}); + +test("profile account refs must be provider/account shaped", () => { + const errors = validateSettingsProfile({ settingsVersion: "1", id: "s", allowedAccounts: ["justone"], providers: [], defaultAccounts: {} }); + assert.ok(errors.some((e) => e.code === "invalid-account-ref-list")); +}); + +test("seat selection accepts fork pin field, validates account refs", () => { + assert.equal(validateSeatSelection({ selectionVersion: "1", profile: "research", accounts: { "openai-codex": "openai-codex/homelab" }, updatedAt: "2026-09-10T00:00:00Z", pinnedFromSession: "abc123" }).length, 0); + assert.ok(validateSeatSelection({ selectionVersion: "1", profile: "research", accounts: { "openai-codex": "nope" } }).some((e) => e.code === "invalid-account-ref")); +}); + +test("harness manifest id must equal executable (gate 1)", () => { + assert.ok(validateHarnessManifest({ harnessVersion: "1", id: "pi", executable: "claude", adapter: "x", compatibleRange: ">=1", executionMode: "container", materializers: ["m"] }).some((e) => e.code === "id-executable-mismatch")); +}); + +test("CLI validate: duplicate provider id across files refuses", () => { + const root = makeRoot(); + try { + const tree = validTree(); + tree["auth/providers/dup.json"] = { ...tree["auth/providers/openai-codex.json"] }; + writeTree(root, tree); + const r = runCli(root); + assert.equal(r.status, 1); + assert.match(r.stderr, /duplicate-id|id-path-mismatch/); + } finally { rmSync(root, { recursive: true, force: true }); } +}); + +test("CLI validate: missing referenced provider/account refuse", () => { + const root = makeRoot(); + try { + const tree = validTree(); + delete tree["auth/providers/openai-codex.json"]; + writeTree(root, tree); + const r = runCli(root); + assert.equal(r.status, 1); + assert.match(r.stderr, /missing-provider/); + } finally { rmSync(root, { recursive: true, force: true }); } +}); + +test("CLI validate: broken JSON refuses without secret echo", () => { + const root = makeRoot(); + try { + const tree = validTree(); + tree["auth/providers/openai-codex.json"] = "{ not json"; + writeTree(root, tree); + const r = runCli(root); + assert.equal(r.status, 1); + assert.match(r.stderr, /invalid-json/); + } finally { rmSync(root, { recursive: true, force: true }); } +}); + +test("CLI usage errors exit 2", () => { + assert.equal(runCli(validFixture, "bogus").status, 2); + assert.equal(spawnSync(process.execPath, [cli, "validate"], { encoding: "utf8" }).status, 2); +}); + +test("credential.json sibling presence does not break validation and is never read", () => { + const root = makeRoot(); + try { + const tree = validTree(); + tree["auth/accounts/openai-codex/homelab/credential.json"] = { secretMarker: "FIXTURE-NOT-A-SECRET" }; + writeTree(root, tree); + const r = runCli(root); + assert.equal(r.status, 0, r.stderr); + const l = runCli(root, "list"); + assert.ok(!l.stdout.includes("FIXTURE-NOT-A-SECRET")); + } finally { rmSync(root, { recursive: true, force: true }); } +}); diff --git a/scripts/test-auth.sh b/scripts/test-auth.sh index de032483..e9580f8c 100755 --- a/scripts/test-auth.sh +++ b/scripts/test-auth.sh @@ -36,7 +36,7 @@ CFG="MOSAIC_CONFIG=$SANDBOX/mock-config.json" # release pointer so agent.sh's ensure stays on the no-op fast path REL="$(tr -d '[:space:]' < RELEASE)" -printf '{"pointerVersion":1,"release":"%s","imageTag":"mosaic-poc-agent:0.84.4-r%s","activatedAt":"2026-01-01T00:00:00Z"}\n' "$REL" "$REL" > "$SANDBOX/data/state/active.json" +printf '{"pointerVersion":1,"release":"%s","imageTag":"mosaic-poc-agent:0.85.1-r%s","activatedAt":"2026-01-01T00:00:00Z"}\n' "$REL" "$REL" > "$SANDBOX/data/state/active.json" # fixtures: a default-harness credential and two mosaic-managed accounts printf '{"anthropic":{"type":"oauth","access":"%s"},"zai":{"type":"api_key","key":"%s"}}' "$TOKEN" "$SECRET" > "$SANDBOX/pi/auth.json" diff --git a/scripts/test-darkwing-launch.mjs b/scripts/test-darkwing-launch.mjs index bc816ea5..fba6d0ee 100644 --- a/scripts/test-darkwing-launch.mjs +++ b/scripts/test-darkwing-launch.mjs @@ -7,7 +7,7 @@ import { join, resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; const source = resolve(import.meta.dirname, '..'); -for (const agent of ['darkwing', 'dewey', 'filbert']) test(`${agent} launch context, resources, session recovery, and refusals`, () => { +for (const agent of ['darkwing', 'dewey', 'filbert', 'sage']) test(`${agent} launch context, resources, session recovery, and refusals`, () => { const root = mkdtempSync(join(tmpdir(), `${agent} launch `)); try { for (const dir of ['agents', 'scripts', 'contracts', 'user', 'node_modules/.bin', 'extensions/goal']) { @@ -24,7 +24,7 @@ for (const agent of ['darkwing', 'dewey', 'filbert']) test(`${agent} launch cont cpSync(join(source, 'scripts/agent-host-dev.sh'), join(root, 'scripts/agent-host-dev.sh')); cpSync(join(source, 'scripts/agent.sh'), join(root, 'scripts/agent.sh')); cpSync(join(source, 'skills'), join(root, 'skills'), { recursive: true }); - writeFileSync(join(root, 'package.json'), JSON.stringify({ dependencies: { '@earendil-works/pi-coding-agent': '0.84.4' } })); + writeFileSync(join(root, 'package.json'), JSON.stringify({ dependencies: { '@earendil-works/pi-coding-agent': '0.85.1' } })); for (const file of ['contracts/CONSTITUTION.md', 'contracts/STANDARDS.md', 'user/USER.md', 'AGENTS.md']) { writeFileSync(join(root, file), `fixture ${file}\n`); } @@ -33,7 +33,7 @@ for (const agent of ['darkwing', 'dewey', 'filbert']) test(`${agent} launch cont writeFileSync(join(root, 'scripts/sync-dev-extensions.sh'), '#!/bin/sh\nexit 0\n', { mode: 0o755 }); writeFileSync(join(root, 'node_modules/.bin/pi'), `#!/usr/bin/env node const fs = require('node:fs'); -if (process.argv[2] === '--version') { console.log('0.84.4'); process.exit(0); } +if (process.argv[2] === '--version') { console.log('0.85.1'); process.exit(0); } fs.writeFileSync('capture.json', JSON.stringify({args: process.argv.slice(2), agent: process.env.MOSAIC_AGENT_NAME, incarnation: process.env.MOSAIC_LAUNCH_INCARNATION})); `, { mode: 0o755 }); const launch = (args = '', tty = true) => spawnSync(tty ? 'script' : 'bash', tty @@ -61,8 +61,9 @@ fs.writeFileSync('capture.json', JSON.stringify({args: process.argv.slice(2), ag let captured = JSON.parse(readFileSync(join(root, 'capture.json'))); assert.equal(captured.incarnation, undefined); assert.equal(captured.agent, agent); - assert.equal(captured.args[captured.args.indexOf('--provider') + 1], agent === 'filbert' ? 'openai-codex' : 'test'); - assert.equal(captured.args[captured.args.indexOf('--model') + 1], agent === 'filbert' ? 'gpt-6-astra' : 'test-model'); + assert.equal(captured.args[captured.args.indexOf('--provider') + 1], agent === 'filbert' ? 'openai-codex' : agent === 'sage' ? 'zai' : 'test'); + assert.equal(captured.args[captured.args.indexOf('--model') + 1], agent === 'filbert' ? 'gpt-6-astra' : agent === 'sage' ? 'glm-5.3' : 'test-model'); + if (agent === 'sage') assert.equal(captured.args[captured.args.indexOf('--thinking') + 1], 'high'); if (agent === 'filbert') assert.equal(captured.args[captured.args.indexOf('--thinking') + 1], 'low'); assert.equal(captured.args[captured.args.indexOf('--session-dir') + 1], join(root, `.pi/state/${agent}/sessions`)); assert.equal(captured.args.includes('--continue'), false); diff --git a/scripts/test-task.sh b/scripts/test-task.sh index e157538a..df06e514 100755 --- a/scripts/test-task.sh +++ b/scripts/test-task.sh @@ -220,7 +220,7 @@ EOF EOF REL="$(tr -d '[:space:]' < RELEASE)" mkdir -p "$SANDBOX/data/state" - printf '{"pointerVersion":1,"release":"%s","imageTag":"mosaic-poc-agent:0.84.4-r%s","activatedAt":"2026-01-01T00:00:00Z"}\n' "$REL" "$REL" > "$SANDBOX/data/state/active.json" + printf '{"pointerVersion":1,"release":"%s","imageTag":"mosaic-poc-agent:0.85.1-r%s","activatedAt":"2026-01-01T00:00:00Z"}\n' "$REL" "$REL" > "$SANDBOX/data/state/active.json" scripts/skill.sh install ms-tools >/dev/null 2>&1 scripts/skill.sh activate ms-tools >/dev/null 2>&1 env MOSAIC_CONFIG="$SANDBOX/mock-config.json" MOSAIC_MOCK_RESPONSE=MOCKED \