mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-01 20:33:50 +00:00
docs: OpenStack.md 0.1, one Markdown file for what a project is built on
A new LogicSRC spec in the catalogs family. One heading, an identity block (Kind, Web, Repo, Operator, License, Extends, Updated), one line, then eleven layers: Languages, Runtimes, Interfaces (one ### per way in: web, api, cli, tui, mcp, desktop, mobile, worker, extension, bot), Data, Services, Modules, Tooling, Hosting, Auth, Conventions, Not. An item is one bullet: name, version, an optional status word (trial, hold, leaving) and a role. Extends inherits a parent file, sections replace, Conventions and Not accumulate. Rule 8 is the reading rule for agents: use what is listed, prefer listed over new, ask before adding a layer or a service, never add a Not, keep the file true. Discovery at OpenStack.md in the repo, /.well-known/openstack.md, rel=openstack, or a platform path. JSON is derived and never the source. logicsrc.com serves its own at /.well-known/openstack.md: the route extracts the worked example from docs/openstack.md, and a contract test holds the two together and checks the file follows its own rules. rel=openstack in <head> and in the Link header beside openprofile. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
08490aecf3
commit
fc9014e8b2
8 changed files with 684 additions and 4 deletions
43
apps/logicsrc-web/contract/openstack.contract.test.ts
Normal file
43
apps/logicsrc-web/contract/openstack.contract.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { GET, openstackBody } from "../src/app/.well-known/openstack.md/route";
|
||||
import { readDoc } from "../src/lib/docs";
|
||||
|
||||
// The site that publishes a spec serves its own file, and that file is the
|
||||
// worked example inside the spec, so the two can never drift apart.
|
||||
describe("OpenStack.md: the site's own file", () => {
|
||||
it("serves the spec's worked example as text/markdown", async () => {
|
||||
const res = GET();
|
||||
expect(res.headers.get("content-type")).toBe("text/markdown; charset=utf-8");
|
||||
const body = await res.text();
|
||||
expect(body.startsWith("# LogicSRC\n")).toBe(true);
|
||||
expect(readDoc("openstack")).toContain(body.trim());
|
||||
});
|
||||
|
||||
it("follows its own rules: one name, an identity block, the fixed layers, a Not section", () => {
|
||||
const body = openstackBody();
|
||||
expect(body.match(/^# /gm)).toHaveLength(1);
|
||||
expect(body).toMatch(/^- \*\*Kind\*\*: monorepo$/m);
|
||||
expect(body).toMatch(/^- \*\*Web\*\*: https:\/\/logicsrc\.com$/m);
|
||||
expect(body).toMatch(/^- \*\*Operator\*\*: https:\/\/logicsrc\.com\/\.well-known\/openprofile\.md$/m);
|
||||
for (const layer of [
|
||||
"Languages",
|
||||
"Runtimes",
|
||||
"Interfaces",
|
||||
"Data",
|
||||
"Services",
|
||||
"Modules",
|
||||
"Tooling",
|
||||
"Hosting",
|
||||
"Auth",
|
||||
"Conventions",
|
||||
"Not"
|
||||
]) {
|
||||
expect(body).toMatch(new RegExp(`^## ${layer}$`, "m"));
|
||||
}
|
||||
// every interface this monorepo ships is declared
|
||||
for (const iface of ["Web", "CLI", "TUI", "MCP", "API"]) {
|
||||
expect(body).toMatch(new RegExp(`^### ${iface}$`, "m"));
|
||||
}
|
||||
expect(body).not.toContain(String.fromCharCode(0x2014));
|
||||
});
|
||||
});
|
||||
|
|
@ -14,7 +14,8 @@ describe.each([
|
|||
{ slug: "openabtest", name: "OpenABTest", family: "process" },
|
||||
{ slug: "openfleet", name: "OpenFleet", family: "process" },
|
||||
{ slug: "openrental", name: "OpenRental", family: "catalogs" },
|
||||
{ slug: "openwall", name: "OpenWall", family: "people" }
|
||||
{ slug: "openwall", name: "OpenWall", family: "people" },
|
||||
{ slug: "openstack", name: "OpenStack.md", family: "catalogs" }
|
||||
])("$name public discovery", ({ slug, name, family }) => {
|
||||
it("serves the specification through its family and docs index", () => {
|
||||
expect(familyOfSpec(slug)?.slug).toBe(family);
|
||||
|
|
|
|||
|
|
@ -24,11 +24,15 @@ const securityHeaders = [
|
|||
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
||||
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
||||
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
|
||||
// OpenProfile.md discovery for responses that are not HTML (feeds, JSON,
|
||||
// the specs as Markdown): the same relation the root layout puts in <head>.
|
||||
// OpenProfile.md and OpenStack.md discovery for responses that are not HTML
|
||||
// (feeds, JSON, the specs as Markdown): the same relations the root layout
|
||||
// puts in <head>.
|
||||
{
|
||||
key: "Link",
|
||||
value: `<${(process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "")}/.well-known/openprofile.md>; rel="openprofile"`,
|
||||
value: [
|
||||
`<${(process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "")}/.well-known/openprofile.md>; rel="openprofile"`,
|
||||
`<${(process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "")}/.well-known/openstack.md>; rel="openstack"`,
|
||||
].join(", "),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
20
apps/logicsrc-web/src/app/.well-known/openstack.md/route.ts
Normal file
20
apps/logicsrc-web/src/app/.well-known/openstack.md/route.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { readDoc } from "@/lib/docs";
|
||||
|
||||
// GET /.well-known/openstack.md: LogicSRC's own OpenStack.md, the file the spec
|
||||
// at /openstack says a project serves about what it is built on. The body is
|
||||
// the worked example in docs/openstack.md, the first ```markdown fence, so the
|
||||
// specification's example and the file this site serves can never disagree.
|
||||
export function openstackBody(): string {
|
||||
const spec = readDoc("openstack") ?? "";
|
||||
const match = spec.match(/```markdown\n([\s\S]*?)\n```/);
|
||||
return match ? `${match[1]}\n` : "# LogicSRC\n";
|
||||
}
|
||||
|
||||
export function GET(): Response {
|
||||
return new Response(openstackBody(), {
|
||||
headers: {
|
||||
"content-type": "text/markdown; charset=utf-8",
|
||||
"cache-control": "public, max-age=3600",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -81,6 +81,8 @@ export default function RootLayout({ children }: { children: ReactNode }): React
|
|||
{/* OpenProfile.md discovery (/openprofile, rule "A link element"): the
|
||||
site points at its own profile, the one relays name as operator. */}
|
||||
<link rel="openprofile" href={`${SITE_URL}/.well-known/openprofile.md`} />
|
||||
{/* OpenStack.md discovery (/openstack, rule 9): what this site is built on. */}
|
||||
<link rel="openstack" href={`${SITE_URL}/.well-known/openstack.md`} />
|
||||
</head>
|
||||
<body>
|
||||
<script
|
||||
|
|
|
|||
291
apps/logicsrc-web/src/app/openstack/page.tsx
Normal file
291
apps/logicsrc-web/src/app/openstack/page.tsx
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
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: "OpenStack.md · LogicSRC",
|
||||
description:
|
||||
"OpenStack.md is one Markdown file that says what a piece of software is built on: languages, runtimes, every interface and what each is made of, data, services, tooling, hosting, the rules, and what it must never depend on. Read by a person in a minute, by an agent in one prompt, and inherited by the next project through Extends.",
|
||||
alternates: { canonical: "/openstack" }
|
||||
};
|
||||
|
||||
const FILE = `# LogicSRC
|
||||
|
||||
- **Kind**: monorepo
|
||||
- **Web**: https://logicsrc.com
|
||||
- **Repo**: https://github.com/profullstack/logicsrc
|
||||
- **Operator**: https://logicsrc.com/.well-known/openprofile.md
|
||||
- **License**: MIT
|
||||
- **Updated**: 2026-09-21
|
||||
|
||||
Open coordination standards for humans and AI agents, with the
|
||||
reference implementation of each standard beside its text.
|
||||
|
||||
## Languages
|
||||
|
||||
- TypeScript 5.9: everything, as ES modules with \`.js\` specifiers in source
|
||||
|
||||
## Runtimes
|
||||
|
||||
- Node 24: the CLI, the MCP server, the TUI, the API and the web app's server
|
||||
|
||||
## Interfaces
|
||||
|
||||
### Web
|
||||
|
||||
- Next.js 16 (App Router): logicsrc.com, the specs and the blog
|
||||
- React 19
|
||||
|
||||
### CLI
|
||||
|
||||
- @logicsrc/cli: \`logicsrc\`, every standard and tool as one command
|
||||
|
||||
### MCP
|
||||
|
||||
- @profullstack/logicsrc-mcp: the standards as tools, on @modelcontextprotocol/sdk 1
|
||||
|
||||
## Data
|
||||
|
||||
- Postgres (Supabase): the blog and CommandBoard
|
||||
- libSQL (Turso): the credentials app
|
||||
|
||||
## Tooling
|
||||
|
||||
- npm 11 workspaces
|
||||
- vitest 4: unit and contract tests
|
||||
|
||||
## Not
|
||||
|
||||
- No ORM: SQL is written by hand`;
|
||||
|
||||
const CHILD = `# nixamp
|
||||
|
||||
- **Kind**: product
|
||||
- **Web**: https://nixamp.com
|
||||
- **Extends**: https://profullstack.com/.well-known/openstack.md
|
||||
- **Updated**: 2026-09-21
|
||||
|
||||
Free radio and live streams, no account needed.
|
||||
|
||||
## Data
|
||||
|
||||
- libSQL (Turso): channels, recordings and playlists
|
||||
|
||||
## Interfaces
|
||||
|
||||
### Web
|
||||
|
||||
- Next.js 16 (App Router)
|
||||
- @profullstack/player: the player
|
||||
|
||||
## Not
|
||||
|
||||
- No client-side analytics: the stats script is enough`;
|
||||
|
||||
const ITEM = `- Name version (status): role
|
||||
|
||||
- Next.js 16 (App Router): logicsrc.com, the specs and the blog
|
||||
- Playwright 1.57: end to end
|
||||
- Redis (hold): the old session store, nothing new goes here
|
||||
- Bun 1 (trial): the worker, to see if it holds up`;
|
||||
|
||||
const LAYERS: Array<[string, string]> = [
|
||||
["Languages", "Every language source is written in, human or query."],
|
||||
["Runtimes", "What executes it: a Node or Bun major, a browser, Deno, Electron, an edge runtime."],
|
||||
["Interfaces", "Every way in, one ### each: web, api, cli, tui, mcp, desktop, mobile, worker, extension, bot, library."],
|
||||
["Data", "Where state lives: databases, caches, queues, object stores, files."],
|
||||
["Services", "Third parties the running software calls: payments, email, search, models, analytics."],
|
||||
["Modules", "The packages the project treats as its own foundation: a house library, a shared kit."],
|
||||
["Tooling", "What builds, tests, lints and ships it: package manager, bundler, test runner, CI."],
|
||||
["Hosting", "Where it runs and how it is deployed."],
|
||||
["Auth", "How a person or an agent signs in."],
|
||||
["Conventions", "The rules the code follows that a reader could not infer."],
|
||||
["Not", "What the project must not depend on, and why when the why is short."]
|
||||
];
|
||||
|
||||
const READER: Array<[string, string]> = [
|
||||
["Use what is listed", "At the versions listed, for what the role says."],
|
||||
["Prefer a listed item", "Over a new one that does the same job. A project with vitest listed gets its tests in vitest."],
|
||||
["Ask before adding a layer or a service", "A new database, a new third party, a new runtime is the author's decision. A small package inside an existing layer is ordinary work."],
|
||||
["Never add anything under Not", "And never build new work on an item marked hold or leaving."],
|
||||
["Keep the file true", "An agent that adds something, with permission, adds the bullet."]
|
||||
];
|
||||
|
||||
const ABSENT: Array<[string, string]> = [
|
||||
["No inventory", "A stack is the things a maintainer would name, not the lockfile. A project that needs the closure has an SBOM; this is the other file."],
|
||||
["No pins", "A version is what the code is written against. The manifest holds the exact one, and it changes every week."],
|
||||
["No detection", "The file is what the project says. A scanner guesses; this does not. A directory may compare the two."],
|
||||
["No verification", "Nothing checks that a project uses what it lists. The author is the one who has to build on it."],
|
||||
["No registry", "A stack is a file on a site or in a repository. A directory is optional."],
|
||||
["No scoring", "The file says what is used, not whether that was wise."]
|
||||
];
|
||||
|
||||
export default function OpenStackPage(): ReactNode {
|
||||
return (
|
||||
<SiteShell active="OpenStack.md">
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<p className="eyebrow">LogicSRC standards surface</p>
|
||||
<h2>OpenStack.md</h2>
|
||||
<p>
|
||||
One Markdown file that says what a piece of software is built on. The languages, the
|
||||
runtimes, every interface and what each is made of, the data, the services, the
|
||||
tooling, the hosting, the rules, and what it must never depend on. Read by a person in
|
||||
a minute, by an agent in one prompt, and inherited by the next project.
|
||||
</p>
|
||||
</div>
|
||||
<p style={{ color: "#41505d" }}>
|
||||
Every project has a stack and nowhere to say it. The lockfile lists nine hundred
|
||||
packages and cannot tell the twelve that matter from the rest. The README says
|
||||
"built with Next.js" and stops. An agent handed the repository guesses the
|
||||
framework from the imports, guesses the database from a connection string, and adds a
|
||||
dependency the team took out last year, because nothing in the repository said not to.
|
||||
And a team that has settled its stack well cannot hand it to the next project except by
|
||||
copying a repository and deleting most of it.
|
||||
</p>
|
||||
<p style={{ color: "#5b6b7a" }}>
|
||||
Status: 0.1. This site serves its own at{" "}
|
||||
<a href="/.well-known/openstack.md">/.well-known/openstack.md</a>, and that file is the
|
||||
worked example in the specification.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>The file</h2>
|
||||
<p>
|
||||
<code style={mono}>OpenStack.md</code> beside the README, and{" "}
|
||||
<code style={mono}>/.well-known/openstack.md</code> on the site. One heading, an identity
|
||||
block, one line, then the layers. Abridged here; the whole file is at the link above.
|
||||
</p>
|
||||
</div>
|
||||
<pre style={pre}>{FILE}</pre>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>An item</h2>
|
||||
<p>
|
||||
One bullet per thing: the name as its maintainers spell it, the major the code is
|
||||
written against, one status word when it is not simply in use, and the role, which is
|
||||
the part no manifest has.
|
||||
</p>
|
||||
</div>
|
||||
<pre style={pre}>{ITEM}</pre>
|
||||
<p style={{ color: "#41505d" }}>
|
||||
<code style={mono}>trial</code> is being tried in one place, <code style={mono}>hold</code>{" "}
|
||||
is still present but nothing new goes on it, <code style={mono}>leaving</code> is on its way
|
||||
out. Absent means in use: build with it. A version is not a pin; the manifest holds the pin.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Eleven layers</h2>
|
||||
<p>
|
||||
Fixed section names, matched loosely, any order, every one optional. Interfaces get a{" "}
|
||||
<code style={mono}>###</code> each and may carry their own layers when one differs from
|
||||
the rest.
|
||||
</p>
|
||||
</div>
|
||||
<table style={table}>
|
||||
<tbody>
|
||||
{LAYERS.map(([name, what]) => (
|
||||
<tr key={name}>
|
||||
<td style={td}>
|
||||
<strong>{name}</strong>
|
||||
</td>
|
||||
<td style={td}>{what}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Extends: how a stack is shared</h2>
|
||||
<p>
|
||||
A house publishes one file. Every project under it names the parent and writes the ten
|
||||
lines that differ. A section the child writes replaces the parent's;{" "}
|
||||
<code style={mono}>Conventions</code> and <code style={mono}>Not</code> accumulate, so a
|
||||
rule a parent set cannot be silently unset.
|
||||
</p>
|
||||
</div>
|
||||
<pre style={pre}>{CHILD}</pre>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>What a reader owes the file</h2>
|
||||
<p>
|
||||
This is the rule for agents, and the reason the file exists. A person shares a stack
|
||||
by pasting the file or pointing at its URL; a repository's AGENTS.md says{" "}
|
||||
<code style={mono}>Stack: see OpenStack.md</code> once instead of restating it.
|
||||
</p>
|
||||
</div>
|
||||
<table style={table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={th}>rule</th>
|
||||
<th style={th}>meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{READER.map(([rule, meaning]) => (
|
||||
<tr key={rule}>
|
||||
<td style={td}>
|
||||
<strong>{rule}</strong>
|
||||
</td>
|
||||
<td style={td}>{meaning}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
<Link href="/docs/openstack">Specification</Link>: the file, the identity block, the
|
||||
eleven layers, interfaces, the item grammar, inheritance, the reading rule for agents,
|
||||
discovery, the derived JSON, what a directory owes a project
|
||||
</li>
|
||||
<li>
|
||||
<a href="/.well-known/openstack.md">logicsrc.com/.well-known/openstack.md</a>: this
|
||||
site's own file, the first one served
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/openprd">OpenPRD</Link>, whose Tech Stack section can now say "see
|
||||
OpenStack.md"; <Link href="/openprofile">OpenProfile.md</Link>, the operator behind
|
||||
a project; <Link href="/docs/openserver">OpenServer</Link>, what a hosting provider
|
||||
sells where this says what a project bought
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
|
|
@ -96,6 +96,7 @@ export const FAMILIES: Family[] = [
|
|||
s("openthreat", "OpenThreat", "One file a security tool serves about what it found in the open: public subjects only, secrets never located"),
|
||||
s("openrental", "OpenRental", "One file an operator serves about the agents and file swarms it rents out: members, metadata and rates through CoinPay", { landing: undefined, status: "draft" }),
|
||||
s("opensite", "OpenSite", "One record about a page or a site: the card a reader would draw, declared by the site or read from it, kept by an index"),
|
||||
s("openstack", "OpenStack.md", "One Markdown file a project serves about what it is built on: every interface and its layers, the rules, what it must never depend on, inherited by the next project through Extends"),
|
||||
s("openwebring", "OpenWebring", "A webring that says who made it: one file a ring serves about its members, one a member serves about itself, and made_by on every member"),
|
||||
s("openwiki", "OpenWiki", "A wiki that is a folder of Markdown files: [[Page]] links, a small front matter, a descriptor and a page index, every page and revision readable as Markdown, and made_by on every revision"),
|
||||
s("openl10n", "OpenL10n", "The record of what a file says, in any language: one transcript per media per language, kept once, translations beside the original", { parent: "openfile" }),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue