feat(server): enforce worker launch policies on upstream 0.81.1 base - #1
Merged
Conversation
…ce-expansion update brace-expansion version
This PR: - Adds retainedTail to compaction entries in the new agent harness so we don't have to walk up the tree for the 2000 tokens before compaction, - Changes getPathToRoot to getPathToRootOrCompaction to only load until last compaction, as unnecessary to access all nodes where it is called, - Adds a SQLite storage backend, in a separate packages/session-backend-sqlite, with a migration system and schemas as per on-site discussions: sessions to match session header messages (except for metadata, which I couldn't understand what it's used for or where it gets written, so I omitted it), session_entries for shared entry types as columns plus payload as a json for what remains, session_sequences to represent the append-only, serialized nature of the jsonl files, branch_entries to attribute nodes to branches (relationship one-to-many), and session_materialized with the session info (see /session in TUI) to act as a "cache" or quick-access for costs, message count, token info, labels, session name, and model-thinking-level config (e.g. for fast resume). - This is compatible with the new agent harness Session abstraction.
fixes earendil-works#6647 compaction (auto & manual) and branch summarization retry on transient failures. use the same retry policy from settings. emit events for the tui to show indication of retries
…b-actions-versions update deprecated github actions
…summary-requests-2
…6647-retry-summary-requests-2 compaction & branch summarization follow retry policy
Keep streamFn required for typed callers while preserving the legacy runtime fallback for extensions that omit it.\n\nfixes earendil-works#6915
…lite-node package
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Synchronize the maintained Pi fork with upstream Pi 0.81.1 and establish the server-compat branch after upstream renamed packages/orchestrator to packages/server. Port worker launch-policy version 1 capability negotiation, strict role-specific tool allowlist validation, disabled extension/skill/prompt-template/context-file discovery, and exact applied-policy attestation into packages/server. Preserve ESM import-condition resolution of @earendil-works/pi-coding-agent/rpc-entry, keep the existing server CLI and server.sock behavior, document the security boundary, and add focused compatibility tests. This branch is consumed by pi-build-conductor through PI_SERVER_DIR and server.sock.
What Changed
packages/orchestrator->packages/serverrename, the new@earendil-works/pi-storage-sqlite-nodeSQLite session storage package, retry-policy support for compaction and branch summarization, release source archives, and regenerated model catalogs.packages/server: capability negotiation overserver.sock, strict validation of spawnlaunchPolicyagainst role-specific tool allowlists, workers spawned with extension/skill/prompt-template/context-file discovery disabled, and exactappliedPolicyattestation echoed back and persisted, with ESM import-condition resolution of@earendil-works/pi-coding-agent/rpc-entrypreserved and covered by new compatibility tests inpackages/server/test/compatibility.test.ts.vitestdevDependency topi-serverso its new test script runs under CI (auto-fixed by the pipeline, which verified the policy enforcement end-to-end over a liveserver.sock), and documented the fork-only launch-policy changelog decision and security boundary in the server package docs.Risk Assessment
✅ Low: The feature is well-bounded, fails closed with strict exact-match validation, is documented and tested, and fully satisfies every required intent criterion; the only finding is a behavior-identical defense-in-depth hardening, not a current defect.
Testing
I ran the package's focused compatibility test (3/3 green) and then drove the real product surface end-to-end: a live
server servewith PI_SERVER_DIR, talked to server.sock the way pi-build-conductor does, and captured a full CLI/socket transcript showing capability negotiation returning version 1, seven tampered launch policies each refused with "Invalid worker launch policy", review/implementation/repair spawns attesting the exact applied policy in the spawn response plusserver listand persisted instances.json, policy-free spawns still working unchanged, and the socket cleaned up on shutdown. I recorded each worker process's own argv from inside the child, which shows the exact role allowlist and all four discovery-disabling flags actually delivered to the worker, and confirmed the runtime effect behaviorally: in a fixture project with a .pi skill and prompt template, the policy-free worker discovers them via get_commands while both policy workers discover nothing. I also confirmed the ESM import-condition resolution is both necessary and preserved (require.resolve fails with ERR_PACKAGE_PATH_NOT_EXPORTED where import.meta.resolve succeeds), and that dist is byte-identical after a rebuild so the E2E exercised the current source. This change has no UI, HTML, or rendered surface - it is a Unix-socket protocol plus CLI, so the reviewer-visible evidence is CLI/socket transcripts and captured worker command lines rather than screenshots. One limitation: without model API credentials I could not make a worker attempt a denied tool call, so the tool allowlist is evidenced as the exact flags delivered to each launched worker rather than by observing a blocked tool invocation; discovery disablement was verified behaviorally. Everything passed and the worktree is clean, with transient sandbox state removed.Evidence: End-to-end server.sock + CLI transcript (capability negotiation, policy rejections, attestation, discovery effect, shutdown)
Evidence: Exact command lines the worker processes were launched with (recorded inside each worker)
<repo>/packages/coding-agent/dist/rpc-entry.js --tools read,grep,find,ls --no-extensions --no-skills --no-prompt-templates --no-context-files <repo>/packages/coding-agent/dist/rpc-entry.js --tools read,grep,find,ls,bash,edit,write --no-extensions --no-skills --no-prompt-templates --no-context-files <repo>/packages/coding-agent/dist/rpc-entry.js --tools read,grep,find,ls,bash,edit,write --no-extensions --no-skills --no-prompt-templates --no-context-files <repo>/packages/coding-agent/dist/rpc-entry.jsEvidence: ESM import-condition resolution of @earendil-works/pi-coding-agent/rpc-entry
import.meta.resolve -> <repo>/packages/coding-agent/dist/rpc-entry.js require.resolve -> FAILED: ERR_PACKAGE_PATH_NOT_EXPORTED Package subpath './rpc-entry' is not defined by "exports" in node_modules/@earendil-works/pi-coding-agent/package.jsonEvidence: Capability negotiation and policy rejection over server.sock (excerpt)
Evidence: E2E driver script (reproducible)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
.github/APPROVED_CONTRIBUTORS- branch carries 31 commit(s) that exist on your local main branch but were never pushed to origin/main; rebasing would bundle this unrelated work (151 file(s)) into the PR:Push main to origin, or rebase your branch onto origin/main, before gating.
packages/server/src/rpc-process.ts:31- getRpcSpawnCommand builds the enforced --tools sandbox allowlist from the client-supplied launchPolicy.tools rather than from the server-trusted ROLE_TOOLS[role] constant. It is behavior-identical today because assertWorkerLaunchPolicy (handler.ts:64) requires launchPolicy.tools to exactly equal ROLE_TOOLS[policy.role], so no drift is currently possible. As defense-in-depth for this sandbox boundary, prefer deriving the --tools value from ROLE_TOOLS[policy.role] and treating the client array purely as an assertion to validate; that way enforcement cannot drift from attestation if the validation is ever weakened (e.g. changed to a subset or order-insensitive check).🔧 **Test** - 1 issue found → auto-fixed ✅
packages/server/package.json:47- The pi-server package added atestscript (vitest --run) andtest/compatibility.test.ts, but did not declarevitestas a devDependency (base packages/orchestrator had no test script). vitest is not hoisted to root, sonpm run test --workspace @earendil-works/pi-serverand rootnpm test(which CI runs) fail with exit 127 (vitest: not found) - the intent's required focused compatibility tests cannot run and CI's test job breaks. Auto-fixed by addingvitest: 4.1.9(matching sibling packages and the repo exact-pin rule) to devDependencies and regenerating package-lock.json; npm hoists the now-shared vitest to root and dedups the three nested copies (that dedup, plus in-range bumps of vitest-only transitive deps picomatch/es-module-lexer/std-env, is the large lockfile diff). After the fix,npm ci --ignore-scriptssucceeds and the compatibility test passes 3/3.npm run test --workspace @earendil-works/pi-server --if-present- initially failed exit 127 (vitest not found); passes 3/3 after adding the vitest devDependencypackages/server/test/compatibility.test.tsvia vitest 4.1.9 - 3/3 pass (capability v1, allowlist-expansion rejection, ESM rpc-entry + policy flags)npm ci --ignore-scripts- exact CI install; succeeds after regenerating package-lock.json (confirms package.json/lock consistency)Live server.sock round-trip: spawned realnode dist/cli.js serveunder a throwaway PI_SERVER_DIR (Radius disabled) and drove capabilities + two policy-violating spawn requests + list over the actual unix socket (socket-e2e.mjs)Real IPC entry points vianode --experimental-strip-types policy-driver.mjs: handleIpcRequest (capabilities/spawn-rejection/status attestation) + getRpcSpawnCommand per role (implementation/review/repair)node dist/cli.js --versionand--helpfrom the built server binary (CLI behavior preserved)Verifiedimport.meta.resolve('@earendil-works/pi-coding-agent/rpc-entry')resolves to dist/rpc-entry.js under plain Node (production import condition)🔧 Fix: add missing vitest devDependency to pi-server
✅ Re-checked - no issues remain.
npm run test --workspace @earendil-works/pi-server(vitest --run, packages/server/test/compatibility.test.ts, 3/3 pass)npm run build --workspace @earendil-works/pi-serverthen md5sum diff of packages/server/dist -> identical, confirming the E2E ran against the current srcManual E2E drivernode /tmp/no-mistakes-evidence/01KY84F7F5YD7K82D2YY8F6BR3/e2e-launch-policy.mjswith PI_SERVER_DIR pointed at a sandbox: startedserver serve, verified server.sock creation and removal on shutdownRaw UNIX-socket request{"type":"capabilities"}->{"type":"capabilities_result","ok":true,"capabilities":{"workerLaunchPolicyVersions":[1]}}Seven tampered{"type":"spawn",...,"launchPolicy":...}requests over server.sock (added bash, dropped tool, reordered allowlist, cross-role allowlist, unknown role, resourceDiscovery=enabled, version=2) -> all{"ok":false,"error":"Invalid worker launch policy"}Accepted spawns for review / implementation / repair roles -> exactappliedPolicyechoed in spawn_resultRecorded each worker process's ownprocess.argvvia a NODE_OPTIONS preload ->rpc-entry.js --tools read,grep,find,ls --no-extensions --no-skills --no-prompt-templates --no-context-files(and the implementation/repair allowlist), policy-free worker launched with no policy flagsserver rpc <instance-id> '{"type":"get_commands"}'against a fixture project containing .pi/skills/danger, .pi/prompts/leak.md and AGENTS.md -> policy-free worker discovers the project skill and prompt template, policy workers discover neitherserver listandcat $PI_SERVER_DIR/instances.json-> appliedPolicy persisted and surfacedserver --version,server --help,server stop <instance-id>-> unchanged CLI behaviorimport.meta.resolvevsrequire.resolvefor @earendil-works/pi-coding-agent/rpc-entry -> ESM resolves, CJS fails with ERR_PACKAGE_PATH_NOT_EXPORTED🔧 **Document** - 1 issue found → auto-fixed ✅
packages/server/CHANGELOG.md:1- The worker launch policy feature (capabilities negotiation, spawn launchPolicy, appliedPolicy attestation) has no CHANGELOG entry. AGENTS.md requires all new entries under a '## [Unreleased]' section, and no such section exists on this branch (the v0.81.1 release commit consumed it and no 'Add [Unreleased] section' commit followed). I did not add one: global instructions forbid manually modifying CHANGELOG.md files. Needs the maintainer to create the '## [Unreleased]' / '### Added' section, or an explicit decision that fork-only server-compat work stays out of the upstream changelog.🔧 Fix: document fork-only launch policy changelog decision
✅ Re-checked - no issues remain.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.