Project onboarding
The shared product journey starts at https://cloud.detent.build: sign in, create
or join an organization, then open a project explicitly shared with you. The
organization chooser, creation/provisioning state, scoped Work/project navigation
and billing reuse the existing Templ/HTMX shell. There is no forced tenant-domain
navigation or persistent global billing banner. Preserve compact/cozy and narrow
layouts, keyboard access, contextual errors and the tab's current run selection.
The organization lifecycle and acceptance trace below are the reviewed-design target for #2341/#2342/#2343, not shipped self-service behavior. Today's hosted implementation starts with a reserved organization; its project/runner setup is reused. Organization administrators create projects; collaboration and runner management remain separate explicit grants. Retrying project creation with the same name and creator's write grant resumes that project. Return to its scoped page to resume setup; infrastructure errors do not require another organization or host identity.
A new hosted project starts with the workflow Todo → In Progress →
Merging → Done, with Blocked and Human Review beside it. Todo, In Progress and
Merging dispatch work; Blocked and Human Review neither dispatch nor end it. By
default review.human is false, so nobody has to review a run. A run
that committed a change publishes a version the policy already accepts, and the
runner's orchestrator moves it straight to Merging, where the runner that
holds the project lands the head on the base branch with plain git and the Hub
finishes the issue in Done. A run that committed nothing moves to Done.
Human Review is where a Change Request waits for a person only when the
project sets review.human: true. With the default false, an unaccepted
version or refused landing moves to Blocked with its reason. A person approves the
change in the client, which moves it to Merging, or requests changes, which
sends it back to In Progress. Human Review is the default
auto_promote.source_state.
Hub migrations 36, 38, 39 and 41 move projects whose workflow is exactly an earlier
template onto this one. A customized workflow is left unchanged; it needs a
Blocked lane reachable from In Progress and Merging when review.human is
false, or a review lane matching auto_promote.source_state when it is true.
Accepted changes need a dispatchable Merging lane reachable from In Progress.
Hub migration 40 gives every project with an approved repository policy a
default review policy; approving the policy again now seeds require_review
from review.human.
The hosted hostname migration is tracked in the Cloud domain runbook. Its provisioning results and pending browser cutover are recorded there.
Runner access through Luna
In Add runner > AI assisted, an own-machine request such as “add a runner
on my laptop” asks once whether agents should run in the sandbox or with full
access. Sandbox is the default for a shared machine; native-trusted is
available for a dedicated machine. Sandbox restricts agent access, while full
access lets agents use the machine's files, credentials and network. Luna
carries the answer into the enrollment isolation_tier field and the
detent hub runner register --isolation-tier command. Obtain enrollment tokens
through the secure enrollment dialog, never through chat.
Fly Sprites are dedicated VMs and new Sprite runners use Full access
(native-trusted). An owner or admin can approve switching an individual Sprite
runner to Sandbox to request its isolation probe. Sandbox work waits until that
probe reports support. See Sprite runners.
For an existing runner, Luna reads its selected tier, backend support and
current problems. tier_unavailable means its agent backends do not support
the selected tier. An organization owner or admin can approve Luna's preview
to switch to a supported tier without enrolling again. Approval is always
required for runner access changes, even with ordinary chat confirmation off;
non-admin members cannot make this change. Projects requiring sandbox remain
ineligible for a native-trusted runner. Luna reads the runner again after the
change to verify the tier and identify any remaining problem.
Organization provisioning and recovery
Implemented by detent cloud serve when its configuration has an allocation
block (see shared entry). Without
that block the shared entry offers no creation and routes only registered tenants.
The entry keeps each intent in its registry keyed by the verified subject and a
per-form creation key, and advances it through the checkpoints below with bounded
retries (retry_limit, exponential backoff up to five minutes). WorkOS creation is
recovered by the organization's external ID, owner membership is looked up before it
is created, the tenant database is initialized by the tenant process itself, and
the tenant accepts the owner only after the provider reports that exact subject as
an active owner. A restarted entry resumes pending intents and relaunches ready
tenants. The shared web client screens for these states follow the
web/conversation client; the entry currently serves minimal pages in the existing
shell (/organizations/new, /organizations/ORG/provisioning,
/organizations/ORG/delete) and JSON at /api/cloud/organizations/ORG/provisioning.
A verified signed-in identity submits POST /organizations with a session-bound
CSRF token and stable creation idempotency key. Persist a fingerprint of the
validated creation input and an opaque organization ID before external effects.
Same user/key/payload returns the same operation; a changed payload conflicts.
Separate tabs retrying the same intent cannot acquire duplicate owners or slots.
An organization display name is never identity or proof of ownership.
| Durable checkpoint | Required action and recovery invariant |
|---|---|
requested |
Bind creator to verified provider issuer/subject and creation intent, enforce eligibility and per-identity quotas. Unverified email, client-submitted owner IDs and support impersonation cannot bootstrap ownership |
allocating: reservation |
Atomically reserve bounded process/memory/disk capacity with an allocation generation and fenced worker lease before creating resources |
allocating: provider |
Create/recover the WorkOS organization and owner membership for that exact subject; persist provider IDs and step result. Query/reconcile an uncertain response before another create |
allocating: tenant |
Allocate a private path and service identity from trusted IDs, initialize a fresh tenant database through its sole owner, persist immutable organization/provider/generation binding and that creator's owner membership |
allocating: free access |
Assign the configured versioned free plan idempotently. No Stripe customer, card or paid subscription is required |
ready |
Publish the registry route only after database health, tenant/provider membership, allocation and plan references agree. Link to /organizations/ORG/work and existing project/runner setup |
failed |
Preserve intent, reservation/resource inventory, completed checkpoints, bounded error and retry eligibility. Offer Resume for recoverable failures, without generating a new identity. No capacity shows a retryable state; existing tenants stay healthy |
deleting / deleted |
Apply the RFC owner-confirmed retirement contract, fence routing/writers and track erasure/retention results. Tombstones prevent reuse and resurrection |
A provider create without a usable idempotency/reconciliation mechanism cannot be blindly retried after an uncertain response. Record it as unresolved and reconcile through the provider's supported lookup or operator repair of that same intent. Likewise reconcile live process ownership before replacement. Retry workers have finite attempt/deadline/backoff limits; exhaustion exposes a sanitized resumable failure and retains evidence/resources safely. Cleanup uses an explicit allocation inventory and ownership checks, never recursive deletion of an inferred user path. Do not erase customer data after a failed signup.
The verified subject can recover a pending intent after login or process restart. Provider email changes do not transfer ownership. An existing organization must be joined through current membership or a provider-validated exact-recipient invitation, never claimed by matching its name/domain or replaying a bootstrap subject. Membership removal, re-invitation and last-owner protections remain in hosted identity. Account and organization deletion follow the RFC lifecycle contract.
Repository configuration and policy
Before increasing agent concurrency, use the project delivery guide to choose validation boundaries, test ownership, host capacity and release requirements. These are project choices; Detent's own repository policy is not a template every project must use.
Keep the customer checkout and its detent.yaml / WORKFLOW.md on the customer
host. Inspect existing files before using detent onboarding draft-answers,
explain-answers, or build-workflow. The builder's --help describes its local
output options. Review generated files before applying them. Cloud does not write
repository files. A supplied repository definition owns the native workflow;
Cloud Markdown authoring is the fallback when no repository definition exists.
Workflow definition authority
The configured workflow path and workflow_ref select the definition anchor.
The split layout puts machine configuration in detent.yaml beside agent
instructions in WORKFLOW.md; legacy YAML frontmatter in WORKFLOW.md is also
supported. Markdown bullets are instructions, never structured configuration.
A prose-only WORKFLOW.md without detent.yaml supplies no structured workflow:
native runners load the Cloud-authored definition when one exists, otherwise
report that a definition must be supplied. Malformed, mixed-layout or invalid
repository definitions fail with parser feedback and never fall back to Cloud
or the hosted template. Inaccessible files, unavailable Git and invalid refs
remain instance failures, not evidence that the repository has no definition.
The resolved configuration supplies native states, allowed transitions, terminal
and dispatchable classification, runner scheduling, and Cloud Board, List and
lane-picker contents. An enabled plan contributes its configured stop (normally
Plan Review) as a nondispatchable lane. Empty lanes remain available.
Native defaults permit moves between configured states; explicit
server.kanban.allowed_transitions entries constrain their source states for
both authorized issue moves and execution progress. Include completion and
landing transitions when constraining execution states.
The runner reports bounded state and transition metadata with the resolved repository configuration. Reports from the bound repository's default-branch history apply the shared workflow atomically with its source revision. States occupied by issues, including archived issues, cannot be removed; move those issues through ordinary authorized transitions before removing a state. Configuration changes never remap work items or revoke active leases.
Cloud settings show the repository source and revision and direct definition edits to the repository. UI, API and MCP definition writes cannot override repository authority. Authorized issue moves and other integration settings remain available.
With no supplied repository definition, the Workflow settings editor accepts
Markdown using the same legacy frontmatter schema: tracker.kind: hub_native,
tracker.active_states, tracker.observed_states, tracker.terminal_states,
and optional server.kanban.allowed_transitions. The body contains agent
instructions. Cloud persists that Markdown and derives its lanes with the
existing parser. Runners load and report the resolved definition. Adding a
repository definition transfers workflow authority without losing work items.
Repository-controlled definitions cannot be replaced by Cloud Markdown or
independent state arrays.
For the shared-site target, use client.hub_url: https://cloud.detent.build
and set client.organization_id to the selected immutable organization ID.
Select the runner's projects through enrollment project_ids or update its
allowed projects through runner routing.
The project's page shows its native ID. Run detent doctor against that configuration; resolve
reported problems locally. Sign in to the selected agent provider on that host.
Only checkbox acknowledgments of these local checks are saved in Cloud; they are
explicitly user-reported and do not override runtime validation.
Runners report the configuration resolved from detent.yaml and WORKFLOW.md.
Repository configuration reports do not require approval before dispatch. Trusted workflow definitions from the bound repository's default-branch history apply automatically. Explicit approval is required for administrative configuration adoption, such as applying an external local definition or changing local-binding permission. Configuration identity changes do not revoke active leases or invalidate recovered source.
On a self-hosted Hub without the web app, read get_project_policy and
get_onboarding, then use approve_project_policy with the current
expected_policy_id and the chosen observed_policy_id. The REST equivalent is
GET and PUT on /api/v2/organizations/{organization}/projects/{project}/policy,
with GET on the project's /onboarding for observed candidates. Approval needs
administrator authority and preserves revision checks. Cloud owners and admins
can continue approving in Settings > Integrations.
See policy approval without the web app for the complete API/MCP procedure.
Edit validation, review, auto-promotion and merge settings in the repository; the runner reports the resulting configuration when it reloads. Configuration identities remain attached to attempts and published source for provenance. A configuration ID change does not revoke an active lease, discard a checkpoint, or send completed work back to Rework.
Runner enrollment, organization and project access, execution isolation, lease fencing and the project's validation and review requirements still apply. Full-machine execution is the operator's chosen trust boundary; a second repository-policy approval is not required to use it. The legacy policy-revocation operation is retired and returns HTTP 410 without deleting configuration. Pause or drain a runner, or remove its project access, to stop execution.
A native project (tracker.kind: hub_native) makes no GitHub REST or GraphQL
calls by default. The runner does not give the agent the instance GitHub
credential, open or look up pull requests, or ask the agent for Workpad comments;
it opens the native Change Request from the attempt's commits after a successful
run, and the local gate is the CI. Git transport (clone, fetch) still works. The
agent prompt ends with a native completion contract that overrides GitHub steps
in WORKFLOW.md. detent doctor warns when the
repository WORKFLOW.md still describes GitHub steps; remove them from a
native project's workflow. Setting worker.github_token explicitly in the
project is the only way to give its workers GitHub access.
Customer host enrollment
In Settings > Providers & runners, choose Add runner > Manual to enroll a runner, give it a name and pick its projects. The dialog preselects macOS or Linux from your browser; switch it if the target machine uses the other OS. Install Detent before creating the one-time token, which expires after 15 minutes. The existing installer selects the latest release for macOS or Linux and detects amd64 or arm64:
curl -fsSL https://raw.githubusercontent.com/digitaldrywood/detent/main/install.sh | sh
On macOS, Homebrew is an alternative:
brew install digitaldrywood/tap/detent
The dialog shows the Hub's minimum runner version. Check detent version on
an existing installation and, if older, run detent update --yes --from-release
or brew upgrade digitaldrywood/tap/detent for Homebrew before registering.
The registration output also tells older binaries to update.
For a fresh machine, copy the dialog's combined install and register command. It adds the install directories to PATH so the new binary is available in the same shell:
curl -fsSL https://raw.githubusercontent.com/digitaldrywood/detent/main/install.sh | sh && \
export PATH="$HOME/.local/bin:/usr/local/bin:$PATH" && \
detent hub runner register --url https://cloud.detent.build/organizations/ORGANIZATION_ID \
--token TOKEN --name "Build host" --service
If Detent is already installed, copy the register command alone:
detent hub runner register --url https://cloud.detent.build/organizations/ORGANIZATION_ID \
--token TOKEN --name "Build host" --service
AI assisted setup with Luna gives the same installation guidance before registration on your own machine. Keep the token out of chat; the dialog masks it by default and copies it only into the command you run on the target host.
The register command generates the host's identity locally, redeems the one-time token,
writes ~/.config/detent-runner/global.yaml and identity.json, and installs
the detent.runner background service. Clone each project's repository into
the directory it prints (~/detent-runner/PROJECT by default) first, or run the
detent start --config ... --yes command it prints after cloning. Nobody copies
a runner or machine ID by hand. See docs/hub-api.md "Register a runner with
one command" for what each step writes.
Enrolling needs runner management on every organization project. An owner or administrator who creates a project receives it automatically; grants for other members are set in Organization.
Retry register with the same configuration directory: it reuses the identity
it generated, never creates a second runner, and never rewrites an existing
global.yaml. Enrolled runners reject unknown configuration keys at startup and
name their full paths, including nested machine settings. Before upgrading an
existing runner, remove projects, weight, priority, scheduling,
fair_share, global.agents.model_selection, client.native_projects and other
local project policy defaults. The warning-and-ignore transition has ended;
leaving these keys in place prevents startup. Cloud owns project membership and
model policy. Do not copy an enrolled identity file to another machine. The
older detent hub runner init and enroll commands, and the
--host-identity-file protocol for several runners on one host, remain for
scripted setups.
The page lists only runners authorized for this project. Names, host IDs, tags, health and exclusion reasons come from the existing routing evaluator. Tag edits preserve the runner's full access scope and use its expected revision. Other projects' grants, leases and provider account records are omitted from readiness.
Runner in a Fly Sprite
Inside a fresh Sprite, run sprite-runner-bootstrap.sh and paste the Hub's enrollment command when it prompts:
bash scripts/sprite-runner-bootstrap.sh
The prompt does not echo. Without a terminal, send the command on standard
input, for example from a private file you delete afterwards. The script splits
the command without a shell and rejects $, backquotes and shell operators. It
passes the token to detent hub runner register through
DETENT_RUNNER_ENROLLMENT_TOKEN, so the token never appears in an argument
list, the service launcher or the script's output.
The script installs the Go toolchain named in go.mod (the adjacent checkout's,
--go-mod PATH, or the pinned tag's), gh, the npm builds of Codex and Claude
Code, and the checksum-verified Detent v0.117.41 release. --version vX.Y.Z
selects another release; --from-source /path/to/detent builds a checkout
instead and is never the default. Registration uses the documented defaults,
~/.config/detent-runner/global.yaml and workspace root ~/detent-runner,
unless the command or --config and --workspace-root name others.
The host --service flag is replaced by a Sprite Service named
detent-runner that runs detent --headless in the foreground. The Sprite
runtime restarts it after a cold wake or crash and resumes it after a warm
wake, so polling continues whenever the Sprite runs. Whether the runner's
outbound polling alone keeps a Sprite from pausing is not yet verified; a
paused runner picks up work only after its next wake.
Re-running is safe. With empty input the script reuses the registered runner
and restarts the service. With a pasted command, register keeps the existing
identity and global.yaml, so no second runner is created. One runner per
Sprite is supported; do not copy its identity to another Sprite.
The script creates an installation checkpoint with sprite-env checkpoints create
and prints the ID. This does not verify authentication, registration health,
project setup or a real task. Failure output names the last completed step and
resume command. Follow the complete Sprite guide to verify
those steps and save the final ready checkpoint. Restore
the same Sprite with sprite-env checkpoints restore CHECKPOINT_ID. A failed
checkpoint fails the script but leaves the enrollment and service in place.
Remaining manual steps, which the script never performs:
- Sign in to the providers the project uses:
claude auth login,codex login(codex login --device-authwithout a browser), or a Sprites provider connector. - For private clones and pushes, run
gh auth login --hostname github.com --git-protocol https --webandgh auth setup-git, or set up the project's own git credentials, and set the git author. - The runner clones allowed projects into
workspace_root/PROJECT_IDand runs repository-declaredhooks.runner_setup. Use the bootstrap’s printed absolute paths and verify the checkout origin before running repository-pinned tools there. Do not prepare a separate manual clone; chain directory changes and dependency commands with&&. See Sprite setup. - Check the project's reported repository policy in the Hub; approve only if offered. Policy provenance is not a recurring claims or publication gate.
- Re-run the script with empty input, check
sprite-env services get detent-runnerand the Hub's runner health, then route a Todo issue and confirm it pushes a branch. - Take a new checkpoint so the baseline includes the sign-ins and checkouts.
Runner restart and claim recovery
The default claim lease is ten minutes, renewed every thirty seconds. This window covers a normal update's drain, binary swap and reconnect. Draining or interrupted runners retain in-flight claims until their leases expire, including when a policy or heartbeat error interrupts execution. Other runners cannot claim that work during the live lease.
A returning runner renews its retained leases with their existing fencing tokens
and resumes the recorded attempts, local worktrees and provider sessions. If it
does not return before expiry, any eligible runner can claim the work with a new
fencing token and recover from the pushed branch and Change evidence. Revocation
or removal releases the runner's claims immediately. An explicitly configured
client.lease_ttl_seconds continues to determine the lease window.
Artifact history and GitHub
Preserve the September 7 choices: local-only history, customer-managed durable
storage, and explicit opt-in hosted storage through the portable S3 adapter
(DigitalOcean Spaces initially). No setup default silently uploads artifacts.
Local history keeps artifacts on your own runner without a separate storage
account or fee; losing that machine loses its artifacts. Customer-managed
history uses an S3-compatible bucket and artifact gateway that you run and pay
for. Detent records the binding only and stores neither your artifacts nor your
storage credentials. For history with execution runners offline, follow
artifact deployment:
private S3-compatible bucket, durable catalog, separate TLS gateway, dedicated
publisher identity, and local storage credentials. Register only the gateway
origin, service ID and publisher token ID. Never enter the publisher token secret
or storage credentials in the hosted form. Run verify-storage, then test opening
a retained artifact with execution runners stopped. A registered binding is
configuration evidence, not a storage-health or offline-availability guarantee.
Native issue creation works with GitHub disabled. To connect GitHub:
- Associate the repository as
owner/namein Project setup. An enrolled runner clones it using its own Git credentials; setup shows progress until the runner reports the matching checkout. No manual clone is required. - Install the Detent Cloud GitHub App on the repository or organization. Choose all repositories or only selected repositories, including this project's repository. The App requests Issues and Pull requests read/write, and Contents, Checks, Commit statuses and Metadata read permissions.
- Open Project setup to read whether Detent Cloud is installed on
owner/name. New GitHub issues enter Triage automatically once the App is installed.
If the deployment has GitHub transport, attach a repository before manual imports, summary projection or repository/PR integration. Each choice is separate; saving setup progress does not activate an integration. Existing profile, repository binding, idle-work and revision checks still apply. Import execution and summary publication remain separate actions through the existing native API.
Shared protocol and recovery
Self-hosted Hub uses the same scoped API with customer-selected authentication, without a Detent Cloud account, subscription or Stripe connection. These are currently supported project setup endpoints:
GET /api/v2/organizations/ORG/projects/PROJECT/onboardingreads persisted progress, current approved policy, eligible runners, bindings and latest run.PUTto the same path acceptsidempotency_keyandprogress, containing stringrevision,repository(existing/generate), booleansdoctorandprovider, andartifacts(local/customer).- Writes compare revisions and reuse the native idempotency journal. Restarting the service retains progress. Browser retries retain a command key and payload fingerprint in session storage; no issue body or provider credential is stored there. In the shared-site target, browser retry keys also bind organization, project and user so concurrent tabs cannot replay into another scope.
- The
/onboarding/policy,/onboarding/artifact-services/SERVICE,/onboarding/integrationand/onboarding/repositoryadapters require scoped administration and call the existing validated implementations.
A stale revision returns 409 and requires rereading current state. Invalid input returns 422, access failures require account/grant correction, and service failures require retrying the existing operation. The first native issue uses the ordinary idempotent work-item API. Its state determines whether it queues; policy and runner checks remain authoritative for execution.
The current artifacts progress field accepts only local/customer; the
approved opt-in hosted mode is a target storage choice, not a new value accepted
by this API today. Future setup must save explicit custody consent and use the
configured portable artifact service rather than overloading local/customer.
Shared-site acceptance trace
This is a test specification for #2341/#2342/#2343 and the expanded #2199 evidence,
not a claim that current reserved-tenant fixtures pass it. Use synthetic providers,
ephemeral services and one test hostname representing cloud.detent.build.
Alice and Bob are unrelated verified identities. Alice creates A, Bob creates B, and Casey receives separate invitations to both. A has project PA and B has PB. Each tenant owns a separate database and process. Every application URL below is on https://cloud.detent.build; WorkOS/Stripe hosted screens may temporarily leave the site only for their authenticated provider flow and return to that same host.
| Step | Action and expected evidence |
|---|---|
| Signup | Alice and Bob create A/B from /organizations/new without operator YAML, bootstrap user, DNS or public port allocation. Crash/retry after every checkpoint returns the original A/B IDs with exactly one verified owner each |
| Capacity | Concurrent creates at the admitted limit leave a resumable capacity failure; already-ready A/B remain routable, no extra process or unsafe disk allocation appears |
| Invitations | A/B admins invite Casey through the common /invite entry. Exact recipient and provider organization must match; wrong-account, alias, expired/replayed and guessed-organization attempts fail. Casey sees both memberships, never an unrelated organization |
| Grants | Grant Casey read-only PA and write PB, separately from runner management. Ownership/admin status alone never reveals project content. Bob cannot read PA by guessing its ID; cross-tenant searches/cursors return no A content |
| Concurrent tabs | Casey opens /organizations/A/projects/PA/... in one tab and /organizations/B/projects/PB/... in another. Selecting a B run, submitting a B form, reauthenticating B or opening billing leaves A's URL, CSRF context, stream and run/attempt selection intact |
| Mutations and streams | A write remains denied and B write commits only to B. Swap route/body/header IDs, reuse a CSRF token in the other scope, spoof proxy authority, and bypass the entry service: all fail. Frames/cursors from A never appear in B |
| Billing | Free signup creates zero Stripe customers. Alice's first paid action creates/reuses A's test customer; Bob's creates/reuses B's. Casey cannot purchase as member. Swapped customer IDs, A return URLs in B and cross-account/mode webhooks cannot grant or expose another organization's plan/invoices |
| Runners | Enroll named/tagged RA for A/PA and RB for B/PB against the same base URL. Each renews/posts events/claims only its own scope; cookie changes and forged IDs cannot move credentials. Exact-host no-match waits without fallback |
| Artifacts | Test local-only without uploads, customer-managed durable service, and explicit opt-in hosted Spaces-compatible fixtures. An A grant fails for B, expired/revoked grants fail, and retained uploaded artifacts remain readable with execution runners stopped. Local-only makes no offline-availability promise |
| Review policy | PA requires human review; PB permits automatic merge after checks. Repo-local policy remains authoritative for both, with external branch protections and independent-check principals preserved |
| Revocation | Remove Casey from A while both streams are idle: A protected requests/download authorization fail immediately on recheck and its stream closes within 30 seconds. B stays usable. Shared logout/expiry closes both scopes |
| Restore/deletion | Restore paired registry/tenant fixtures with current tombstones and a new generation; stale authority fails. A owner deletion remains resumable until erasure completes, does not affect B, and is never reversed by old snapshots/webhooks. Casey account deletion removes both memberships without deleting Alice/Bob's work |
| Free self-hosting | With custom/local auth, block all Detent Cloud, WorkOS and Stripe networking. Create native work, enroll a runner, execute/checkpoint, export and recover with no subscription or hosted entitlement requirement. Repeat using a local WorkOS fixture with Cloud/Stripe blocked: auth selection still performs no billing call |
Record browser evidence for the existing shell in compact/cozy and narrow layouts, no-access/expired-session states, provisioning interruption and the two-tab trace. Record tenant database IDs and bounded routing/provider call counts without content or credentials. #2199's free pilot checks free/complimentary billing UI; paid customer/webhook cases are #2343 fixtures and do not block that free pilot. The retained single-tenant evidence is a baseline, not shared-site launch evidence.
Independent Change checks
Known setup limitation: the private non-hosted maintenance recipe below is not executable for an already bound hosted database, which rejects local-mode reopen. #2350 tracks a supported credential-maintenance path and correction of that recipe. Keep the binding checks intact. The existing scoped independent-check submission protocol remains valid; this RFC does not add token provisioning/rotation/revocation authority.
Hosted admission permits a dedicated operator bearer to submit only
POST /api/v2/organizations/ORG/projects/PROJECT/work-items/ITEM/changes/CHANGE/versions/VERSION/checks.
The credential must be active, belong to the hosted organization through an
explicit current project grant, and be named by an independent required check
in that project's current approved Change review policy. An administrator bearer,
execution worker, hosted user cookie, or unapproved operator cannot substitute.
This exception grants no reads, publication, review decisions, policy changes,
imports, or token administration.
Enrolled runners retain their existing collaborate operation for expected
customer checks. They cannot satisfy an independent check or change the
source pinned by the immutable version.
Provision and maintain credentials for an already initialized hosted tenant during
an explicitly authorized private maintenance window. Stop the hosted process,
then start the sole database owner with the same hosted configuration, database
path, organization/provider/bootstrap identity and public URL. Set
workos_organization_id explicitly to the allocated provider organization
if the original bootstrap configuration omitted it:
detent hub serve --database /var/lib/detent/hub.db \
--hosted-config /etc/detent/hosted.yaml \
--credential-maintenance --listen 127.0.0.1:7778
Keep this listener private, outside all reverse-proxy routes and port forwarding.
Maintenance rejects non-loopback listeners and --trusted-proxy. Each request
below goes to http://127.0.0.1:7778 with
Authorization: Bearer <existing-instance-administrator-secret>. Loopback
alone provides no authority. Use the existing unscoped instance administrator
credential retained at initial setup; the default DETENT_HUB_ADMIN_TOKEN
environment variable is still required by the serve command, but maintenance
never creates or resets a bootstrap credential. Browser cookies, staff sessions,
scoped administrators, CI reporters and runners cannot authorize maintenance.
Only the four token creation, rotation, revocation and project-grant routes are served. Requests record the administrator principal in hosted audit history. The normal database ownership lock prevents a second writer. Tenant binding checks remain enabled; ordinary non-hosted reopen still fails. Shut down maintenance before restoring the original hosted command. Public hosted serving continues to deny instance token administration.
- Create a dedicated token with
POST /api/v1/tokens, using{"name":"project-independent-ci","scope":"operator"}. Deliver the returned secret once through the CI secret store; retain its publicidfor policy. - Grant exactly the intended project with
POST /api/v2/tokens/ID/grants, using{"organization_id":"ORG","project_id":"PROJECT"}. This also marks the token native-only. Use a separate principal per project when independent revocation is needed. Restore hosted serving after maintenance. - An organization owner/admin with project write access approves
PUT /api/v2/organizations/ORG/projects/PROJECT/change-review-policyusing their hosted session and CSRF token. Includeidempotency_key, the currentexpected_review_policy_idwhen replacing a policy, andpolicycontaining the approved repositorypolicy_id,require_review, andrequired_checks. Each check pinsname, the credential ID asprincipal_id,workflow_id,workflow_sha256,source: "independent", andmax_age_seconds(60–604800). Repository review and check floors still apply. - Publish a version, then deliver its exact check expectation and immutable
head, run, policy/configuration and workflow identities to the CI job through
the publishing integration. The result bearer cannot fetch them itself.
Submit a terminal result and evidence references with a unique idempotency
key to the exact version's
/checksroute.
To revoke submission access immediately, the hosted project administrator
replaces the approved review policy with an eligible replacement CI principal,
using its current policy identity. The removed principal cannot submit or replay
results, even for old versions. Publish a new version after policy replacement;
old versions retain their immutable policy and display stale-policy readiness.
For permanent credential revocation use DELETE /api/v1/tokens/ID through the
private maintenance administration path. Rotation uses
POST /api/v1/tokens/ID/rotate, invalidates the old secret, and preserves the
principal ID and grants; distribute the replacement only to the authorized CI.
A removed project grant also immediately denies submissions.
Credential activity, hash, expiry, scope, project grant and current policy pin
are rechecked within the mutation transaction before returning cached results.
Results still validate the version's expected principal, head/run, policy/config,
check-run ID, workflow digest, independent source and completion timestamp.
Changed terminal results conflict; retries cannot approve another version.
Evidence loses readiness at its freshness bound. Human-review repositories also
need current-version human approval; automatic repositories need passing checks.
Native reviewed status preserves the separate GitHub branch-protection gate and
does not authorize an external merge.