docs: add M3 rollout support runbook (#81)

Co-authored-by: HWLAB Code Queue <code-queue@pikastech.local>
This commit is contained in:
Lyon
2026-05-22 14:20:07 +08:00
committed by GitHub
parent 456f618685
commit 4f93bc07bf
4 changed files with 148 additions and 1 deletions
+3 -1
View File
@@ -11,7 +11,8 @@ entry points that later service implementations must follow.
- MVP scope is DEV only.
- DEV browser/frontend endpoint is fixed at `http://74.48.78.17:16666/`.
- DEV API/edge/live endpoint is fixed at `http://74.48.78.17:16667`.
- PROD is not an MVP acceptance target.
- Legacy `:6666` and `:6667` are historical/deprecated only and are not MVP
acceptance targets.
- 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.
@@ -60,6 +61,7 @@ The MVP branches may add implementation files under these paths:
- 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)
- M3 rollout support runbook: [docs/reference/m3-loop-rollout-runbook.md](docs/reference/m3-loop-rollout-runbook.md)
## Validation
+85
View File
@@ -0,0 +1,85 @@
# M3 Loop Rollout Runbook
This reference is support / diagnostics / contract for the HWLAB P0 M3 virtual
hardware trusted loop only. It is not P0 live evidence and it must not relabel
SOURCE / LOCAL / DRY-RUN / fixture output as `DEV-LIVE`.
## M3 Loop Boundary
- P0 scope: `2 x hwlab-box-simu`, `2 x hwlab-gateway-simu`, `1 x hwlab-patch-panel`.
- Accepted chain for live M3 proof: `DO1 -> patch-panel -> DI1`.
- Current public endpoints: browser/frontend `http://74.48.78.17:16666/` and
API/edge/live `http://74.48.78.17:16667/`.
- Legacy public ports `:6666` and `:6667` are historical / deprecated only.
- `pikasTech/HWLAB#63` remains a known CD blocker until suspended Job immutable
apply is fixed.
## Source Of Truth
`deploy/deploy.json` is the deploy intent source. `deploy/artifact-catalog.dev.json`
is the artifact identity source. No shell scrollback, issue comment, or report
fixture is authoritative over those two files.
| Fact | Read From | Notes |
| --- | --- | --- |
| source commit | `deploy/deploy.json.commitId` and `deploy/artifact-catalog.dev.json.commitId` | They must match. |
| image tag | `deploy/deploy.json.services[].image` and `deploy/artifact-catalog.dev.json.services[].imageTag` | The tag must stay tied to the source commit. |
| digest | `deploy/artifact-catalog.dev.json.services[].digest` | Real digests come only from publish evidence; otherwise keep `not_published`. |
| public endpoint | `deploy/deploy.json.publicEndpoints.*.url` and `deploy/deploy.json.endpoint` | Current endpoints are `16666/16667`; do not backslide to legacy ports. |
| frp | `deploy/deploy.json.frp.proxies[]` | Derive proxy name, local service, local port, and remote port from the manifest. |
| k3s service | `deploy/deploy.json.k3s.serviceMappings[]` | Derive service name, namespace, port, and targetPort from the manifest. |
| health path | `deploy/deploy.json.health.path` and `deploy/deploy.json.services[].healthPath` | Must stay on `/health/live`. |
## Rollout Support
Use this order for M3 loop support work:
1. Render the manifest contract and compare it with the artifact catalog.
2. Verify the M3 inventory cardinality for the two boxes, two gateways, and
one patch-panel.
3. Check that the current public endpoints stay on `16666/16667`.
4. Confirm the health path is `/health/live`.
5. Record any manual hotfix facts back into the issue, PR, and runbook before
closing the task.
Recommended checks:
```sh
node scripts/deploy-contract-plan.mjs --pretty
node scripts/deploy-contract-plan.mjs --check
node scripts/validate-dev-m3-cardinality.mjs
npm run docs:validate:m3-rollout
```
These checks are support only. They do not apply to DEV, do not touch PROD, do
not restart services, and do not prove live M3 acceptance.
## Rollback Support
If a manual hotfix or rollback is required, record the following before the
task closes:
- source commit and branch or PR;
- image reference, image tag, and digest;
- public endpoint and whether it was read-only or routed;
- FRP proxy name and ownership;
- k3s service mapping and ownership;
- health path used for verification;
- any field that diverged from `deploy/deploy.json` or the artifact catalog.
Rollback support must keep the live chain honest. If the chain was not actually
`DO1 -> patch-panel -> DI1`, do not claim live M3. Only write support,
diagnostics, or contract evidence.
## Prohibited Actions
- Do not treat this runbook as a general console or PROD flow.
- Do not deploy or smoke PROD.
- Do not read, print, or infer secrets, tokens, kubeconfig, or tunnel
credentials.
- Do not restart Code Queue, `backend-core`, or any runtime service from the
runner.
- Do not run heavyweight e2e or destructive hardware tests.
- Do not bypass `pikasTech/HWLAB#63` and then claim CD is green.
- Do not promote SOURCE / LOCAL / DRY-RUN / fixture output to `DEV-LIVE`.
+1
View File
@@ -25,6 +25,7 @@
"artifact-catalog:refresh-blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked",
"dev-artifact:preflight": "node scripts/dev-artifact-publish.mjs --preflight",
"dev-artifact:publish": "node scripts/dev-artifact-publish.mjs --publish",
"docs:validate:m3-rollout": "node scripts/validate-m3-rollout-runbook.mjs",
"d601:k3s:readonly": "node scripts/d601-k3s-readonly-observability.mjs",
"runner:issue-visibility:preflight": "node scripts/runner-issue-visibility-preflight.mjs"
}
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const docPath = "docs/reference/m3-loop-rollout-runbook.md";
async function readText(relativePath) {
return readFile(path.join(repoRoot, relativePath), "utf8");
}
function extractSection(doc, heading) {
const marker = `## ${heading}\n`;
const start = doc.indexOf(marker);
assert.ok(start >= 0, `missing section ${heading}`);
const bodyStart = start + marker.length;
const nextHeading = doc.indexOf("\n## ", bodyStart);
return doc.slice(bodyStart, nextHeading >= 0 ? nextHeading + 1 : doc.length);
}
function assertIncludes(text, snippet, label) {
assert.ok(text.includes(snippet), `${label} must include ${snippet}`);
}
function assertNotIncludes(text, snippet, label) {
assert.equal(text.includes(snippet), false, `${label} must not include ${snippet}`);
}
async function main() {
const doc = await readText(docPath);
const boundary = extractSection(doc, "M3 Loop Boundary");
assertIncludes(doc, "support / diagnostics / contract", "m3 runbook");
assertIncludes(doc, "2 x hwlab-box-simu", "m3 runbook");
assertIncludes(doc, "2 x hwlab-gateway-simu", "m3 runbook");
assertIncludes(doc, "1 x hwlab-patch-panel", "m3 runbook");
assertIncludes(doc, "DO1 -> patch-panel -> DI1", "m3 runbook");
assertIncludes(doc, "deploy/deploy.json", "m3 runbook");
assertIncludes(doc, "deploy/artifact-catalog.dev.json", "m3 runbook");
assertIncludes(doc, "pikasTech/HWLAB#63", "m3 runbook");
assertIncludes(doc, "Do not promote SOURCE / LOCAL / DRY-RUN / fixture output to `DEV-LIVE`.", "m3 runbook");
assertNotIncludes(boundary, "http://74.48.78.17:6666/", "m3 current boundary");
assertNotIncludes(boundary, "http://74.48.78.17:6667/", "m3 current boundary");
assertIncludes(boundary, "http://74.48.78.17:16666/", "m3 current boundary");
assertIncludes(boundary, "http://74.48.78.17:16667/", "m3 current boundary");
assertIncludes(boundary, ":6666", "m3 historical boundary");
assertIncludes(boundary, ":6667", "m3 historical boundary");
console.log(`validated ${docPath}`);
}
try {
await main();
} catch (error) {
process.stderr.write(`[validate-m3-rollout-runbook] ${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;
}