diff --git a/docs/contract-baseline.md b/docs/contract-baseline.md index ac5c209..c8ea987 100644 --- a/docs/contract-baseline.md +++ b/docs/contract-baseline.md @@ -46,7 +46,7 @@ The top-level **color** object is optional. When present it can include: ## Where contract semantics live in the repo -- **Schema file:** `packages/interfacectl-validator/src/schema/surfaces.web.contract.schema.json`. This is the authoritative JSON Schema. The CLI uses a bundled copy of this schema by default. You may override it with `--schema ` if needed. +- **Schema file:** `packages/interfacectl-validator/src/schema/web.surface.contract.schema.json`. This is the authoritative JSON Schema. The CLI uses a bundled copy of this schema by default. You may override it with `--schema ` if needed. Built-in contract schemas are named by surface runtime and contract type (e.g. `web.surface.contract.schema.json`), not by application. - **Structure validation:** The function **validateContractStructure** in `packages/interfacectl-validator/src/index.ts` loads the schema and validates contract JSON (via AJV). It returns errors or the parsed contract. - **Compliance evaluation:** The function **evaluateContractCompliance** in the same file takes a validated contract and a list of surface descriptors. It returns a validation summary with per-surface reports and violations. Compliance rules for fonts, colors, layout, motion, sections, and pageFrame are implemented in **evaluateSurfaceCompliance** in the same file. - **CLI entry point:** The validate command is implemented in `packages/interfacectl-cli/src/commands/validate.ts`. It calls **collectSurfaceDescriptors** (in `packages/interfacectl-cli/src/descriptors/static-analysis.ts`) to gather descriptors from the codebase, then passes the contract and descriptors to **evaluateContractCompliance**. The CLI maps violation types to stable codes in this command implementation. diff --git a/docs/plans/contract-first-enforcement-alignment.md b/docs/plans/contract-first-enforcement-alignment.md index 0315c65..2808e45 100644 --- a/docs/plans/contract-first-enforcement-alignment.md +++ b/docs/plans/contract-first-enforcement-alignment.md @@ -34,7 +34,7 @@ Interfacectl enforces this by: ## Contract Impact ### 1. Contract definition -The authoritative contract schema lives in `packages/interfacectl-validator/src/schema/surfaces.web.contract.schema.json`. Contracts are JSON files (default path `contracts/surfaces.web.contract.json` or `SURFACES_CONTRACT`). The schema already includes: +The authoritative contract schema lives in `packages/interfacectl-validator/src/schema/web.surface.contract.schema.json`. Contracts are JSON files (default path `contracts/surfaces.web.contract.json` or `SURFACES_CONTRACT`). The schema already includes: - **Version and scope:** `contractId`, `version` (semver pattern). - **Surfaces:** per-surface `requiredSections`, `allowedFonts`, `layout` (maxContentWidth, requiredContainers, optional pageFrame), deprecated `allowedColors`. - **Sections:** `id`, `intent`, `description` (intent is present but not yet a first-class enforcement hook). @@ -122,7 +122,7 @@ Migration: `allowedColors` is deprecated in favor of `color.sourceOfTruth` and ` ## Implementation Outline ### Phase 0. Contract baseline (mostly done; align and document) -- **Reuse:** Contract schema is in `packages/interfacectl-validator/src/schema/surfaces.web.contract.schema.json`. Validation is `validateContractStructure` in `packages/interfacectl-validator/src/index.ts` (AJV). Error taxonomy is `DriftViolationType` and the code map in `packages/interfacectl-cli/src/commands/validate.ts` (e.g. `font.disallowed`). +- **Reuse:** Contract schema is in `packages/interfacectl-validator/src/schema/web.surface.contract.schema.json`. Validation is `validateContractStructure` in `packages/interfacectl-validator/src/index.ts` (AJV). Error taxonomy is `DriftViolationType` and the code map in `packages/interfacectl-cli/src/commands/validate.ts` (e.g. `font.disallowed`). - **New/minimal:** Document the minimal authoritative contract (required fields, optional color policy, deprecated allowedColors). Optionally add a short "contract concepts" section to API.md or docs. No schema change required unless adding breaking/non-breaking metadata later. ### Phase 1. Generation-time gating (map to existing commands; optional alias) @@ -167,7 +167,7 @@ Work in this phase is contingent on observing Phase 4 consumption patterns. ## Builder Checklist Before implementation begins: -- **Contract location:** Canonical schema is `packages/interfacectl-validator/src/schema/surfaces.web.contract.schema.json`. Contract files are project-specific (default `contracts/surfaces.web.contract.json` or `SURFACES_CONTRACT`). Ownership is repo; surfaces.systems is the intended source of canonical definitions per strategy. +- **Contract location:** Canonical schema is `packages/interfacectl-validator/src/schema/web.surface.contract.schema.json`. Contract files are project-specific (default `contracts/surfaces.web.contract.json` or `SURFACES_CONTRACT`). Ownership is repo; surfaces.systems is the intended source of canonical definitions per strategy. - **Generation-time gating:** Current CI (`.github/workflows/ci.yml`) runs verify, build, test only. Gating fits as an optional job (e.g. `interfacectl validate` or `enforce --mode fail` against a repo contract). Confirm with maintainers where to add it (e.g. on PR, on main, or as an optional job). - **Runtime consumers:** No runtime consumers for compiled bundles exist yet. Before implementing `compile`, confirm at least one consumer or a concrete bundle format requirement; otherwise defer or define a minimal format and document it for future consumers. diff --git a/packages/interfacectl-cli/dist/commands/generate-contract.d.ts.map b/packages/interfacectl-cli/dist/commands/generate-contract.d.ts.map index 887bd19..1fe5260 100644 --- a/packages/interfacectl-cli/dist/commands/generate-contract.d.ts.map +++ b/packages/interfacectl-cli/dist/commands/generate-contract.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"generate-contract.d.ts","sourceRoot":"","sources":["../../src/commands/generate-contract.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,MAAM,CAAC,CA2DjB"} \ No newline at end of file +{"version":3,"file":"generate-contract.d.ts","sourceRoot":"","sources":["../../src/commands/generate-contract.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,MAAM,CAAC,CA+DjB"} \ No newline at end of file diff --git a/packages/interfacectl-cli/dist/commands/generate-contract.js b/packages/interfacectl-cli/dist/commands/generate-contract.js index cf65d6e..f34b426 100644 --- a/packages/interfacectl-cli/dist/commands/generate-contract.js +++ b/packages/interfacectl-cli/dist/commands/generate-contract.js @@ -40,7 +40,11 @@ export async function runGenerateContractCommand(options) { await mkdir(path.dirname(contractPath), { recursive: true }); await mkdir(path.dirname(reportPath), { recursive: true }); const contractJson = stableStringify(contract); - const reportJson = stableStringify(report); + const reportForOutput = { + ...report, + appRoot: path.relative(cwd, report.appRoot), + }; + const reportJson = stableStringify(reportForOutput); await writeFile(contractPath, `${contractJson}\n`, "utf-8"); await writeFile(reportPath, `${reportJson}\n`, "utf-8"); console.log(`Wrote contract: ${contractPath}`); diff --git a/packages/interfacectl-cli/test/fixtures/minimal-project/contracts/ui.contract.json b/packages/interfacectl-cli/test/fixtures/minimal-project/contracts/ui.contract.json index e62de3a..6ec2124 100644 --- a/packages/interfacectl-cli/test/fixtures/minimal-project/contracts/ui.contract.json +++ b/packages/interfacectl-cli/test/fixtures/minimal-project/contracts/ui.contract.json @@ -1,5 +1,5 @@ { - "$schema": "https://contracts.surfaces.local/surfaces.web.contract.schema.json", + "$schema": "https://contracts.surfaces.local/web.surface.contract.schema.json", "contractId": "portable.fixture", "version": "0.0.1", "surfaces": [ diff --git a/packages/interfacectl-cli/test/fixtures/traceability/contracts/ui.contract.json b/packages/interfacectl-cli/test/fixtures/traceability/contracts/ui.contract.json index 15c2699..019cca2 100644 --- a/packages/interfacectl-cli/test/fixtures/traceability/contracts/ui.contract.json +++ b/packages/interfacectl-cli/test/fixtures/traceability/contracts/ui.contract.json @@ -1,5 +1,5 @@ { - "$schema": "https://contracts.surfaces.local/surfaces.web.contract.schema.json", + "$schema": "https://contracts.surfaces.local/web.surface.contract.schema.json", "contractId": "traceability.fixture", "version": "0.0.1", "surfaces": [ diff --git a/packages/interfacectl-extractor/dist/extract.js b/packages/interfacectl-extractor/dist/extract.js index c04ecca..f08d82f 100644 --- a/packages/interfacectl-extractor/dist/extract.js +++ b/packages/interfacectl-extractor/dist/extract.js @@ -78,7 +78,7 @@ function buildContract(surfaceId, extracted) { ? "Surfaces Web" : surfaceId.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); return { - $schema: "https://contracts.surfaces.local/surfaces.web.contract.schema.json", + $schema: "https://contracts.surfaces.local/web.surface.contract.schema.json", contractId: `${surfaceId}.generated`, version: "0.1.0", description: "Extracted from Next.js app (Phase 0).", diff --git a/packages/interfacectl-extractor/src/extract.ts b/packages/interfacectl-extractor/src/extract.ts index fb4565e..1743574 100644 --- a/packages/interfacectl-extractor/src/extract.ts +++ b/packages/interfacectl-extractor/src/extract.ts @@ -102,7 +102,7 @@ function buildContract( return { $schema: - "https://contracts.surfaces.local/surfaces.web.contract.schema.json", + "https://contracts.surfaces.local/web.surface.contract.schema.json", contractId: `${surfaceId}.generated`, version: "0.1.0", description: "Extracted from Next.js app (Phase 0).", diff --git a/packages/interfacectl-validator/dist/index.js b/packages/interfacectl-validator/dist/index.js index 1494f2b..13b10c2 100644 --- a/packages/interfacectl-validator/dist/index.js +++ b/packages/interfacectl-validator/dist/index.js @@ -1,6 +1,6 @@ import AjvModule from "ajv/dist/2020.js"; import addFormats from "ajv-formats"; -import bundledSchema from "./schema/surfaces.web.contract.schema.json" with { +import bundledSchema from "./schema/web.surface.contract.schema.json" with { type: "json" }; const frozenBundledSchema = Object.freeze(bundledSchema); diff --git a/packages/interfacectl-validator/dist/schema/surfaces.web.contract.schema.json b/packages/interfacectl-validator/dist/schema/surfaces.web.contract.schema.json deleted file mode 100644 index fa18d91..0000000 --- a/packages/interfacectl-validator/dist/schema/surfaces.web.contract.schema.json +++ /dev/null @@ -1,411 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://contracts.surfaces.local/surfaces.web.contract.schema.json", - "title": "Surfaces Web Contract", - "type": "object", - "additionalProperties": false, - "required": [ - "contractId", - "version", - "surfaces", - "sections", - "constraints" - ], - "properties": { - "$schema": { - "type": "string", - "format": "uri-reference" - }, - "contractId": { - "type": "string", - "minLength": 1 - }, - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" - }, - "description": { - "type": "string" - }, - "surfaces": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/surface" - } - }, - "sections": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/section" - } - }, - "constraints": { - "$ref": "#/$defs/constraints" - }, - "color": { - "$ref": "#/$defs/color" - }, - "x_extracted": { - "type": "object", - "description": "Phase 0: fields extracted from app code; not used by validator semantics.", - "additionalProperties": true, - "properties": { - "routes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, - "hasShell": { "type": "boolean" }, - "designSystemComponents": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, - "authAware": { "type": "boolean" } - } - } - }, - "$defs": { - "surface": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "displayName", - "type", - "requiredSections", - "allowedFonts", - "layout" - ], - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z0-9-]+$" - }, - "displayName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": [ - "web", - "cli" - ] - }, - "requiredSections": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "pattern": "^[a-z0-9]+(\\.[a-z0-9]+)*$" - }, - "uniqueItems": true - }, - "allowedFonts": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "allowedColors": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true, - "description": "Deprecated: Use color.sourceOfTruth and color.rawValues instead. This field will be removed in a future version." - }, - "layout": { - "type": "object", - "additionalProperties": false, - "required": [ - "maxContentWidth" - ], - "properties": { - "maxContentWidth": { - "type": "number", - "minimum": 1 - }, - "requiredContainers": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "pageFrame": { - "$ref": "#/$defs/pageFrame" - } - } - }, - "phase0": { - "type": "object", - "description": "Phase 0.5: optional expectations for extracted reality (routes, auth, shell, design system).", - "additionalProperties": false, - "properties": { - "authPosture": { - "type": "string", - "enum": ["public", "auth-aware", "auth-first"] - }, - "requiresShell": { "type": "boolean" }, - "expectsAuthRoutes": { "type": "boolean" }, - "expectsDesignSystem": { "type": "boolean" } - } - }, - "domain": { - "type": "string", - "description": "Informational: production domain (e.g. www.surfaces.systems)." - }, - "owner": { - "type": "string", - "description": "Phase 2: informational owner (email or GitHub handle). No enforcement." - } - } - }, - "section": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "intent", - "description" - ], - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z0-9]+(\\.[a-z0-9]+)*$" - }, - "intent": { - "type": "string", - "minLength": 1 - }, - "description": { - "type": "string", - "minLength": 1 - } - } - }, - "constraints": { - "type": "object", - "additionalProperties": false, - "required": [ - "motion" - ], - "properties": { - "motion": { - "type": "object", - "additionalProperties": false, - "required": [ - "allowedDurationsMs", - "allowedTimingFunctions" - ], - "properties": { - "allowedDurationsMs": { - "type": "array", - "minItems": 1, - "items": { - "type": "integer", - "minimum": 1 - }, - "uniqueItems": true - }, - "allowedTimingFunctions": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - } - } - } - } - }, - "pageFrame": { - "type": "object", - "additionalProperties": false, - "required": [ - "containerSelector", - "containerMaxWidthPx", - "paddingXpx" - ], - "properties": { - "containerSelector": { - "type": "string", - "minLength": 1 - }, - "containerMaxWidthPx": { - "type": "number", - "minimum": 0 - }, - "paddingXpx": { - "type": "number", - "minimum": 0 - }, - "alignment": { - "type": "string", - "enum": ["center", "left"], - "default": "center" - }, - "enforcement": { - "type": "string", - "enum": ["strict", "warn"], - "default": "strict" - } - } - }, - "color": { - "type": "object", - "additionalProperties": false, - "properties": { - "sourceOfTruth": { - "$ref": "#/$defs/colorSourceOfTruth" - }, - "rawValues": { - "$ref": "#/$defs/colorRawValues" - }, - "semantics": { - "$ref": "#/$defs/colorSemantics" - }, - "consistency": { - "$ref": "#/$defs/colorConsistency" - } - } - }, - "colorSourceOfTruth": { - "type": "object", - "additionalProperties": false, - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["tokens", "none"] - }, - "tokenNamespaces": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - } - }, - "if": { - "properties": { - "type": { - "const": "tokens" - } - } - }, - "then": { - "required": ["tokenNamespaces"] - } - }, - "colorRawValues": { - "type": "object", - "additionalProperties": false, - "required": ["policy"], - "properties": { - "policy": { - "type": "string", - "enum": ["off", "warn", "strict"] - }, - "allowlist": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "denylist": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - } - } - }, - "colorSemantics": { - "type": "object", - "additionalProperties": false, - "properties": { - "roles": { - "type": "object", - "additionalProperties": false, - "properties": { - "accent": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - } - } - }, - "text": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - } - } - }, - "background": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - } - } - }, - "border": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - } - } - } - } - } - } - }, - "colorConsistency": { - "type": "object", - "additionalProperties": false, - "properties": { - "acrossSurfaces": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - }, - "signals": { - "type": "array", - "items": { - "type": "string", - "enum": ["token-name", "css-var-name", "class-fragment"] - }, - "uniqueItems": true - } - } - } - } - } - } -} - diff --git a/packages/interfacectl-validator/dist/schema/web.surface.contract.schema.json b/packages/interfacectl-validator/dist/schema/web.surface.contract.schema.json new file mode 100644 index 0000000..6c0895b --- /dev/null +++ b/packages/interfacectl-validator/dist/schema/web.surface.contract.schema.json @@ -0,0 +1 @@ +{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://contracts.surfaces.local/web.surface.contract.schema.json","title":"Web Surface Contract Schema","description":"Built-in contract type schema for the web surface runtime. Not application-specific.","type":"object","additionalProperties":false,"required":["contractId","version","surfaces","sections","constraints"],"properties":{"$schema":{"type":"string","format":"uri-reference"},"contractId":{"type":"string","minLength":1},"version":{"type":"string","pattern":"^[0-9]+\\.[0-9]+\\.[0-9]+$"},"description":{"type":"string"},"surfaces":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/surface"}},"sections":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/section"}},"constraints":{"$ref":"#/$defs/constraints"},"color":{"$ref":"#/$defs/color"},"x_extracted":{"type":"object","description":"Phase 0: fields extracted from app code; not used by validator semantics.","additionalProperties":true,"properties":{"routes":{"type":"array","items":{"type":"string"},"uniqueItems":true},"hasShell":{"type":"boolean"},"designSystemComponents":{"type":"array","items":{"type":"string"},"uniqueItems":true},"authAware":{"type":"boolean"}}}},"$defs":{"surface":{"type":"object","additionalProperties":false,"required":["id","displayName","type","requiredSections","allowedFonts","layout"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9-]+$"},"displayName":{"type":"string","minLength":1},"type":{"type":"string","enum":["web","cli"]},"requiredSections":{"type":"array","minItems":1,"items":{"type":"string","pattern":"^[a-z0-9]+(\\.[a-z0-9]+)*$"},"uniqueItems":true},"allowedFonts":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true},"allowedColors":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true,"description":"Deprecated: Use color.sourceOfTruth and color.rawValues instead. This field will be removed in a future version."},"layout":{"type":"object","additionalProperties":false,"required":["maxContentWidth"],"properties":{"maxContentWidth":{"type":"number","minimum":1},"requiredContainers":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true},"pageFrame":{"$ref":"#/$defs/pageFrame"}}},"phase0":{"type":"object","description":"Phase 0.5: optional expectations for extracted reality (routes, auth, shell, design system).","additionalProperties":false,"properties":{"authPosture":{"type":"string","enum":["public","auth-aware","auth-first"]},"requiresShell":{"type":"boolean"},"expectsAuthRoutes":{"type":"boolean"},"expectsDesignSystem":{"type":"boolean"}}},"domain":{"type":"string","description":"Informational: production domain (e.g. www.surfaces.systems)."},"owner":{"type":"string","description":"Phase 2: informational owner (email or GitHub handle). No enforcement."}}},"section":{"type":"object","additionalProperties":false,"required":["id","intent","description"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9]+(\\.[a-z0-9]+)*$"},"intent":{"type":"string","minLength":1},"description":{"type":"string","minLength":1}}},"constraints":{"type":"object","additionalProperties":false,"required":["motion"],"properties":{"motion":{"type":"object","additionalProperties":false,"required":["allowedDurationsMs","allowedTimingFunctions"],"properties":{"allowedDurationsMs":{"type":"array","minItems":1,"items":{"type":"integer","minimum":1},"uniqueItems":true},"allowedTimingFunctions":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true}}}}},"pageFrame":{"type":"object","additionalProperties":false,"required":["containerSelector","containerMaxWidthPx","paddingXpx"],"properties":{"containerSelector":{"type":"string","minLength":1},"containerMaxWidthPx":{"type":"number","minimum":0},"paddingXpx":{"type":"number","minimum":0},"alignment":{"type":"string","enum":["center","left"],"default":"center"},"enforcement":{"type":"string","enum":["strict","warn"],"default":"strict"}}},"color":{"type":"object","additionalProperties":false,"properties":{"sourceOfTruth":{"$ref":"#/$defs/colorSourceOfTruth"},"rawValues":{"$ref":"#/$defs/colorRawValues"},"semantics":{"$ref":"#/$defs/colorSemantics"},"consistency":{"$ref":"#/$defs/colorConsistency"}}},"colorSourceOfTruth":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","enum":["tokens","none"]},"tokenNamespaces":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}},"if":{"properties":{"type":{"const":"tokens"}}},"then":{"required":["tokenNamespaces"]}},"colorRawValues":{"type":"object","additionalProperties":false,"required":["policy"],"properties":{"policy":{"type":"string","enum":["off","warn","strict"]},"allowlist":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true},"denylist":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}}},"colorSemantics":{"type":"object","additionalProperties":false,"properties":{"roles":{"type":"object","additionalProperties":false,"properties":{"accent":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]}}},"text":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]}}},"background":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]}}},"border":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]}}}}}}},"colorConsistency":{"type":"object","additionalProperties":false,"properties":{"acrossSurfaces":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]},"signals":{"type":"array","items":{"type":"string","enum":["token-name","css-var-name","class-fragment"]},"uniqueItems":true}}}}}}} diff --git a/packages/interfacectl-validator/scripts/copy-schema.mjs b/packages/interfacectl-validator/scripts/copy-schema.mjs index 9cdaf51..ee0d9b1 100644 --- a/packages/interfacectl-validator/scripts/copy-schema.mjs +++ b/packages/interfacectl-validator/scripts/copy-schema.mjs @@ -8,7 +8,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const schemas = [ - "surfaces.web.contract.schema.json", + "web.surface.contract.schema.json", "interfacectl.diff.schema.json", "interfacectl.policy.schema.json", "interfacectl.fix-summary.schema.json", diff --git a/packages/interfacectl-validator/src/index.ts b/packages/interfacectl-validator/src/index.ts index 160436c..b0773e4 100644 --- a/packages/interfacectl-validator/src/index.ts +++ b/packages/interfacectl-validator/src/index.ts @@ -10,7 +10,7 @@ import { ContractSurface, PageFrameLayoutDescriptor, } from "./types.js"; -import bundledSchema from "./schema/surfaces.web.contract.schema.json" with { +import bundledSchema from "./schema/web.surface.contract.schema.json" with { type: "json", }; diff --git a/packages/interfacectl-validator/src/schema/surfaces.web.contract.schema.json b/packages/interfacectl-validator/src/schema/surfaces.web.contract.schema.json deleted file mode 100644 index fa18d91..0000000 --- a/packages/interfacectl-validator/src/schema/surfaces.web.contract.schema.json +++ /dev/null @@ -1,411 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://contracts.surfaces.local/surfaces.web.contract.schema.json", - "title": "Surfaces Web Contract", - "type": "object", - "additionalProperties": false, - "required": [ - "contractId", - "version", - "surfaces", - "sections", - "constraints" - ], - "properties": { - "$schema": { - "type": "string", - "format": "uri-reference" - }, - "contractId": { - "type": "string", - "minLength": 1 - }, - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" - }, - "description": { - "type": "string" - }, - "surfaces": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/surface" - } - }, - "sections": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/section" - } - }, - "constraints": { - "$ref": "#/$defs/constraints" - }, - "color": { - "$ref": "#/$defs/color" - }, - "x_extracted": { - "type": "object", - "description": "Phase 0: fields extracted from app code; not used by validator semantics.", - "additionalProperties": true, - "properties": { - "routes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, - "hasShell": { "type": "boolean" }, - "designSystemComponents": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, - "authAware": { "type": "boolean" } - } - } - }, - "$defs": { - "surface": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "displayName", - "type", - "requiredSections", - "allowedFonts", - "layout" - ], - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z0-9-]+$" - }, - "displayName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": [ - "web", - "cli" - ] - }, - "requiredSections": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "pattern": "^[a-z0-9]+(\\.[a-z0-9]+)*$" - }, - "uniqueItems": true - }, - "allowedFonts": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "allowedColors": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true, - "description": "Deprecated: Use color.sourceOfTruth and color.rawValues instead. This field will be removed in a future version." - }, - "layout": { - "type": "object", - "additionalProperties": false, - "required": [ - "maxContentWidth" - ], - "properties": { - "maxContentWidth": { - "type": "number", - "minimum": 1 - }, - "requiredContainers": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "pageFrame": { - "$ref": "#/$defs/pageFrame" - } - } - }, - "phase0": { - "type": "object", - "description": "Phase 0.5: optional expectations for extracted reality (routes, auth, shell, design system).", - "additionalProperties": false, - "properties": { - "authPosture": { - "type": "string", - "enum": ["public", "auth-aware", "auth-first"] - }, - "requiresShell": { "type": "boolean" }, - "expectsAuthRoutes": { "type": "boolean" }, - "expectsDesignSystem": { "type": "boolean" } - } - }, - "domain": { - "type": "string", - "description": "Informational: production domain (e.g. www.surfaces.systems)." - }, - "owner": { - "type": "string", - "description": "Phase 2: informational owner (email or GitHub handle). No enforcement." - } - } - }, - "section": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "intent", - "description" - ], - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z0-9]+(\\.[a-z0-9]+)*$" - }, - "intent": { - "type": "string", - "minLength": 1 - }, - "description": { - "type": "string", - "minLength": 1 - } - } - }, - "constraints": { - "type": "object", - "additionalProperties": false, - "required": [ - "motion" - ], - "properties": { - "motion": { - "type": "object", - "additionalProperties": false, - "required": [ - "allowedDurationsMs", - "allowedTimingFunctions" - ], - "properties": { - "allowedDurationsMs": { - "type": "array", - "minItems": 1, - "items": { - "type": "integer", - "minimum": 1 - }, - "uniqueItems": true - }, - "allowedTimingFunctions": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - } - } - } - } - }, - "pageFrame": { - "type": "object", - "additionalProperties": false, - "required": [ - "containerSelector", - "containerMaxWidthPx", - "paddingXpx" - ], - "properties": { - "containerSelector": { - "type": "string", - "minLength": 1 - }, - "containerMaxWidthPx": { - "type": "number", - "minimum": 0 - }, - "paddingXpx": { - "type": "number", - "minimum": 0 - }, - "alignment": { - "type": "string", - "enum": ["center", "left"], - "default": "center" - }, - "enforcement": { - "type": "string", - "enum": ["strict", "warn"], - "default": "strict" - } - } - }, - "color": { - "type": "object", - "additionalProperties": false, - "properties": { - "sourceOfTruth": { - "$ref": "#/$defs/colorSourceOfTruth" - }, - "rawValues": { - "$ref": "#/$defs/colorRawValues" - }, - "semantics": { - "$ref": "#/$defs/colorSemantics" - }, - "consistency": { - "$ref": "#/$defs/colorConsistency" - } - } - }, - "colorSourceOfTruth": { - "type": "object", - "additionalProperties": false, - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["tokens", "none"] - }, - "tokenNamespaces": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - } - }, - "if": { - "properties": { - "type": { - "const": "tokens" - } - } - }, - "then": { - "required": ["tokenNamespaces"] - } - }, - "colorRawValues": { - "type": "object", - "additionalProperties": false, - "required": ["policy"], - "properties": { - "policy": { - "type": "string", - "enum": ["off", "warn", "strict"] - }, - "allowlist": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "denylist": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - } - } - }, - "colorSemantics": { - "type": "object", - "additionalProperties": false, - "properties": { - "roles": { - "type": "object", - "additionalProperties": false, - "properties": { - "accent": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - } - } - }, - "text": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - } - } - }, - "background": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - } - } - }, - "border": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - } - } - } - } - } - } - }, - "colorConsistency": { - "type": "object", - "additionalProperties": false, - "properties": { - "acrossSurfaces": { - "type": "object", - "additionalProperties": false, - "properties": { - "enforcement": { - "type": "string", - "enum": ["off", "warn", "strict"] - }, - "signals": { - "type": "array", - "items": { - "type": "string", - "enum": ["token-name", "css-var-name", "class-fragment"] - }, - "uniqueItems": true - } - } - } - } - } - } -} - diff --git a/packages/interfacectl-validator/src/schema/web.surface.contract.schema.json b/packages/interfacectl-validator/src/schema/web.surface.contract.schema.json new file mode 100644 index 0000000..6c0895b --- /dev/null +++ b/packages/interfacectl-validator/src/schema/web.surface.contract.schema.json @@ -0,0 +1 @@ +{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://contracts.surfaces.local/web.surface.contract.schema.json","title":"Web Surface Contract Schema","description":"Built-in contract type schema for the web surface runtime. Not application-specific.","type":"object","additionalProperties":false,"required":["contractId","version","surfaces","sections","constraints"],"properties":{"$schema":{"type":"string","format":"uri-reference"},"contractId":{"type":"string","minLength":1},"version":{"type":"string","pattern":"^[0-9]+\\.[0-9]+\\.[0-9]+$"},"description":{"type":"string"},"surfaces":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/surface"}},"sections":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/section"}},"constraints":{"$ref":"#/$defs/constraints"},"color":{"$ref":"#/$defs/color"},"x_extracted":{"type":"object","description":"Phase 0: fields extracted from app code; not used by validator semantics.","additionalProperties":true,"properties":{"routes":{"type":"array","items":{"type":"string"},"uniqueItems":true},"hasShell":{"type":"boolean"},"designSystemComponents":{"type":"array","items":{"type":"string"},"uniqueItems":true},"authAware":{"type":"boolean"}}}},"$defs":{"surface":{"type":"object","additionalProperties":false,"required":["id","displayName","type","requiredSections","allowedFonts","layout"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9-]+$"},"displayName":{"type":"string","minLength":1},"type":{"type":"string","enum":["web","cli"]},"requiredSections":{"type":"array","minItems":1,"items":{"type":"string","pattern":"^[a-z0-9]+(\\.[a-z0-9]+)*$"},"uniqueItems":true},"allowedFonts":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true},"allowedColors":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true,"description":"Deprecated: Use color.sourceOfTruth and color.rawValues instead. This field will be removed in a future version."},"layout":{"type":"object","additionalProperties":false,"required":["maxContentWidth"],"properties":{"maxContentWidth":{"type":"number","minimum":1},"requiredContainers":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true},"pageFrame":{"$ref":"#/$defs/pageFrame"}}},"phase0":{"type":"object","description":"Phase 0.5: optional expectations for extracted reality (routes, auth, shell, design system).","additionalProperties":false,"properties":{"authPosture":{"type":"string","enum":["public","auth-aware","auth-first"]},"requiresShell":{"type":"boolean"},"expectsAuthRoutes":{"type":"boolean"},"expectsDesignSystem":{"type":"boolean"}}},"domain":{"type":"string","description":"Informational: production domain (e.g. www.surfaces.systems)."},"owner":{"type":"string","description":"Phase 2: informational owner (email or GitHub handle). No enforcement."}}},"section":{"type":"object","additionalProperties":false,"required":["id","intent","description"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9]+(\\.[a-z0-9]+)*$"},"intent":{"type":"string","minLength":1},"description":{"type":"string","minLength":1}}},"constraints":{"type":"object","additionalProperties":false,"required":["motion"],"properties":{"motion":{"type":"object","additionalProperties":false,"required":["allowedDurationsMs","allowedTimingFunctions"],"properties":{"allowedDurationsMs":{"type":"array","minItems":1,"items":{"type":"integer","minimum":1},"uniqueItems":true},"allowedTimingFunctions":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true}}}}},"pageFrame":{"type":"object","additionalProperties":false,"required":["containerSelector","containerMaxWidthPx","paddingXpx"],"properties":{"containerSelector":{"type":"string","minLength":1},"containerMaxWidthPx":{"type":"number","minimum":0},"paddingXpx":{"type":"number","minimum":0},"alignment":{"type":"string","enum":["center","left"],"default":"center"},"enforcement":{"type":"string","enum":["strict","warn"],"default":"strict"}}},"color":{"type":"object","additionalProperties":false,"properties":{"sourceOfTruth":{"$ref":"#/$defs/colorSourceOfTruth"},"rawValues":{"$ref":"#/$defs/colorRawValues"},"semantics":{"$ref":"#/$defs/colorSemantics"},"consistency":{"$ref":"#/$defs/colorConsistency"}}},"colorSourceOfTruth":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","enum":["tokens","none"]},"tokenNamespaces":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}},"if":{"properties":{"type":{"const":"tokens"}}},"then":{"required":["tokenNamespaces"]}},"colorRawValues":{"type":"object","additionalProperties":false,"required":["policy"],"properties":{"policy":{"type":"string","enum":["off","warn","strict"]},"allowlist":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true},"denylist":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}}},"colorSemantics":{"type":"object","additionalProperties":false,"properties":{"roles":{"type":"object","additionalProperties":false,"properties":{"accent":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]}}},"text":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]}}},"background":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]}}},"border":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]}}}}}}},"colorConsistency":{"type":"object","additionalProperties":false,"properties":{"acrossSurfaces":{"type":"object","additionalProperties":false,"properties":{"enforcement":{"type":"string","enum":["off","warn","strict"]},"signals":{"type":"array","items":{"type":"string","enum":["token-name","css-var-name","class-fragment"]},"uniqueItems":true}}}}}}}