Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions apps/logicsrc-web/contract/openpkg.contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, it, vi } from "vitest";
import { familyOfSpec } from "../src/lib/specs";
import { listDocs, readDoc } from "../src/lib/docs";
import { GET as llms } from "../src/app/llms.txt/route";
import { GET as llmsFull } from "../src/app/llms-full.txt/route";
import sitemap from "../src/app/sitemap";
import { EXIT_CODES, TARGETS } from "../src/app/openpkg/data";

// Spec discovery must work even when the optional blog database is offline.
vi.mock("../src/lib/supabase", () => ({ publicClient: () => { throw new Error("offline"); } }));

const doc = readDoc("openpkg") ?? "";

/** The first-column `code` cells of the table under a heading line. */
function tableKeys(heading: string): string[] {
const start = doc.indexOf(`\n${heading}\n`);
expect(start, `docs/openpkg.md has a ${heading} section`).toBeGreaterThan(-1);
const next = doc.indexOf("\n#", start + 1);
const section = doc.slice(start, next === -1 ? undefined : next);
return section
.split("\n")
.map((line) => line.match(/^\| `([^`]+)` \|/))
.filter((m): m is RegExpMatchArray => m !== null)
.map((m) => m[1]);
}

describe("OpenPkg public discovery", () => {
it("is registered in the process family with a 1.0 Draft specification", () => {
expect(familyOfSpec("openpkg")?.slug).toBe("process");
expect(listDocs()).toContainEqual(expect.objectContaining({ slug: "openpkg", title: "OpenPkg" }));
expect(doc).toMatch(/Status: \*\*1\.0 Draft\*\*/);
});

it("is in the LLM feeds and the sitemap", async () => {
const text = await llms().text();
expect(text).toMatch(/\[OpenPkg\]\(https:\/\/[^)]+\/(docs\/)?openpkg\)/);
expect(text).toContain("/docs/openpkg");
expect(await llmsFull().text()).toContain(doc.trim());
const entries = await sitemap();
expect(entries.some((entry) => new URL(entry.url).pathname === "/docs/openpkg")).toBe(true);
});
});

describe("OpenPkg: the spec and its landing page agree", () => {
it("lists the same exit codes", () => {
expect(EXIT_CODES.map(([code]) => code)).toEqual(tableKeys("### 6.2 Exit codes"));
expect(tableKeys("### 6.2 Exit codes")).toEqual(["0", "1", "2", "3", "4", "5", "130"]);
});

it("defines every target document the page lists, in order", () => {
const headings = [...doc.matchAll(/^### 3\.\d .*?: `([^`]+)`$/gm)].map((m) => m[1]);
expect(TARGETS.map(([path]) => path)).toEqual(headings);
});

it("names the reference implementation and the guide", () => {
expect(doc).toContain("https://github.com/profullstack/w3bs.org/tree/main/package-manager");
expect(doc).toContain("https://w3bs.org/docs/package-manager.md");
expect(doc).toContain("without permission");
});

it("has no em dashes", () => {
expect(doc).not.toContain(String.fromCharCode(0x2014));
expect(readDoc("openpkg")).toBeTruthy();
});
});
21 changes: 21 additions & 0 deletions apps/logicsrc-web/src/app/openpkg/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// The facts the /openpkg landing page shows, kept apart from page.tsx so
// contract/openpkg.contract.test.ts can hold them against docs/openpkg.md.

/** The target documents a repository serves, in the order docs/openpkg.md defines them. */
export const TARGETS: Array<[string, string]> = [
["packages/index.json", "What the repository offers: each package identity and the path of its channel indexes. Candidates, not ownership."],
["channels/<name>/<channel>.json", "The releases on one channel, and an explicit revoked list of artifact digests. Omission is not revocation."],
["manifests/<name>/<version>.json", "One immutable release: requirements that fail closed, one artifact per platform with its length, SHA-256, unpacked size limit and commands. No hooks."],
["catalog/index.json", "Tools with no signed repository, each with its publisher's https curl installer. The listing is signed; the script is not."]
];

