AcmeCode is a fictional managed distribution on piship/v1. It signs users in with OIDC, obtains a runtime credential from an http-broker, and sends inference to an OpenAI-compatible gateway with a three-model allowlist, an enforced theme, and private-only networking. On top of that access layer it declares governance: a company policy, trust-classed resources, a governed MCP server, a Plan/Build workflow, a required OS sandbox, and audit. It also declares signed release channels and a release policy. It contains no private data or credentials. The endpoints are ACMECODE_* runtime variables, so the lock stays machine-independent.
The managed surface, governance, and the release lifecycle are candidates: all are verified with the deterministic local fixtures below, not with a live identity provider or gateway. The enterprise reference stack runs a distribution that follows this one section by section against a real Keycloak, a reference broker, and LiteLLM. See status for the current evidence and compatibility for the Pi contract.
- Policy.
acme-engineering@1defaults toask. Enforced rules deny reading~/.ssh/**and callingdocs:delete_*; defaults allow the gateway models, skill, instruction, and extension loading, workspace reads, tool calls, and the handbook MCP server and tools, and ask before workspace writes and shell commands. Headless runs have no approval channel, so everyaskbecomes deny there. - Resources by trust class. Company instructions, skills, and the
enterprise-contextextension; a certifiedrelease-notesskill whose tree digest is checked at lock and launch; and the builtinpiship-ask-userandpiship-workflowextensions. - Project trust. Repositories whose origin is on
git.acme.exampleand that are checked out under/srv/srcare company projects. The origin remote alone is only a claim, so the matcher requires both (manifest); changepathin a copy to where your managed checkouts live. Every other workspace is unknown and keeps the managed defaults (no project extensions, hooks, agents, MCP, or providers). - Governed MCP.
resources/mcp/docs-server.mjsis a small handbook server started over stdio withexpectedServerName: acme-docs. It offerssearch,get_document, anddelete_document. The first two reach the model asmcp__docs__searchandmcp__docs__get_document.delete_documentis on the server's tool list but denied by the enforced policy, so it is never offered to the model and a call to it never reaches the server. - Plan and Build. Sessions start in Plan mode: the model can read and search the handbook but cannot write, edit, or run commands.
/buildswitches to Build mode, where tools follow the policy;/planswitches back. - Sandbox.
sandbox.required: truewith networkdeny, every default credential directory (~/.ssh,~/.aws,~/.gnupg,~/.config/gcloud,~/.azure,~/.kube,~/.docker,~/.netrc,~/.npmrc,~/.pi) hidden (the manifest restates them, since a declared list replaces the defaults), and writes limited to the workspace and a private temp directory.bash,!commands, and the MCP stdio server run inside it. The launch fails withSANDBOX_UNAVAILABLEif it cannot be enforced. - Audit. An optional local file sink records metadata-only events in the distribution state under
logs/audit.jsonl. - Release channels. Users start on
stableand may switch tocandidate;devis not allowed. Update metadata is read from${ACMECODE_UPDATE_SOURCE}, the previous release is retained for rollback, and releases are built for Linux x64, macOS arm64, and Windows x64 fromhttps://registry.npmjs.orgpackages, failing onhighorcriticaladvisories. The example pins no update trust (noupdates.trust.bootstrap), soupdatefails closed andpiship releaserefuses it until an owner adds root and channel keys.
fixtures/local-services.mjs starts a loopback OIDC provider, credential broker, and gateway. It auto-approves every sign-in for a fictional demo user and returns canned replies. It is test infrastructure, not a real identity provider, and not evidence of a live integration.
The demo uses the system secret store (credential.storage.provider: system). On Linux this needs a running, unlocked Secret Service and secret-tool; macOS uses Keychain and Windows uses Credential Manager. Without one, login fails with SECRET_STORE_UNAVAILABLE. To use the plaintext file fallback instead, edit a copy of this example to set storage: {provider: file, acknowledgePlaintext: true}; the example itself does not opt in.
The required sandbox needs bubblewrap (bwrap) with unprivileged user namespaces on Linux, or /usr/bin/sandbox-exec on macOS. Windows has no sandbox adapter, so the demo refuses to start there with SANDBOX_UNAVAILABLE; to try the rest of the demo on Windows, set sandbox.required: false in a copy.
In a first terminal, from the repository root with Node.js 22.19.0 or newer:
npm ci
npm run build
node examples/demo-company/fixtures/local-services.mjsIt prints export ACMECODE_...=... lines (set lines on Windows) and keeps running. Pass --port <n> for a fixed port. In a second terminal, paste those lines, then:
node packages/cli/dist/bin.js validate examples/demo-company/piship.yaml
node packages/cli/dist/bin.js lock examples/demo-company/piship.yaml
node packages/cli/dist/bin.js build examples/demo-company/piship.yaml
node dist/acmecode/piship.mjs install dist/acmecode
~/.local/bin/acmecode --version
~/.local/bin/acmecode login
~/.local/bin/acmecode --smoke
~/.local/bin/acmecode --smoke-model
~/.local/bin/acmecode models
~/.local/bin/acmecode --model acme/review --smoke # MODEL_UNAVAILABLE: not entitled
~/.local/bin/acmecode doctor
~/.local/bin/acmecode config explain
~/.local/bin/acmecode capabilities
~/.local/bin/acmecode policy explain mcp.tool.call docs:delete_document
~/.local/bin/acmecode policy explain filesystem.read ~/.ssh/id_ed25519 --json
~/.local/bin/acmecode policy explain shell.execute "git status"
~/.local/bin/acmecode logout
node dist/acmecode/piship.mjs uninstall acmecodelogin prints the sign-in URL and opens a browser; the fixture approves it at once and redirects to http://127.0.0.1:8765/callback, so that port must be free. Set PISHIP_NO_BROWSER=1 to only print the URL. Before login, --smoke fails with IDENTITY_REQUIRED. --smoke checks Pi startup, declared resources, the read tool, session resume, and the access summary without a model call, and adds a governance summary: policy, project origin, sandbox level and planes, workflow mode, capabilities, resource decisions, MCP servers and their exposed tools, and audit state. --smoke-model sends one prompt through the fixture gateway. The declared enterprise-context extension adds a demo_context tool that reads the token-free enterprise context. config set theme light is refused because the theme is enforced; config set model acme/general is permitted.
What to look for:
doctoradds the Resources, Policy, Governance, Project, Capabilities, Sandbox, Workspace, MCP, and Audit groups (doctor report). On Linux with bubblewrap, the sandbox line readsenforced (linux-bubblewrap: filesystem-read-deny, filesystem-write-allowlist, network-deny, environment-filter), proven by a live probe, andmcp docsishealthy (stdio; 2 tool(s)). The certified skill showsintegrity verified.capabilitiesshowspermissionsandworkfloweffective through their builtin providers;checkpoint,subagents,code-intel, andacpare not supported in this release.policy explain mcp.tool.call docs:delete_documentprintsDENIEDby the enforced ruleacme.docs.destructive, with the defaultacme.docs.readallow listed as another matching rule.filesystem.read ~/.ssh/id_ed25519is denied byacme.secrets.readwith enforcementsandboxwhen the sandbox is enforced.shell.execute "git status"needs approval underacme.shell.- In the interactive TUI (
~/.local/bin/acmecode), the session starts in Plan mode. Ask for a plan that searches the handbook; writes and commands are refused until you type/build. In Build mode, writes and commands ask for approval, and approved!commands run inside the sandbox:!cat ~/.ssh/configfinds nothing to read and!curl https://example.orghas no network.
To try the user and project layers, write rules to ~/.piship/acmecode/config/policy.json (or $PISHIP_STATE_HOME/acmecode/config/policy.json) or to .piship/policy.json in a project. This is a managed distribution, so both files are narrowing only: a user or project rule can tighten a default such as acme.shell from ask to deny, their allow rules are ignored and reported, and neither can relax the enforced rules. policy explain shows which layer decided. The demo leaves policy.userAuto at its default, off, so acmecode auto on is refused with POLICY_DENIED; a distribution that declares policy.userAuto: allowed lets each user switch on an audited auto mode that stops ask prompts from the defaults.
Every branded command resolves the ACMECODE_* variables at launch, so keep them set in that shell. The fixture keeps its sessions in memory: after restarting it, run login again. logout revokes the credential and tokens at the fixture, clears local secrets, and keeps sessions; node dist/acmecode/piship.mjs purge acmecode --yes removes the state, including the audit log, after uninstall, and deletes the secret-store entries it references first (each deletion confirmed, nothing revoked: a secret that cannot be deleted fails the purge before any state is removed). node dist/acmecode/piship.mjs uninstall acmecode --purge --yes does the uninstall and the purge in one command. Because purge revokes nothing, both refuse before deleting anything while you are signed in; remove AcmeCode in this order:
acmecode logout # revokes the credential at the broker
node dist/acmecode/piship.mjs uninstall acmecode --purge --yesAfter a plain uninstall, sign out with the release's own command, node dist/acmecode/bin/acmecode logout, before purge. Only with no release left to run logout, --without-logout (after --yes) purges anyway and warns that the credential stays live at the broker until it expires.
This walkthrough plays both the owner and the user on one machine, with a local directory as the channel. ACMECODE_UPDATE_SOURCE is read only by update, never at launch, so the steps above work without it. Run these commands from the repository root, and work in a copy of the example so the demo stays unchanged.
-
As the owner, create an offline root key and a channel release key outside the repository. A managed release needs the two roles on distinct keys.
keygenrefuses to overwrite a file or to write inside a git work tree unless the path is git-ignored, and prints the public key and fingerprint:cp -r examples/demo-company /tmp/acmecode mkdir -p ~/acme-keys node packages/cli/dist/bin.js keygen ~/acme-keys/root.pem --id acme-root-2026 node packages/cli/dist/bin.js keygen ~/acme-keys/release.pem --id acme-release-2026
-
Add
trust.bootstrapunderupdatesin the copiedpiship.yamlwith the printed entries. Only public keys go in the manifest; the private keys stay out of the repository, CI logs, and any shared location, and the root key stays offline:updates: trust: bootstrap: version: 1 expires: 2027-10-01T00:00:00Z keys: - id: acme-root-2026 publicKey: MCowBQYDK2VwAyEA... - id: acme-release-2026 publicKey: MCowBQYDK2VwAyEA... roles: root: { keyIds: [acme-root-2026], threshold: 1 } channel: { keyIds: [acme-release-2026], threshold: 1 }
-
Lock, build a release for this machine, and verify it. On Windows set
sandbox.required: falsefirst; a release that requires the sandbox is refused forwin32-x64. The dependency scan needs registry access:node packages/cli/dist/bin.js lock /tmp/acmecode/piship.yaml node packages/cli/dist/bin.js release /tmp/acmecode/piship.yaml node packages/cli/dist/bin.js verify-release dist/releases/acmecode-1.0.0-<target>.tar.gz
-
Install it from the extracted release with its own script (
install.ps1on Windows), which verifies it again first. Uninstall any earlieracmecodeinstall first, and pass--use-existing-stateto keep its state:tar -xzf dist/releases/acmecode-1.0.0-<target>.tar.gz -C /tmp sh /tmp/acmecode-1.0.0-<target>/install.sh
On Windows, run the script from PowerShell with
powershell -ExecutionPolicy Bypass -File <extracted folder>\install.ps1(the default execution policy refuses an unsigned.ps1). The installed command isacmecode.cmdin the bin directory. -
Change
app.versionin/tmp/acmecode/piship.yamlto1.1.0, lock, and release again. Sign it into the stable channel directory, then point the installed command at it:node packages/cli/dist/bin.js sign-channel /tmp/acme-channel dist/releases/acmecode-1.1.0-<target>.tar.gz \ --channel stable --key ~/acme-keys/release.pem --key-id acme-release-2026 export ACMECODE_UPDATE_SOURCE=/tmp/acme-channel
-
As the user:
~/.local/bin/acmecode update --check # 1.1.0 is available, signed by acme-release-2026 ~/.local/bin/acmecode update # verified, then activated; 1.0.0 is retained ~/.local/bin/acmecode doctor # Supply Chain and Update sections ~/.local/bin/acmecode rollback # back to 1.0.0; sessions kept, credentials not restored
<target> is this machine's linux-x64, darwin-arm64, or win32-x64. To serve the channel to others, publish the channel directory over HTTPS and set ACMECODE_UPDATE_SOURCE to its URL; --from <dir|url> overrides it for one run. Re-sign the channel with sign-channel before its metadata expires (30 days by default). See release for what each step verifies and the release checklist.
To try AcmeCode against real services you are authorized to use, set the same variables before login:
| Variable | Value |
|---|---|
ACMECODE_OIDC_ISSUER |
Issuer URL of an OIDC provider |
ACMECODE_OIDC_CLIENT_ID |
A public native client (no secret) with Authorization Code + PKCE S256 and the registered redirect http://127.0.0.1:8765/callback |
ACMECODE_CREDENTIAL_BROKER_URL |
A service implementing the http-broker protocol |
ACMECODE_CREDENTIAL_REVOKE_URL |
Its revoke endpoint |
ACMECODE_LLM_GATEWAY_URL |
An OpenAI-compatible gateway base URL that serves GET /models and the allowed model IDs |
ACMECODE_UPDATE_SOURCE |
Optional, read only by update: the HTTPS URL of a channel directory signed with a key pinned in your copy |
All URLs must use HTTPS. Managed mode is always private-only, so OIDC endpoints that discovery returns on other hosts, and any Streamable HTTP MCP server or HTTP audit sink on another host, must be added to network.allowHosts, and an enterprise CA goes in network.tls.additionalCA; make these changes, and any model ID changes, in a copy, then lock and build it. The project has not yet recorded such a live run.