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
131 changes: 123 additions & 8 deletions scripts/structure-ssot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export type Manifest = {
absentPaths: { path: string; reason: string }[];
tiers: { id: number; name: string; purpose: string }[];
docs: { path: string; tier: number; title: string; scope: string; documents: string[] }[];
contracts?: {
version: 1;
entries: {
id: string;
owner: { document: string; anchor: string };
dependents: string[];
}[];
};
grace: {
undocumentedSourceAreas: { path: string; reason: string }[];
unboundInvariants: { id: string; reason: string }[];
Expand Down Expand Up @@ -87,6 +95,34 @@ export function loadManifest(raw: string): { manifest: Manifest } | { error: str
if (!isArray(doc?.documents)) problems.push("docs[" + i + "].documents must be an array");
});
}
if (m.contracts !== undefined) {
if (typeof m.contracts !== "object" || m.contracts === null || isArray(m.contracts)) {
problems.push("contracts must be an object");
} else {
const contracts = m.contracts as Partial<NonNullable<Manifest["contracts"]>>;
if (contracts.version !== 1) problems.push("contracts.version must be 1");
if (!isArray(contracts.entries)) problems.push("contracts.entries must be an array");
else {
contracts.entries.forEach((entry, i) => {
if (typeof entry?.id !== "string" || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(entry.id)) {
problems.push("contracts.entries[" + i + "].id must be a kebab-case string");
}
if (typeof entry?.owner !== "object" || entry.owner === null || isArray(entry.owner)) {
problems.push("contracts.entries[" + i + "].owner must be an object");
} else {
if (typeof entry.owner.document !== "string") problems.push("contracts.entries[" + i + "].owner.document must be a string");
if (typeof entry.owner.anchor !== "string") problems.push("contracts.entries[" + i + "].owner.anchor must be a string");
}
if (!isArray(entry?.dependents)) problems.push("contracts.entries[" + i + "].dependents must be an array");
else {
entry.dependents.forEach((dependent, j) => {
if (typeof dependent !== "string") problems.push("contracts.entries[" + i + "].dependents[" + j + "] must be a string");
});
}
});
}
}
}
const grace = m.grace as Partial<Manifest["grace"]> | undefined;
if (!grace) problems.push("grace must be an object");
else {
Expand Down Expand Up @@ -210,8 +246,7 @@ export function renderIndex(manifest: Manifest): string {
lines.push("## Which doc describes which source");
lines.push("");
lines.push("A source area can be described by more than one doc, because these docs are organised by topic and");
lines.push(BT + "src/" + BT + " is organised by module. Changing an area obliges the same change to update every doc listed");
lines.push("for it; see [" + BT + "AGENTS.md" + BT + "](AGENTS.md).");
lines.push(BT + "src/" + BT + " is organised by module. Changing an area requires review of every listed document. Edit only the documents whose local explanation changes; named cross-cutting authorities and dependents are listed below.");
lines.push("");
lines.push("| Source path | Described by |");
lines.push("| --- | --- |");
Expand All @@ -232,6 +267,23 @@ export function renderIndex(manifest: Manifest): string {
lines.push("| " + BT + row.path + BT + " | " + row.reason + " |");
}
lines.push("");
if (manifest.contracts?.entries.length) {
lines.push("## Cross-cutting contracts");
lines.push("");
lines.push("Source review remains defined by the source-to-doc map above. This registry names each authoritative statement and the documents that review it. Link validation proves declared topology, not behavioral correctness.");
lines.push("");
lines.push("| Contract | Authority | Review dependents |");
lines.push("| --- | --- | --- |");
for (const contract of [...manifest.contracts.entries].sort((a, b) => a.id.localeCompare(b.id))) {
Comment thread
lidge-jun marked this conversation as resolved.
const owner = contract.owner.document + "#" + contract.owner.anchor;
const dependents = [...contract.dependents]
.sort((a, b) => a.localeCompare(b))
.map((document) => "[" + BT + document + BT + "](" + document + ")")
.join("<br>") || "—";
lines.push("| " + BT + contract.id + BT + " | [" + BT + owner + BT + "](" + owner + ") | " + dependents + " |");
}
lines.push("");
}
lines.push("## Decision records");
lines.push("");
lines.push("Superseded reasoning lives in " + BT + "decisions/" + BT + " as numbered records. A doc states the contract that holds now and");
Expand Down Expand Up @@ -376,7 +428,54 @@ export function runStructureChecks(repoRoot: string): string[] {
if (isTracked(absent.path)) fail(absent.path + " is declared absent in manifest.json but is tracked; the docs describing its absence are wrong");
}

// 4. decision records
// 4. cross-cutting contract authority
const contractIds = new Set<string>();
for (const contract of manifest.contracts?.entries ?? []) {
if (contractIds.has(contract.id)) fail("contract " + contract.id + " is declared twice");
contractIds.add(contract.id);

const owner = contract.owner.document;
const ownerAbs = join(structureDir, owner);
if (!declared.includes(owner)) fail("contract " + contract.id + " owner " + owner + " is not a declared structure document");
if (!present.includes(owner)) fail("contract " + contract.id + " owner structure/" + owner + " is missing");
else if (!headingAnchors(readFileSync(ownerAbs, "utf8")).has(contract.owner.anchor)) {
fail("contract " + contract.id + " owner structure/" + owner + " has no #" + contract.owner.anchor + " heading anchor");
}

const dependents = new Set<string>();
for (const dependent of contract.dependents) {
if (dependents.has(dependent)) fail("contract " + contract.id + " lists dependent " + dependent + " twice");
dependents.add(dependent);
if (dependent === owner) fail("contract " + contract.id + " lists its owner " + owner + " as a dependent");
if (!declared.includes(dependent)) fail("contract " + contract.id + " dependent " + dependent + " is not a declared structure document");
if (!present.includes(dependent)) {
fail("contract " + contract.id + " dependent structure/" + dependent + " is missing");
continue;
}
const dependentAbs = join(structureDir, dependent);
const body = withoutFences(readFileSync(dependentAbs, "utf8"));
let linked = false;
let hit: RegExpExecArray | null;
linkRe.lastIndex = 0;
while ((hit = linkRe.exec(body))) {
const separator = hit[1].indexOf("#");
if (separator === -1) continue;
const file = hit[1].slice(0, separator);
const fragment = hit[1].slice(separator + 1);
const resolved = file === "" ? dependentAbs : resolve(dirname(dependentAbs), file);
const target = toPosix(relative(structureDir, resolved));
if (target === owner && fragment === contract.owner.anchor) {
linked = true;
break;
}
}
if (!linked) {
fail("contract " + contract.id + " dependent structure/" + dependent + " does not link " + owner + "#" + contract.owner.anchor);
}
}
}

// 5. decision records
const adrFiles = present.filter((p) => p.startsWith("decisions/"));
const referenced = new Map<string, Set<string>>();
// Ownership is the declared link form, read with fences removed. A record path mentioned in prose
Expand Down Expand Up @@ -417,7 +516,7 @@ export function runStructureChecks(repoRoot: string): string[] {
}
for (const key of referenced.keys()) if (!adrFiles.includes(key)) fail("a doc links structure/" + key + ", which does not exist");

// 5. invariant-to-test bindings
// 6. invariant-to-test bindings
const overviewPath = join(structureDir, "overview.md");
if (!existsSync(overviewPath)) {
fail("structure/overview.md is missing; it is the invariant index, and its absence would silence every binding check");
Expand Down Expand Up @@ -472,7 +571,7 @@ export function runStructureChecks(repoRoot: string): string[] {
}
}

// 6. source-to-doc map
// 7. source-to-doc map
const described = new Map<string, string[]>();
// What a doc actually names, so a manifest claim cannot invent coverage the prose does not have.
const namedByDoc = new Map<string, string[]>();
Expand Down Expand Up @@ -530,7 +629,7 @@ export function runStructureChecks(repoRoot: string): string[] {
fail(area + " is described by no doc; add it to a doc's " + BT + "documents" + BT + " list or record it in grace.undocumentedSourceAreas with a reason");
}

// 7. generated index parity
// 8. generated index parity
const indexPath = join(structureDir, "INDEX.md");
const expected = renderIndex(manifest);
if (!existsSync(indexPath)) fail("structure/INDEX.md is missing; run bun run structure:index");
Expand All @@ -541,11 +640,27 @@ export function runStructureChecks(repoRoot: string): string[] {
return failures;
}

/**
* Rewrite the generated index only after the manifest validates. The renderer trusts the typed
* shape, so a malformed manifest (for example a string where `contracts.entries` belongs) would
* otherwise crash the renderer with a TypeError and could write a broken index before the checker
* ever ran. Validation failure is an actionable schema diagnostic and leaves INDEX.md untouched.
*/
export function writeGeneratedIndex(repoRoot: string): { wrote: true } | { error: string } {
const loaded = loadManifest(readFileSync(join(repoRoot, "structure", "manifest.json"), "utf8"));
if ("error" in loaded) return { error: loaded.error };
writeFileSync(join(repoRoot, "structure", "INDEX.md"), renderIndex(loaded.manifest), "utf8");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return { wrote: true };
}

if (import.meta.main) {
const repoRoot = resolve(import.meta.dir, "..");
if (process.argv.includes("--fix")) {
const manifest = JSON.parse(readFileSync(join(repoRoot, "structure/manifest.json"), "utf8")) as Manifest;
writeFileSync(join(repoRoot, "structure/INDEX.md"), renderIndex(manifest), "utf8");
const fixed = writeGeneratedIndex(repoRoot);
if ("error" in fixed) {
console.error(fixed.error);
process.exit(1);
}
console.log("wrote structure/INDEX.md");
}
const failures = runStructureChecks(repoRoot);
Expand Down
27 changes: 23 additions & 4 deletions structure/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ the inverse.
the Responses transport doc and the Images doc. An earlier revision of this folder demanded exactly
one owner per area, and that rule was simply false here — a false rule is worse than none, because
the gate reports green while the map sends a maintainer to the wrong doc.
- **Changing an area obliges the same change to update every doc listed for it.** Not a follow-up,
not a later cleanup pass.
- **Changing an area obliges the same change to review every doc listed for it.** Review fan-out is
unchanged by contract authority: update the authority when the shared contract changes, and update
a dependent only when its local explanation or consequence changes. A reviewed document whose
content remains accurate does not need copied unchanged prose.
- Describing an area means naming a path inside it. If a doc explains a subsystem without ever citing
a path, the map cannot see it, and the area lands in `grace.undocumentedSourceAreas` instead — which
is a signal to add the path reference, not a place to park work.
Expand All @@ -62,6 +64,16 @@ What the map still does not do: it cannot tell you that two docs describe the sa
contradictory words. Avoiding that is a review judgement. Prefer one statement and a link over two
statements that will drift apart.

Cross-cutting authority is declared by the optional versioned `contracts` object in
[`manifest.json`](manifest.json). Each entry has a stable kebab-case `id`, one `owner` with a
manifest-declared document and heading anchor, and a `dependents` array of manifest-declared
documents. Every dependent links the exact owner anchor. The registry supplements the source map;
it never narrows which documents a source change requires review of.

The gate proves declared topology: identifiers are unique, files and anchors exist, and each
dependent carries the declared link. It does not compare prose or prove that the owner statement is
behaviorally correct. Those remain review judgements.

## Decision records

`decisions/ADR-NNNN-<slug>.md` holds the reasoning: intent, prior constraints, alternatives, the
Expand Down Expand Up @@ -103,8 +115,10 @@ violated is worse than admitting the gap: it converts an open question into fals

1. Write or move the file.
2. Add or update its `manifest.json` entry: `path`, `tier`, `title`, `scope`, `documents`.
3. `bun run structure:index` to regenerate [`INDEX.md`](INDEX.md).
4. `bun run structure:check` until it is green.
3. Add or update any affected contract authority and dependent links in `manifest.json`; review every
document mapped to the changed source area even when its text remains accurate.
4. `bun run structure:index` to regenerate [`INDEX.md`](INDEX.md).
5. `bun run structure:check` until it is green.

## What the gate checks

Expand All @@ -128,12 +142,17 @@ verifies that:
- every bound invariant names an existing test that names the id back, and every unbound one is
recorded with a reason;
- every `src/` directory and top-level module is described by a doc or recorded as undescribed;
- contract ids are unique, owner and dependent documents exist in the manifest, owner anchors exist,
and every dependent links its declared authority without listing the owner as a dependent;
- the manifest itself parses and has the shape the gate expects, reported as a failure rather than a
stack trace;
- `overview.md` exists, because its absence would otherwise silence every invariant check at once;
- `INDEX.md` matches what the manifest generates, compared after newline normalisation so a CRLF
checkout is not a failure.

Contract checks establish declared authority and links only. They make no automatic claim that an
owner's prose is semantically complete or that a dependent's explanation is behaviorally correct.

Checks are scanned with fenced code blocks removed, so an example inside a fence does not trip a rule
it is only illustrating.

Expand Down
13 changes: 11 additions & 2 deletions structure/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ Background service, docs, release, and design discipline.
## Which doc describes which source

A source area can be described by more than one doc, because these docs are organised by topic and
`src/` is organised by module. Changing an area obliges the same change to update every doc listed
for it; see [`AGENTS.md`](AGENTS.md).
`src/` is organised by module. Changing an area requires review of every listed document. Edit only the documents whose local explanation changes; named cross-cutting authorities and dependents are listed below.

| Source path | Described by |
| --- | --- |
Expand Down Expand Up @@ -147,6 +146,16 @@ for it; see [`AGENTS.md`](AGENTS.md).
| `src/sidecar/` | no doc names a path here; ops/service-and-sidecars.md describes sidecar behavior in prose only |
| `src/types/` | shared declarations plus the tool-name and wire-pin resolvers, which no doc currently describes |

## Cross-cutting contracts

Source review remains defined by the source-to-doc map above. This registry names each authoritative statement and the documents that review it. Link validation proves declared topology, not behavioral correctness.

| Contract | Authority | Review dependents |
| --- | --- | --- |
| `paginated-history-writer` | [`codex-home.md#paginated-history-writer-boundary`](codex-home.md#paginated-history-writer-boundary) | [`catalog.md`](catalog.md)<br>[`config.md`](config.md)<br>[`gui-and-management-api.md`](gui-and-management-api.md)<br>[`ops/docs-and-release.md`](ops/docs-and-release.md)<br>[`providers/openai-tiers.md`](providers/openai-tiers.md)<br>[`runtime.md`](runtime.md)<br>[`subagents.md`](subagents.md) |
| `request-copy-accounting` | [`transports/byte-accounting.md#request-copy-accounting`](transports/byte-accounting.md#request-copy-accounting) | [`adapters/registry.md`](adapters/registry.md)<br>[`catalog.md`](catalog.md)<br>[`clients/claude-desktop.md`](clients/claude-desktop.md)<br>[`clients/integrations.md`](clients/integrations.md)<br>[`data-planes/images.md`](data-planes/images.md)<br>[`data-planes/inbound-compat.md`](data-planes/inbound-compat.md)<br>[`ops/docs-and-release.md`](ops/docs-and-release.md)<br>[`ops/service-and-sidecars.md`](ops/service-and-sidecars.md)<br>[`overview.md`](overview.md)<br>[`providers/chat-compat.md`](providers/chat-compat.md)<br>[`providers/cursor.md`](providers/cursor.md)<br>[`providers/xai-grok.md`](providers/xai-grok.md)<br>[`runtime.md`](runtime.md)<br>[`subagents.md`](subagents.md)<br>[`transports/inventory.md`](transports/inventory.md)<br>[`transports/streaming-health.md`](transports/streaming-health.md) |
| `stream-buffer-accounting` | [`transports/byte-accounting.md#stream-buffer-accounting`](transports/byte-accounting.md#stream-buffer-accounting) | [`adapters/registry.md`](adapters/registry.md)<br>[`catalog.md`](catalog.md)<br>[`clients/claude-desktop.md`](clients/claude-desktop.md)<br>[`clients/integrations.md`](clients/integrations.md)<br>[`data-planes/images.md`](data-planes/images.md)<br>[`data-planes/inbound-compat.md`](data-planes/inbound-compat.md)<br>[`ops/docs-and-release.md`](ops/docs-and-release.md)<br>[`ops/service-and-sidecars.md`](ops/service-and-sidecars.md)<br>[`overview.md`](overview.md)<br>[`providers/chat-compat.md`](providers/chat-compat.md)<br>[`providers/cursor.md`](providers/cursor.md)<br>[`providers/xai-grok.md`](providers/xai-grok.md)<br>[`runtime.md`](runtime.md)<br>[`subagents.md`](subagents.md)<br>[`transports/inventory.md`](transports/inventory.md)<br>[`transports/streaming-health.md`](transports/streaming-health.md) |

## Decision records

Superseded reasoning lives in `decisions/` as numbered records. A doc states the contract that holds now and
Expand Down
4 changes: 1 addition & 3 deletions structure/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,7 @@ its defaults and exclusions are owned by [Responses transport](transports/respon

Provider `showThinkingSummary` is a Responses request default; it does not rewrite catalog summary defaults or client configuration. See [Google summaries](providers/google.md).

## Paginated history writer boundary

`src/codex/history-provider.ts` refuses external writes to paginated or migration-capable history. `src/codex/inject.ts` checks affected rows and manifest-owned restore targets before and after config/profile/journal changes, including successful journal and fallback restores, and compensates refused restore/removal transitions. Failed config restore stops later catalog/history work and rolls back a coordinated remove transition. Apply retains an existing provider definition before candidate admission even when history preflight passes, so migration after artifact commit or during worker startup cannot leave earlier conversations without their provider. See the [history writer contract](codex-home.md#paginated-history-writer-boundary) for guarantees and concurrent-writer limits.
Paginated and migration-capable history follows the [authoritative writer contract](codex-home.md#paginated-history-writer-boundary); this document adds no independent writer guarantee.

Codex pool settings and their consumers follow the [reset-first ordering contract](providers/openai-tiers.md#reset-first-account-ordering), including independent-quota fallback, preserved affinity, strategy-specific threshold summaries, and shared short-observation freshness for switch warnings.

Expand Down
Loading
Loading