Skip to content

feat(permissions): add capabilities and scoped tokens - #6

Open
PiquelChips wants to merge 1 commit into
feat/syncfrom
feat/permissions
Open

PiquelChips wants to merge 1 commit into
feat/syncfrom
feat/permissions

Conversation

@PiquelChips

@PiquelChips PiquelChips commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Replaces the old origin.toml permissions. policy.toml now says who may do what:

  • [[paths]] gives each user, node or group none, read or write under a path.
  • [grants] gives the annotate, land and admin capabilities.

Only admin lands on policy, so nobody can grant themselves rights.

Tokens:

  • Servers accept static tokens from their configuration.
  • They also accept short-lived scoped tokens, signed with the server's node key and verified without storage (origin token issue).
  • A scope only narrows its holder's current rights, admin included.
  • A token lasts at most 7 days, and never longer than the token that issued it.

Reading:

  • A caller who can read no path sees nothing at all.
  • Snapshot walks withhold the content of unreadable paths.
  • Revisions that only change such paths are hidden, along with their statements and events.
  • Descriptions and payloads become attachments, an object kind of their own, so no record can carry a file's content.

Writing:

  • Records must be the caller's, and statements must be about records the caller can see, of a kind they may annotate. That includes promotions, and reserved kinds come only from their authors.
  • Revisions may only change paths the caller can write, and may only hold content they sent or that already sits at the same path in a snapshot they refer to.
  • Landings may only change what the submitter can write.

Taking over a change: on paths a writer can't write, a new revision must have exactly the effect of each of its predecessors. That means the same files, changed from the same contents to the same ones, on the same change below, with the same conflicts unresolved.

  • Anyone can take a change over and pass it on.
  • But no choice of base, chain of revisions or detour through another change lets them empty, revert or resolve what they can't write.
  • A change's history is fixed: no new first revision, and no predecessor from another change. Only its creator, or an admin, can abandon it.

Clones:

  • They record each user's rights, keyed by user and token scope, and keep withheld files out of the working copy.
  • They check pushes locally, the same way the server would.

New commands and endpoints:

  • whoami, permissions and token issue.
  • GET /tree and GET /file, which browse any snapshot without its history.

Benchmarks: permission checks cost nothing measurable. A landing takes about 16 ms with or without rules, and a 20k-file snapshot walk about 38 ms.

Review fixes: a pushed core.conflict must come with its list, or be refused. Proved with focused tests that fail without their fix, and just (332 tests).

Reviewed with review-loop: 2 rounds with 2× GPT-6.1 Sol and Claude Opus 5.5. Verdict: approved

Note

Written by Claude Opus 5.5 in Claude Code, reviewed by GPT-6.1 Sol and Claude Opus 5.5 over three review rounds (via T3 Code).

🤖 Generated with Claude Code

@PiquelChips
PiquelChips added this pull request to stack #7 October 14, 2026 14:38
@PiquelChips
PiquelChips force-pushed the feat/permissions branch 2 times, most recently from 6cef652 to 202bf52 Compare October 14, 2026 17:32
@PiquelChips
PiquelChips force-pushed the feat/permissions branch 2 times, most recently from 8af0822 to fe22e3e Compare October 14, 2026 18:50
@PiquelChips
PiquelChips force-pushed the feat/permissions branch 2 times, most recently from e4017c6 to 2121267 Compare October 14, 2026 21:46
@PiquelChips PiquelChips self-assigned this Oct 15, 2026
@PiquelChips PiquelChips added the enhancement New feature or request label Oct 15, 2026
@github-project-automation github-project-automation Bot moved this to Needs Triage in origin-src.dev Oct 15, 2026
@PiquelChips
PiquelChips force-pushed the feat/permissions branch 2 times, most recently from 2c1a19f to df030d0 Compare October 28, 2026 16:49
@PiquelChips
PiquelChips force-pushed the feat/permissions branch 2 times, most recently from 8c541f7 to 3dc97d7 Compare November 2, 2026 14:14
@PiquelChips
PiquelChips force-pushed the feat/permissions branch 2 times, most recently from d736f8d to 7cd65f8 Compare November 2, 2026 23:34
@PiquelChips
PiquelChips removed this pull request from stack #18 November 4, 2026 18:00
@PiquelChips
PiquelChips added this pull request to stack #38 November 4, 2026 18:00
@PiquelChips
PiquelChips force-pushed the feat/permissions branch 6 times, most recently from 8d93a6a to e24349d Compare November 4, 2026 19:41
@PiquelChips
PiquelChips force-pushed the feat/permissions branch 3 times, most recently from 1c2976b to bb3a243 Compare November 4, 2026 20:50
policy.toml now says who may do what: [[paths]] rules give each user,
node or group none, read or write under a path, and [grants] the
annotate, land and admin capabilities. Only admin lands on `policy`, so
no one grants themselves rights. Servers authenticate bearer tokens:
static ones from their configuration, or short-lived scoped ones they
sign with their node key and verify without storing, which only narrow
their principal's rights (`origin token issue`). A token lasts at most
7 days, and one issued with a token expires with it.

OriginService filters what each caller reads: a caller who reads no path
sees nothing of the repository, snapshot walks withhold the content of
unreadable paths, and revisions that only change such paths are hidden
with their statements and events. Writes are checked: records put must
be the caller's, statements must be about what they see and of a kind
they annotate, promotions included, revisions may only change paths they
write and only hold content they supplied or that a snapshot they refer
to holds at the same path, and landings may only change what the
submitter writes, whatever they land. Where a writer may not write, a
successor must have exactly each predecessor's effect: the same files
changed from the same entries to the same ones, on the same change below,
with exactly the same conflicts unresolved; a root lists only those of
the revision it's on. So anyone may take a change over and hand it on,
but no base, chain of revisions or other change makes it revert, replace
or resolve what they can't write, or add a conflict there. Only a new change gets
a root, and revisions evolve only from their own change's, so no one
becomes a change's creator, who abandons it, by adding one. Reserved kinds come only from
those core names, whoever puts them. A batch is read before the store,
so it may hold a stack. Promotions only copy results from records the
caller sees. Descriptions and payloads become attachments, an object kind
of their own, so no record can carry a file's content. Snapshots are
fetched by the record they belong to, never by a tree within one.

Clones record each user's rights, keep withheld files out of the working
copy and in their snapshots, and refuse pushes the server would reject. A
checkout asks the remote only for the files the clone lacks, so one that
needs nothing from it works offline.
Each user of a clone, and each scope of their tokens, keeps their own
rights and event cursor, so the workspaces of one clone can act for
different users and agents. Once their rights
change, the next fetch compares every change again: the events since the
last fetch don't show what they couldn't see then. The API also lists
directories and reads files of any snapshot, without its history. New
commands: `whoami`, `permissions`, `token issue`. An admin may also
abandon anyone's change (`Policy::authorizes`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/permissions Capabilities, path rules, credentials and tokens enhancement New feature or request security Trust boundaries, authentication and access control

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant