chore: establish hwlab l0 contract skeleton
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# OS and editor files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Logs and local environment
|
||||
*.log
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Build outputs
|
||||
bin/
|
||||
dist/
|
||||
build/
|
||||
coverage/
|
||||
tmp/
|
||||
|
||||
# Go
|
||||
*.test
|
||||
*.out
|
||||
vendor/
|
||||
|
||||
# Node package managers, for future web work
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Deploy/runtime state
|
||||
*.local.json
|
||||
@@ -0,0 +1,69 @@
|
||||
# HWLAB
|
||||
|
||||
HWLAB is the hardware lab runtime and control plane for MVP development.
|
||||
|
||||
This repository is intentionally small at L0. It defines the shared protocol
|
||||
contract, schema skeletons, deploy manifest schema, and minimal validation
|
||||
entry points that later service implementations must follow.
|
||||
|
||||
## MVP Freeze
|
||||
|
||||
- MVP scope is DEV only.
|
||||
- DEV endpoint is fixed at `http://74.48.78.17:6667`.
|
||||
- PROD endpoint `:6666` is reserved and is not an MVP acceptance target.
|
||||
- UniDesk may be used only as an external scheduling, CI, or CD base.
|
||||
- HWLAB runtime services must not be replaced by UniDesk backend,
|
||||
provider-gateway, or microservice proxy implementations.
|
||||
|
||||
## Frozen Service IDs
|
||||
|
||||
- `hwlab-cloud-api`
|
||||
- `hwlab-cloud-web`
|
||||
- `hwlab-agent-mgr`
|
||||
- `hwlab-agent-worker`
|
||||
- `hwlab-gateway`
|
||||
- `hwlab-gateway-simu`
|
||||
- `hwlab-box-simu`
|
||||
- `hwlab-patch-panel`
|
||||
- `hwlab-router`
|
||||
- `hwlab-tunnel-client`
|
||||
- `hwlab-edge-proxy`
|
||||
- `hwlab-cli`
|
||||
- `hwlab-agent-skills`
|
||||
|
||||
## Frozen Repository Layout
|
||||
|
||||
The MVP branches may add implementation files under these paths:
|
||||
|
||||
- `cmd/...`
|
||||
- `internal/...`
|
||||
- `web/hwlab-cloud-web`
|
||||
- `tools/hwlab-cli`
|
||||
- `skills`
|
||||
- `protocol/schemas`
|
||||
- `deploy/deploy.json`
|
||||
- `deploy/k8s/base/dev/prod`
|
||||
- `deploy/master-edge`
|
||||
- `deploy/frp`
|
||||
- `scripts/bootstrap-skills.sh`
|
||||
- `scripts/worker-entrypoint.sh`
|
||||
|
||||
## Contract Index
|
||||
|
||||
- Protocol contract: [protocol/README.md](protocol/README.md)
|
||||
- JSON-RPC envelope: [protocol/json-rpc.md](protocol/json-rpc.md)
|
||||
- Error codes: [protocol/errors.md](protocol/errors.md)
|
||||
- Audit fields: [protocol/audit.md](protocol/audit.md)
|
||||
- Capability model: [protocol/capabilities.md](protocol/capabilities.md)
|
||||
- Topology constraints: [protocol/topology.md](protocol/topology.md)
|
||||
- MVP e2e contract: [protocol/mvp-e2e-contract.md](protocol/mvp-e2e-contract.md)
|
||||
- JSON Schemas: [protocol/schemas](protocol/schemas)
|
||||
- Deploy schema: [deploy/deploy.schema.json](deploy/deploy.schema.json)
|
||||
|
||||
## Validation
|
||||
|
||||
Run the lightweight validation suite:
|
||||
|
||||
```sh
|
||||
npm run check
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Deploy Contract
|
||||
|
||||
`deploy.schema.json` defines the future `deploy/deploy.json` manifest shape.
|
||||
|
||||
The MVP acceptance environment is DEV only, with endpoint
|
||||
`http://74.48.78.17:6667`. PROD profile fields may be represented in schema for
|
||||
future compatibility, but PROD deployment is not part of MVP acceptance.
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/deploy/deploy.schema.json",
|
||||
"title": "HWLAB Deploy Manifest",
|
||||
"type": "object",
|
||||
"required": ["manifestVersion", "environment", "commitId", "profiles", "services"],
|
||||
"properties": {
|
||||
"manifestVersion": {
|
||||
"type": "string",
|
||||
"pattern": "^v[0-9]+$"
|
||||
},
|
||||
"environment": {
|
||||
"type": "string",
|
||||
"enum": ["dev"]
|
||||
},
|
||||
"commitId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{7,40}$"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 63,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"
|
||||
},
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"const": "http://74.48.78.17:6667"
|
||||
},
|
||||
"profiles": {
|
||||
"type": "object",
|
||||
"required": ["dev"],
|
||||
"properties": {
|
||||
"dev": {
|
||||
"$ref": "#/$defs/profile"
|
||||
},
|
||||
"prod": {
|
||||
"$ref": "#/$defs/profile"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"services": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/service"
|
||||
}
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"$defs": {
|
||||
"profile": {
|
||||
"type": "object",
|
||||
"required": ["name", "enabled"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["dev", "prod"]
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string"
|
||||
},
|
||||
"endpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"notes": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"service": {
|
||||
"type": "object",
|
||||
"required": ["serviceId", "image", "namespace", "healthPath", "profile"],
|
||||
"properties": {
|
||||
"serviceId": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"hwlab-cloud-api",
|
||||
"hwlab-cloud-web",
|
||||
"hwlab-agent-mgr",
|
||||
"hwlab-agent-worker",
|
||||
"hwlab-gateway",
|
||||
"hwlab-gateway-simu",
|
||||
"hwlab-box-simu",
|
||||
"hwlab-patch-panel",
|
||||
"hwlab-router",
|
||||
"hwlab-tunnel-client",
|
||||
"hwlab-edge-proxy",
|
||||
"hwlab-cli",
|
||||
"hwlab-agent-skills"
|
||||
]
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"healthPath": {
|
||||
"type": "string",
|
||||
"pattern": "^/"
|
||||
},
|
||||
"profile": {
|
||||
"type": "string",
|
||||
"enum": ["dev", "prod"]
|
||||
},
|
||||
"replicas": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
export const ENVIRONMENT_DEV = "dev";
|
||||
export const DEV_ENDPOINT = "http://74.48.78.17:6667";
|
||||
export const JSON_RPC_VERSION = "2.0";
|
||||
|
||||
export const SERVICE_IDS = Object.freeze([
|
||||
"hwlab-cloud-api",
|
||||
"hwlab-cloud-web",
|
||||
"hwlab-agent-mgr",
|
||||
"hwlab-agent-worker",
|
||||
"hwlab-gateway",
|
||||
"hwlab-gateway-simu",
|
||||
"hwlab-box-simu",
|
||||
"hwlab-patch-panel",
|
||||
"hwlab-router",
|
||||
"hwlab-tunnel-client",
|
||||
"hwlab-edge-proxy",
|
||||
"hwlab-cli",
|
||||
"hwlab-agent-skills"
|
||||
]);
|
||||
|
||||
export const TABLES = Object.freeze([
|
||||
"projects",
|
||||
"gateway_sessions",
|
||||
"box_resources",
|
||||
"box_capabilities",
|
||||
"wiring_configs",
|
||||
"patch_panel_status",
|
||||
"hardware_operations",
|
||||
"audit_events",
|
||||
"agent_sessions",
|
||||
"worker_sessions",
|
||||
"agent_trace_events",
|
||||
"evidence_records"
|
||||
]);
|
||||
|
||||
export const ERROR_CODES = Object.freeze({
|
||||
parseError: -32700,
|
||||
invalidRequest: -32600,
|
||||
methodNotFound: -32601,
|
||||
invalidParams: -32602,
|
||||
internalError: -32603,
|
||||
hwlabUnknown: -32000,
|
||||
projectNotFound: -32010,
|
||||
sessionNotFound: -32011,
|
||||
capabilityUnavailable: -32020,
|
||||
resourceLocked: -32021,
|
||||
wiringInvalid: -32022,
|
||||
operationRejected: -32023,
|
||||
evidenceMissing: -32030,
|
||||
auditRequired: -32040
|
||||
});
|
||||
|
||||
const methodPattern = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9_]*)+$/;
|
||||
|
||||
export function isFrozenServiceId(serviceId) {
|
||||
return SERVICE_IDS.includes(serviceId);
|
||||
}
|
||||
|
||||
export function validateMeta(meta) {
|
||||
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
||||
throw new Error("meta must be an object");
|
||||
}
|
||||
if (!meta.traceId) {
|
||||
throw new Error("meta.traceId is required");
|
||||
}
|
||||
if (!isFrozenServiceId(meta.serviceId)) {
|
||||
throw new Error(`unknown serviceId ${JSON.stringify(meta.serviceId)}`);
|
||||
}
|
||||
if (meta.environment !== ENVIRONMENT_DEV) {
|
||||
throw new Error(`unsupported environment ${JSON.stringify(meta.environment)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateRequest(envelope) {
|
||||
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
|
||||
throw new Error("request must be an object");
|
||||
}
|
||||
if (envelope.jsonrpc !== JSON_RPC_VERSION) {
|
||||
throw new Error(`jsonrpc must be ${JSON_RPC_VERSION}`);
|
||||
}
|
||||
if (envelope.id === undefined || envelope.id === null) {
|
||||
throw new Error("id is required");
|
||||
}
|
||||
if (!methodPattern.test(envelope.method)) {
|
||||
throw new Error(`invalid method ${JSON.stringify(envelope.method)}`);
|
||||
}
|
||||
if (
|
||||
envelope.params !== undefined &&
|
||||
(!envelope.params || typeof envelope.params !== "object" || Array.isArray(envelope.params))
|
||||
) {
|
||||
throw new Error("params must be an object when present");
|
||||
}
|
||||
validateMeta(envelope.meta);
|
||||
}
|
||||
|
||||
export function validateResponse(envelope) {
|
||||
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
|
||||
throw new Error("response must be an object");
|
||||
}
|
||||
if (envelope.jsonrpc !== JSON_RPC_VERSION) {
|
||||
throw new Error(`jsonrpc must be ${JSON_RPC_VERSION}`);
|
||||
}
|
||||
if (envelope.id === undefined || envelope.id === null) {
|
||||
throw new Error("id is required");
|
||||
}
|
||||
const hasResult = Object.hasOwn(envelope, "result");
|
||||
const hasError = Object.hasOwn(envelope, "error");
|
||||
if (hasResult === hasError) {
|
||||
throw new Error("response must contain exactly one of result or error");
|
||||
}
|
||||
if (hasError) {
|
||||
validateRpcError(envelope.error);
|
||||
}
|
||||
validateMeta(envelope.meta);
|
||||
}
|
||||
|
||||
export function validateRpcError(error) {
|
||||
if (!error || typeof error !== "object" || Array.isArray(error)) {
|
||||
throw new Error("error must be an object");
|
||||
}
|
||||
if (!Number.isInteger(error.code)) {
|
||||
throw new Error("error.code must be an integer");
|
||||
}
|
||||
if (!error.message || typeof error.message !== "string") {
|
||||
throw new Error("error.message is required");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "hwlab",
|
||||
"version": "0.0.0-l0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"validate": "node scripts/validate-contract.mjs",
|
||||
"check": "node --check internal/protocol/index.mjs && node --check scripts/validate-contract.mjs && node scripts/validate-contract.mjs"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# HWLAB Protocol Contract
|
||||
|
||||
The L0 protocol contract defines the common language between cloud services,
|
||||
gateway services, simulated boxes, agent workers, CLI tooling, and future
|
||||
hardware integrations.
|
||||
|
||||
All MVP implementations must preserve these invariants:
|
||||
|
||||
- Use JSON-RPC 2.0 envelopes for command-style API calls unless a later
|
||||
service contract explicitly narrows the transport.
|
||||
- Emit auditable records for user, system, gateway, worker, and hardware
|
||||
actions that mutate state or produce evidence.
|
||||
- Describe box hardware through capabilities, resource state, and wiring
|
||||
constraints instead of service-specific ad hoc fields.
|
||||
- Treat DEV as the only MVP acceptance environment.
|
||||
- Keep service identities within the frozen service ID list in the root
|
||||
README.
|
||||
|
||||
## Frozen Tables
|
||||
|
||||
The database contract is intentionally named here before migrations exist so
|
||||
parallel MVP work shares stable terminology:
|
||||
|
||||
- `projects`
|
||||
- `gateway_sessions`
|
||||
- `box_resources`
|
||||
- `box_capabilities`
|
||||
- `wiring_configs`
|
||||
- `patch_panel_status`
|
||||
- `hardware_operations`
|
||||
- `audit_events`
|
||||
- `agent_sessions`
|
||||
- `worker_sessions`
|
||||
- `agent_trace_events`
|
||||
- `evidence_records`
|
||||
|
||||
## Schema Status
|
||||
|
||||
Schemas in `protocol/schemas` are draft-2020-12 JSON Schemas. They define L0
|
||||
field names, identities, relationships, timestamps, and state enums. Later
|
||||
service work may tighten validation, but must not silently rename these L0
|
||||
contract fields.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Audit Fields
|
||||
|
||||
Mutating calls and evidence-producing workflows must carry audit metadata from
|
||||
the caller through to persisted `audit_events`.
|
||||
|
||||
## Required Fields
|
||||
|
||||
- `auditId`: stable audit event identifier.
|
||||
- `traceId`: cross-service trace identifier.
|
||||
- `actorType`: `user`, `service`, `agent`, `worker`, or `system`.
|
||||
- `actorId`: stable identity for the actor.
|
||||
- `action`: dotted action name such as `hardware.operation.requested`.
|
||||
- `targetType`: resource family affected by the action.
|
||||
- `targetId`: stable target identifier.
|
||||
- `serviceId`: frozen HWLAB service ID emitting the event.
|
||||
- `environment`: `dev` for MVP.
|
||||
- `occurredAt`: RFC 3339 timestamp.
|
||||
|
||||
## Recommended Fields
|
||||
|
||||
- `projectId`: project scope for the action.
|
||||
- `gatewaySessionId`: gateway session participating in the action.
|
||||
- `workerSessionId`: worker session participating in the action.
|
||||
- `operationId`: hardware operation connected to the action.
|
||||
- `outcome`: `accepted`, `succeeded`, `failed`, `rejected`, or `canceled`.
|
||||
- `reason`: short machine-readable reason for rejection or failure.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Capability Model
|
||||
|
||||
Box hardware is described with resources and capabilities.
|
||||
|
||||
## Resource
|
||||
|
||||
A box resource is a physical or simulated component that can be addressed by
|
||||
the gateway:
|
||||
|
||||
- board
|
||||
- power supply
|
||||
- serial port
|
||||
- network port
|
||||
- relay
|
||||
- sensor
|
||||
- simulator endpoint
|
||||
|
||||
Resources carry lifecycle state such as `available`, `reserved`, `running`,
|
||||
`maintenance`, or `offline`.
|
||||
|
||||
## Capability
|
||||
|
||||
A capability describes an action or measurement exposed by a resource. Examples:
|
||||
|
||||
- `power.cycle`
|
||||
- `power.set_voltage`
|
||||
- `serial.console`
|
||||
- `network.link`
|
||||
- `firmware.flash`
|
||||
- `sensor.read`
|
||||
|
||||
Capabilities declare:
|
||||
|
||||
- direction: `input`, `output`, or `bidirectional`
|
||||
- value type: `boolean`, `integer`, `number`, `string`, `object`, or `binary`
|
||||
- optional unit
|
||||
- constraints for allowed values or ranges
|
||||
- whether the operation mutates hardware state
|
||||
|
||||
## Operation Binding
|
||||
|
||||
Hardware operations reference capabilities by `capabilityId` and resources by
|
||||
`resourceId`. Implementations must not infer a capability from a free-form
|
||||
operation name when a capability record exists.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Error Codes
|
||||
|
||||
HWLAB uses JSON-RPC standard errors plus a reserved HWLAB server-error range.
|
||||
|
||||
| Code | Name | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `-32700` | `parse_error` | Invalid JSON payload. |
|
||||
| `-32600` | `invalid_request` | Invalid JSON-RPC envelope. |
|
||||
| `-32601` | `method_not_found` | Method is unknown to the target service. |
|
||||
| `-32602` | `invalid_params` | Method parameters failed validation. |
|
||||
| `-32603` | `internal_error` | Unexpected service failure. |
|
||||
| `-32000` | `hwlab_unknown` | Unclassified HWLAB runtime error. |
|
||||
| `-32010` | `project_not_found` | Project ID is not known or not visible. |
|
||||
| `-32011` | `session_not_found` | Gateway, agent, or worker session is missing. |
|
||||
| `-32020` | `capability_unavailable` | Requested hardware capability is unavailable. |
|
||||
| `-32021` | `resource_locked` | Box resource is already reserved or running. |
|
||||
| `-32022` | `wiring_invalid` | Wiring config violates topology constraints. |
|
||||
| `-32023` | `operation_rejected` | Hardware operation cannot be accepted. |
|
||||
| `-32030` | `evidence_missing` | Required evidence record was not produced. |
|
||||
| `-32040` | `audit_required` | A mutating action was attempted without audit metadata. |
|
||||
|
||||
Error responses must include `error.message`. `error.data` should carry stable
|
||||
machine-readable fields such as `projectId`, `sessionId`, `resourceId`, or
|
||||
`operationId` when available.
|
||||
@@ -0,0 +1,70 @@
|
||||
# JSON-RPC Envelope
|
||||
|
||||
HWLAB command-style APIs use JSON-RPC 2.0 envelopes.
|
||||
|
||||
## Request
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "req_01J00000000000000000000000",
|
||||
"method": "hardware.operation.request",
|
||||
"params": {
|
||||
"projectId": "prj_01J00000000000000000000000"
|
||||
},
|
||||
"meta": {
|
||||
"traceId": "trc_01J00000000000000000000000",
|
||||
"actorId": "usr_01J00000000000000000000000",
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"environment": "dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "req_01J00000000000000000000000",
|
||||
"result": {
|
||||
"accepted": true
|
||||
},
|
||||
"meta": {
|
||||
"traceId": "trc_01J00000000000000000000000",
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"environment": "dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "req_01J00000000000000000000000",
|
||||
"error": {
|
||||
"code": -32020,
|
||||
"message": "Requested hardware capability is unavailable",
|
||||
"data": {
|
||||
"capabilityId": "cap_01J00000000000000000000000"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"traceId": "trc_01J00000000000000000000000",
|
||||
"serviceId": "hwlab-gateway",
|
||||
"environment": "dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Envelope Rules
|
||||
|
||||
- `jsonrpc` is always `2.0`.
|
||||
- `id` is required for request/response flows and must be echoed unchanged.
|
||||
- `method` names use dotted lower-case namespaces.
|
||||
- `params` must be an object when present.
|
||||
- A response contains exactly one of `result` or `error`.
|
||||
- `meta.traceId` is required for all cross-service calls.
|
||||
- `meta.environment` is `dev` for MVP.
|
||||
@@ -0,0 +1,31 @@
|
||||
# MVP E2E Contract
|
||||
|
||||
The L0 e2e contract is a sequence contract for future MVP tests. It is not a
|
||||
heavy e2e test suite.
|
||||
|
||||
## DEV Endpoint
|
||||
|
||||
All MVP acceptance traffic targets:
|
||||
|
||||
```text
|
||||
http://74.48.78.17:6667
|
||||
```
|
||||
|
||||
## Minimal Flow
|
||||
|
||||
1. Create or select a project.
|
||||
2. Start a gateway session for `hwlab-gateway` or `hwlab-gateway-simu`.
|
||||
3. Register box resources and capabilities.
|
||||
4. Apply a wiring config that passes topology validation.
|
||||
5. Start an agent session and worker session.
|
||||
6. Request a hardware operation through JSON-RPC.
|
||||
7. Emit trace events as the operation is accepted, dispatched, executed, and
|
||||
completed.
|
||||
8. Persist an evidence record for the operation.
|
||||
9. Emit audit events for mutating steps and final outcome.
|
||||
|
||||
## Acceptance Boundary
|
||||
|
||||
For L0, acceptance is limited to parseable schemas, stable contract names, and
|
||||
minimal in-process validation. PROD deployment, service restarts, and full e2e
|
||||
execution are explicitly out of scope.
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/agent-session.schema.json",
|
||||
"title": "HWLAB Agent Session",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"agentSessionId",
|
||||
"projectId",
|
||||
"serviceId",
|
||||
"status",
|
||||
"environment",
|
||||
"startedAt",
|
||||
"updatedAt"
|
||||
],
|
||||
"properties": {
|
||||
"agentSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"serviceId": {
|
||||
"const": "hwlab-agent-mgr"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["starting", "active", "waiting", "completed", "failed", "canceled"]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"requestedBy": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"goal": {
|
||||
"type": "string",
|
||||
"maxLength": 4000
|
||||
},
|
||||
"startedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"completedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/audit-event.schema.json",
|
||||
"title": "HWLAB Audit Event",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"auditId",
|
||||
"traceId",
|
||||
"actorType",
|
||||
"actorId",
|
||||
"action",
|
||||
"targetType",
|
||||
"targetId",
|
||||
"serviceId",
|
||||
"environment",
|
||||
"occurredAt"
|
||||
],
|
||||
"properties": {
|
||||
"auditId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"traceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"actorType": {
|
||||
"$ref": "common.json#/$defs/actorType"
|
||||
},
|
||||
"actorId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
|
||||
},
|
||||
"targetType": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"targetId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"gatewaySessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"workerSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"operationId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"serviceId": {
|
||||
"$ref": "common.json#/$defs/serviceId"
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"outcome": {
|
||||
"$ref": "common.json#/$defs/auditOutcome"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
},
|
||||
"occurredAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/box-capability.schema.json",
|
||||
"title": "HWLAB Box Capability",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"capabilityId",
|
||||
"resourceId",
|
||||
"projectId",
|
||||
"name",
|
||||
"direction",
|
||||
"valueType",
|
||||
"mutatesState",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
],
|
||||
"properties": {
|
||||
"capabilityId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"resourceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 1000
|
||||
},
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"enum": ["input", "output", "bidirectional"]
|
||||
},
|
||||
"valueType": {
|
||||
"type": "string",
|
||||
"enum": ["boolean", "integer", "number", "string", "object", "binary"]
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"maxLength": 32
|
||||
},
|
||||
"constraints": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
},
|
||||
"mutatesState": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"createdAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/box-resource.schema.json",
|
||||
"title": "HWLAB Box Resource",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resourceId",
|
||||
"projectId",
|
||||
"gatewaySessionId",
|
||||
"boxId",
|
||||
"resourceType",
|
||||
"state",
|
||||
"environment",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
],
|
||||
"properties": {
|
||||
"resourceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"gatewaySessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"boxId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"resourceType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"board",
|
||||
"power_supply",
|
||||
"serial_port",
|
||||
"network_port",
|
||||
"relay",
|
||||
"sensor",
|
||||
"simulator_endpoint"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 160
|
||||
},
|
||||
"state": {
|
||||
"$ref": "common.json#/$defs/resourceState"
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
},
|
||||
"createdAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/common.json",
|
||||
"title": "HWLAB Common Schema Definitions",
|
||||
"type": "object",
|
||||
"$defs": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 128,
|
||||
"pattern": "^[a-z][a-z0-9]*_[A-Za-z0-9._:-]+$"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"environment": {
|
||||
"type": "string",
|
||||
"enum": ["dev"]
|
||||
},
|
||||
"serviceId": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"hwlab-cloud-api",
|
||||
"hwlab-cloud-web",
|
||||
"hwlab-agent-mgr",
|
||||
"hwlab-agent-worker",
|
||||
"hwlab-gateway",
|
||||
"hwlab-gateway-simu",
|
||||
"hwlab-box-simu",
|
||||
"hwlab-patch-panel",
|
||||
"hwlab-router",
|
||||
"hwlab-tunnel-client",
|
||||
"hwlab-edge-proxy",
|
||||
"hwlab-cli",
|
||||
"hwlab-agent-skills"
|
||||
]
|
||||
},
|
||||
"lifecycleStatus": {
|
||||
"type": "string",
|
||||
"enum": ["pending", "active", "inactive", "failed", "deleted"]
|
||||
},
|
||||
"resourceState": {
|
||||
"type": "string",
|
||||
"enum": ["available", "reserved", "running", "maintenance", "offline"]
|
||||
},
|
||||
"operationStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"requested",
|
||||
"accepted",
|
||||
"queued",
|
||||
"running",
|
||||
"succeeded",
|
||||
"failed",
|
||||
"rejected",
|
||||
"canceled",
|
||||
"timed_out"
|
||||
]
|
||||
},
|
||||
"actorType": {
|
||||
"type": "string",
|
||||
"enum": ["user", "service", "agent", "worker", "system"]
|
||||
},
|
||||
"auditOutcome": {
|
||||
"type": "string",
|
||||
"enum": ["accepted", "succeeded", "failed", "rejected", "canceled"]
|
||||
},
|
||||
"traceLevel": {
|
||||
"type": "string",
|
||||
"enum": ["debug", "info", "warn", "error"]
|
||||
},
|
||||
"labels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
}
|
||||
},
|
||||
"jsonObject": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/evidence-record.schema.json",
|
||||
"title": "HWLAB Evidence Record",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"evidenceId",
|
||||
"projectId",
|
||||
"operationId",
|
||||
"kind",
|
||||
"uri",
|
||||
"sha256",
|
||||
"serviceId",
|
||||
"environment",
|
||||
"createdAt"
|
||||
],
|
||||
"properties": {
|
||||
"evidenceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"operationId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"agentSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"workerSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["log", "screenshot", "measurement", "artifact", "report", "trace"]
|
||||
},
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2048
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"sha256": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{64}$"
|
||||
},
|
||||
"sizeBytes": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"serviceId": {
|
||||
"$ref": "common.json#/$defs/serviceId"
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
},
|
||||
"createdAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/gateway-session.schema.json",
|
||||
"title": "HWLAB Gateway Session",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gatewaySessionId",
|
||||
"projectId",
|
||||
"serviceId",
|
||||
"gatewayId",
|
||||
"status",
|
||||
"environment",
|
||||
"startedAt"
|
||||
],
|
||||
"properties": {
|
||||
"gatewaySessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"serviceId": {
|
||||
"enum": ["hwlab-gateway", "hwlab-gateway-simu"]
|
||||
},
|
||||
"gatewayId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["starting", "connected", "degraded", "disconnected", "stopped"]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"startedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"lastSeenAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"stoppedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "common.json#/$defs/labels"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/hardware-operation.schema.json",
|
||||
"title": "HWLAB Hardware Operation",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"operationId",
|
||||
"projectId",
|
||||
"gatewaySessionId",
|
||||
"resourceId",
|
||||
"capabilityId",
|
||||
"requestedBy",
|
||||
"status",
|
||||
"environment",
|
||||
"requestedAt",
|
||||
"updatedAt"
|
||||
],
|
||||
"properties": {
|
||||
"operationId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"gatewaySessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"agentSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"workerSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"resourceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"capabilityId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"requestedBy": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"input": true,
|
||||
"output": true,
|
||||
"status": {
|
||||
"$ref": "common.json#/$defs/operationStatus"
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"requestedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"startedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"completedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/json-rpc-envelope.schema.json",
|
||||
"title": "HWLAB JSON-RPC Envelope",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["jsonrpc", "id", "method", "meta"],
|
||||
"properties": {
|
||||
"jsonrpc": {
|
||||
"const": "2.0"
|
||||
},
|
||||
"id": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"method": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9]*(\\.[a-z][a-z0-9_]*)+$"
|
||||
},
|
||||
"params": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/$defs/meta"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["jsonrpc", "id", "result", "meta"],
|
||||
"properties": {
|
||||
"jsonrpc": {
|
||||
"const": "2.0"
|
||||
},
|
||||
"id": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"result": true,
|
||||
"meta": {
|
||||
"$ref": "#/$defs/meta"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"required": ["error"]
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["jsonrpc", "id", "error", "meta"],
|
||||
"properties": {
|
||||
"jsonrpc": {
|
||||
"const": "2.0"
|
||||
},
|
||||
"id": {
|
||||
"type": ["string", "number", "null"]
|
||||
},
|
||||
"error": {
|
||||
"type": "object",
|
||||
"required": ["code", "message"],
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"data": true
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/$defs/meta"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"required": ["result"]
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"$defs": {
|
||||
"meta": {
|
||||
"type": "object",
|
||||
"required": ["traceId", "serviceId", "environment"],
|
||||
"properties": {
|
||||
"traceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"actorId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"serviceId": {
|
||||
"$ref": "common.json#/$defs/serviceId"
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/patch-panel-status.schema.json",
|
||||
"title": "HWLAB Patch Panel Status",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"patchPanelStatusId",
|
||||
"projectId",
|
||||
"gatewaySessionId",
|
||||
"serviceId",
|
||||
"state",
|
||||
"environment",
|
||||
"observedAt"
|
||||
],
|
||||
"properties": {
|
||||
"patchPanelStatusId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"gatewaySessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"wiringConfigId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"serviceId": {
|
||||
"const": "hwlab-patch-panel"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["unknown", "ready", "applying", "active", "faulted", "offline"]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"activeConnections": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["fromResourceId", "fromPort", "toResourceId", "toPort"],
|
||||
"properties": {
|
||||
"fromResourceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"fromPort": {
|
||||
"type": "string"
|
||||
},
|
||||
"toResourceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"toPort": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"observedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/project.schema.json",
|
||||
"title": "HWLAB Project",
|
||||
"type": "object",
|
||||
"required": ["projectId", "name", "status", "environment", "createdAt", "updatedAt"],
|
||||
"properties": {
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 160
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 2000
|
||||
},
|
||||
"status": {
|
||||
"$ref": "common.json#/$defs/lifecycleStatus"
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "common.json#/$defs/labels"
|
||||
},
|
||||
"createdBy": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"createdAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/trace-event.schema.json",
|
||||
"title": "HWLAB Agent Trace Event",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"traceEventId",
|
||||
"traceId",
|
||||
"projectId",
|
||||
"serviceId",
|
||||
"level",
|
||||
"message",
|
||||
"environment",
|
||||
"occurredAt"
|
||||
],
|
||||
"properties": {
|
||||
"traceEventId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"traceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"agentSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"workerSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"operationId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"serviceId": {
|
||||
"$ref": "common.json#/$defs/serviceId"
|
||||
},
|
||||
"level": {
|
||||
"$ref": "common.json#/$defs/traceLevel"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 4000
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
},
|
||||
"occurredAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/wiring-config.schema.json",
|
||||
"title": "HWLAB Wiring Config",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"wiringConfigId",
|
||||
"projectId",
|
||||
"gatewaySessionId",
|
||||
"status",
|
||||
"connections",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
],
|
||||
"properties": {
|
||||
"wiringConfigId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"gatewaySessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 160
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["draft", "valid", "active", "rejected", "retired"]
|
||||
},
|
||||
"connections": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/connection"
|
||||
}
|
||||
},
|
||||
"constraints": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
},
|
||||
"createdAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"$defs": {
|
||||
"endpoint": {
|
||||
"type": "object",
|
||||
"required": ["resourceId", "port"],
|
||||
"properties": {
|
||||
"resourceId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"port": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"capabilityId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"connection": {
|
||||
"type": "object",
|
||||
"required": ["from", "to"],
|
||||
"properties": {
|
||||
"from": {
|
||||
"$ref": "#/$defs/endpoint"
|
||||
},
|
||||
"to": {
|
||||
"$ref": "#/$defs/endpoint"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["exclusive", "shared", "monitor"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://hwlab.pikastech.local/protocol/schemas/worker-session.schema.json",
|
||||
"title": "HWLAB Worker Session",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"workerSessionId",
|
||||
"agentSessionId",
|
||||
"projectId",
|
||||
"serviceId",
|
||||
"status",
|
||||
"environment",
|
||||
"startedAt",
|
||||
"updatedAt"
|
||||
],
|
||||
"properties": {
|
||||
"workerSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"agentSessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"projectId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"serviceId": {
|
||||
"const": "hwlab-agent-worker"
|
||||
},
|
||||
"gatewaySessionId": {
|
||||
"$ref": "common.json#/$defs/id"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["starting", "active", "blocked", "completed", "failed", "canceled"]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "common.json#/$defs/environment"
|
||||
},
|
||||
"startedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"completedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$ref": "common.json#/$defs/timestamp"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "common.json#/$defs/jsonObject"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# Topology Constraints
|
||||
|
||||
The MVP topology separates cloud, gateway, worker, simulated box, patch panel,
|
||||
router, tunnel, and edge proxy responsibilities.
|
||||
|
||||
## Constraints
|
||||
|
||||
- `hwlab-cloud-api` is the cloud API boundary for DEV.
|
||||
- `hwlab-cloud-web` consumes cloud APIs and must not control hardware directly.
|
||||
- `hwlab-agent-mgr` schedules agent work and creates worker sessions.
|
||||
- `hwlab-agent-worker` executes scoped work through declared gateway and box
|
||||
capabilities.
|
||||
- `hwlab-gateway` is the hardware control boundary.
|
||||
- `hwlab-gateway-simu` and `hwlab-box-simu` may simulate gateway and box
|
||||
behavior for MVP validation.
|
||||
- `hwlab-patch-panel` owns patch panel state.
|
||||
- `hwlab-router`, `hwlab-tunnel-client`, and `hwlab-edge-proxy` own network
|
||||
reachability concerns.
|
||||
- `hwlab-cli` is a client and must not bypass service contracts.
|
||||
- `hwlab-agent-skills` contains reusable agent skill assets.
|
||||
|
||||
## Wiring
|
||||
|
||||
Wiring configs describe allowed connections between resource ports. A valid
|
||||
wiring config must:
|
||||
|
||||
- belong to a project
|
||||
- reference known gateway and box resources
|
||||
- declare endpoints explicitly
|
||||
- avoid cycles unless a later capability explicitly allows them
|
||||
- represent one active config per resource group unless the patch panel reports
|
||||
an override
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
|
||||
mkdir -p "$repo_root/skills"
|
||||
|
||||
echo "HWLAB skills directory ready: $repo_root/skills"
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
import assert from "node:assert/strict";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
ENVIRONMENT_DEV,
|
||||
SERVICE_IDS,
|
||||
TABLES,
|
||||
validateRequest,
|
||||
validateResponse
|
||||
} from "../internal/protocol/index.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
async function parseJSONFiles(dir) {
|
||||
const entries = await readdir(path.join(repoRoot, dir), { withFileTypes: true });
|
||||
const parsed = [];
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
||||
continue;
|
||||
}
|
||||
const relativePath = path.join(dir, entry.name);
|
||||
const raw = await readFile(path.join(repoRoot, relativePath), "utf8");
|
||||
const doc = JSON.parse(raw);
|
||||
assert.ok(doc.$schema, `${relativePath} missing $schema`);
|
||||
assert.ok(doc.$id, `${relativePath} missing $id`);
|
||||
parsed.push({ relativePath, doc });
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function assertUnique(name, values) {
|
||||
assert.equal(new Set(values).size, values.length, `${name} must be unique`);
|
||||
}
|
||||
|
||||
function assertCommonSchema(commonSchema) {
|
||||
const schemaServiceIds = commonSchema.$defs.serviceId.enum;
|
||||
assert.deepEqual(schemaServiceIds, SERVICE_IDS, "common service ids must match runtime constants");
|
||||
assert.deepEqual(commonSchema.$defs.environment.enum, [ENVIRONMENT_DEV]);
|
||||
}
|
||||
|
||||
function assertDeploySchema(deploySchema) {
|
||||
const serviceIds = deploySchema.$defs.service.properties.serviceId.enum;
|
||||
assert.deepEqual(serviceIds, SERVICE_IDS, "deploy service ids must match runtime constants");
|
||||
assert.equal(deploySchema.properties.endpoint.const, "http://74.48.78.17:6667");
|
||||
}
|
||||
|
||||
function assertEnvelopeValidation() {
|
||||
validateRequest({
|
||||
jsonrpc: "2.0",
|
||||
id: "req_01J00000000000000000000000",
|
||||
method: "hardware.operation.request",
|
||||
params: {
|
||||
projectId: "prj_01J00000000000000000000000"
|
||||
},
|
||||
meta: {
|
||||
traceId: "trc_01J00000000000000000000000",
|
||||
serviceId: "hwlab-cloud-api",
|
||||
environment: "dev"
|
||||
}
|
||||
});
|
||||
|
||||
validateResponse({
|
||||
jsonrpc: "2.0",
|
||||
id: "req_01J00000000000000000000000",
|
||||
result: {
|
||||
accepted: true
|
||||
},
|
||||
meta: {
|
||||
traceId: "trc_01J00000000000000000000000",
|
||||
serviceId: "hwlab-agent-mgr",
|
||||
environment: "dev"
|
||||
}
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
validateResponse({
|
||||
jsonrpc: "2.0",
|
||||
id: "req_01J00000000000000000000000",
|
||||
result: {},
|
||||
error: { code: -32000, message: "bad" },
|
||||
meta: {
|
||||
traceId: "trc_01J00000000000000000000000",
|
||||
serviceId: "hwlab-agent-mgr",
|
||||
environment: "dev"
|
||||
}
|
||||
}),
|
||||
/exactly one/
|
||||
);
|
||||
}
|
||||
|
||||
const schemas = await parseJSONFiles("protocol/schemas");
|
||||
const deploySchemas = await parseJSONFiles("deploy");
|
||||
const docsByName = new Map([...schemas, ...deploySchemas].map((item) => [path.basename(item.relativePath), item.doc]));
|
||||
|
||||
assert.ok(schemas.length >= 13, "expected protocol schemas");
|
||||
assert.ok(TABLES.includes("patch_panel_status"), "frozen tables must include patch_panel_status");
|
||||
assertUnique("service ids", SERVICE_IDS);
|
||||
assertUnique("tables", TABLES);
|
||||
assertCommonSchema(docsByName.get("common.json"));
|
||||
assertDeploySchema(docsByName.get("deploy.schema.json"));
|
||||
assertEnvelopeValidation();
|
||||
|
||||
console.log(`validated ${schemas.length + deploySchemas.length} JSON files and ${SERVICE_IDS.length} service ids`);
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
echo "HWLAB worker entrypoint placeholder"
|
||||
exec "$@"
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user