Skip to content

Commit 61a768e

Browse files
Illia Pashkovclaude
authored andcommitted
feat: v0.2 release — industrial bridges, protocol spec, discovery, SDKs (998 tests)
Industrial bridge packages: - @sint/bridge-mqtt-sparkplug: Sparkplug B v3 MQTT → SINT tier mapping (T0-T3), safety-critical DCMD/NCMD escalation, estop/interlock keyword detection (8 tests) - @sint/bridge-opcua: OPC UA 1.05+ PLC/OT adapter, safety-critical node detection, T3 for write/call on safety nodes (6 tests) - @sint/bridge-open-rmf: Open-RMF v2.x fleet dispatch bridge, emergency stop → T3, multi-fleet warehouse orchestration (5 tests) Protocol specification (SIP-0001): - docs/SINT_v0.2_SPEC.md: frozen public surface (9 nouns), discovery contract, compatibility rules, 3 deployment profiles, 7 bridge profiles - docs/SIPS.md + docs/sips/: governance process for protocol evolution - @sint/core: BridgeProfile, SiteProfile, ConstraintEnvelope, Revocation types (protocol.ts) - @sint/core: SINT_BRIDGE_PROFILES (7), SINT_SITE_PROFILES (3), SINT_SCHEMA_CATALOG (6 schemas) - @sint/core: SINT_OWASP_COVERAGE map (7 full, 2 partial, 1 gap=ASI06) Discovery endpoints in @sint/gateway-server: - GET /.well-known/sint.json (full protocol metadata) - GET /v1/schemas, GET /v1/schemas/{name} (JSON Schema 2020-12 catalog) - GET /v1/openapi.json (OpenAPI 3.1.0 spec) Conformance fixtures (5 new, 77 total): - industrial-benchmark-scenarios.test.ts: human-in-aisle escalation, stale corridor denial, revocation-under-load stress test - industrial-interoperability.test.ts: cross-protocol tier equivalence (A2A→RMF→ROS2 and Sparkplug both yield T2_ACT for warehouse move intent) Multi-language SDKs: - sdks/python/sint_client.py: zero-dependency urllib client - sdks/go/sintclient/client.go: stdlib HTTP client CI/CD: .github/workflows/industrial-benchmark-report.yml — automated benchmark report generation with GitHub Step Summary integration Total: 998 tests / 0 failures across 27 packages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1699b1e commit 61a768e

72 files changed

Lines changed: 3201 additions & 12 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Industrial Benchmark Report
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: [master]
7+
pull_request:
8+
branches: [master]
9+
10+
jobs:
11+
generate-benchmark-report:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: pnpm/action-setup@v4
18+
with:
19+
version: 9
20+
21+
- uses: actions/setup-node@v4
22+
with:
23+
node-version: 22
24+
cache: pnpm
25+
26+
- name: Install dependencies
27+
run: pnpm install --frozen-lockfile
28+
29+
- name: Build workspace
30+
run: pnpm run build
31+
32+
- name: Generate industrial benchmark report
33+
run: pnpm run benchmark:report
34+
35+
- name: Publish report summary
36+
run: cat docs/reports/industrial-benchmark-report.md >> "$GITHUB_STEP_SUMMARY"
37+
38+
- name: Upload benchmark artifacts
39+
uses: actions/upload-artifact@v4
40+
with:
41+
name: industrial-benchmark-report
42+
path: |
43+
docs/reports/industrial-benchmark-vitest.json
44+
docs/reports/industrial-benchmark-report.json
45+
docs/reports/industrial-benchmark-report.md

