Forge Cultures - Proposed way to collect ways of working

Chapter 1: Agreeing on a role system

I spent the last few weeks working on Object Capabilities. One of the many challenges I’ve had is to figure out how to handle role attenuation in a federated situation. Example:

  • Imagine a system with 4 roles: Admin > Maintainer > Developer > Guest
  • There’s a project P
  • There’s a team T
  • Alice is an Admin in team T
  • Bob is a Developer in team T
  • Team T has Maintainer access to project P

So which level of access to Alice and Bob have in project P? I’ll save you the details of how OCAP delegation works, here’s the final answer:

  • Alice has Maintainer access to P. Although she’s an Admin in T, Maintainer is what P is willing to give.
  • Bob has Developer access to P. Although P is wiling to give Maintainer access, he’s only a Developer because he’s limited by the role he has in T.

This works very nicely, because the OCAP delegation chain travels through actors that use the same role system. If they don’t, it’s not going to work. Federation between e.g. Gitea and GitLab won’t work, unless they use the same set of roles. Not just the role names, but the actual actions they allow.

What do we do? Force the entire fediverse to agree on a single set of roles? Force all forges to agree on a single set of operations for each role?

Recently I’ve been rolling this idea in my head: What if, much like the forge federation can host repos, tickets, messages, etc. it also hosts another kind of object, called a set of roles? And then actors can refer to role sets they understand. And forge related actors can delegate OCAPs, if they can find at least one set they have in common. Each side may try to match the roles it uses internally with the roles they have in common. Gradually, we can create sets of roles together, and federated forges can implement them, and things will converge. Makes sense?

Chapter 2: Custom ticket fields / Agreeing on issue labels

In Vervis, my ForgeFed implementation, a feature I’ve had for a long time is custom fields for tickets. It’s an extension of what’s commonly found as “issue labels” in popular forges. Some projects don’t use labels. Some use a simple small set. Some develop a rich set. And either way, it’s always possible to evolve it. But each project has its own separate set. When you create a project, perhaps your forge of choice lets you choose among some predefined sets, or copy a set from another project.

Can we release the process of creating useful label sets from the limitations of forges? Can we have a place where label sets are published and collaboratively evolved, and any project on any forge can then use any label set out there?

What if projects can mix and match different sets of labels from different places?

I know, it’s probably a really minor feature for most people…

Final Chapter: Forge Cultures

So, I’m seeing these pieces of the development workflow, that are sort of about the “culture” we have as a team/group/community of developers:

  • Which roles do we have in our organization? What’s the authority structure in our team?
  • Which data do we use and collect, both manually and automatically, about our issues, MRs, epics, milestones etc. that we then use to search, sort and filter them, and manage our work and our community effectively?

So, I have this very initial idea: Much like forges host repos and issues etc. etc., another kind of shared resource could a special kind of island called a culture. There, people can collaboratively define stuff like roles, custom issue fields and custom rules for transitions between them, maybe even custom git flows defining how issues, branches, PRs, merges, rebases, tags, reviews, approvals, code scans and releases play together. And projects can freely pick and use whatever they like from those things.

That way, as a community, we can evolve those things together. Have roles, labels, fields, flows, etc. as first-class objects in our forges.

Does this stuff make sense? Or does it sound like some crazy idea that will quickly fade when it meets pieces of reality?

This is not just for fun and exploration; it’s a practical question! Because I need to figure out the roles stuff to finish ForgeFed’s OCAP system :slight_smile:

Seems unnecessarily complicated. The question of scope is something addressed by e.g. OAuth, where you declare a desired scope and the provider will determine if that token should be generated or not.

I think what you call a “role” should not be standardized at all. Instead, an OCap grant should contain a Collection of standardized permissions. These permissions should be granular. Beyond this, roles should be an implementation detail.

I’m not sure of how best to define / standardize / signal these permissions, though. Perhaps something like this?


Representing an OCAP grant as a collection of capabilities and capability tokens

Project P sends this to Team T, representing their “Maintainer” role:

id = remote.com/grants/1
type = Grant
summary = "Project P made Team T a maintainer"
attachment = [
  remote.com/capabilities/1,
  remote.com/capabilities/2
]
audience = <Team T>
attributedTo = <Project P>

The “Maintainer” role might allow commits and updating the child repositories

id = remote.com/capabilities/1
type = Capability
summary = "Commit to all repositories in Project P"
token = <some token>
audience = <Team T>
attributedTo = <Project P>
tag = [
  <Collection of repositories in Project P>
]
id = remote.com/capabilities/2
type = Capability
summary = "Update info for all repositories in Project P"
token = <some token>
audience = <Team T>
attributedTo = <Project P>
tag = [
  <Collection of repositories in Project P>
]

