Repository navigation
feat(seat): per-agent integration access - #51
Merged
Merged
Conversation
Crewlet now stores, per agent seat and per vendor, whether the seat is on
there, what it may do in the vendor's own terms and where, and holds the
vendor to it (crewlet/crewlet feat/agents-access). Seat.spec.integrations
follows it: every block takes a required enabled, and gitlab, jira,
confluence and datadog join slack, github and mattermost.
- datadog: role, a Datadog role id.
- github: permissions (contents, issues, pullRequests, checks, actions,
workflows), each Read or Write, in place of tier and workflows.
- gitlab: accessLevel (Guest to Owner, rendered as GitLab's integer) or
memberRoleID, and projects.
- jira: projectRole and projects; confluence: spaceRole or operations,
and spaces.
- mattermost: roles, one of Mattermost's sets for a bot, and channels in
place of channel.
CEL holds each vendor's fixed set and the access a block needs while the
seat is on; live-listed ids are the engine's to check against the vendor.
Moved off Integration: GitLab's engineProvisioning.accessLevel and
accessLevels and Datadog's role go to each Seat; GitLab's tokenScopes go,
since a seat's token follows its role.
Credentials: the engine seals a credential it mints for a seat under its
own name, <VENDOR>_<FIELD>_<HANDLE>, and binds it to the vendor's tool
server itself, so spec.mcp takes Secrets only and a Mattermost bot token
is a Secret only, for a bot made outside the engine. provisioned remains
in spec.sandbox.env as {vendor, field} and renders the engine's name,
which secretsync.ProvisionedName now copies from envref.SealedName under a
mirror fingerprint. ADR-0004 is rewritten for it.
This reshapes v1alpha1 with no alias or conversion: no tag has shipped
the surface. Objects that set the removed fields must be rewritten.
Signed-off-by: pinotelio <ahmadreza.mollapour@gmail.com>
Crewlet's access pass acts on a kind's seat blocks only where the document names the kind in seat_access_migrated, its guard against reading a document from before per-seat access as every seat off. The operator renders that schema natively, and under the Enforce policy the engine's own migration cannot add the list, so every document now names every kind: atlassian, datadog, github, gitlab, mattermost and slack. The list is held to the engine's seataccess.Kinds in the mirror table, and Company.spec.engineConfig may not set the key. The merged engine also offers Deployments among a seat's GitHub App permissions, which Seat.spec.integrations.github.permissions now takes. ADR-0004's heading takes a colon in place of the dash. Signed-off-by: pinotelio <ahmadreza.mollapour@gmail.com>
Crewlet binds each seat's own credential to a per-seat tool server by
the variable or header the server reads, which it knows for the servers
it recognises by their command or address and otherwise takes from the
server's seat_credentials. MCPServer.spec.seatCredentials declares them:
the integration, for Atlassian the account's Token or Email, the key, and
an optional prefix such as "Bearer ". A declaration replaces what the
engine would assume, and a shared server takes none. CEL holds the
integrations, the Atlassian-only field, one header per key, and no
${NAME} in a prefix; the lists are held to the engine's in the mirror
table. as is not offered: Crewlet takes the transport's, the only value
it accepts.
A seat on Jira or Confluence that lists no projects or spaces now holds
the product's access alone, so projectRole, and spaceRole or operations,
are required only where it does.
The engine's new integrations.slack.app_config_token, with which it
creates each agent's Slack app itself, is listed as not offered: an
operator's Slack seats are apps made outside the engine whose
credentials each Seat hands in.
Signed-off-by: pinotelio <ahmadreza.mollapour@gmail.com>
Review follow-ups to per-agent integration access. - A seat turned off on Jira or Confluence keeps its account and its product access: the engine takes it out of its project roles or its space access and narrows its token. The enabled docs said product access went, which is not what the engine does. - Under Enforce, "Keep the vendor's setting" and "Keep off" become changes to the Seat; "Turn back on" acts at the vendor and still runs. - ProvisionedName's example pair is nova-1 and nova1: a handle holds no '.'. - An empty list reads as no list, as Crewlet reads it: a Jira seat with projects: [] needs no projectRole, and a Confluence spaceRole beside operations: [] is one grant. The CEL rules check size() and a CRD case sends the empty lists. Signed-off-by: pinotelio <ahmadreza.mollapour@gmail.com>
This was referenced Oct 30, 2026
Closed
Crewlet's main merged Chatlet (crewlet#142), the engine's own chat, as a ninth state log every data node runs whatever its chat backend. Its 1 GiB floor raised what a fresh node reserves on its event stream to 8120172544 bytes for the logs and 10826896725 with the nats object bucket, so a data pod on the smallest volume the API admitted (12Gi, a 9 GiB stream) refused to boot with "the broker refused to reserve the chat log's ceiling", and so did every engine the suites start on a 9 GiB stream. - EngineClass: data.storage.size admits no less than 14Gi with the files in the stream's bucket and 11Gi in S3, the least whose three-quarter share holds the engine's floor; TestContractStreamFloor holds both to `crewlet validate`. The getting-started guide, the e2e classes and the engine the suites run move to the new floor. - The data pod's startup window counts the ninth log: the join reads one more trim floor listing, one more row in each and one more log's stats, and the admission one more maintenance window, so the boot's own bounds are 1866 seconds (2006 with S3), and the API's floors on startupSeconds follow. The window is now computed from dataPodStateLogs, which TestDataPodBootCountsTheEnginesStateLogs holds to the replication loops a running data node reports in /fleet (engine.FleetNode.StateLogs), so the next log the register gains fails a test instead of shortening the window. - Unit channel: the engine now refuses a channel that is not a bare name (lowercase letters, digits, - and _, up to 64 characters), which failed every document with "#platform". The API takes the engine's pattern, TestUnitChannelFollowsTheEngine holds it to `crewlet validate` both ways, and the sample and fixtures name bare channels. - Chatlet is the default chat of a company with no Mattermost or Slack Integration. The getting-started unit names a channel, so a new company gets a team room whose members follow the org chart and the engine has nothing to say about the guide (and the README's copy of it); the engine now warns of a Human seat with no chat contact only on a vendor chat, which the integrations guide says. - Mirrors read again at abd826b859: the launch's decisions moved into (*agentLauncher).launch, the inbox relay became the live relay, and the boot's handshake replaced its call to admit, each now mirrored; every other moved declaration copies what it copied. Signed-off-by: pinotelio <ahmadreza.mollapour@gmail.com>
pinotelio
marked this pull request as ready for review
October 30, 2026 18:45
8 of 9 tasks
aliyousefiaan
added a commit
that referenced
this pull request
Oct 30, 2026
Unit.channel's doc says what the field holds and how Crewlet spells it, and since crewlet#142 that on Chatlet each channel is a room whose members follow the org chart. It did not say that such a room belongs to one unit: Crewlet refuses a second unit naming the same channel, so a child unit that should share its parent's room names none, which is the one way to share it. The rule over the field itself, and its test against the engine (TestUnitChannelFollowsTheEngine), came with #51. Signed-off-by: Ali Yousefian <aliyousefiaan@gmail.com>
aliyousefiaan
added a commit
that referenced
this pull request
Oct 30, 2026
Crewlet now runs a chat of its own, Chatlet: a company that declares no Slack or Mattermost (no integrations block for either, and no seat's Slack app) derives chat.backend chatlet, which is every company the operator renders without such an Integration. Two of its advisories moved with it. The warning of a Human seat with no chat contact is given only on a vendor surface, judged per surface: a seat with no account on the Slack or Mattermost the company talks on, a GitHub, GitLab or Atlassian id not counting. And a company on Chatlet whose units declare no channel is advised at units[0].channel that no team has a room the chart keeps. #51 already expects what the engine says of the guide's company and of the optional-keys case, which names no channel and is advised at units[0].channel, and the guide names its unit's channel. That left nothing holding the narrowed contact advisory, which the Seat API and two guides describe, so a new contract case, TestContractContactAdvisoryFollowsTheChatSurface, holds it on the Slack case: a Human seat with a GitHub login alone is warned at its contact of having no account on Slack, and one with a Slack member id is not. It goes red with either expectation flipped. The Seat contact doc, integrations.md and people.md now say the warning is given only on Slack or Mattermost, which accounts count, and that on Chatlet a person reads the rooms on the dashboard and nothing is warned; the troubleshooting page's list of what the engine says names both advisories, and two test comments stop calling a GitHub login a chat contact. Signed-off-by: Ali Yousefian <aliyousefiaan@gmail.com>
9 tasks done
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.
The operator companion of per-agent integration access in Crewlet: crewlet/crewlet#478 (
feat/agents-access). A Seat now says, per vendor, whether the agent is on there, what it may do in the vendor's own terms, and where; the engine holds the vendor to it.Breaking change to
v1alpha1. No tag has shipped this surface, so it is a plain reshape with no alias or conversion. Existing objects must be rewritten as below; a field the CRD no longer has is pruned on the next write.What changes
Seat.spec.integrationsgainsgitlab,jira,confluenceanddatadog, and every block takes a requiredenabled(an absent block is off):datadogrole: a Datadog role idgithubpermissions:contents,issues,pullRequests,checks,actions,deployments,workflows, eachReadorWrite(replacestierandworkflows: true)reposgitlabaccessLevel: Guest, Planner, Reporter, Developer, Maintainer, Owner; ormemberRoleIDprojectsjiraprojectRole: a project role id (none, with noprojects, is product access only)projectsconfluencespaceRole, oroperationswhere the site has no space roles (neither, with nospaces, is product access only)spacesmattermostroles:SystemUser, alone or with one ofSystemPostAllPublic,SystemPostAll,SystemAdminchannels(replaceschannel)slackCEL holds each fixed vendor set (GitLab roles, Mattermost role sets, GitHub permission keys and levels,
workflowsasWritealone),accessLevelormemberRoleID,spaceRoleoroperations, and the access each block needs while the seat is on; live-listed ids (Datadog, Jira, Confluence roles, GitLab custom roles) are strings the engine checks against the vendor. A scope outside the company's reach, and a credential the engine manages written where it may not be, are refused by the engine's dry run and reported as Invalid on the Seat.Moved off
Integration:spec.gitlab.engineProvisioning.accessLevelandaccessLevelsbecome each Seat'sspec.integrations.gitlab.accessLevel.spec.datadog.rolebecomes each Seat'sspec.integrations.datadog.role.spec.gitlab.engineProvisioning.tokenScopesis removed: a seat's token scopes follow its role.Credentials:
<VENDOR>_<FIELD>_<HANDLE>, and bound to the vendor's tool server by the engine. Seats no longer name it inspec.mcp; the engine's dry run refuses one there (reported Invalid on the Seat).spec.mcpvalues come from Secrets only (provisionedis gone there).Seat.spec.integrations.mattermost.botTokenis now optional and a Secret only (unset under provisioning Engine).provisionedsurvives inspec.sandbox.envas{vendor, field}and renders the engine's own name, e.g.${GITLAB_TOKEN_ANA}for git in the seat's box.secretsync.ProvisionedNamenow copies the engine'senvref.SealedName(fingerprinted in the mirror table); ADR-0004 is rewritten in place for this.MCPServer.spec.seatCredentialsdeclares which integration's seat credential a per-seat server reads and under which variable or header (kind, for Atlassianfield,key,prefix), replacing the engine's defaults for servers it recognises; refused on a shared server. Crewlet'sasis not offered, since it must equal the transport's.integrations.slack.app_config_token(the engine creating each agent's Slack app) is listed as not offered: operator Slack seats are apps made outside the engine.Render: the role blocks with
enabledalways written, GitHubpermissionsas GitHub's keys, GitLabaccess_levelas GitLab's integer, Mattermost roles in Mattermost's order;provisioning.role,access_levelandaccess_levelsare no longer rendered. Every document names every integration kind in the top-levelseat_access_migrated, the engine's gate for acting on a kind's seat blocks, since the operator renders that schema natively and under Enforce the engine's migration cannot write the list;engineConfigmay not set the key.Docs: integrations guide ("An agent's access" and each vendor), credentials concept page and guide, tool-servers guide, troubleshooting, company-document, ADR-0004/0005, samples, generated API reference.
Tests
test/crd: every new CEL rule, accepted and refused, plus the moved fields and the reshapedprovisionedsource.GitHubPermissionKeys,GitLabRoles,MattermostRoleSets) andenvref.SealedName.Verified locally against the engine at
feat/agents-accessba705774db (make crewlet CREWLET_VERSION=ba705774db), which carries every part of per-seat access merged, the seat credential binding and product-only access: the render and contract suites (crewlet validateon every golden, the samples and getting-started), the CRD and docs suites, secretsync and the controller suite, golangci-lint on both modules. The chart suite was not run locally (thehelmtarget's checksum step needs GNUsha256sum); CI runs it.To re-run once it lands on
feat/agents-access: the narrowedSeatCredentialMode, under which operator-rendered Slack and GitHub seats are external.CI runs against Crewlet's
main, so it stays red until crewlet/crewlet#478 merges; land that one first.