README.md

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,13 @@ where each Δ ∈ {0, +1}: human presence detected, trust score below threshold,
8282
┌──────────────────▼───────────────────────────────────────────┐
8383
│ SINT Bridge Layer (L1) │
8484
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ │
85-
│ │ bridge-mcp │ │ bridge-ros2│ │ bridge-a2a │ │ (future) │ │
86-
│ │ MCP tools │ │ ROS topics │ │ Google A2A │ │ OPC-UA │ │
85+
│ │ bridge-mcp │ │ bridge-ros2│ │ bridge-a2a │ │ bridge- │ │
86+
│ │ MCP tools │ │ ROS topics │ │ Google A2A │ │ open-rmf │ │
8787
│ └────────────┘ └────────────┘ └────────────┘ └──────────┘ │
88+
│ ┌──────────────────────┐ ┌───────────────────────────────┐ │
89+
│ │ bridge-mqtt-sparkplug│ │ bridge-opcua │ │
90+
│ │ Industrial IoT │ │ PLC / OT control plane bridge │ │
91+
│ └──────────────────────┘ └───────────────────────────────┘ │
8892
│ Per-resource state: UNREGISTERED→PENDING_AUTH→AUTHORIZED │
8993
│ →ACTIVE→SUSPENDED (real-time revocation without restart) │
9094
└──────────────────┬───────────────────────────────────────────┘
@@ -120,22 +124,25 @@ where each Δ ∈ {0, +1}: human presence detected, trust score below threshold,
120124
| [`@sint/bridge-mcp`](packages/bridge-mcp) | MCP tool call interception and risk classification | 43 |
121125
| [`@sint/bridge-ros2`](packages/bridge-ros2) | ROS 2 topic/service/action interception with physics extraction | 20 |
122126
| [`@sint/bridge-a2a`](packages/bridge-a2a) | Google A2A Protocol bridge for multi-agent coordination | 24 |
127+
| [`@sint/bridge-mqtt-sparkplug`](packages/bridge-mqtt-sparkplug) | MQTT Sparkplug profile mapping with industrial command tiering defaults | 8 |
128+
| [`@sint/bridge-opcua`](packages/bridge-opcua) | OPC UA node/method mapping with safety-critical write/call promotion | 6 |
129+
| [`@sint/bridge-open-rmf`](packages/bridge-open-rmf) | Open-RMF fleet/facility mapping for warehouse dispatch workflows | 5 |
123130
| [`@sint/bridge-economy`](packages/bridge-economy) | Economy bridge: balance, budget, trust, billing ports | 55 |
124131
| [`@sint/persistence`](packages/persistence) | Storage interfaces + in-memory/PG/Redis implementations | 26 |
125132
| [`@sint/client`](packages/client) | TypeScript SDK for the Gateway API (delegation, SSE) | 12 |
126-
| [`@sint/conformance-tests`](packages/conformance-tests) | Security regression suite — all phases | 57 |
133+
| [`@sint/conformance-tests`](packages/conformance-tests) | Security regression suite — all phases | 77 |
127134
| [`@sint/gateway-server`](apps/gateway-server) | Hono HTTP API with approvals, SSE streaming, A2A routes | 57 |
128135
| [`@sint/mcp`](apps/sint-mcp) | Security-first multi-MCP proxy server | 90 |
129136
| [`@sint/dashboard`](apps/dashboard) | Real-time approval dashboard with operator auth | 29 |
130-
| **Total** | **14 packages** | **815+** |
137+
| **Total** | **17 packages** | **Workspace-wide conformance suite** |
131138

132139
## Quick Start
133140

134141
```bash
135142
# Prerequisites: Node.js >= 22, pnpm >= 9
136143
pnpm install
137144
pnpm run build
138-
pnpm run test # 815+ tests
145+
pnpm run test # full workspace regression suite
139146
```
140147

