From 8e4ea2f997b6f8b1557780d800358bc273581181 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 21 Sep 2026 02:21:12 -0700 Subject: [PATCH] docs: OpenStack.md 0.1, one Markdown file for what a project is built on (#191) 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 and in the Link header beside openprofile. Co-authored-by: Claude Fable 5.1 --- .../contract/openstack.contract.test.ts | 43 +++ .../contract/spec-discovery.contract.test.ts | 3 +- apps/logicsrc-web/next.config.ts | 10 +- .../src/app/.well-known/openstack.md/route.ts | 20 ++ apps/logicsrc-web/src/app/layout.tsx | 2 + apps/logicsrc-web/src/app/openstack/page.tsx | 291 ++++++++++++++++ apps/logicsrc-web/src/lib/specs.ts | 1 + docs/openstack.md | 318 ++++++++++++++++++ 8 files changed, 684 insertions(+), 4 deletions(-) create mode 100644 apps/logicsrc-web/contract/openstack.contract.test.ts create mode 100644 apps/logicsrc-web/src/app/.well-known/openstack.md/route.ts create mode 100644 apps/logicsrc-web/src/app/openstack/page.tsx create mode 100644 docs/openstack.md diff --git a/apps/logicsrc-web/contract/openstack.contract.test.ts b/apps/logicsrc-web/contract/openstack.contract.test.ts new file mode 100644 index 0000000..db5d9b8 --- /dev/null +++ b/apps/logicsrc-web/contract/openstack.contract.test.ts @@ -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)); + }); +}); diff --git a/apps/logicsrc-web/contract/spec-discovery.contract.test.ts b/apps/logicsrc-web/contract/spec-discovery.contract.test.ts index 5324102..41527a3 100644 --- a/apps/logicsrc-web/contract/spec-discovery.contract.test.ts +++ b/apps/logicsrc-web/contract/spec-discovery.contract.test.ts @@ -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); diff --git a/apps/logicsrc-web/next.config.ts b/apps/logicsrc-web/next.config.ts index 72ca626..93affae 100644 --- a/apps/logicsrc-web/next.config.ts +++ b/apps/logicsrc-web/next.config.ts @@ -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 . + // 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 . { 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(", "), }, ]; diff --git a/apps/logicsrc-web/src/app/.well-known/openstack.md/route.ts b/apps/logicsrc-web/src/app/.well-known/openstack.md/route.ts new file mode 100644 index 0000000..45f0ce2 --- /dev/null +++ b/apps/logicsrc-web/src/app/.well-known/openstack.md/route.ts @@ -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", + }, + }); +} diff --git a/apps/logicsrc-web/src/app/layout.tsx b/apps/logicsrc-web/src/app/layout.tsx index cb45c6e..d3bc5b5 100644 --- a/apps/logicsrc-web/src/app/layout.tsx +++ b/apps/logicsrc-web/src/app/layout.tsx @@ -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. */} + {/* OpenStack.md discovery (/openstack, rule 9): what this site is built on. */} +