Skip to content

Latest commit

 

History

History
157 lines (118 loc) · 15.5 KB

README.md

File metadata and controls

157 lines (118 loc) · 15.5 KB

Demo company distribution example

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.

What the demo shows

  • Policy. acme-engineering@1 defaults to ask. Enforced rules deny reading ~/.ssh/** and calling docs: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 every ask becomes deny there.
  • Resources by trust class. Company instructions, skills, and the enterprise-context extension; a certified release-notes skill whose tree digest is checked at lock and launch; and the builtin piship-ask-user and piship-workflow extensions.
  • Project trust. Repositories whose origin is on git.acme.example and that are checked out under /srv/src are company projects. The origin remote alone is only a claim, so the matcher requires both (manifest); change path in 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.mjs is a small handbook server started over stdio with expectedServerName: acme-docs. It offers search, get_document, and delete_document. The first two reach the model as mcp__docs__search and mcp__docs__get_document. delete_document is 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. /build switches to Build mode, where tools follow the policy; /plan switches back.
  • Sandbox. sandbox.required: true with network deny, 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 with SANDBOX_UNAVAILABLE if 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 stable and may switch to candidate; dev is 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 from https://registry.npmjs.org packages, failing on high or critical advisories. The example pins no update trust (no updates.trust.bootstrap), so update fails closed and piship release refuses it until an owner adds root and channel keys.

Deterministic local path

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.mjs

It 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 acmecode

login 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:

  • doctor adds the Resources, Policy, Governance, Project, Capabilities, Sandbox, Workspace, MCP, and Audit groups (doctor report). On Linux with bubblewrap, the sandbox line reads enforced (linux-bubblewrap: filesystem-read-deny, filesystem-write-allowlist, network-deny, environment-filter), proven by a live probe, and mcp docs is healthy (stdio; 2 tool(s)). The certified skill shows integrity verified.
  • capabilities shows permissions and workflow effective through their builtin providers; checkpoint, subagents, code-intel, and acp are not supported in this release.
  • policy explain mcp.tool.call docs:delete_document prints DENIED by the enforced rule acme.docs.destructive, with the default acme.docs.read allow listed as another matching rule. filesystem.read ~/.ssh/id_ed25519 is denied by acme.secrets.read with enforcement sandbox when the sandbox is enforced. shell.execute "git status" needs approval under acme.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/config finds nothing to read and !curl https://example.org has 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 --yes

After 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.

Release, update, and rollback

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.

  1. 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. keygen refuses 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
  2. Add trust.bootstrap under updates in the copied piship.yaml with 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 }
  3. Lock, build a release for this machine, and verify it. On Windows set sandbox.required: false first; a release that requires the sandbox is refused for win32-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
  4. Install it from the extracted release with its own script (install.ps1 on Windows), which verifies it again first. Uninstall any earlier acmecode install first, and pass --use-existing-state to 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 is acmecode.cmd in the bin directory.

  5. Change app.version in /tmp/acmecode/piship.yaml to 1.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
  6. 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.

Authorized live path

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.