141148
### Start the Gateway Server
@@ -145,6 +152,28 @@ pnpm --filter @sint/gateway-server dev
145152
# → http://localhost:3100/v1/health
146153
```
147154

155+
### Standardization and Deployment Artifacts
156+
157+
- v0.2 protocol surface spec: [`docs/SINT_v0.2_SPEC.md`](docs/SINT_v0.2_SPEC.md)
158+
- SIP governance track: [`docs/SIPS.md`](docs/SIPS.md)
159+
- Release notes: [`docs/RELEASE_NOTES_v0.2.md`](docs/RELEASE_NOTES_v0.2.md)
160+
- Conformance/certification matrix: [`docs/CONFORMANCE_CERTIFICATION_MATRIX_v0.2.md`](docs/CONFORMANCE_CERTIFICATION_MATRIX_v0.2.md)
161+
- Deployment profile templates:
162+
- [`docs/profiles/warehouse-amr.policy.template.json`](docs/profiles/warehouse-amr.policy.template.json)
163+
- [`docs/profiles/industrial-cell.policy.template.json`](docs/profiles/industrial-cell.policy.template.json)
164+
- [`docs/profiles/edge-gateway.policy.template.json`](docs/profiles/edge-gateway.policy.template.json)
165+
- Runnable demos:
166+
- [`examples/hello-world/README.md`](examples/hello-world/README.md)
167+
- [`examples/warehouse-amr/README.md`](examples/warehouse-amr/README.md)
168+
- [`examples/industrial-cell/README.md`](examples/industrial-cell/README.md)
169+
- Multi-language SDK starters:
170+
- [`sdks/python/sint_client.py`](sdks/python/sint_client.py)
171+
- [`sdks/go/sintclient/client.go`](sdks/go/sintclient/client.go)
172+
- Benchmark report automation:
173+
- Script: [`scripts/generate-industrial-benchmark-report.mjs`](scripts/generate-industrial-benchmark-report.mjs)
174+
- CI workflow: [`.github/workflows/industrial-benchmark-report.yml`](.github/workflows/industrial-benchmark-report.yml)
175+
- Generated artifacts: [`docs/reports/industrial-benchmark-report.md`](docs/reports/industrial-benchmark-report.md)
176+
148177
## Approval Tiers
149178

150179
Graduated authorization mapped to physical consequence severity:
@@ -242,7 +271,11 @@ The `@sint/bridge-a2a` package implements the Google Agent-to-Agent (A2A) protoc
242271

243272
| Method | Endpoint | Description |
244273
|--------|----------|-------------|
274+
| `GET` | `/.well-known/sint.json` | Public protocol discovery (version, bridges, profiles, schemas) |
245275
| `GET` | `/v1/health` | Health check |
276+
| `GET` | `/v1/schemas` | List machine-readable public schemas |
277+
| `GET` | `/v1/schemas/:name` | Fetch schema by name |
278+
| `GET` | `/v1/openapi.json` | OpenAPI surface for gateway integration |
246279
| `POST` | `/v1/intercept` | Evaluate a single request |
247280
| `POST` | `/v1/intercept/batch` | Evaluate multiple requests (207 Multi-Status) |
248281
| `POST` | `/v1/tokens` | Issue a capability token |
@@ -254,7 +287,7 @@ The `@sint/bridge-a2a` package implements the Google Agent-to-Agent (A2A) protoc
254287
| `GET` | `/v1/approvals/events` | SSE stream for real-time approval events |
255288
| `POST` | `/v1/a2a` | JSON-RPC 2.0 A2A protocol endpoint |
256289
| `GET/POST` | `/v1/a2a/agents` | Agent Card registration |
257-
| `GET` | `/metrics` | Prometheus metrics |
290+
| `GET` | `/v1/metrics` | Prometheus metrics |
258291

259292
## Development Phases
260293

@@ -264,7 +297,7 @@ The `@sint/bridge-a2a` package implements the Google Agent-to-Agent (A2A) protoc
264297
| **Phase 2** (complete) | Engine Core — bridge-mcp, bridge-ros2, engine packages, persistence, gateway-server | +221 (646) |
265298
| **Phase 3** (complete) | Economy Bridge — @sint/bridge-economy with port/adapter pattern, EconomyPlugin | +91 (737) |
266299
| **Phase 4** (complete) | Standards Alignment — A2A bridge, rate limiting, M-of-N quorum, W3C DID identity | +78 (815) |
267-
| **Phase 5** (planned) | Avatar Layer — trust interface, dynamic consent, CSML-driven tier escalation | |
300+
| **Phase 5** (complete) | Protocol Surface v0.2 — discovery/OpenAPI/schema endpoints, industrial profiles, token execution metadata | shipped |
268301

269302
## Tech Stack
270303

apps/gateway-server/__tests__/api.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,32 @@ describe("Gateway Server API", () => {
6060
expect(body.protocol).toBe("SINT Gate");
6161
});
6262

63+
it("GET /.well-known/sint.json returns discovery metadata", async () => {
64+
const res = await app.request("/.well-known/sint.json");
65+
expect(res.status).toBe(200);
66+
const body = await res.json();
67+
expect(body.name).toBe("SINT Protocol");
68+
expect(body.version).toBeDefined();
69+
expect(Array.isArray(body.supportedBridges)).toBe(true);
70+
expect(Array.isArray(body.deploymentProfiles)).toBe(true);
71+
});
72+
73+
it("GET /v1/schemas returns schema catalog", async () => {
74+
const res = await app.request("/v1/schemas");
75+
expect(res.status).toBe(200);
76+
const body = await res.json();
77+
expect(body.total).toBeGreaterThan(0);
78+
expect(Array.isArray(body.schemas)).toBe(true);
79+
});
80+
81+
it("GET /v1/openapi.json returns OpenAPI document", async () => {
82+
const res = await app.request("/v1/openapi.json");
83+
expect(res.status).toBe(200);
84+
const body = await res.json();
85+
expect(body.openapi).toBe("3.1.0");
86+
expect(body.paths["/.well-known/sint.json"]).toBeDefined();
87+
});
88+
6389
// ── Intercept ──
6490

6591
it("POST /v1/intercept with valid request", async () => {

apps/gateway-server/__tests__/auth.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ describe("Authentication Middleware", () => {
131131
expect(res.status).toBe(200);
132132
});
133133

134+
it("exempts /.well-known/sint.json from signature requirement", async () => {
135+
const res = await app.request("/.well-known/sint.json");
136+
expect(res.status).toBe(200);
137+
});
138+
134139
it("exempts /v1/keypair from signature requirement", async () => {
135140
const res = await app.request("/v1/keypair", { method: "POST" });
136141
expect(res.status).toBe(200);
@@ -179,6 +184,11 @@ describe("Authentication Middleware", () => {
179184
expect(res.status).toBe(200);
180185
});
181186

187+
it("exempts /.well-known/sint.json from API key requirement", async () => {
188+
const res = await app.request("/.well-known/sint.json");
189+
expect(res.status).toBe(200);
190+
});
191+
182192
it("skips API key auth when no key is configured (dev mode)", async () => {
183193
const devApp = createApp(ctx);
184194
const res = await devApp.request("/v1/ledger");

apps/gateway-server/src/middleware/auth.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ import type { Context, Next } from "hono";
1212
import { verify } from "@sint/gate-capability-tokens";
1313

1414
/** Paths exempt from all authentication. */
15-
const EXEMPT_PATHS = new Set(["/v1/health", "/v1/keypair"]);
15+
const EXEMPT_PATHS = new Set([
16+
"/v1/health",
17+
"/v1/keypair",
18+
"/.well-known/sint.json",
19+
"/v1/openapi.json",
20+
"/v1/schemas",
21+
]);
1622

1723
/** Paths that require admin API key (not agent signature). */
1824
const ADMIN_PATHS = [

apps/gateway-server/src/routes/approvals.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export function approvalRoutes(ctx: ServerContext): Hono {
112112
agentId: request.request.agentId,
113113
params: request.request.params,
114114
physicalContext: request.request.physicalContext,
115+
executionContext: request.request.executionContext,
115116
fallbackAction: request.fallbackAction,
116117
timeoutMs: request.timeoutMs,
117118
createdAt: request.createdAt,
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* SINT Gateway Server — Discovery and schema routes.
3+
*
4+
* Exposes machine-readable protocol metadata and schema catalog.
5+
*
6+
* Endpoints:
7+
* - GET /.well-known/sint.json
8+
* - GET /v1/schemas
9+
* - GET /v1/schemas/:name
10+
* - GET /v1/openapi.json
11+
*
12+
* @module @sint/gateway-server/routes/discovery
13+
*/
14+
15+
import { Hono } from "hono";
16+
import {
17+
SINT_PROTOCOL_VERSION,
18+
SINT_PROTOCOL_BOUNDARY,
19+
SINT_BRIDGE_PROFILES,
20+
SINT_SITE_PROFILES,
21+
SINT_SCHEMA_CATALOG,
22+
} from "@sint/core";
23+
24+
export function discoveryRoutes(): Hono {
25+
const app = new Hono();
26+
27+
app.get("/.well-known/sint.json", (c) => {
28+
return c.json({
29+
name: "SINT Protocol",
30+
version: SINT_PROTOCOL_VERSION,
31+
boundary: SINT_PROTOCOL_BOUNDARY,
32+
identityMethods: ["ed25519", "did:key"],
33+
attestationModes: ["intel-sgx", "arm-trustzone", "amd-sev", "tpm2", "none"],
34+
deploymentProfiles: SINT_SITE_PROFILES,
35+
supportedBridges: SINT_BRIDGE_PROFILES,
36+
schemaCatalog: Object.keys(SINT_SCHEMA_CATALOG).map((name) => ({
37+
name,
38+
path: `/v1/schemas/${name}`,
39+
})),
40+
openapi: "/v1/openapi.json",
41+
});
42+
});
43+
44+
app.get("/v1/schemas", (c) => {
45+
return c.json({
46+
total: Object.keys(SINT_SCHEMA_CATALOG).length,
47+
schemas: Object.keys(SINT_SCHEMA_CATALOG).map((name) => ({
48+
name,
49+
path: `/v1/schemas/${name}`,
50+
})),
51+
});
52+
});
53+
54+
app.get("/v1/schemas/:name", (c) => {
55+
const name = c.req.param("name");
56+
const schema = SINT_SCHEMA_CATALOG[name];
57+
if (!schema) {
58+
return c.json({
59+
error: "Schema not found",
60+
available: Object.keys(SINT_SCHEMA_CATALOG),
61+
}, 404);
62+
}
63+
return c.json(schema);
64+
});
65+
66+
app.get("/v1/openapi.json", (c) => {
67+
return c.json({
68+
openapi: "3.1.0",
69+
info: {
70+
title: "SINT Gateway API",
71+
version: SINT_PROTOCOL_VERSION,
72+
description: SINT_PROTOCOL_BOUNDARY,
73+
},
74+
paths: {
75+
"/.well-known/sint.json": { get: { summary: "Protocol discovery document" } },
76+
"/v1/health": { get: { summary: "Health status" } },
77+
"/v1/metrics": { get: { summary: "Prometheus metrics" } },
78+
"/v1/intercept": { post: { summary: "Intercept one request" } },
79+
"/v1/intercept/batch": { post: { summary: "Intercept a batch of requests" } },
80+
"/v1/tokens": { post: { summary: "Issue capability token" } },
81+
"/v1/tokens/delegate": { post: { summary: "Delegate capability token" } },
82+
"/v1/tokens/revoke": { post: { summary: "Revoke capability token" } },
83+
"/v1/ledger": { get: { summary: "Query evidence ledger" } },
84+
"/v1/approvals/pending": { get: { summary: "List pending approvals" } },
85+
"/v1/approvals/{requestId}/resolve": { post: { summary: "Resolve approval" } },
86+
"/v1/a2a": { post: { summary: "A2A JSON-RPC endpoint" } },
87+
"/v1/a2a/agents": { get: { summary: "List A2A agents" }, post: { summary: "Register A2A agent" } },
88+
"/v1/schemas": { get: { summary: "List public JSON schemas" } },
89+
"/v1/schemas/{name}": { get: { summary: "Fetch schema by name" } },
90+
},
91+
components: {
92+
schemas: SINT_SCHEMA_CATALOG,
93+
},
94+
});
95+
});
96+
97+
return app;
98+
}

apps/gateway-server/src/routes/intercept.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function interceptRoutes(ctx: ServerContext): Hono {
3232
resource: parsed.data.resource,
3333
action: parsed.data.action,
3434
decision: decision.action,
35+
executionContext: parsed.data.executionContext,
3536
},
3637
});
3738

@@ -79,6 +80,7 @@ export function interceptRoutes(ctx: ServerContext): Hono {
7980
resource: parsed.data.resource,
8081
action: parsed.data.action,
8182
decision: decision.action,
83+
executionContext: parsed.data.executionContext,
8284
},
8385
});
8486

apps/gateway-server/src/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { interceptRoutes } from "./routes/intercept.js";
3535
import { tokenRoutes } from "./routes/tokens.js";
3636
import { ledgerRoutes } from "./routes/ledger.js";
3737
import { approvalRoutes } from "./routes/approvals.js";
38+
import { discoveryRoutes } from "./routes/discovery.js";
3839
import { economyRoutes, type EconomyRouteContext } from "./routes/economy.js";
3940
import { a2aRoutes, type A2ARouteContext } from "./routes/a2a.js";
4041
import type { SintConfig } from "./config.js";
@@ -209,6 +210,7 @@ export function createApp(ctx?: ServerContext, opts?: ServerOptions): Hono {
209210
app.route("", tokenRoutes(context));
210211
app.route("", ledgerRoutes(context));
211212
app.route("", approvalRoutes(context));
213+
app.route("", discoveryRoutes());
212214
app.route("", metricsRoutes());
213215

214216
// Economy routes (optional — only when economy context is configured)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# SINT v0.2 Conformance and Certification Matrix
2+
3+
This matrix tracks canonical fixture coverage for major interoperability paths.
4+
5+
| Path | Protocol Surface | Canonical Fixture | Expected Outcome |
6+
|---|---|---|---|
7+
| MCP tool call interception | `@sint/bridge-mcp` + gateway | `packages/conformance-tests/src/bridge-mcp-regression.test.ts` | Correct tiering, denial on invalid token, ledger evidence |
8+
| A2A delegated task path | `@sint/bridge-a2a` + gateway | `packages/conformance-tests/src/phase4-regression.test.ts` | Unauthorized denied, physical tasks escalated |
9+
| ROS 2 physical command path | `@sint/bridge-ros2` + gateway | `packages/conformance-tests/src/bridge-ros2-regression.test.ts` | Constraint enforcement, T2/T3 escalation deterministic |
10+
| MQTT Sparkplug command path | `@sint/bridge-mqtt-sparkplug` + gateway | `packages/conformance-tests/src/industrial-interoperability.test.ts` | Equivalent approval behavior vs ROS2/RMF route |
11+
| OPC UA control path | `@sint/bridge-opcua` + gateway | `packages/bridge-opcua/__tests__/opcua-resource-mapper.test.ts` | Safety-critical writes/calls elevated |
12+
| Open-RMF dispatch path | `@sint/bridge-open-rmf` + gateway | `packages/conformance-tests/src/industrial-interoperability.test.ts` | Dispatch actions mapped to T2 escalation |
13+
| Revocation under load | token store + gateway | `packages/conformance-tests/src/industrial-benchmark-scenarios.test.ts` | No T2/T3 fail-open after revocation |
14+
| Stale corridor envelope | gateway execution envelope checks | `packages/conformance-tests/src/industrial-benchmark-scenarios.test.ts` | Deterministic deny on stale/mismatch corridor |
15+
16+
## Operational Certification Artifacts
17+
18+
- Discovery contract: `/.well-known/sint.json`
19+
- Public schema catalog: `/v1/schemas`, `/v1/schemas/:name`
20+
- OpenAPI surface: `/v1/openapi.json`
21+
- Benchmark report outputs:
22+
- `docs/reports/industrial-benchmark-report.json`
23+
- `docs/reports/industrial-benchmark-report.md`

0 commit comments

Comments
 (0)