/** Exit codes, in the order of the ## 6.2 Exit codes table. */
export const EXIT_CODES: Array<[string, string]> = [
["0", "OK. Current, pinned or held is not an error."],
["1", "A package operation failed."],
["2", "Invalid arguments, an ambiguous name or invalid configuration."],
["3", "Approval or a trust decision is required."],
["4", "A global security, state or environment preflight failed."],
["5", "Another client holds the mutation lock."],
["130", "Interrupted."]
];
148 changes: 148 additions & 0 deletions apps/logicsrc-web/src/app/openpkg/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import Link from "next/link";
import type { ReactNode } from "react";
import type { Metadata } from "next";
import { specMetadata } from "@/lib/page-meta";
import { SiteShell } from "@/components/site-shell";
import { mono, pre, table, td } from "../openontology/ui";
import { EXIT_CODES, TARGETS } from "./data";

export const metadata: Metadata = specMetadata(
"/openpkg",
"OpenPkg is the open format behind W3BS Package Manager: publisher-qualified package names, a TUF repository profile with first-use trust by root fingerprint, signed release manifests and channels, a catalog of curl installers, and the rules a client follows when it installs, updates, rolls back and removes."
);

const USAGE = `w3bs repo add tools https://tools.example/repository/
w3bs install tools.example/atlas
w3bs update # everything, within each installed major
w3bs rollback atlas # previous version, pinned; files only`;

const LAYOUT = `<base>/metadata/1.root.json first root: its SHA-256 is the fingerprint
<base>/metadata/root.json latest root
<base>/metadata/targets.json signs every target's length and SHA-256
<base>/metadata/snapshot.json
<base>/metadata/timestamp.json written last
<base>/targets/... the documents and artifacts`;

const RULES: Array<[string, string]> = [
["The host is a label", "tools.example/atlas keeps two publishers' names apart. Authority comes from the root the user trusted, never from the domain in the name."],
["Versions are immutable", "A version and platform bind to one digest. A publisher tool refuses to change them and a client refuses a manifest that does."],
["Compatible by default", "Updates stay within the installed major, or the minor for 0.x. --allow-major widens that, never past a pin, never to a lower version."],
["Atomic per package", "A journal per package, then staging, version directory, current link, command links, commit, receipt. An interrupted run ends at the old version or the new one."],
["Rollback is files", "The previous version comes back and is pinned. Configuration, data and migrations are never claimed to roll back."],
["Owned files only", "Removal deletes what the client wrote. A command link it did not write is a conflict, not an overwrite."]
];