But there might be other capabilities not granted, such as the ability to update the project itself (which perhaps only “Admins” can do):

id = remote.com/capabilities/3
type = Capability
summary = "Update info for Project P"
token = <some token>
audience = <Team T>
attributedTo = <Project P>
tag = <Project P>

This way, Team T can never hand out more than it has, because it does not know the Capability URIs or the associated tokens for anything beyond what was sent to it.


Attenuation by direct delegation of capabilities and tokens

Team T can now attenuate this Grant and give Developer B a subset of the capabilities, let’s say commit access only:

id = local.com/grants/1
type = Grant
summary = "B was granted Developer capabilities for [repositories in] Project P"
attachment = [
  remote.com/capabilities/1
]
audience = <Developer B>
attributedTo = <Team T>
tag = [
  <Collection of repositories in Project P>
]

If Developer B abuses this capability, then Project P can revoke the Capability for all of Team T by invalidating the associated token. It can then re-issue a new Capability if it trusts Team T to have resolved the issue.


Standardizing capability sub-types

Perhaps we can standardize the Capabilities into sub-types at the JSON-LD vocab level:

id = remote.com/capabilities/1
type = CommitCapability
summary = "Commit to all repositories in Project P"
token = <some token>
audience = <Team T>
attributedTo = <Project P>
tag = [
  <Collection of repositories in Project P>
]

If you wanted to get even more granular, then you could hand out capabilities to individual repos instead of one capability for all repos – and any time a new repo is created, Project P would have to send out the associated grants for capabilities on that new repo:

id = remote.com/capabilities/4
type = CommitCapability
summary = "Commit to project-p/repo1"
token = <some token>
audience = <Team T>
attributedTo = <Project P>
tag = [
  <project-p/repo1>
]
id = remote.com/capabilities/5
type = CommitCapability
summary = "Commit to project-p/repo2"
token = <some token>
audience = <Team T>
attributedTo = <Project P>
tag = [
  <project-p/repo2>
]

Just use tag imo with natural language properties like name or content

id = git.trwnh.com/a/umi
type = Repository
name = "umi"
summary = "A self-hosted Discord bot with a music player and VC join/leave logging."
tag = [
  {type = Note, name = "discord", url = git.trwnh.com/explore/repos?q=discord},
  {type = Note, name = "selfhosted", url = git.trwnh.com/explore/repos?q=selfhosted},
  {type = Note, name = "python", url = git.trwnh.com/explore/repos?q=python},
  {type = Note, name = "music bot", url = git.trwnh.com/explore/repos?q=music-bot}
]

Those more-granular capabilities could work as well, yes. But I’m not sure there’s anything about them that is inherently better than roles. If you see something like that, please do enlighten me :slight_smile:

ActivityPub based OCAPs are nominal/symbolic - they don’t provide a pointer to executable functions, just a description of an operation. Both roles and more specific operations define some “general” case, and the resource being operated on compares that with some more “specific” detail to decide whether your requested action is authorized.

Example for the more granular OCAPs:

  • You try to push to a repo, using an OCAP titled “push-to-repo”
  • The repo has some branch protection rules, so it makes sure those rules don’t exclude you from pushing to the specific branch you’re pushing to

Example for the role based OCAPs:

  • You try to push using an OCAP titled “developer”
  • The repo checks in its rules whether “developer” role is allowed to push to this specific branch

Since repos can have hundreds of branches, which can appear and disappear at any time, it’s impossible to send out a perfectly-granular OCAP for every tiny operation. So when trying to pick a set of “titles” for OCAPs, I’ve been asking myself:

What would be a set of terms that we can most easily agree on?

I went for roles because:

  • There can be many many granular operations, and the more granular we do, the more they differ among different forge software, and obviously any time someone can invent some new kind of tool (imagine the first time someone hooked CI into a forge) and it can’t (fully) federate until other servers add vocabulary support for those new kinds of operations
  • OTOH, roles are very simple: GitLab has IIRC ~5 standard project roles (excluding the “external contractor” stuff) and doesn’t even allow to define custom roles. Gitea has just 3. Github has 5 too, and allows some basic customization, which I suspect is rarely used, and only/mostly within companies.

So those roles seem to me like something that is way easier to agree on, a human concept that then lets the software figure out what exactly you’re allowed and not allowed to do. Which happens either way, so, lets at least enjoy the benefit of easily agreeing.

I’m making a note to examine this more deeply, thank you for writing that stuff :pray: But for now finishing the current version of the OCAP system using roles, because it’s much simpler.

