@suluk/cockpit 0.1.17 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -5
- package/package.json +8 -7
- package/src/conformance.ts +107 -0
- package/src/index.ts +4 -0
- package/test/conformance.test.ts +73 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<h1 align="center">@suluk/cockpit</h1>
|
|
8
8
|
|
|
9
|
-
<p align="center"><b>The pure cockpit core
|
|
9
|
+
<p align="center"><b>The pure cockpit core — one brain, two faces: the cycle model, the builder model, codegen, deploy planning, and the validate/audit/preview/converge helpers shared by the VS Code extension and the <code>/superadmin</code> web panel.</b></p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<em>Part of <a href="https://github.com/MahmoodKhalil57/suluk">Suluk</a> — one typed OpenAPI v4 contract projecting into every full-stack layer.</em>
|
|
@@ -24,11 +24,168 @@
|
|
|
24
24
|
bun add @suluk/cockpit
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## What it does
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
Given **one** v4 "Suluk" document, the cockpit computes every view a developer tool needs over it — as
|
|
30
|
+
**pure functions, no host API**. The VS Code extension (`suluk-vscode`) and the web admin panel (`@suluk/admin`)
|
|
31
|
+
both consume this exact core, so a projection is never reimplemented in a shell.
|
|
32
|
+
|
|
33
|
+
- **The cycle model** — `buildCycle` projects the document into a layered view (data · contract · auth · cost ·
|
|
34
|
+
docs · state · UI · providers · tests). Each layer is a *projection* of the same source, so you can read the
|
|
35
|
+
lineage at a glance. The model is also a function of the requesting principal — pass scopes and gated
|
|
36
|
+
operations drop out.
|
|
37
|
+
- **Validate / audit / preview** — `validateSource` (meta-schema), `auditSource` (doc-coverage findings), and
|
|
38
|
+
`previewHtml` (a self-contained Scalar or Swagger page) over raw source text.
|
|
39
|
+
- **The builder model + codegen** — `buildBuilderModel` walks the tiered composition (pages → sections → blocks →
|
|
40
|
+
components); `generateForm` / `generateTable` / `generateAppFiles` land real TSX + a shadcn registry.
|
|
41
|
+
- **Lifecycle & coherence** — `contractGates` + `shipSummary` (the "are you ready to ship?" checklist),
|
|
42
|
+
`convergeContract` (cross-cutting contradictions a clean merge leaves behind), `diffContracts` (local-vs-deployed
|
|
43
|
+
drift), `crossCut` (one contract refracted through every viewer), `contractToD2` (ERD / cycle / operation diagrams).
|
|
44
|
+
- **Deploy planning** — `deployPlan` / `deployMarkdown` turn the contract into a Cloudflare plan + a `DEPLOY.md`.
|
|
45
|
+
The cockpit **plans**; it never runs `wrangler` and holds no credentials.
|
|
46
|
+
|
|
47
|
+
## When to reach for it
|
|
48
|
+
|
|
49
|
+
- You are building **another shell** over the Suluk contract (an editor integration, a CI gate, a dashboard) and
|
|
50
|
+
want the same brain the VS Code extension and `/superadmin` panel use — not a re-derivation.
|
|
51
|
+
- You need a **single, principal-aware projection** of a v4 document: what entities, operations, costs, providers
|
|
52
|
+
and gated surfaces it implies, plus its ship-readiness and coherence.
|
|
53
|
+
|
|
54
|
+
When **not** to: if you only need *one* projection in isolation, depend on it directly — `@suluk/scalar` /
|
|
55
|
+
`@suluk/swagger` for docs HTML, `@suluk/shadcn` for forms/tables, `@suluk/builder` for the app graph, `@suluk/deploy`
|
|
56
|
+
for the plan. The cockpit's value is **composing** them into the tool views above; reach past it when you don't need that.
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### The cycle model (the spine)
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { parseDocument } from "@suluk/core";
|
|
64
|
+
import { buildCycle, cycleSummary } from "@suluk/cockpit";
|
|
65
|
+
|
|
66
|
+
const doc = parseDocument(source); // a v4 "Suluk" document
|
|
67
|
+
const model = buildCycle(doc);
|
|
68
|
+
|
|
69
|
+
model.valid; // passes the v4 meta-schema?
|
|
70
|
+
model.coverage; // documentation coverage 0..1
|
|
71
|
+
cycleSummary(model);
|
|
72
|
+
// → [{ layer: "Data (entities)", summary: "3 entities", status: "ok" }, … ]
|
|
73
|
+
|
|
74
|
+
// Project for a principal — scope-gated operations they can't reach drop out of every layer:
|
|
75
|
+
const asViewer = buildCycle(doc, { principal: { scopes: ["read:pets"] } });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Validate · audit · preview (over raw source text)
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { validateSource, auditSource, previewHtml } from "@suluk/cockpit";
|
|
82
|
+
|
|
83
|
+
const { ok, diagnostics } = validateSource(yamlOrJsonText);
|
|
84
|
+
const { findings } = auditSource(yamlOrJsonText); // under-documented routes
|
|
85
|
+
const { html } = previewHtml(yamlOrJsonText, "scalar"); // or "swagger" — a self-contained page
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Ship-readiness + coherence
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { contractGates, shipSummary, convergeContract } from "@suluk/cockpit";
|
|
92
|
+
import type { Baseline } from "@suluk/visual";
|
|
93
|
+
|
|
94
|
+
const baseline: Baseline = {}; // the pixel-confidence baseline (empty ⇒ not yet verified)
|
|
95
|
+
const gates = contractGates(doc, baseline);
|
|
96
|
+
const { ready, line } = shipSummary(gates);
|
|
97
|
+
// → { ready: false, line: "1 blocker · 1 to do · 3/5 pass" }
|
|
98
|
+
|
|
99
|
+
const report = convergeContract(doc); // cross-cutting contradictions
|
|
100
|
+
report.clean; // true ⇒ no dangling refs / undeclared schemes / orphan scopes / empty paths
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Drift, cross-cut, diagrams
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { diffContracts, crossCut, defaultViewers, contractToD2 } from "@suluk/cockpit";
|
|
107
|
+
|
|
108
|
+
// "what's drifted in prod" — your LOCAL contract vs the DEPLOYED /openapi.json
|
|
109
|
+
diffContracts(local, deployed).summary; // "1+ 0- 2~ ops · 1+ 0- 0~ schemas" | "in sync — local matches deployed"
|
|
110
|
+
|
|
111
|
+
// one contract refracted through every viewer — the scope-gated surface
|
|
112
|
+
crossCut(doc, defaultViewers(doc)).gated; // operations not visible to every viewer
|
|
113
|
+
|
|
114
|
+
// another projection: D2 diagram source ("erd" | "cycle" | "operations")
|
|
115
|
+
const d2 = contractToD2(doc, "erd"); // render with the d2 CLI / playground / kroki.io
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Codegen + deploy planning (the actions that land artifacts)
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { generateForm, generateAppFiles, deployPlan, deployMarkdown } from "@suluk/cockpit";
|
|
122
|
+
|
|
123
|
+
const formTsx = generateForm(doc, "Pet"); // a shadcn form component (TSX) for an entity
|
|
124
|
+
const files = generateAppFiles(doc); // [{ path, content }] — openapi.json, components, pages, registry, diagrams
|
|
125
|
+
|
|
126
|
+
const plan = deployPlan(doc); // a Cloudflare DeployPlan
|
|
127
|
+
const md = deployMarkdown(plan); // a DEPLOY.md the user follows (Suluk never runs wrangler)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Modules — install a contract fragment, re-project for free
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { installModule, ECOMMERCE, buildCycle } from "@suluk/cockpit";
|
|
134
|
+
|
|
135
|
+
const { doc: merged } = installModule(host, ECOMMERCE); // ECOMMERCE | CRM | BILLING are first-party fragments
|
|
136
|
+
buildCycle(merged); // every layer now includes the module's entities, operations, cost and provider slots
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Agents (OBSERVE-only)
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { agentsView, agentsSummary } from "@suluk/cockpit";
|
|
143
|
+
|
|
144
|
+
const view = agentsView(doc); // the x-suluk-agents tier tree, effective scope, gate findings, reachable surface
|
|
145
|
+
agentsSummary(view); // a one-line digest — read-only; agent execution + secrets live OUTSIDE the cockpit
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## API
|
|
149
|
+
|
|
150
|
+
The package exposes a single entry point (`@suluk/cockpit`). The core groups:
|
|
151
|
+
|
|
152
|
+
| Export | What it does |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `buildCycle`, `cycleSummary`, `docChecks` | the layered cycle model (principal-aware) + doc-level contract checks |
|
|
155
|
+
| `validateSource`, `auditSource`, `previewHtml`, `looksLikeV4` | validate / audit / preview over raw source text |
|
|
156
|
+
| `buildBuilderModel`, `builderTree`, `entitiesFromDoc`, `generateAppFiles`, `generateRegistryJson` | the tiered builder model + the app/registry generators |
|
|
157
|
+
| `entityNames`, `generateForm`, `generateTable`, `generateStoresModule`, `exportV4Json` | per-entity codegen + the canonical JSON export |
|
|
158
|
+
| `contractGates`, `shipSummary` | the contract-level ship-readiness gates + a one-line summary |
|
|
159
|
+
| `convergeContract` | a coherence audit (dangling refs / undeclared schemes / orphan scopes / preview backdoors) |
|
|
160
|
+
| `diffContracts`, `canonical` | local-vs-deployed contract drift |
|
|
161
|
+
| `crossCut`, `documentScopes`, `defaultViewers`, `previewRoles`, `previewAllowedRoles`, `previewLaunchUrl` | the per-viewer / role-preview security surface |
|
|
162
|
+
| `contractToD2`, `diagramViews` | D2 diagram source (ERD / cycle / operations) |
|
|
163
|
+
| `componentReport`, `approveComponents`, `primitiveCss` | UI-primitive decomposition + pixel-confidence (surfaces `@suluk/visual`) |
|
|
164
|
+
| `deployPlan`, `deployMarkdown`, `previewDeployPlan`, `previewDeployMarkdown` | Cloudflare deploy planning + the rendered `DEPLOY.md` |
|
|
165
|
+
| `agentsView`, `agentsSummary` | the `x-suluk-agents` OBSERVE view (tier tree · scope · context · model selection) |
|
|
166
|
+
| `installModule`, `namespaceModule`, `previewInstall`, `gradeModule`, `composeModules`, `planComposition` | install / compose contract-fragment modules into the hub doc |
|
|
167
|
+
| `ECOMMERCE`, `CRM`, `BILLING`, `FIRST_PARTY_REGISTRY`, `PROVIDER_CATALOG`, `STACK_TEMPLATES` | the first-party module + provider + stack-template catalogs |
|
|
168
|
+
| `providerFacets`, `readProviders`, `swapProvider`, `parseRegistry`, `validateModule`, `resolveTemplate` | provider-slot + registry + template helpers (re-exported from `@suluk/builder`) |
|
|
169
|
+
| `formatMicroUsd`, `summarize` | cost formatting (re-exported from `@suluk/cost`) for a live ledger |
|
|
170
|
+
|
|
171
|
+
Every function is pure and typed against `OpenAPIv4Document` from `@suluk/core`; the type exports (`CycleModel`,
|
|
172
|
+
`Gate`, `ConvergeReport`, `ContractDiff`, `CrossCut`, `AgentsView`, …) ride alongside each group.
|
|
173
|
+
|
|
174
|
+
## Boundary
|
|
175
|
+
|
|
176
|
+
The cockpit is **L3 — it renders and generates, never hosts** (the C023 line). It is pure logic with no host API:
|
|
177
|
+
it returns models, strings and file lists; the *shell* writes files, fetches the deployed `/openapi.json`, opens
|
|
178
|
+
terminals and renders webviews. Two consequences:
|
|
179
|
+
|
|
180
|
+
- **No credentials seam.** Deploy planning emits a `DEPLOY.md` and the plan; it never runs `wrangler` or touches a
|
|
181
|
+
token — `wrangler login`'s OAuth happens in *your* terminal so credentials never reach Suluk (C020). Role-preview
|
|
182
|
+
links are computed (`previewLaunchUrl`) but the cockpit never mints or holds a session.
|
|
183
|
+
- **Agents are OBSERVE-only.** `agentsView` derives the static tier tree, effective scope, gate findings and a
|
|
184
|
+
*projection preview* (file/tool **names**). It never executes an agent, fetches a preprompt, or reads a secret.
|
|
185
|
+
|
|
186
|
+
Inject the host concerns (the bytes to fetch, the deployed document to diff against, the baseline to approve at);
|
|
187
|
+
keep the projections here. To contribute a new view, add a pure function over `OpenAPIv4Document` and let both
|
|
188
|
+
shells render it.
|
|
32
189
|
|
|
33
190
|
## License
|
|
34
191
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suluk/cockpit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The pure cockpit core (cycle model · builder model · codegen · deploy planning · validate/audit/preview) shared by the vscode extension and the /superadmin web admin panel. CANDIDATE tooling.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -19,16 +19,17 @@
|
|
|
19
19
|
".": "./src/index.ts"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@suluk/core": "^0.1.
|
|
23
|
-
"@suluk/hono": "^0.1.
|
|
24
|
-
"@suluk/scalar": "^0.
|
|
22
|
+
"@suluk/core": "^0.1.13",
|
|
23
|
+
"@suluk/hono": "^0.1.5",
|
|
24
|
+
"@suluk/scalar": "^0.8.0",
|
|
25
25
|
"@suluk/swagger": "^0.1.2",
|
|
26
26
|
"@suluk/shadcn": "^0.1.2",
|
|
27
27
|
"@suluk/builder": "^0.1.11",
|
|
28
|
-
"@suluk/deploy": "^0.1.
|
|
29
|
-
"@suluk/cost": "^0.
|
|
28
|
+
"@suluk/deploy": "^0.1.4",
|
|
29
|
+
"@suluk/cost": "^0.2.0",
|
|
30
|
+
"@suluk/harden": "^0.2.0",
|
|
30
31
|
"@suluk/visual": "^0.1.3",
|
|
31
|
-
"@suluk/agents": "^0.1.
|
|
32
|
+
"@suluk/agents": "^0.1.6"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@types/bun": "latest"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conformance gates (C045) — the UNIFIED contract audit, the cockpit's "are you ready to ship?" checklist grown to fold
|
|
3
|
+
* the readiness DIMENSIONS into the same `Gate[]` model as `contractGates`. It COMPOSES shipped Suluk primitives — no
|
|
4
|
+
* new audit logic lives here:
|
|
5
|
+
* • input hardening + schema readiness ← @suluk/harden (auditDocument · auditReadiness, C043)
|
|
6
|
+
* • cost completeness ← @suluk/cost (costAudit)
|
|
7
|
+
* • settlement "names a lever" ← @suluk/cost (settlementAudit, C044)
|
|
8
|
+
* • implied error responses declared ← @suluk/cost (impliedErrorStatuses vs the op's declared statuses, C044)
|
|
9
|
+
*
|
|
10
|
+
* This is the generic form of toolfactory's `conformance-gate` (which hand-folds contract+errors+governance+stores+
|
|
11
|
+
* hardening): a consumer's CI collapses to `shipSummary([...contractGates(doc, baseline), ...conformanceGates(doc)])`
|
|
12
|
+
* (or `assertConformance(doc)`). Pure (no host) → unit-tested. cockpit never deps journeys (BDD coverage folds in via
|
|
13
|
+
* harden's combineGrades upstream, C043).
|
|
14
|
+
*/
|
|
15
|
+
import { type OpenAPIv4Document, type Request } from "@suluk/core";
|
|
16
|
+
import { auditDocument, auditReadiness, type Grade } from "@suluk/harden";
|
|
17
|
+
import { costAudit, settlementAudit, impliedErrorStatuses, eachOperation } from "@suluk/cost";
|
|
18
|
+
import type { Gate, GateStatus } from "./lifecycle";
|
|
19
|
+
|
|
20
|
+
const GRADE_STATUS: Record<Grade, GateStatus> = { A: "ok", B: "ok", C: "todo", D: "todo", F: "error" };
|
|
21
|
+
|
|
22
|
+
/** The numeric statuses a request DECLARES (responses may be a map keyed by status or an array of {status}). */
|
|
23
|
+
function declaredStatuses(req: Request): Set<number> {
|
|
24
|
+
const r = (req as { responses?: unknown }).responses;
|
|
25
|
+
const out = new Set<number>();
|
|
26
|
+
if (Array.isArray(r)) {
|
|
27
|
+
for (const x of r) {
|
|
28
|
+
const s = Number((x as { status?: unknown }).status);
|
|
29
|
+
if (Number.isFinite(s)) out.add(s);
|
|
30
|
+
}
|
|
31
|
+
} else if (r && typeof r === "object") {
|
|
32
|
+
for (const k of Object.keys(r)) {
|
|
33
|
+
const s = Number(k);
|
|
34
|
+
if (Number.isFinite(s)) out.add(s);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const findingStatus = (findings: { severity: string }[]): GateStatus =>
|
|
41
|
+
findings.length === 0 ? "ok" : findings.some((f) => f.severity === "high" || f.severity === "error") ? "error" : "todo";
|
|
42
|
+
|
|
43
|
+
/** The CONFORMANCE gates — the readiness dimensions, each composed from a shipped Suluk audit. No host needed. */
|
|
44
|
+
export function conformanceGates(doc: OpenAPIv4Document): Gate[] {
|
|
45
|
+
const gates: Gate[] = [];
|
|
46
|
+
|
|
47
|
+
// input hardening (security)
|
|
48
|
+
const sec = auditDocument(doc);
|
|
49
|
+
gates.push({
|
|
50
|
+
id: "hardened", title: "Input hardening",
|
|
51
|
+
status: GRADE_STATUS[sec.grade],
|
|
52
|
+
detail: `grade ${sec.grade} (${sec.score}/100) — ${sec.bySeverity.high} high · ${sec.bySeverity.medium} medium`,
|
|
53
|
+
action: GRADE_STATUS[sec.grade] === "ok" ? undefined : "harden the input schemas (maxLength/pattern/maximum/maxItems; close objects)",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// schema readiness (computed-required / missing-example)
|
|
57
|
+
const rd = auditReadiness(doc);
|
|
58
|
+
gates.push({
|
|
59
|
+
id: "readiness", title: "Schema readiness",
|
|
60
|
+
status: GRADE_STATUS[rd.grade],
|
|
61
|
+
detail: `grade ${rd.grade} (${rd.score}/100) — ${rd.findings.length} finding${rd.findings.length === 1 ? "" : "s"}`,
|
|
62
|
+
action: rd.findings.length ? "fix computed-required fields + add request examples" : undefined,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// cost completeness
|
|
66
|
+
const cf = costAudit(doc);
|
|
67
|
+
gates.push({
|
|
68
|
+
id: "costed", title: "Cost declared",
|
|
69
|
+
status: findingStatus(cf),
|
|
70
|
+
detail: cf.length ? `${cf.length} cost finding${cf.length === 1 ? "" : "s"}` : "every priced op declares its cost",
|
|
71
|
+
action: cf.length ? "complete x-suluk-cost" : undefined,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// settlement — every priced op names a lever (C044)
|
|
75
|
+
const sf = settlementAudit(doc);
|
|
76
|
+
gates.push({
|
|
77
|
+
id: "settled", title: "Cost settlement (the lever)",
|
|
78
|
+
status: findingStatus(sf),
|
|
79
|
+
detail: sf.length ? `${sf.length} settlement finding${sf.length === 1 ? "" : "s"} (${[...new Set(sf.map((f) => f.rule))].join(", ")})` : "every priced op names a lever (credit | rate-limited | free)",
|
|
80
|
+
action: sf.length ? "add x-suluk-cost.settlement (or the missing x-suluk-ratelimit cap)" : undefined,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// implied error responses declared (C044)
|
|
84
|
+
let missing = 0;
|
|
85
|
+
for (const { req } of eachOperation(doc)) {
|
|
86
|
+
const declared = declaredStatuses(req);
|
|
87
|
+
for (const s of impliedErrorStatuses(req)) if (!declared.has(s)) missing++;
|
|
88
|
+
}
|
|
89
|
+
gates.push({
|
|
90
|
+
id: "errors", title: "Implied error responses declared",
|
|
91
|
+
status: missing ? "todo" : "ok",
|
|
92
|
+
detail: missing ? `${missing} facet-implied error status${missing === 1 ? "" : "es"} not declared (credit→402, auth→401, scope→403, ratelimit→429, upstream→502)` : "every facet-implied error response is declared",
|
|
93
|
+
action: missing ? "declare the implied error responses on each operation" : undefined,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return gates;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** CI gate (the hard incentive): throw if any conformance gate is an `error` (a blocker). Returns the gates otherwise. */
|
|
100
|
+
export function assertConformance(doc: OpenAPIv4Document): Gate[] {
|
|
101
|
+
const gates = conformanceGates(doc);
|
|
102
|
+
const blockers = gates.filter((g) => g.status === "error");
|
|
103
|
+
if (blockers.length) {
|
|
104
|
+
throw new Error(`@suluk/cockpit: contract is not conformant — ${blockers.map((g) => `${g.title}: ${g.detail}`).join(" · ")}`);
|
|
105
|
+
}
|
|
106
|
+
return gates;
|
|
107
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,10 @@ export { componentReport, approveComponents, type ComponentReport } from "./visu
|
|
|
23
23
|
export { type Baseline, primitiveCss } from "@suluk/visual";
|
|
24
24
|
// lifecycle / ship-readiness (L3): the round-trip loop as one checklist — authored → coherent → confident → generated → deployed.
|
|
25
25
|
export { contractGates, shipSummary, type Gate, type GateStatus } from "./lifecycle";
|
|
26
|
+
// conformance (C045): the UNIFIED contract audit — the readiness DIMENSIONS (harden security + readiness, cost,
|
|
27
|
+
// settlement/lever, implied-errors) folded into the same Gate[] model. A consumer's CI collapses to
|
|
28
|
+
// shipSummary([...contractGates, ...conformanceGates]) or assertConformance(doc).
|
|
29
|
+
export { conformanceGates, assertConformance } from "./conformance";
|
|
26
30
|
// agents (C027, OBSERVE): the x-suluk-agents tier tree, effective scope, gate findings, reachable surface + a
|
|
27
31
|
// projection preview — read-only; agent execution + secrets live OUTSIDE the cockpit (C020 no-credentials seam).
|
|
28
32
|
export {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { test, expect, describe } from "bun:test";
|
|
2
|
+
import { conformanceGates, assertConformance, shipSummary } from "../src/index";
|
|
3
|
+
import type { OpenAPIv4Document } from "@suluk/core";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* C045 — the unified contract audit: conformanceGates composes harden(security+readiness) + cost + settlement(lever) +
|
|
7
|
+
* implied-errors into the cockpit's Gate[] model. The generic form of toolfactory's conformance/governance/cost/errors
|
|
8
|
+
* gates. Composition only — no new audit logic here.
|
|
9
|
+
*/
|
|
10
|
+
const docOf = (requests: Record<string, unknown>): OpenAPIv4Document => ({ openapi: "4.0.0-candidate", info: { title: "T" }, paths: { "/x": { requests } } }) as unknown as OpenAPIv4Document;
|
|
11
|
+
|
|
12
|
+
describe("conformanceGates — the five composed dimensions", () => {
|
|
13
|
+
const dirty = docOf({
|
|
14
|
+
charge: {
|
|
15
|
+
method: "post",
|
|
16
|
+
contentSchema: { type: "object", additionalProperties: false, required: ["amountCents", "balance"], properties: { amountCents: { type: "integer" }, balance: { type: "integer", "x-suluk-origin": "computed" } } },
|
|
17
|
+
"x-suluk-cost": { estimateMicroUsd: 1000, components: [] }, // priced, no settlement
|
|
18
|
+
"x-suluk-access": { requires: "authenticated" }, // implies 401
|
|
19
|
+
responses: { "200": { status: 200 } }, // 401 not declared
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
const gates = Object.fromEntries(conformanceGates(dirty).map((g) => [g.id, g]));
|
|
23
|
+
|
|
24
|
+
test("emits the five dimensions", () => {
|
|
25
|
+
expect(Object.keys(gates).sort()).toEqual(["costed", "errors", "hardened", "readiness", "settled"]);
|
|
26
|
+
});
|
|
27
|
+
test("hardening + readiness carry a grade", () => {
|
|
28
|
+
expect(gates.hardened.detail).toMatch(/grade [A-F]/);
|
|
29
|
+
expect(gates.readiness.detail).toMatch(/grade [A-F]/);
|
|
30
|
+
});
|
|
31
|
+
test("a priced op with no settlement is flagged on the settlement gate", () => {
|
|
32
|
+
expect(gates.settled.status).not.toBe("ok");
|
|
33
|
+
expect(gates.settled.detail).toContain("cost-without-settlement");
|
|
34
|
+
});
|
|
35
|
+
test("an undeclared facet-implied error is flagged on the errors gate", () => {
|
|
36
|
+
expect(gates.errors.status).toBe("todo"); // 401 implied by auth, not declared
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("a clean contract passes every conformance gate", () => {
|
|
41
|
+
const clean = docOf({
|
|
42
|
+
clean: {
|
|
43
|
+
method: "post",
|
|
44
|
+
contentSchema: { type: "object", additionalProperties: false, required: ["name"], properties: { name: { type: "string", maxLength: 64, pattern: "^[a-z]+$" } }, examples: [{ name: "abc" }] },
|
|
45
|
+
"x-suluk-cost": { estimateMicroUsd: 100, components: [{ source: "compute", basis: "per-call", microUsd: 100 }], settlement: { method: "credit", credits: 1 } },
|
|
46
|
+
"x-suluk-access": { requires: "authenticated" },
|
|
47
|
+
responses: { "200": { status: 200 }, "401": { status: 401 }, "402": { status: 402 } },
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("all gates ok; assertConformance returns without throwing", () => {
|
|
52
|
+
const gates = conformanceGates(clean);
|
|
53
|
+
expect(gates.every((g) => g.status === "ok")).toBe(true);
|
|
54
|
+
expect(() => assertConformance(clean)).not.toThrow();
|
|
55
|
+
expect(shipSummary(gates).ready).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("assertConformance gates CI on error-status dimensions", () => {
|
|
60
|
+
const broken = docOf({
|
|
61
|
+
free: {
|
|
62
|
+
method: "post",
|
|
63
|
+
contentSchema: { type: "object", additionalProperties: false, required: ["n"], properties: { n: { type: "string", maxLength: 8, pattern: "^[a-z]+$" } }, examples: [{ n: "a" }] },
|
|
64
|
+
// settled by rate-limiting but NO x-suluk-ratelimit → a HIGH settlement finding → the settled gate is `error`
|
|
65
|
+
"x-suluk-cost": { estimateMicroUsd: 500, components: [], settlement: { method: "rate-limited" } },
|
|
66
|
+
responses: { "200": { status: 200 } },
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("throws when a conformance gate is an error blocker", () => {
|
|
71
|
+
expect(() => assertConformance(broken)).toThrow(/not conformant/i);
|
|
72
|
+
});
|
|
73
|
+
});
|