mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-01 20:33:50 +00:00
OpenAccess 0.1: OAuth 2.1 with a grant you can carry (#158)
A new LogicSRC spec at /openaccess and /docs/openaccess. A person, an agent or an organisation keeps one account at a hub; every app keeps its own users and links each to that account once. Grants delegate narrower to agents, and a subscription bought in one app is honoured by every app that honours the product. The app descriptor, the hub metadata, the four flows, the token, signed webhooks and the hub's own doors. Registered in DOC_SLUGS, NAV, STATIC_ROUTES and llms.txt. Reference implementation: github.com/logicsrc/openaccess, hub at openaccess.logicsrc.com. Claude-Session: https://claude.ai/code/session_01SWRffW4ifQPUrGXJtgYWMd Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
8951e824d5
commit
ef115fc85a
6 changed files with 441 additions and 0 deletions
|
|
@ -22,6 +22,7 @@ export function GET(): Response {
|
|||
- [OpenProfile.md](${SITE_URL}/openprofile): One Markdown file for who and where, people and agents alike. This site's own is at ${SITE_URL}/.well-known/openprofile.md.
|
||||
- [OpenProfile.md](${SITE_URL}/openprofile): One Markdown file that says who you are and where you are, for people and agents alike: identity block, accounts, topics, reshare terms and operator, discovered at /.well-known/openprofile.md or through rel="openprofile".
|
||||
- [OpenMCP](${SITE_URL}/openmcp): An open catalog of MCP relays: a relay serves /.well-known/openmcp.json, a catalog probes it and lists only what it found, and clients reach every relay through the catalog's REST, its own MCP endpoint, or signed webhooks.
|
||||
- [OpenAccess](${SITE_URL}/openaccess): OAuth 2.1 with a grant you can carry: one hub account per person or agent, apps keep their own users and link them once, grants delegate narrower to agents, and a subscription bought in one app is honoured by every app that honours the product. Reference hub at openaccess.logicsrc.com.
|
||||
- [AgentSwarm](${SITE_URL}/agent-swarm): Provider-neutral agent orchestration, model routing, and cost controls.
|
||||
- [AgentByte](${SITE_URL}/agentbyte): Agent screening sessions, policy events, and APIs.
|
||||
- [Credential Sharing](${SITE_URL}/credential-sharing): End-to-end-encrypted team vaults, plus source/target credential diffs, approval, sync, rollback, and audit.
|
||||
|
|
|
|||
214
apps/logicsrc-web/src/app/openaccess/page.tsx
Normal file
214
apps/logicsrc-web/src/app/openaccess/page.tsx
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { SiteShell } from "@/components/site-shell";
|
||||
import { mono, pre, table, td, th } from "../openontology/ui";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "OpenAccess · LogicSRC",
|
||||
description:
|
||||
"OpenAccess is OAuth 2.1 with a grant you can carry: one hub account per person or agent, every app keeps its own users and links them once, permissions delegate narrower to agents, and a subscription bought in one app is honoured by every app that honours the product. Reference hub at openaccess.logicsrc.com.",
|
||||
alternates: { canonical: "/openaccess" }
|
||||
};
|
||||
|
||||
const DESCRIPTOR = `{
|
||||
"openaccess": "0.1",
|
||||
"name": "Agentic Jobs",
|
||||
"url": "https://agenticjobs.work",
|
||||
"operator": "https://profullstack.com/.well-known/openprofile.md",
|
||||
"redirect_uris": ["https://agenticjobs.work/api/v1/openaccess/callback"],
|
||||
"jwks": { "keys": [{ "kty": "OKP", "crv": "Ed25519", "kid": "2026-09", "x": "..." }] },
|
||||
"scopes": { "jobs:read": "See job posts", "jobs:apply": "Apply on your behalf" },
|
||||
"offers": [{ "product": "agenticjobs.work/pro", "name": "Pro", "price": "9 USD/month",
|
||||
"links": { "pay": "https://agenticjobs.work/pay/pro?principal={principal}",
|
||||
"cancel": "https://agenticjobs.work/me/billing",
|
||||
"promote": "https://agenticjobs.work/?ref={principal}" } }],
|
||||
"honours": ["agenticjobs.work/pro", "profullstack.com/all-access"],
|
||||
"webhooks": "https://agenticjobs.work/api/v1/openaccess/events",
|
||||
"hubs": ["https://openaccess.logicsrc.com"]
|
||||
}`;
|
||||
|
||||
const TOKEN = `{
|
||||
"iss": "https://openaccess.logicsrc.com",
|
||||
"sub": "oa_7f3c9e2a",
|
||||
"aud": "agenticjobs.work",
|
||||
"scope": "openid email jobs:read jobs:apply entitlements",
|
||||
"grant": "g_2b1e", "parent": "g_0a9d",
|
||||
"limits": { "per_call": "5 USD", "per_day": "50 USD" },
|
||||
"entitlements": ["agenticjobs.work/pro"],
|
||||
"exp": 1757703600
|
||||
}`;
|
||||
|
||||
const CLI = `npx @logicsrc/openaccess serve --url https://hub.example # run a hub
|
||||
openaccess keygen # an Ed25519 key for your app's descriptor
|
||||
openaccess descriptor https://your.site > .well-known/openaccess.json
|
||||
openaccess app add https://agenticjobs.work # register by URL; the hub reads the descriptor
|
||||
openaccess login # device flow: a code, approve it in the browser
|
||||
openaccess grants | entitlements # what you hold
|
||||
openaccess delegate --for https://bot.example/.well-known/openprofile.md --scope "jobs:read" --per-day "50 USD"
|
||||
openaccess revoke g_2b1e # and every child under it`;
|
||||
|
||||
const FLOWS: Array<[string, string, string]> = [
|
||||
["Link", "Authorization code with PKCE, unchanged from OAuth 2.1", "The app stores sub beside its own user. Its user table is otherwise untouched."],
|
||||
["Device", "RFC 8628: a CLI or agent shows a code, the person approves it at the hub", "The token arrives at the terminal."],
|
||||
["Delegate", "POST /v1/grants with a parent token: a subset of scopes, limits no larger, expiry no later", "An agent gets a refresh token. Revoking the parent revokes every child."],
|
||||
["Entitle", "The seller reports a sale or a cancellation as itself, signed with the key its descriptor names", "Every app that honours the product hears, and every token it is issued carries the product."]
|
||||
];
|
||||
|
||||
const ABSENT: Array<[string, string]> = [
|
||||
["No passwords", "A hub signs people in however it likes; the reference uses a magic link. An app never sees a hub credential."],
|
||||
["No money", "The hub records that a person is entitled; the seller charged them, by card, by CoinPay, by invoice. The hub is the ledger of standing, not of payment."],
|
||||
["No roles", "owner, admin and member are an app's local bundles of scopes. What crosses the wire is the scopes, so two apps can mean the same thing by the same string."],
|
||||
["No silent narrowing", "A scope is granted or the request fails with its name. A system that returns 200 OK with fewer scopes than asked for has broken a person's invoicing before."]
|
||||
];
|
||||
|
||||
export default function OpenAccessPage(): ReactNode {
|
||||
return (
|
||||
<SiteShell active="OpenAccess">
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<p className="eyebrow">LogicSRC standards surface</p>
|
||||
<h2>OpenAccess</h2>
|
||||
<p>
|
||||
OAuth 2.1 with a grant you can carry. One account at a hub, every app keeps its own
|
||||
users and links them once, and what you gave and what you paid for is yours across all
|
||||
of them.
|
||||
</p>
|
||||
</div>
|
||||
<p style={{ color: "#41505d" }}>
|
||||
Every app has a permissions model and a billing model, and no two agree. In one
|
||||
company's fifteen repositories we counted twelve permission vocabularies and not one
|
||||
shared validator. An agent working across them holds twelve credentials and understands
|
||||
none. A person who pays for a product pays again next door, because the subscription
|
||||
lives in the seller's table and nowhere else. OpenAccess puts the pieces that already
|
||||
exist, OAuth 2.1, <code style={mono}>/.well-known/</code> and webhooks, together so the
|
||||
grant and the entitlement belong to the person, and travel.
|
||||
</p>
|
||||
<p style={{ color: "#5b6b7a" }}>
|
||||
Status: 0.1. A hub is running at{" "}
|
||||
<a href="https://openaccess.logicsrc.com/">openaccess.logicsrc.com</a>: sign in, see every
|
||||
app you linked, revoke a grant, hand a narrower one to an agent, pay or cancel a
|
||||
subscription from one page. Reference implementation at{" "}
|
||||
<a href="https://github.com/logicsrc/openaccess">github.com/logicsrc/openaccess</a>: a hub
|
||||
on Node 24 with one SQLite file, and a client and CLI.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>The app descriptor</h2>
|
||||
<p>
|
||||
Served at <code style={mono}>/.well-known/openaccess.json</code>. The scopes an app
|
||||
understands, the products it sells, the products it honours, and the key it signs with.
|
||||
</p>
|
||||
</div>
|
||||
<pre style={pre}>{DESCRIPTOR}</pre>
|
||||
<p style={{ color: "#41505d" }}>
|
||||
<code style={mono}>scopes</code> is the registry: a hub refuses any scope not named here,
|
||||
by name, and never narrows silently. <code style={mono}>offers</code> carry the pay,
|
||||
cancel, manage, upgrade and promote links, with <code style={mono}>{"{principal}"}</code>{" "}
|
||||
filled in so a sale or a referral is attributed. <code style={mono}>honours</code> is the
|
||||
app's promise to treat an entitlement as paid whoever sold it.{" "}
|
||||
<code style={mono}>jwks</code> is the app's credential; there is no shared secret to
|
||||
leak. <code style={mono}>operator</code> is the person answerable, as an{" "}
|
||||
<Link href="/openprofile">OpenProfile.md</Link>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Four flows</h2>
|
||||
<p>Three are OAuth as it stands. The fourth is the one OAuth lacks.</p>
|
||||
</div>
|
||||
<table style={table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={th}>Flow</th>
|
||||
<th style={th}>How</th>
|
||||
<th style={th}>Then</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{FLOWS.map(([flow, how, then]) => (
|
||||
<tr key={flow}>
|
||||
<td style={td}>
|
||||
<strong>{flow}</strong>
|
||||
</td>
|
||||
<td style={td}>{how}</td>
|
||||
<td style={td}>{then}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>The token</h2>
|
||||
<p>A JWT signed by the hub with Ed25519, verified offline against its JWKS.</p>
|
||||
</div>
|
||||
<pre style={pre}>{TOKEN}</pre>
|
||||
<p style={{ color: "#41505d" }}>
|
||||
<code style={mono}>grant</code> is the handle, <code style={mono}>parent</code> the path
|
||||
back to the person, <code style={mono}>limits</code> the app's to enforce and the
|
||||
hub's to keep no larger than the parent's.{" "}
|
||||
<code style={mono}>entitlements</code> is a snapshot of the honoured products the
|
||||
principal holds. Webhooks carry{" "}
|
||||
<code style={mono}>X-OpenAccess-Signature: ed25519=<signature of the raw body></code>,
|
||||
verified against the same key that signs tokens.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>From a terminal</h2>
|
||||
</div>
|
||||
<pre style={pre}>{CLI}</pre>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>What is deliberately absent</h2>
|
||||
</div>
|
||||
<table style={table}>
|
||||
<tbody>
|
||||
{ABSENT.map(([what, why]) => (
|
||||
<tr key={what}>
|
||||
<td style={td}>
|
||||
<strong>{what}</strong>
|
||||
</td>
|
||||
<td style={td}>{why}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Where everything lives</h2>
|
||||
</div>
|
||||
<ul style={{ color: "#41505d", lineHeight: 1.9, paddingLeft: "1.1rem" }}>
|
||||
<li>
|
||||
<a href="https://openaccess.logicsrc.com/">openaccess.logicsrc.com</a>: a live hub to
|
||||
sign in to, link apps to, or point a CLI at
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/docs/openaccess">Specification</Link>: the descriptor, the hub, linking,
|
||||
delegation, entitlements, the token, webhooks, the doors
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/logicsrc/openaccess">github.com/logicsrc/openaccess</a>: the
|
||||
reference hub, client and CLI, <code style={mono}>npx @logicsrc/openaccess</code>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/openprofile">OpenProfile.md</Link>, the operator behind an app and the
|
||||
identity of an agent; <Link href="/openmcp">OpenMCP</Link>, where a relay names a hub as
|
||||
its auth; <Link href="/opencreds">OpenCreds</Link>, where an agent keeps the token it was
|
||||
handed
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ const STATIC_ROUTES: Array<{
|
|||
{ path: "/asdlc", changeFrequency: "weekly", priority: 0.9 },
|
||||
{ path: "/openprofile", changeFrequency: "weekly", priority: 0.9 },
|
||||
{ path: "/openmcp", changeFrequency: "weekly", priority: 0.9 },
|
||||
{ path: "/openaccess", changeFrequency: "weekly", priority: 0.9 },
|
||||
{ path: "/openontology/explore", changeFrequency: "daily", priority: 0.7 },
|
||||
{ path: "/openspec", changeFrequency: "weekly", priority: 0.8 },
|
||||
{ path: "/agent-swarm", changeFrequency: "weekly", priority: 0.8 },
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const NAV: Array<{ href: string; label: string; external?: boolean }> = [
|
|||
{ href: "/asdlc", label: "ASDLC" },
|
||||
{ href: "/openprofile", label: "OpenProfile" },
|
||||
{ href: "/openmcp", label: "OpenMCP" },
|
||||
{ href: "/openaccess", label: "OpenAccess" },
|
||||
{ href: "/#cli", label: "CLI" },
|
||||
{ href: "/docs", label: "Docs" },
|
||||
{ href: "/blog", label: "Blog" },
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const DOC_SLUGS = [
|
|||
"openresume",
|
||||
"openprofile",
|
||||
"openmcp",
|
||||
"openaccess",
|
||||
"openstream",
|
||||
"openspec-comparison",
|
||||
"data-model",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue