These decisions constrain every change to PiShip. Each names where it is enforced; a change that would break one needs an explicit, reviewed update to this file first.
| # | Decision | Enforced by |
|---|---|---|
| 1 | Pi stays upstream. No fork, vendored copy, source patch, or private import. | scripts/check-boundaries.mjs (only packages/pi imports Pi, public entrypoint only); compatibility |
| 2 | PiShip is open source and company-first. A first production consumer proves the managed model but does not own the generic API; personal mode reuses the same contracts. | Public examples are neutral (examples/demo-company, examples/personal, examples/developer); consumer-specific code lives in its own distribution repository |
| 3 | The manifest declares static intent, the lock records resolved static facts, and runtime diagnostics report dynamic state. | @piship/schema, the piship.lock builder in @piship/core, doctor |
| 4 | Identity, runtime credential, and inference are separate contracts, and identity may be absent. | IdentityProvider, CredentialProvider, SecretStore, InferenceProvider in @piship/contracts; identity, credentials, and inference may import only contracts |
| 5 | Runtime credentials never live in ordinary configuration. PiShip-managed secrets use a secret store and process- or provider-scoped injection; Pi-native auth is an explicit personal delegation. | Schema secret checks; SecretValue redaction; credentials |
| 6 | In managed gateway profiles, upstream provider and gateway administration credentials stay server-side; the device holds only a scoped runtime credential. | http-broker protocol; security |
| 7 | Pi resources and PiShip capability providers are separate; ordinary extensions are not wrapped in a capability contract. | resources and capabilities manifest sections; manifest |
| 8 | Managed mode disables ambient inheritance: undeclared global or project resources and personal credentials do not load. | Resource loader with discovery disabled; managed environment sanitizing; in-memory model credentials |
| 9 | When distribution policy governs resources and instructions, PiShip owns discovery. | Governance session resource admission and project trust in @piship/policy |
| 10 | Governance is not containment. Permission hooks never replace an OS or process sandbox. | Separate @piship/sandbox; security |
| 11 | Every decision names its enforcement plane: control-plane, sandbox, gateway, or audit-only. An audit-only decision is never reported as prevented, and an action with no runtime seam is reported unsupported, never enforced. |
PolicyDecision.enforcement; RUNTIME_SEAMS in @piship/policy; policy explain |
| 12 | Capability state has six independent axes: supported, resolved, enabled, compatible, healthy, effective. | @piship/policy capability state; capabilities |
| 13 | Reproducibility covers the static envelope; entitlements and remote health are never lock facts. | Lock builder; piship reproducibility; release-candidate workflow |
| 14 | Standard protocols first: OIDC and OAuth, and OpenAI-compatible inference, before organization-specific adapters. | @piship/identity, @piship/inference |
| 15 | PiShip does not implement OIDC or JWT cryptography; it uses a maintained library. | openid-client in @piship/identity |
| 16 | Runtime credentials are never rollback state. Rollback may require signing in again; it never restores a secret. | Update and rollback credential handling in @piship/core; lifecycle E2E |
| 17 | A private-only profile never silently contacts an undeclared public endpoint. | Managed fetch and network.privateOnly in @piship/contracts |
| 18 | PiShip owns contracts, not every implementation; upstream Pi and certified or distribution providers are used where appropriate. | Capability provider trust classes |
| 19 | Product parity requirements of one consumer belong to that consumer's profile, not to the universal contract. | Review rule; see contributing |
| 20 | Personal mode is a profile, not a separate architecture: identity, broker, gateway, sandbox, and audit may be omitted, while manifest, lock, isolation, resources, compatibility, and lifecycle stay shared. | One schema family and lifecycle for both deployment.mode values |
| 21 | Adapter authors use two packages of their own: @piship/adapter-sdk (the supported surface for writing an adapter) and @piship/adapter-conformance (kits that test an adapter through the SDK only). The SDK is not a @piship/contracts subpath: its sandbox helper wraps customBackend() from @piship/sandbox, which depends on contracts, so a subpath would make contracts and sandbox depend on each other. Both are built to a publishable shape (exports, files, no private imports) but stay private: true; publishing them needs a separate maintainer decision. The SDK ships in every payload, so a single-file adapter can import it; the conformance kits never do. |
scripts/check-boundaries.mjs (the SDK may import only contracts and sandbox, the kits only the SDK); workspacePackages in @piship/core |
| 22 | PiShip-derived state belongs to one principal, (issuer, subject): the runtime credential and its entitlement, the stored sandbox credential, and the model selection are bound to it and cleared when another principal signs in, and one user's credential is never usable by the next. A failed remote revocation does not block the next sign-in: the local secret is always deleted and the revocation is retried and reported. Pi session history is project data owned by Pi; with an identity it is kept per principal in a directory Pi's public API accepts. |
Principal binding and no-residue rules in @piship/core access and @piship/credentials; user-switching E2E and the reference stack's user-switching test; user switching |
| 23 | A headless or workload run uses the identity adapter it already has (identity.mode: adapter declaring interactive: false), with no manifest change and no stored interactive login. The workload session is held in memory only; the runtime credential uses an existing secret store, and there is no memory-only credential store. |
DistributionAccess workload path; headless E2E; workload identity, headless storage |
| 24 | The HTTP audit sink speaks the published piship-audit-batch/v1 and nothing else: every event carries a random id for de-duplication (an additive field), a distribution cannot declare its own sink type, and PiShip does not authenticate to a sink; a collector restricts senders at its own ingress. |
JSON Schema in the integration contract, checked by packages/audit/src/wire-schema.test.ts |
| 25 | Mandatory audit has one policy: a required sink buffers up to audit.buffer.maxEvents while it is down, and governed actions, including model requests, fail closed once that buffer is full. Events a required sink did not take are reported with AUDIT_UNAVAILABLE, never dropped silently. There is no separate buffer-or-fail setting. |
@piship/audit and the governance session close; audit |
| 26 | Sandbox backend maturity is recorded per backend on the status page, not as surfaces in compatibility/pi.json, and does not change the weakest-surface rule in release.json. The native backend on Windows stays unavailable. |
Review rule; compatibility/pi.json lists only the four Pi surfaces |
| 27 | (Applies to the alpha schemas; decision 34 carries it over to piship/v1.) While the manifest and lock schemas are preview (alpha), a backward-compatible addition keeps the schema version: a new enum value or a new optional lock key. Removing a field or value, making one required, or giving an existing one another meaning needs a new schema version. v0.7 adds sandbox.credential: stored and the lock keys runtime.stateSchemas.sandboxCredential and credentialIssuance this way and stays on piship/v1alpha4 and piship-lock/v1alpha4, with no migration and no relock. A release that uses a new value installs by update only from a release that knows it. |
Review rule; manifest; lock compatibility in payloadStateSchemas |
| 28 | Workspace consistency and git-control protection are reported separately. A shared or synchronized workspace is verified by the two-way propagation check alone. Git control is its own result: a protected path the sandbox could write or create fails closed, while one it might only rename is not-verified (or attested-renames when the mount structure rules renames out), keeps the commands running, and is shown by doctor and the sandbox warning, not counted as protected. On native Linux, bubblewrap cannot guard a protected file that does not exist yet, so git control is not-verified there in every regular repository; that is a stated non-claim, not a failure. |
@piship/sandbox workspace check; sandbox; status |
| 29 | Audit user under piship-audit/v1 is, from v0.7, the principal as principalId gives it: the issuer, #, then the subject. v0.6 wrote the bare subject. The meaning is now fixed: any further change to it needs a new audit schema, and there is no separate principal field. Records written before v0.7 are not rewritten. |
principalId tests in @piship/contracts; audit events in branded-logout.test.ts; integration contract |
| 30 | A rollback to a release that predates the Linux Secret Service part layout (a lock without runtime.stateSchemas.credentialIssuance) is a credential transition: the switching release deletes the identity and credential secrets, every part included, confirms the deletion, and removes their metadata before the switch; a failed deletion stops the rollback before activation, and the target signs in again. |
applySecretLayoutTransition in @piship/core; storage-transition.test.ts |
| 31 | A managed client identifies itself to the credential broker and the LLM gateway with an additive PiShip-Client request header (an RFC 8941 dictionary of distribution, version, piship, and an integer protocol, 1 for the v0.7.x contract). It is a self-reported label for the service's own policy, such as refusing releases below a minimum version; PiShip never makes an authentication or authorization decision from it, and a service must not treat it as proof. Members are only added; protocol increases only for a wire change a service must know about. No other service receives it. |
pishipClientHeader in @piship/contracts and its tests; broker and gateway requests in @piship/credentials, @piship/inference, and the Pi provider registration; client identification |
| 32 | A managed user may relax ask, and nothing else, only where the distribution opts in: policy.userAuto: allowed (default off, added to piship/v1alpha5 under decision 27, so a manifest without it locks byte-identically). Auto mode turns a final ask from policy.defaults or policy.default into an approval, interactive or headless; deny and every ask from policy.enforced, a team or project rule, or the user's own rules are untouched, so an administrator keeps a prompt by enforcing it. It is opt-in per distribution because it trades the person's per-action confirmation for fewer prompts, which only the administrator who chose ask can accept; it is audited instead (policy.auto_approved, policy.auto_enabled, policy.auto_disabled). The switch is user state (config/auto.json): bound to the principal binding and reset when another principal signs in, like the model selection; kept by uninstall, deleted by purge; inert under a release that does not allow it. Personal manifests reject the field: there the user owns the policy and already relaxes a default with an allow rule in config/policy.json, so a second mechanism would add nothing. |
parsePolicy in @piship/schema; PolicyEngine.keepsPrompt; GovernanceSession.decide; user-auto tests in @piship/schema, @piship/core, and @piship/pi; user auto mode, policy precedence |
| 33 | Plain HTTP to a private or internal host is admitted by default, with no per-endpoint opt-in, because intranets use plain HTTP a lot: the update channel (updates.transport), the gateway, the broker, the OIDC issuer, MCP servers, audit sinks, and remote sandboxes (httpTransport). An explicit https forces one endpoint to HTTPS-only (http-allowed is the default spelled out), a public host is always refused, and a plain-HTTP update source needs bootstrap update trust. Update integrity comes from signatures, digests, and the sequence floor, never from the transport, so HTTP lets a network attacker withhold or delay updates (and withhold a root rotation) but not forge or replay them. The boundary rules did not move: the runtime credential is never sent over plain HTTP (an MCP server or sandbox with credential: runtime stays https-only), identity.oidc.redirectUri stays loopback, plain HTTP reaches only the endpoint's own origin, a plain-HTTP request through a proxy that is not itself private is refused, and validate warns on one line what travels unencrypted. |
updates.transport in @piship/schema; checkSourceUrl in @piship/core; the plainHttp option of createManagedFetch in @piship/contracts, passed only by update; update-http-transport.test.ts; manifest |
| 34 | piship/v1 and piship-lock/v1 are stable. They carry the v1alpha6 content unchanged; only the schema ids differ. Decision 27's alpha rule becomes the stable rule: a backward-compatible addition (an optional field, a new enum value, an optional lock key) keeps the version, and anything that removes, renames, requires, narrows, redefines, or changes the default of an existing field needs a new major schema with its own migration. A security fix may reject an unsafe input and is a listed behavior change. A manifest digest includes its schema id, so a v1alpha6 manifest and lock keep their digests, and a v1 manifest has its own. |
Support policy; packages/schema/src/v1.test.ts, packages/core/src/lock-v1.test.ts |
| 35 | piship/v1alpha6 stays fully accepted (validate, lock, build, release, migrate) through every 0.11.x and 1.0.x release, and leaves build and release only in a new major version, announced in the release before it. A schema stays migratable at least until the major after the one that removed it from build. migrate --check is read-only and answers migratable, requires review, or cannot migrate; a value only the owner can decide is reported, never rewritten. A runtime that predates piship/v1 cannot update an installation to a piship/v1 release, so the first release on v1 follows one release built by this PiShip on piship/v1alpha6. |
Support policy; packages/schema/src/migrations/migrations.test.ts, packages/core/src/lifecycle-schema-compat.test.ts |
| 36 | Every user-visible restriction answers four questions: the threat model, the protected boundary, the attack or failure it prevents, and why lower friction is not enough. A managed distribution fails closed only at identity, credential, policy, a required sandbox, and artifact integrity; an optional service degrades and never makes the agent unavailable. | UX contract |
Before adding a feature, ask whether it helps describe, build, govern, distribute, secure, diagnose, reproduce, or maintain a Pi-based coding-agent distribution. Generic agent-loop or runtime behavior belongs upstream in Pi. Behavior that exists for one organization's product belongs in that distribution, unless the contract is broadly reusable.