Compare commits

..
5 changed files with 322 additions and 1 deletions
+158
View File
@@ -0,0 +1,158 @@
{
"schemaVersion": 1,
"registries": [
{
"registryId": "FE-REG-ROUTE",
"path": "src/contracts/routes.js",
"exportName": "ROUTE_REGISTRY",
"owner": "feature-routing-navigation-guard-contract",
"requiredFields": [
"routeId",
"path",
"paramsSchema",
"searchSchema",
"access",
"loadingSurface",
"errorSurface",
"chunkId"
]
},
{
"registryId": "FE-REG-API",
"path": "src/contracts/api-operations.js",
"exportName": "API_OPERATIONS",
"owner": "feature-api-client-response-envelope-contract",
"requiredFields": [
"method",
"path",
"operationId",
"auth",
"timeoutMs",
"idempotency",
"requestSchema",
"responseSchema",
"owner"
]
},
{
"registryId": "FE-REG-ENV",
"path": "src/contracts/env.js",
"exportName": "ENV_REGISTRY",
"owner": "feature-frontend-env-runtime-config-contract",
"requiredFields": ["phase", "classification", "required", "defaultValue"]
},
{
"registryId": "FE-REG-STORAGE",
"path": "src/contracts/storage-keys.js",
"exportName": "STORAGE_REGISTRY",
"owner": "feature-frontend-storage-registry-contract",
"requiredFields": [
"logicalName",
"physicalKey",
"backend",
"classification",
"schemaVersion",
"ttl",
"migration",
"quotaFallback"
]
},
{
"registryId": "FE-REG-ERROR",
"path": "src/contracts/errors.js",
"exportName": "ERROR_REGISTRY",
"owner": "feature-frontend-error-classification-boundary-contract",
"requiredFields": [
"kind",
"defaultRetryable",
"severity",
"userMessageKey",
"action",
"telemetryEvent",
"redaction"
]
},
{
"registryId": "FE-REG-QUERY",
"path": "src/contracts/query-keys.js",
"exportName": "QUERY_REGISTRY",
"owner": "feature-server-state-caching-contract",
"requiredFields": [
"namespace",
"serialization",
"identity",
"invalidation",
"version",
"persistence"
]
},
{
"registryId": "FE-REG-TELEMETRY",
"path": "src/contracts/telemetry.js",
"exportName": "TELEMETRY_REGISTRY",
"owner": "feature-frontend-observability-logging-trace-contract",
"requiredFields": [
"eventName",
"trigger",
"requiredAttributes",
"optionalAttributes",
"forbiddenAttributes",
"sampling",
"delivery"
]
},
{
"registryId": "FE-REG-RELEASE",
"path": "src/contracts/release-tokens.js",
"exportName": "RELEASE_TOKEN_REGISTRY",
"owner": "feature-frontend-release-cache-rollback-contract",
"requiredFields": ["token", "source", "compatibilityRole"],
"declaredRows": {
"appVersion": {
"token": "appVersion",
"source": "manifest",
"compatibilityRole": "human release label"
},
"buildId": {
"token": "buildId",
"source": "CI build",
"compatibilityRole": "asset and HTML coherence"
},
"commitSha": {
"token": "commitSha",
"source": "VCS",
"compatibilityRole": "source traceability"
},
"configSchemaVersion": {
"token": "configSchemaVersion",
"source": "runtime config schema",
"compatibilityRole": "boot compatibility"
},
"apiContractVersion": {
"token": "apiContractVersion",
"source": "frontend/backend agreement",
"compatibilityRole": "schema compatibility"
},
"assetManifestHash": {
"token": "assetManifestHash",
"source": "build output",
"compatibilityRole": "chunk integrity"
},
"releaseId": {
"token": "releaseId",
"source": "deploy system",
"compatibilityRole": "rollback target"
},
"builtAt": {
"token": "builtAt",
"source": "CI",
"compatibilityRole": "diagnostics only"
}
}
}
],
"compatibilityImpact": {
"allowed": ["none", "additive", "behavior-change", "breaking"],
"current": "additive"
}
}
+2 -1
View File
@@ -29,7 +29,8 @@
"verify:lockfile": "corepack pnpm install --frozen-lockfile",
"generate:supply-chain": "node scripts/generate-supply-chain.mjs",
"scan:security": "node scripts/security-scan.mjs",
"check:browser-security": "node scripts/check-browser-security.mjs"
"check:browser-security": "node scripts/check-browser-security.mjs",
"check:registries": "node scripts/check-registries.mjs"
},
"dependencies": {
"@tanstack/react-query": "5.101.4",
@@ -0,0 +1,29 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"schemaVersion",
"generatedAt",
"compatibilityImpact",
"failures",
"registries"
],
"properties": {
"schemaVersion": { "const": 1 },
"generatedAt": { "type": "string", "format": "date-time" },
"compatibilityImpact": {
"enum": ["none", "additive", "behavior-change", "breaking"]
},
"failures": { "type": "array", "maxItems": 0 },
"registries": {
"type": "array",
"minItems": 8,
"maxItems": 8,
"items": {
"type": "object",
"required": ["registryId", "owner", "source", "rowCount", "rows"]
}
}
},
"additionalProperties": false
}
+112
View File
@@ -0,0 +1,112 @@
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
const governance = JSON.parse(
await readFile("config/contracts/registry-governance.json", "utf8"),
);
const failures = [];
const owners = new Map();
const snapshots = [];
for (const specification of governance.registries) {
if (owners.has(specification.registryId)) {
failures.push(`duplicate owner for ${specification.registryId}`);
}
owners.set(specification.registryId, specification.owner);
let rows = specification.declaredRows;
try {
await access(specification.path);
const module = await import(
`${pathToFileURL(path.resolve(specification.path)).href}?registry-check=${Date.now()}`
);
rows = module[specification.exportName];
} catch {
if (!rows) failures.push(`missing registry source ${specification.path}`);
}
if (!rows || typeof rows !== "object" || Array.isArray(rows)) {
failures.push(`${specification.registryId} is not an object registry`);
continue;
}
for (const [rowName, row] of Object.entries(rows)) {
if (!row || typeof row !== "object" || Array.isArray(row)) {
failures.push(`${specification.registryId}.${rowName} is not an object`);
continue;
}
for (const field of specification.requiredFields) {
if (!(field in row)) {
failures.push(`${specification.registryId}.${rowName} missing ${field}`);
}
}
}
snapshots.push({
registryId: specification.registryId,
owner: specification.owner,
source: specification.path,
rowCount: Object.keys(rows).length,
rows,
});
}
const sourceFiles = [
"src/application",
"src/presentation",
"src/domain",
];
const adHocPatterns = [
{ name: "direct fetch", expression: /\bfetch\s*\(/ },
{ name: "direct localStorage", expression: /\blocalStorage\.(?:get|set|remove)Item/ },
{ name: "direct import.meta.env", expression: /\bimport\.meta\.env\./ },
{ name: "raw API path", expression: /["']\/api\// },
];
/** @param {string} directory */
async function scanDirectory(directory) {
const entries = await import("node:fs/promises").then(({ readdir }) =>
readdir(directory, { withFileTypes: true }),
);
for (const entry of entries) {
const target = path.join(directory, entry.name);
if (entry.isDirectory()) {
await scanDirectory(target);
continue;
}
if (!/\.(js|jsx|mjs)$/.test(entry.name)) continue;
const content = await readFile(target, "utf8");
for (const pattern of adHocPatterns) {
if (pattern.expression.test(content)) {
failures.push(`ad-hoc ${pattern.name} in ${target}`);
}
}
}
}
for (const sourceDirectory of sourceFiles) {
await scanDirectory(sourceDirectory);
}
await mkdir("artifacts/quality", { recursive: true });
await writeFile(
"artifacts/quality/registries.json",
`${JSON.stringify(
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
compatibilityImpact: governance.compatibilityImpact.current,
failures,
registries: snapshots,
},
null,
2,
)}\n`,
);
if (failures.length > 0) {
process.stderr.write(`Registry governance failed:\n${failures.join("\n")}\n`);
process.exit(1);
}
process.stdout.write(`Registry governance: ${snapshots.length} registries PASS\n`);
+21
View File
@@ -0,0 +1,21 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
describe("registry governance manifest", () => {
it("declares exactly eight single-owner registries and impact labels", async () => {
const governance = JSON.parse(
await readFile("config/contracts/registry-governance.json", "utf8"),
);
expect(governance.registries).toHaveLength(8);
expect(new Set(governance.registries.map((entry) => entry.registryId)).size).toBe(
8,
);
expect(governance.registries.every((entry) => entry.owner)).toBe(true);
expect(governance.compatibilityImpact.allowed).toEqual([
"none",
"additive",
"behavior-change",
"breaking",
]);
});
});