What makes capabilities “better” is that it doesn’t require knowing who is making the request at all. You just check if the token is valid for the given scope or not. That’s the whole point of capabilities in the first place. Otherwise, if you’re going to use access control / ACL, then you don’t need to do any of this stuff at all. You could just standardize on a basic role system and use something like HTTP Signatures to authenticate, then have your complicated business logic based on the authenticated user. But that wouldn’t be OCap, and it would be wrong to call it OCap. The whole point of OCap is not to be “symbolic” at all – you have possession of a token that lets you do certain things. It does not matter who you are, it matters what you have.

This is why you generalize your capability to the desired granularity. You don’t need to generate an OCap for every branch operation if you don’t want to (but you can!) – you could instead just generate a token that will let you commit to all branches, or a token that lets you commit to all projects. How powerful or limited that capability is, well… that’s up to the granter/provider to decide. It might make sense to hand out a capability grant to whoever makes a branch, and then that person can share that capability with others.

This is exactly why the forge software should be able to decide granularity for itself.

You wouldn’t need to add vocabulary support at all! Unless you wanted to signal precise permissions, you could just grant a capability via a token (or check the ACL, if you went that route).

I hope I’ve made it clear via the above responses in this post: roles are not OCaps. They are ACL-based.

Unfortunately it’s still not clear to me in what way roles are ACK based while something more granular isn’t:

  • For the roles case, there’s some standard mapping saying which roles have push access, and the repo keeps a table saying which branches are protected and under which conditions etc. etc.
  • For the operations case, the first mapping is irrelevant, but the 2nd one is

There’s some tables and checks either way. Even with OCAPs, having the right OCAP doesn’t guarantee the operation succeeds. Maybe you’re pushing to a branch that doesn’t exist and you then get some error message.

I get the idea that OCAPs are about what you have and not who you are. Both options work that way:

  • Roles = what you have (developer access token)
  • Operations = what you have (push-to-repo access token)

The downside I see about roles is that there’s less flexibility about giving specific operation-OCAPs to specific people. But since even GitLab and githu8 don’t allow that stuff, I don’t think this flexibility is needed…

Another difference I see is that when I want to push, I send a very specific OCAP allowing just to push. Instead of sending a “Developer” OCAP that could accidentally let me do more stuff, that I don’t intend. I see the theoretical benefit in that, sort of “use the least authority”. But is it more than an elegance thing in my head? As a user I don’t choose the OCAP to send. The application chooses it for me. So if I decide to do something destructive, for which I have an OCAP, the application will send it. What’s going to save me is that UI will warn me about destructive operations.

Another point is that if the repo’s side is implemented well and can’t execute more than the OCAP allows, I’m reducing the risk for accidental unwanted operations by sending the weakest OCAP I can. But it’s not entirely eliminating the risk: I can send a push-to-repo OCAP, and the repo can still decide to do some destructive admin-only operation as a reaction. I get the point where I could still be at least helping the repo not do accidental unwanted things. But it seems such a minor thing, mostly because the OCAPs are symbolic: Even when I use a “Developer” OCAP, my Activity document says I want to push commits. So either way, I use a vocabulary to say what I want, and then it’s up to the repo.

What would improve the situation? Execution based OCAPs. On the repo’s side, there’s a function (in the programming language sense) that takes a list of commits and pushes them into the repo. When I create a repo, it sends me a “welcome package” that includes a pointer to that function. Even the repo actor itself can forget that pointer, once it has it encapsulated in the welcome package. I then use that pointer, calling the function, with the argument being the list of commits I want to push.

In that case, there’s much much less likelihood the repo does, or can do, anything other than the function allows. The OCAP isn’t a vocabulary term. The repo actor doesn’t even understands that what I’m asking is to push! It just gets a function from me and executes. Invokes the OCAP.

But I don’t think we can have such OCAPs in ActivityPub. They do exist in stuff like Spritely though.

Also, imagine an OCAP as a list:

developer=[push-to-repo, edit-settings, merge-pr, add-collaborators]

You can see that as a list of operation-ocaps, ignoring its name. Or as a role-ocap, detailng its operations. Maybe it’s both…

One more note

I feel like I wrote too much abstract and unclear stuff… I hope to find clarity about all of this soon…

Hoping to get more feedback from more people (the dream: feedback from @cwebber; even bigger dream is a chance to ask questions) about the OCAP stuff and find clarity along the way…