export default function OpenPkgPage(): ReactNode {
return (
<SiteShell active="OpenPkg">
<div className="band">
<div className="section-head">
<p className="eyebrow">LogicSRC standards surface</p>
<h2>OpenPkg</h2>
<p>
The format a command-line tool is installed and kept current from. A publisher signs a
static directory with TUF and serves it over HTTPS; a client trusts its root once, then
verifies every manifest and artifact against it on every install and update.
</p>
</div>
<p style={{ color: "#41505d" }}>
Most tools ship as a curl installer. That gets a tool once and leaves nothing that knows
what is installed, what is current or how to put the last version back. OpenPkg fixes the
names, the files a repository serves, the rule for listing curl-only tools without
pretending to verify them, and what a client promises about the machine it changes.
</p>
<p style={{ color: "#5b6b7a" }}>
Status: 1.0 Draft. The reference implementation is{" "}
<a href="https://w3bs.org/docs/package-manager.md">W3BS Package Manager</a>, whose source
is <code style={mono}>package-manager/</code> in profullstack/w3bs.org. Anyone may
implement it without permission.
</p>
<pre style={pre}>{USAGE}</pre>
</div>

<div className="band">
<div className="section-head">
<h2>The repository</h2>
<p>
TUF 1.0, four roles, one key per role allowed, <code style={mono}>consistent_snapshot</code>{" "}
false. Publishers write targets before metadata and the timestamp last.
</p>
</div>
<pre style={pre}>{LAYOUT}</pre>
<table style={table}>
<tbody>
{TARGETS.map(([path, what]) => (
<tr key={path}>
<td style={td}>
<code style={mono}>{path}</code>
</td>
<td style={td}>{what}</td>
</tr>
))}
</tbody>
</table>
</div>

<div className="band">
<div className="section-head">
<h2>What a client owes the machine</h2>
</div>
<table style={table}>
<tbody>
{RULES.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>Exit codes</h2>
<p>
With <code style={mono}>--json</code>, one result object on stdout: the operation, an
overall result, a transaction id and one status per package.
</p>
</div>
<table style={table}>
<tbody>
{EXIT_CODES.map(([code, meaning]) => (
<tr key={code}>
<td style={td}>
<code style={mono}>{code}</code>
</td>
<td style={td}>{meaning}</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/openpkg">Specification</Link>: identity grammar, the repository
profile, the four documents with examples, the catalog listing rule, lifecycle rules,
receipts, the result object, security considerations and conformance
</li>
<li>
<a href="https://w3bs.org/packages">The W3BS package catalog</a> and{" "}
<a href="https://w3bs.org/docs/package-manager.md">the user and publisher guide</a>
</li>
<li>
<Link href="/openinstall">OpenInstall</Link>, the script that puts a server application
into service on its box; OpenPkg is for the tools on a person&apos;s own machine
</li>
</ul>
</div>
</SiteShell>
);
}
3 changes: 2 additions & 1 deletion apps/logicsrc-web/src/lib/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const FAMILIES: Family[] = [
name: "Agents and process",
line: "How agents coordinate, settle, stream, and how the software that serves them gets built",
blurb:
"The lifecycle for building software when agents work in parallel and CI is the only gate, the requirement document an agent can execute, the settlement and proof layer under a peer-to-peer swarm, a lossless byte-stream envelope, the five nouns a shared ontology needs, the record an agent session carries about who spawned it and under what ceiling, the one script that puts an application into service on the box it runs on, and the errand file an agent runs on a website with no API while handing every legal, identity and mail step to a person.",
"The lifecycle for building software when agents work in parallel and CI is the only gate, the requirement document an agent can execute, the settlement and proof layer under a peer-to-peer swarm, a lossless byte-stream envelope, the five nouns a shared ontology needs, the record an agent session carries about who spawned it and under what ceiling, the one script that puts an application into service on the box it runs on, the signed package format a command-line tool is installed and updated from, and the errand file an agent runs on a website with no API while handing every legal, identity and mail step to a person.",
specs: [
s("asdlc", "ASDLC", "The Agentic Software Development Lifecycle: nine phases, four conformance levels and the ratchet rule"),
s("openabtest", "OpenABTest", "Portable experiments with sticky assignments, distinct exposure and conversion events, and reconciled profit accounting", { landing: undefined, status: "draft" }),
Expand All @@ -128,6 +128,7 @@ export const FAMILIES: Family[] = [
s("openontology", "OpenOntology", "Five nouns for a shared ontology, with governance and interoperability notes"),
s("openfleet", "OpenFleet", "Agents under a human: the record a session carries about who spawned it, for what and under what ceiling, and the ledger its sysop reads"),
s("openinstall", "OpenInstall", "One idempotent bin/install.sh in every repository that puts the app into service on the box it runs on: runtime, database, build, systemd, nginx and TLS, a health check, and exit codes a deployer can roll back on"),
s("openpkg", "OpenPkg", "The open format behind W3BS Package Manager: publisher-qualified package names, a TUF repository profile, signed manifests, channels and a curl catalog, and what a client owes the machine it installs to", { status: "draft" }),
s("openerrand", "OpenErrand", "One JSON file for an errand on a website with no API: inputs and where each comes from, field rules matched by id then label, human gates a runner never automates, outcomes, a never-retried shared secret, and a hand-off card with no secrets"),
s("openspec", "OpenSpec.dev comparison", "How LogicSRC compares with OpenSpec.dev, and the compatibility mode", { doc: "/docs/openspec-comparison" })
]
Expand Down
Loading
Loading