Not quite. There’s still a difference between authorization by possession, and authorization by authentication. Think of it like this: once you use an account token to authenticate who you are, the token is useless. Authorization is only used to make sure that a network request is allowed to access your user. OCap skips this step and goes further – it directly authorizes the action based only on the network request, without the intermediary check for who is using the token.

Again, the whole point is that you don’t have to know who is using the token. The “tables and checks” are much simpler. You avoid the question of “who are you?” and jump directly to “what can you do?” Anyone with the key can unlock the door, without a doorkeeper to check who is entering.

If you don’t need this flexibility then it’s fine to stick to ACLs. Just don’t call it OCap, because it isn’t. If you’re using tokens to check for identity, you’re not doing capabilities – you’d be doing permissions instead.

And, well, if you’re doing roles, then you might as well keep it basic. Take some inspiration from OAuth scopes.

Unfortunately I have very little time. My feedback here should be considered as just providing ideas to ponder on some high level concepts, and not directly related to the specific details discussed above.

There’s obviously many different moving parts to the models that need to be defined. Above are just some aspects in what will become very intricate models. They say domain modeling (DDD) is best-suited for complex domains and ones that will evolve over time. “Forging Software” is definitely in that category.

In the discussion above I once again get the impression that concepts are discussed that belong in different sub-domains / bounded contexts. While they are or may be related, considering their interactions in a single model makes things very complex. In particular I wondered about:

  • Role: A generic concept. In Microblogging groups you may have “Admin”, “Moderator”. When modeling formal groups (say W3C SocialCG) you may have “Chair”, “Secretary”. In forges domains you may have “Maintainer”, “Developer”, “Admin”, but also e.g. “Tester”, “Technical writer”, etc.

  • Privilege / access permission: A concept that is part of the OCAP you are modeling. Now it may be that here there’s also the notion of a Role within that bounded context. But maybe that is not needed.

In general the separation means you can say:

  • “The Actor is in a Role” → organization structure
  • “The Actor has access permission” → OCAP bounded context

Different bounded contexts, and Roles are a universal concept. They return everywhere in different domains. In the AP Groups discussion in reaction to @Claire and @trwnh I mentioned a way in which arbitrary organization structures might be modeled in a standardized way. If this were a FEP it’d be ready for adoption in many different app types.

Take for instance Valueflows by @lynnfoster and @bhaugen, where currently both @bonfire and @openengiadina are creating implementations. Valueflows currently has their own definition on how to model roles, but they also rely on the The Organization Ontology already.

On Cultures. While I like the notion of specifying the “culture” of a project, I wonder if it is not too abstract a concept (considering the semantic meaning of Culture when people hear the term). I would rather stick with terms more closely related to software development where people are already familiar with. I’d love to elaborate on the idea, as it is very closely related to / part of the FSDL in some way or other.

Given the enormous diversity and variation and ways to set up development projects something very flexible is needed. I am thinking of something different than a “Culture” concept. Something more generic again as a building block of Fediverse domain models: Policies and Policy Types.

Suppose an Object or Actor has a policies collection. You can use it in a kind of aspect-oriented programming way to attach behaviors / bounded contexts / AP extensions to it. Does your Project have a particular organization structure? Well, then attach one or more OrganizationPolicies to it. Does it have a particular governance model? Attach a GovernancePolicy.

Going this route would be very flexible:

  • Define a FEP for Policies.
  • Any app or community can define their own policies, introduce new policy types.
  • Some of these policies would be widely accepted, maybe standardized in additional FEP’s.
  • Others would be very domain- or app-specific.

Again, just some morning :coffee: thoughts. Sorry to not be more specific.


Update: Adding chat msg from ForgeFed matrix chatroom, as additional explanation.

[…] there may well be a concept of Role that is particular to the OCAP bounded context. But it is not the same as a similarly named concept of Role in an organization structure bounded context. There may be a mapping between them, though (in DDD terms this is called an ‘anti-corruption layer’. Each bounded context has a consistency boundary. The model within is always consistent (it is ‘bound’)).

stategic-ddd-bounded-contexts

As reflected in AP msg formats, namespaced, you’d get e.g. org:Role vs. ocap:Role. But I don’t have a clear picture of the OCAP model right now to be saying whether or not a role makes sense there.

That’s true depending on what you mean. Valueflows agrees that Roles in organizations exist but does not specify what they are, what they are called, or how they work. Eg VF does not specify Admin, Moderator, Chair, Secretary, Tester, etc. - just that you might structure your organization to have Roles and they would have names and some of those names might be used, or some other names.

Veering slightly OT, but brief answer: You use a flexible vf:Role but might’ve used org:Role in standardized ontology? (Let’s continue in Valueflows chat if you want, I cannot split threads anymore).