deadbyte-mcp 0.15.21 → 0.15.23
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/MANIFEST.SHA256 +53 -28
- package/PROVENANCE.json +10 -10
- package/README.txt +18 -7
- package/RELEASE.json +51 -16
- package/SBOM.cdx.json +33 -7
- package/TEST-MATRIX.json +95 -18
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/docs/R48-FULL-TOOL-CATALOG.json +124 -0
- package/native/deadbyte-hardware-observer.cpp +535 -12
- package/package.json +95 -93
- package/scripts/activate-skill-creator-1.1.1.mjs +46 -0
- package/scripts/build-advanced-pcb-skill.mjs +208 -0
- package/scripts/build-hardware-r45.ps1 +19 -0
- package/scripts/diagnose-skill-tree.mjs +8 -0
- package/scripts/full-regression-r48-batch.mjs +27 -0
- package/scripts/npm-release-r48.mjs +252 -0
- package/scripts/pcb-skill-src-SKILL.md.txt +67 -0
- package/scripts/pcb-skill-src-bringup-evidence.md.txt +22 -0
- package/scripts/pcb-skill-src-constraint-model.md.txt +17 -0
- package/scripts/pcb-skill-src-emc-mechanical-dfm.md.txt +17 -0
- package/scripts/pcb-skill-src-high-speed-layout.md.txt +27 -0
- package/scripts/pcb-skill-src-kicad-native.md.txt +30 -0
- package/scripts/pcb-skill-src-kicad_pcb_probe.py.txt +159 -0
- package/scripts/pcb-skill-src-pcb_constraint_gate.py.txt +76 -0
- package/scripts/pcb-skill-src-power-integrity.md.txt +15 -0
- package/scripts/prepare-skill-creator-1.1.1.mjs +113 -0
- package/scripts/promote-local-c-generation.ps1 +217 -0
- package/scripts/promote-local-d-generation.ps1 +217 -0
- package/scripts/test-r48-post-bump.ps1 +8 -0
- package/scripts/test-r48-windows-observation.ps1 +8 -0
- package/scripts/test-skill-creator-semver-matrix.mjs +30 -0
- package/scripts/test-skill-creator-semver.mjs +22 -0
- package/src/capability-ledger.mjs +7 -0
- package/src/capability-resolver.mjs +7 -0
- package/src/compact-gateway-mcp-tools.mjs +16 -2
- package/src/deadbyte-cli.mjs +70 -15
- package/src/machine-fs.mjs +16 -3
- package/src/observation-mcp-tools.mjs +13 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +54 -2
- package/src/remote-live-log.mjs +189 -35
- package/src/tool-audit.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/full-max-catalog-r47.test.mjs +1 -1
- package/test/full-max-catalog-r48.test.mjs +78 -0
- package/test/machine-fs-runtime.test.mjs +28 -3
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/r48-release-contract.test.mjs +24 -14
- package/test/remote-live-log-r37.test.mjs +106 -7
- package/test/remote-session-cli.test.mjs +40 -0
- package/test/tool-audit.test.mjs +17 -0
- package/test/windows-observation-r48.test.mjs +63 -0
- package/test/windows-observation-surface-r48.test.mjs +70 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json, sys
|
|
3
|
+
|
|
4
|
+
def load(path):
|
|
5
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
6
|
+
return json.load(f)
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
if len(sys.argv) != 3:
|
|
10
|
+
raise SystemExit("usage: pcb_constraint_gate.py board-probe.json board-contract.json")
|
|
11
|
+
probe = load(sys.argv[1])
|
|
12
|
+
contract = load(sys.argv[2])
|
|
13
|
+
if probe.get("schema") != "deadbyte.kicad-pcb-probe.v1":
|
|
14
|
+
raise SystemExit("unsupported probe schema")
|
|
15
|
+
|
|
16
|
+
checks = []
|
|
17
|
+
def add(rule, ok, observed, required):
|
|
18
|
+
checks.append({"rule": rule, "ok": bool(ok), "observed": observed, "required": required})
|
|
19
|
+
|
|
20
|
+
if "required_layers" in contract:
|
|
21
|
+
observed = set(x.get("name") for x in probe.get("layers", []))
|
|
22
|
+
req = list(contract["required_layers"])
|
|
23
|
+
add("required_layers", all(x in observed for x in req), sorted(observed), req)
|
|
24
|
+
|
|
25
|
+
if "min_copper_layers" in contract:
|
|
26
|
+
observed = probe.get("copper_layer_count")
|
|
27
|
+
req = int(contract["min_copper_layers"])
|
|
28
|
+
add("min_copper_layers", isinstance(observed, int) and observed >= req, observed, req)
|
|
29
|
+
|
|
30
|
+
for key, probe_key in [
|
|
31
|
+
("min_track_width_mm", "track_width_mm"),
|
|
32
|
+
("min_via_diameter_mm", "via_diameter_mm"),
|
|
33
|
+
("min_via_drill_mm", "via_drill_mm"),
|
|
34
|
+
]:
|
|
35
|
+
if key in contract:
|
|
36
|
+
observed = (probe.get(probe_key) or {}).get("min")
|
|
37
|
+
req = float(contract[key])
|
|
38
|
+
add(key, observed is not None and float(observed) >= req, observed, req)
|
|
39
|
+
|
|
40
|
+
if "required_nets" in contract:
|
|
41
|
+
observed = set(probe.get("nets", []))
|
|
42
|
+
req = list(contract["required_nets"])
|
|
43
|
+
add("required_nets", all(x in observed for x in req), sorted(observed), req)
|
|
44
|
+
|
|
45
|
+
for key, count_key in [
|
|
46
|
+
("min_footprints", "footprints"),
|
|
47
|
+
("min_segments", "segments"),
|
|
48
|
+
("min_vias", "vias"),
|
|
49
|
+
("min_zones", "zones"),
|
|
50
|
+
]:
|
|
51
|
+
if key in contract:
|
|
52
|
+
observed = (probe.get("counts") or {}).get(count_key)
|
|
53
|
+
req = int(contract[key])
|
|
54
|
+
add(key, isinstance(observed, int) and observed >= req, observed, req)
|
|
55
|
+
|
|
56
|
+
allowed = {
|
|
57
|
+
"required_layers", "min_copper_layers", "min_track_width_mm",
|
|
58
|
+
"min_via_diameter_mm", "min_via_drill_mm", "required_nets",
|
|
59
|
+
"min_footprints", "min_segments", "min_vias", "min_zones"
|
|
60
|
+
}
|
|
61
|
+
unknown = sorted(set(contract) - allowed)
|
|
62
|
+
if unknown:
|
|
63
|
+
checks.append({"rule": "unknown_contract_keys", "ok": False, "observed": unknown, "required": "no unsupported keys"})
|
|
64
|
+
|
|
65
|
+
passed = all(x["ok"] for x in checks)
|
|
66
|
+
out = {
|
|
67
|
+
"schema": "deadbyte.pcb-constraint-gate.v1",
|
|
68
|
+
"status": "PASS" if passed else "FAIL",
|
|
69
|
+
"probe_sha256": probe.get("sha256"),
|
|
70
|
+
"checks": checks
|
|
71
|
+
}
|
|
72
|
+
print(json.dumps(out, indent=2, sort_keys=True))
|
|
73
|
+
raise SystemExit(0 if passed else 2)
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
main()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Power Integrity and Thermal
|
|
2
|
+
|
|
3
|
+
Power integrity begins with current loops, not capacitor count.
|
|
4
|
+
|
|
5
|
+
For each regulator trace the input current path, keep the high di/dt input loop compact, bound switch-node copper, place bootstrap/gate-drive parts by topology, protect the feedback sense path, close the output-current loop, and provide a credible thermal path.
|
|
6
|
+
|
|
7
|
+
For decoupling separate bulk-energy storage from local high-frequency charge. Account for capacitor mounting inductance, vias, planes, and package inductance. Printed capacitance alone does not prove useful impedance at frequency.
|
|
8
|
+
|
|
9
|
+
For planes and pours verify neck-downs at pads, vias, connectors, and thermal spokes; identify slots caused by keepouts and antipads; preserve return continuity; and check both DC drop and transient current paths.
|
|
10
|
+
|
|
11
|
+
For sensitive analog control where digital and switching currents return. Do not split ground reflexively. Placement, loop control, filtering, and current-path ownership matter more than labels.
|
|
12
|
+
|
|
13
|
+
Thermal claims require dissipation, package thermal path, copper geometry, ambient, airflow, and enclosure assumptions. Missing inputs mean thermal margin UNKNOWN.
|
|
14
|
+
|
|
15
|
+
Bench evidence should include rail voltage, ripple/noise with correct probing, startup sequencing, relevant load transient behavior, and regulator temperature under representative load.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, rm, cp } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { createHash, createPrivateKey, createPublicKey, sign } from "node:crypto";
|
|
7
|
+
import { createSkillPlane } from "../src/skill-plane-r44.mjs";
|
|
8
|
+
|
|
9
|
+
const ex = promisify(execFile);
|
|
10
|
+
const home = os.homedir();
|
|
11
|
+
const stateDir = path.join(home, ".deadbyte-mcp");
|
|
12
|
+
const skillsRoot = path.join(stateDir, "skills");
|
|
13
|
+
const active = path.join(skillsRoot, "skill-creator");
|
|
14
|
+
const expectedOldManifest = "f34b3953db176c8ae9a5e08e7702b39f12b60b013edcd2f4d63a6268af9ba432";
|
|
15
|
+
const oldEnv = JSON.parse(await readFile(path.join(active, "skill.json"), "utf8"));
|
|
16
|
+
if (oldEnv.manifest_sha256 !== expectedOldManifest) {
|
|
17
|
+
throw new Error("unexpected active skill-creator manifest: " + oldEnv.manifest_sha256);
|
|
18
|
+
}
|
|
19
|
+
if (oldEnv.body.version !== "1.1.0") throw new Error("expected active skill-creator 1.1.0");
|
|
20
|
+
|
|
21
|
+
const history = path.join(stateDir, "skill-history", "skill-creator", "1.1.0-" + expectedOldManifest);
|
|
22
|
+
await mkdir(path.dirname(history), {recursive:true});
|
|
23
|
+
try {
|
|
24
|
+
const prior = JSON.parse(await readFile(path.join(history, "skill.json"), "utf8"));
|
|
25
|
+
if (prior.manifest_sha256 !== expectedOldManifest) throw new Error("history path collision");
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (e.code !== "ENOENT") throw e;
|
|
28
|
+
await cp(active, history, {recursive:true, errorOnExist:true});
|
|
29
|
+
}
|
|
30
|
+
const archived = JSON.parse(await readFile(path.join(history, "skill.json"), "utf8"));
|
|
31
|
+
if (archived.manifest_sha256 !== expectedOldManifest) throw new Error("archive manifest mismatch");
|
|
32
|
+
|
|
33
|
+
const stageParent = path.join(stateDir, "skill-staging", "skill-creator-1.1.1");
|
|
34
|
+
const stage = path.join(stageParent, "skill-creator");
|
|
35
|
+
await rm(stageParent, {recursive:true, force:true});
|
|
36
|
+
await mkdir(stageParent, {recursive:true});
|
|
37
|
+
await cp(active, stage, {recursive:true});
|
|
38
|
+
await rm(path.join(stage, "references", "skills_cache.json"), {force:true});
|
|
39
|
+
|
|
40
|
+
const createPath = path.join(stage, "scripts", "create-skill.mjs");
|
|
41
|
+
const oldCreate = await readFile(createPath, "utf8");
|
|
42
|
+
const oldRegex = "const VERSION=/^[0-9]+\\\\.[0-9]+\\\\.[0-9]+(?:-[A-Za-z0-9.-]+)?$/;";
|
|
43
|
+
const newRegex = "const VERSION=/^[0-9]+\\.[0-9]+\\.[0-9]+(?:-[A-Za-z0-9.-]+)?$/;";
|
|
44
|
+
if (!oldCreate.includes(oldRegex)) throw new Error("expected defective VERSION regex not found");
|
|
45
|
+
if (oldCreate.split(oldRegex).length !== 2) throw new Error("VERSION regex occurrence count != 1");
|
|
46
|
+
await writeFile(createPath, oldCreate.replace(oldRegex, newRegex), "utf8");
|
|
47
|
+
|
|
48
|
+
const skillPath = path.join(stage, "SKILL.md");
|
|
49
|
+
const oldSkill = await readFile(skillPath, "utf8");
|
|
50
|
+
const versionLine = "version: 1.1.0";
|
|
51
|
+
if (!oldSkill.includes(versionLine) || oldSkill.split(versionLine).length !== 2) {
|
|
52
|
+
throw new Error("SKILL.md version occurrence count != 1");
|
|
53
|
+
}
|
|
54
|
+
await writeFile(skillPath, oldSkill.replace(versionLine, "version: 1.1.1"), "utf8");
|
|
55
|
+
|
|
56
|
+
const sha = b => createHash("sha256").update(b).digest("hex");
|
|
57
|
+
const canonical = v => Array.isArray(v) ? v.map(canonical) : v && typeof v === "object"
|
|
58
|
+
? Object.fromEntries(Object.keys(v).sort((a,b)=>a.localeCompare(b,"en")).map(k=>[k,canonical(v[k])]))
|
|
59
|
+
: v;
|
|
60
|
+
|
|
61
|
+
const body = structuredClone(oldEnv.body);
|
|
62
|
+
body.version = "1.1.1";
|
|
63
|
+
body.skill_md_sha256 = sha(await readFile(skillPath));
|
|
64
|
+
body.provenance = {
|
|
65
|
+
...body.provenance,
|
|
66
|
+
source_revision: "r47.1-semver-regex-repair-2026-09-27"
|
|
67
|
+
};
|
|
68
|
+
for (const resource of body.resources) {
|
|
69
|
+
resource.sha256 = sha(await readFile(path.join(stage, ...resource.path.split("/"))));
|
|
70
|
+
}
|
|
71
|
+
const bodyBytes = Buffer.from(JSON.stringify(canonical(body)), "utf8");
|
|
72
|
+
const body_sha256 = sha(bodyBytes);
|
|
73
|
+
const priv = createPrivateKey(await readFile(path.join(stateDir, "trust", "ed25519-private.pem")));
|
|
74
|
+
const pub = createPublicKey(priv);
|
|
75
|
+
const keyId = sha(pub.export({type:"spki", format:"der"}));
|
|
76
|
+
const attestation = {
|
|
77
|
+
algorithm: "ed25519",
|
|
78
|
+
domain: "DEADBYTE-SKILL-MANIFEST-V1",
|
|
79
|
+
key_id: keyId,
|
|
80
|
+
signature_base64: sign(null, Buffer.concat([Buffer.from("DEADBYTE-SKILL-MANIFEST-V1\0", "utf8"), bodyBytes]), priv).toString("base64")
|
|
81
|
+
};
|
|
82
|
+
const unsigned = {body, body_sha256, attestation};
|
|
83
|
+
const envelope = {
|
|
84
|
+
...unsigned,
|
|
85
|
+
manifest_sha256: sha(Buffer.from(JSON.stringify(canonical(unsigned)), "utf8"))
|
|
86
|
+
};
|
|
87
|
+
await writeFile(path.join(stage, "skill.json"), JSON.stringify(envelope, null, 2), "utf8");
|
|
88
|
+
|
|
89
|
+
const validator = await ex(process.execPath, [path.join(stage, "scripts", "validate-skill.mjs"), stage], {
|
|
90
|
+
timeout: 30000,
|
|
91
|
+
maxBuffer: 1024 * 1024
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const plane = await createSkillPlane({
|
|
95
|
+
root: stageParent,
|
|
96
|
+
verifyKeyPath: path.join(stateDir, "trust", "ed25519-public.pem")
|
|
97
|
+
});
|
|
98
|
+
const loaded = await plane.load({name:"skill-creator"});
|
|
99
|
+
if (!loaded.verified || loaded.version !== "1.1.1") throw new Error("staging Skill Plane verification failed");
|
|
100
|
+
|
|
101
|
+
console.log(JSON.stringify({
|
|
102
|
+
status:"STAGED",
|
|
103
|
+
active_manifest:oldEnv.manifest_sha256,
|
|
104
|
+
archived_path:history,
|
|
105
|
+
archived_manifest:archived.manifest_sha256,
|
|
106
|
+
stage_parent:stageParent,
|
|
107
|
+
stage_skill:stage,
|
|
108
|
+
candidate_manifest:envelope.manifest_sha256,
|
|
109
|
+
candidate_body_sha256:body_sha256,
|
|
110
|
+
verify_key_id:keyId,
|
|
111
|
+
validator:validator.stdout.trim(),
|
|
112
|
+
skill_plane_verified:loaded.verified
|
|
113
|
+
}, null, 2));
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
[CmdletBinding()]
|
|
3
|
+
param()
|
|
4
|
+
Set-StrictMode -Version 3.0
|
|
5
|
+
$ErrorActionPreference = 'Stop'
|
|
6
|
+
|
|
7
|
+
function Fail([string]$Message) { throw "LOCAL_C_PROMOTION: $Message" }
|
|
8
|
+
function Sha256-File([string]$Path) {
|
|
9
|
+
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
10
|
+
}
|
|
11
|
+
function Sha256-Text([string]$Text) {
|
|
12
|
+
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
13
|
+
try {
|
|
14
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
|
|
15
|
+
return -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString('x2') })
|
|
16
|
+
} finally { $sha.Dispose() }
|
|
17
|
+
}
|
|
18
|
+
function Write-Utf8NoBom([string]$Path,[string]$Text) {
|
|
19
|
+
[System.IO.File]::WriteAllText($Path,$Text,(New-Object System.Text.UTF8Encoding($false)))
|
|
20
|
+
}
|
|
21
|
+
function Read-Raw([string]$Path) {
|
|
22
|
+
return [System.IO.File]::ReadAllText($Path,[System.Text.Encoding]::UTF8)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
$stateRoot = Join-Path $env:USERPROFILE '.deadbyte-mcp'
|
|
26
|
+
$pointerPath = Join-Path $stateRoot 'active-release.json'
|
|
27
|
+
$launcher = Join-Path $stateRoot 'bin\DEADBYTE-MCP.ps1'
|
|
28
|
+
$runtimeRoot = Join-Path $stateRoot 'runtime'
|
|
29
|
+
if (-not (Test-Path -LiteralPath $pointerPath -PathType Leaf)) { Fail 'active pointer missing' }
|
|
30
|
+
if (-not (Test-Path -LiteralPath $launcher -PathType Leaf)) { Fail 'stable launcher missing' }
|
|
31
|
+
|
|
32
|
+
$pointerText = Read-Raw $pointerPath
|
|
33
|
+
$pointer = $pointerText | ConvertFrom-Json
|
|
34
|
+
if ([string]$pointer.schema -ne 'deadbyte.active-release.v1') { Fail 'active pointer schema mismatch' }
|
|
35
|
+
$oldGeneration = [System.IO.Path]::GetFullPath([string]$pointer.generation_root)
|
|
36
|
+
$oldManifest = Join-Path $oldGeneration 'GENERATION.SHA256'
|
|
37
|
+
if (-not (Test-Path -LiteralPath $oldManifest -PathType Leaf)) { Fail 'old generation manifest missing' }
|
|
38
|
+
$oldManifestHash = Sha256-File $oldManifest
|
|
39
|
+
if ($oldManifestHash -ne [string]$pointer.generation_manifest_sha256) { Fail 'old generation manifest hash mismatch' }
|
|
40
|
+
|
|
41
|
+
$manifestLines = @(Get-Content -LiteralPath $oldManifest -Encoding UTF8)
|
|
42
|
+
$entries = @()
|
|
43
|
+
foreach ($line in $manifestLines) {
|
|
44
|
+
if ([string]::IsNullOrWhiteSpace([string]$line)) { continue }
|
|
45
|
+
if ([string]$line -notmatch '^([0-9a-f]{64}) ([A-Za-z0-9._-]+)$') { Fail "malformed old generation manifest line: $line" }
|
|
46
|
+
$expected = [string]$Matches[1]
|
|
47
|
+
$name = [string]$Matches[2]
|
|
48
|
+
$path = Join-Path $oldGeneration $name
|
|
49
|
+
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { Fail "old generation file missing: $name" }
|
|
50
|
+
$actual = Sha256-File $path
|
|
51
|
+
if ($actual -ne $expected) { Fail "old generation hash mismatch: $name" }
|
|
52
|
+
$entries += [pscustomobject]@{ Name=$name; Expected=$expected }
|
|
53
|
+
}
|
|
54
|
+
$names = @($entries | ForEach-Object { $_.Name })
|
|
55
|
+
foreach ($required in @('coding-policy.json','autonomous-policy.json','controller-config.psd1')) {
|
|
56
|
+
if ($required -notin $names) { Fail "required generation file not manifested: $required" }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$oldCodingPath = Join-Path $oldGeneration 'coding-policy.json'
|
|
60
|
+
$oldAutonomousPath = Join-Path $oldGeneration 'autonomous-policy.json'
|
|
61
|
+
$oldCodingHash = Sha256-File $oldCodingPath
|
|
62
|
+
$oldAutonomousHash = Sha256-File $oldAutonomousPath
|
|
63
|
+
|
|
64
|
+
$coding = (Read-Raw $oldCodingPath) | ConvertFrom-Json
|
|
65
|
+
if ([string]$coding.schema -ne 'deadbyte.coding-policy.v1' -or $coding.enabled -ne $true) { Fail 'coding policy schema/enabled mismatch' }
|
|
66
|
+
if ($null -eq $coding.roots) { Fail 'coding roots missing' }
|
|
67
|
+
|
|
68
|
+
$localRoot = [pscustomobject][ordered]@{
|
|
69
|
+
path = 'C:\'
|
|
70
|
+
read = $true
|
|
71
|
+
write = $true
|
|
72
|
+
exec = $true
|
|
73
|
+
deny_paths = @()
|
|
74
|
+
}
|
|
75
|
+
if ($coding.roots.PSObject.Properties.Name -contains 'local_c') {
|
|
76
|
+
$coding.roots.local_c = $localRoot
|
|
77
|
+
} else {
|
|
78
|
+
$coding.roots | Add-Member -NotePropertyName 'local_c' -NotePropertyValue $localRoot
|
|
79
|
+
}
|
|
80
|
+
foreach ($commandProp in @($coding.commands.PSObject.Properties)) {
|
|
81
|
+
$command = $commandProp.Value
|
|
82
|
+
if ($command.PSObject.Properties.Name -contains 'root_ids') {
|
|
83
|
+
$ids = @($command.root_ids)
|
|
84
|
+
if ('local_c' -notin $ids) { $command.root_ids = @($ids + 'local_c') }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
$codingText = ($coding | ConvertTo-Json -Depth 32) + [Environment]::NewLine
|
|
88
|
+
$newCodingHash = Sha256-Text $codingText
|
|
89
|
+
|
|
90
|
+
$autonomous = (Read-Raw $oldAutonomousPath) | ConvertFrom-Json
|
|
91
|
+
if ([string]$autonomous.schema -ne 'deadbyte.autonomous-policy.v1' -or $autonomous.enabled -ne $true) { Fail 'autonomous policy schema/enabled mismatch' }
|
|
92
|
+
$autonomous.coding_policy_sha256 = $newCodingHash
|
|
93
|
+
$autonomousText = ($autonomous | ConvertTo-Json -Depth 32) + [Environment]::NewLine
|
|
94
|
+
$newAutonomousHash = Sha256-Text $autonomousText
|
|
95
|
+
|
|
96
|
+
$seed = $oldManifestHash + [Environment]::NewLine + $newCodingHash + [Environment]::NewLine + $newAutonomousHash + [Environment]::NewLine + 'local_c=C:\'
|
|
97
|
+
$generationSuffix = (Sha256-Text $seed).Substring(0,16)
|
|
98
|
+
$newGenerationId = ([string]$pointer.release_id) + '-g' + $generationSuffix
|
|
99
|
+
$generationStore = Join-Path $stateRoot 'generations'
|
|
100
|
+
$newGeneration = Join-Path $generationStore $newGenerationId
|
|
101
|
+
if (Test-Path -LiteralPath $newGeneration) { Fail "target generation already exists: $newGeneration" }
|
|
102
|
+
New-Item -ItemType Directory -Path $newGeneration | Out-Null
|
|
103
|
+
|
|
104
|
+
$pointerBackup = Join-Path $runtimeRoot 'active-release.before-local-c.json'
|
|
105
|
+
$codingGrantPath = Join-Path $runtimeRoot 'coding-grant.json'
|
|
106
|
+
$autonomousGrantPath = Join-Path $runtimeRoot 'autonomous-grant.json'
|
|
107
|
+
$hadCodingGrant = Test-Path -LiteralPath $codingGrantPath -PathType Leaf
|
|
108
|
+
$hadAutonomousGrant = Test-Path -LiteralPath $autonomousGrantPath -PathType Leaf
|
|
109
|
+
$oldCodingGrantText = if ($hadCodingGrant) { Read-Raw $codingGrantPath } else { $null }
|
|
110
|
+
$oldAutonomousGrantText = if ($hadAutonomousGrant) { Read-Raw $autonomousGrantPath } else { $null }
|
|
111
|
+
$wasAutonomousArmed = $false
|
|
112
|
+
if ($hadCodingGrant -and $hadAutonomousGrant) {
|
|
113
|
+
try {
|
|
114
|
+
$cg = $oldCodingGrantText | ConvertFrom-Json
|
|
115
|
+
$ag = $oldAutonomousGrantText | ConvertFrom-Json
|
|
116
|
+
$wasAutonomousArmed =
|
|
117
|
+
([string]$cg.policy_sha256 -eq $oldCodingHash) -and
|
|
118
|
+
([string]$ag.policy_sha256 -eq $oldAutonomousHash) -and
|
|
119
|
+
([string]$ag.coding_policy_sha256 -eq $oldCodingHash)
|
|
120
|
+
} catch { $wasAutonomousArmed = $false }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
$switched = $false
|
|
124
|
+
try {
|
|
125
|
+
foreach ($entry in $entries) {
|
|
126
|
+
$src = Join-Path $oldGeneration $entry.Name
|
|
127
|
+
$dst = Join-Path $newGeneration $entry.Name
|
|
128
|
+
Copy-Item -LiteralPath $src -Destination $dst
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Write-Utf8NoBom (Join-Path $newGeneration 'coding-policy.json') $codingText
|
|
132
|
+
Write-Utf8NoBom (Join-Path $newGeneration 'autonomous-policy.json') $autonomousText
|
|
133
|
+
|
|
134
|
+
$configPath = Join-Path $newGeneration 'controller-config.psd1'
|
|
135
|
+
$configText = Read-Raw $configPath
|
|
136
|
+
if ($configText.IndexOf($oldGeneration,[System.StringComparison]::OrdinalIgnoreCase) -lt 0) { Fail 'controller config is not bound to old generation root' }
|
|
137
|
+
$configText = $configText.Replace($oldGeneration,$newGeneration)
|
|
138
|
+
Write-Utf8NoBom $configPath $configText
|
|
139
|
+
$cfg = Import-PowerShellDataFile -LiteralPath $configPath
|
|
140
|
+
if ([System.IO.Path]::GetFullPath([string]$cfg.CodingPolicyFile) -ne [System.IO.Path]::GetFullPath((Join-Path $newGeneration 'coding-policy.json'))) { Fail 'new controller CodingPolicyFile binding mismatch' }
|
|
141
|
+
if ([System.IO.Path]::GetFullPath([string]$cfg.AutonomousPolicyFile) -ne [System.IO.Path]::GetFullPath((Join-Path $newGeneration 'autonomous-policy.json'))) { Fail 'new controller AutonomousPolicyFile binding mismatch' }
|
|
142
|
+
|
|
143
|
+
foreach ($entry in $entries) {
|
|
144
|
+
$p = Join-Path $newGeneration $entry.Name
|
|
145
|
+
$text = $null
|
|
146
|
+
try { $text = Read-Raw $p } catch { $text = $null }
|
|
147
|
+
if ($null -ne $text -and $entry.Name -ne 'controller-config.psd1' -and $text.IndexOf($oldGeneration,[System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
|
|
148
|
+
Fail "unexpected old generation binding remains in $($entry.Name)"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
$newManifestLines = @()
|
|
153
|
+
foreach ($entry in $entries) {
|
|
154
|
+
$p = Join-Path $newGeneration $entry.Name
|
|
155
|
+
$newManifestLines += ((Sha256-File $p) + ' ' + $entry.Name)
|
|
156
|
+
}
|
|
157
|
+
$newManifestText = ($newManifestLines -join [Environment]::NewLine) + [Environment]::NewLine
|
|
158
|
+
$newManifestPath = Join-Path $newGeneration 'GENERATION.SHA256'
|
|
159
|
+
Write-Utf8NoBom $newManifestPath $newManifestText
|
|
160
|
+
$newManifestHash = Sha256-File $newManifestPath
|
|
161
|
+
|
|
162
|
+
foreach ($line in $newManifestLines) {
|
|
163
|
+
if ($line -notmatch '^([0-9a-f]{64}) ([A-Za-z0-9._-]+)$') { Fail 'new manifest internal format error' }
|
|
164
|
+
$actual = Sha256-File (Join-Path $newGeneration ([string]$Matches[2]))
|
|
165
|
+
if ($actual -ne [string]$Matches[1]) { Fail "new generation verification failed: $($Matches[2])" }
|
|
166
|
+
}
|
|
167
|
+
if ((Sha256-File (Join-Path $newGeneration 'coding-policy.json')) -ne $newCodingHash) { Fail 'new coding hash self-check failed' }
|
|
168
|
+
if ((Sha256-File (Join-Path $newGeneration 'autonomous-policy.json')) -ne $newAutonomousHash) { Fail 'new autonomous hash self-check failed' }
|
|
169
|
+
|
|
170
|
+
Write-Utf8NoBom $pointerBackup $pointerText
|
|
171
|
+
$pointer.generation_root = $newGeneration
|
|
172
|
+
$pointer.config_path = Join-Path $newGeneration 'controller-config.psd1'
|
|
173
|
+
$pointer.generation_manifest_sha256 = $newManifestHash
|
|
174
|
+
if ($pointer.PSObject.Properties.Name -contains 'generation_id') { $pointer.generation_id = $newGenerationId }
|
|
175
|
+
if ($pointer.PSObject.Properties.Name -contains 'generation_manifest_path') { $pointer.generation_manifest_path = $newManifestPath }
|
|
176
|
+
if ($pointer.PSObject.Properties.Name -contains 'updated_at_utc') { $pointer.updated_at_utc = [DateTime]::UtcNow.ToString('o') }
|
|
177
|
+
$newPointerText = ($pointer | ConvertTo-Json -Depth 32) + [Environment]::NewLine
|
|
178
|
+
$pointerTmp = $pointerPath + '.local-c.tmp'
|
|
179
|
+
Write-Utf8NoBom $pointerTmp $newPointerText
|
|
180
|
+
Move-Item -LiteralPath $pointerTmp -Destination $pointerPath -Force
|
|
181
|
+
$switched = $true
|
|
182
|
+
|
|
183
|
+
$statusText = (& $launcher status 2>&1 | Out-String)
|
|
184
|
+
if ($LASTEXITCODE -ne 0) { Fail "launcher status failed after pointer switch: $statusText" }
|
|
185
|
+
|
|
186
|
+
$armCodingText = (& $launcher armcoding 2>&1 | Out-String)
|
|
187
|
+
if ($LASTEXITCODE -ne 0) { Fail "armcoding failed after pointer switch: $armCodingText" }
|
|
188
|
+
|
|
189
|
+
$armAutonomousText = ''
|
|
190
|
+
if ($wasAutonomousArmed) {
|
|
191
|
+
$armAutonomousText = (& $launcher armautonomous 2>&1 | Out-String)
|
|
192
|
+
if ($LASTEXITCODE -ne 0) { Fail "armautonomous failed after pointer switch: $armAutonomousText" }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Write-Output 'LOCAL_C_PROMOTION=PASS'
|
|
196
|
+
Write-Output ('OLD_GENERATION=' + $oldGeneration)
|
|
197
|
+
Write-Output ('NEW_GENERATION=' + $newGeneration)
|
|
198
|
+
Write-Output ('OLD_GENERATION_MANIFEST_SHA256=' + $oldManifestHash)
|
|
199
|
+
Write-Output ('NEW_GENERATION_MANIFEST_SHA256=' + $newManifestHash)
|
|
200
|
+
Write-Output ('OLD_CODING_POLICY_SHA256=' + $oldCodingHash)
|
|
201
|
+
Write-Output ('NEW_CODING_POLICY_SHA256=' + $newCodingHash)
|
|
202
|
+
Write-Output ('NEW_AUTONOMOUS_POLICY_SHA256=' + $newAutonomousHash)
|
|
203
|
+
Write-Output ('AUTONOMOUS_REARMED=' + $wasAutonomousArmed)
|
|
204
|
+
Write-Output ('ROLLBACK_POINTER=' + $pointerBackup)
|
|
205
|
+
Write-Output 'RESTART_REQUIRED=YES'
|
|
206
|
+
} catch {
|
|
207
|
+
$message = $_.Exception.Message
|
|
208
|
+
if ($switched) {
|
|
209
|
+
try { Write-Utf8NoBom $pointerPath $pointerText } catch {}
|
|
210
|
+
if ($hadCodingGrant) { try { Write-Utf8NoBom $codingGrantPath $oldCodingGrantText } catch {} }
|
|
211
|
+
else { try { Remove-Item -LiteralPath $codingGrantPath -Force -ErrorAction SilentlyContinue } catch {} }
|
|
212
|
+
if ($hadAutonomousGrant) { try { Write-Utf8NoBom $autonomousGrantPath $oldAutonomousGrantText } catch {} }
|
|
213
|
+
else { try { Remove-Item -LiteralPath $autonomousGrantPath -Force -ErrorAction SilentlyContinue } catch {} }
|
|
214
|
+
}
|
|
215
|
+
try { if (Test-Path -LiteralPath $newGeneration) { Remove-Item -LiteralPath $newGeneration -Recurse -Force } } catch {}
|
|
216
|
+
Fail $message
|
|
217
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
[CmdletBinding()]
|
|
3
|
+
param()
|
|
4
|
+
Set-StrictMode -Version 3.0
|
|
5
|
+
$ErrorActionPreference = 'Stop'
|
|
6
|
+
|
|
7
|
+
function Fail([string]$Message) { throw "LOCAL_D_PROMOTION: $Message" }
|
|
8
|
+
function Sha256-File([string]$Path) {
|
|
9
|
+
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
10
|
+
}
|
|
11
|
+
function Sha256-Text([string]$Text) {
|
|
12
|
+
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
13
|
+
try {
|
|
14
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
|
|
15
|
+
return -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString('x2') })
|
|
16
|
+
} finally { $sha.Dispose() }
|
|
17
|
+
}
|
|
18
|
+
function Write-Utf8NoBom([string]$Path,[string]$Text) {
|
|
19
|
+
[System.IO.File]::WriteAllText($Path,$Text,(New-Object System.Text.UTF8Encoding($false)))
|
|
20
|
+
}
|
|
21
|
+
function Read-Raw([string]$Path) {
|
|
22
|
+
return [System.IO.File]::ReadAllText($Path,[System.Text.Encoding]::UTF8)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
$stateRoot = Join-Path $env:USERPROFILE '.deadbyte-mcp'
|
|
26
|
+
$pointerPath = Join-Path $stateRoot 'active-release.json'
|
|
27
|
+
$launcher = Join-Path $stateRoot 'bin\DEADBYTE-MCP.ps1'
|
|
28
|
+
$runtimeRoot = Join-Path $stateRoot 'runtime'
|
|
29
|
+
if (-not (Test-Path -LiteralPath $pointerPath -PathType Leaf)) { Fail 'active pointer missing' }
|
|
30
|
+
if (-not (Test-Path -LiteralPath $launcher -PathType Leaf)) { Fail 'stable launcher missing' }
|
|
31
|
+
|
|
32
|
+
$pointerText = Read-Raw $pointerPath
|
|
33
|
+
$pointer = $pointerText | ConvertFrom-Json
|
|
34
|
+
if ([string]$pointer.schema -ne 'deadbyte.active-release.v1') { Fail 'active pointer schema mismatch' }
|
|
35
|
+
$oldGeneration = [System.IO.Path]::GetFullPath([string]$pointer.generation_root)
|
|
36
|
+
$oldManifest = Join-Path $oldGeneration 'GENERATION.SHA256'
|
|
37
|
+
if (-not (Test-Path -LiteralPath $oldManifest -PathType Leaf)) { Fail 'old generation manifest missing' }
|
|
38
|
+
$oldManifestHash = Sha256-File $oldManifest
|
|
39
|
+
if ($oldManifestHash -ne [string]$pointer.generation_manifest_sha256) { Fail 'old generation manifest hash mismatch' }
|
|
40
|
+
|
|
41
|
+
$manifestLines = @(Get-Content -LiteralPath $oldManifest -Encoding UTF8)
|
|
42
|
+
$entries = @()
|
|
43
|
+
foreach ($line in $manifestLines) {
|
|
44
|
+
if ([string]::IsNullOrWhiteSpace([string]$line)) { continue }
|
|
45
|
+
if ([string]$line -notmatch '^([0-9a-f]{64}) ([A-Za-z0-9._-]+)$') { Fail "malformed old generation manifest line: $line" }
|
|
46
|
+
$expected = [string]$Matches[1]
|
|
47
|
+
$name = [string]$Matches[2]
|
|
48
|
+
$path = Join-Path $oldGeneration $name
|
|
49
|
+
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { Fail "old generation file missing: $name" }
|
|
50
|
+
$actual = Sha256-File $path
|
|
51
|
+
if ($actual -ne $expected) { Fail "old generation hash mismatch: $name" }
|
|
52
|
+
$entries += [pscustomobject]@{ Name=$name; Expected=$expected }
|
|
53
|
+
}
|
|
54
|
+
$names = @($entries | ForEach-Object { $_.Name })
|
|
55
|
+
foreach ($required in @('coding-policy.json','autonomous-policy.json','controller-config.psd1')) {
|
|
56
|
+
if ($required -notin $names) { Fail "required generation file not manifested: $required" }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$oldCodingPath = Join-Path $oldGeneration 'coding-policy.json'
|
|
60
|
+
$oldAutonomousPath = Join-Path $oldGeneration 'autonomous-policy.json'
|
|
61
|
+
$oldCodingHash = Sha256-File $oldCodingPath
|
|
62
|
+
$oldAutonomousHash = Sha256-File $oldAutonomousPath
|
|
63
|
+
|
|
64
|
+
$coding = (Read-Raw $oldCodingPath) | ConvertFrom-Json
|
|
65
|
+
if ([string]$coding.schema -ne 'deadbyte.coding-policy.v1' -or $coding.enabled -ne $true) { Fail 'coding policy schema/enabled mismatch' }
|
|
66
|
+
if ($null -eq $coding.roots) { Fail 'coding roots missing' }
|
|
67
|
+
|
|
68
|
+
$localRoot = [pscustomobject][ordered]@{
|
|
69
|
+
path = 'D:\'
|
|
70
|
+
read = $true
|
|
71
|
+
write = $true
|
|
72
|
+
exec = $true
|
|
73
|
+
deny_paths = @()
|
|
74
|
+
}
|
|
75
|
+
if ($coding.roots.PSObject.Properties.Name -contains 'local_d') {
|
|
76
|
+
$coding.roots.local_d = $localRoot
|
|
77
|
+
} else {
|
|
78
|
+
$coding.roots | Add-Member -NotePropertyName 'local_d' -NotePropertyValue $localRoot
|
|
79
|
+
}
|
|
80
|
+
foreach ($commandProp in @($coding.commands.PSObject.Properties)) {
|
|
81
|
+
$command = $commandProp.Value
|
|
82
|
+
if ($command.PSObject.Properties.Name -contains 'root_ids') {
|
|
83
|
+
$ids = @($command.root_ids)
|
|
84
|
+
if ('local_d' -notin $ids) { $command.root_ids = @($ids + 'local_d') }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
$codingText = ($coding | ConvertTo-Json -Depth 32) + [Environment]::NewLine
|
|
88
|
+
$newCodingHash = Sha256-Text $codingText
|
|
89
|
+
|
|
90
|
+
$autonomous = (Read-Raw $oldAutonomousPath) | ConvertFrom-Json
|
|
91
|
+
if ([string]$autonomous.schema -ne 'deadbyte.autonomous-policy.v1' -or $autonomous.enabled -ne $true) { Fail 'autonomous policy schema/enabled mismatch' }
|
|
92
|
+
$autonomous.coding_policy_sha256 = $newCodingHash
|
|
93
|
+
$autonomousText = ($autonomous | ConvertTo-Json -Depth 32) + [Environment]::NewLine
|
|
94
|
+
$newAutonomousHash = Sha256-Text $autonomousText
|
|
95
|
+
|
|
96
|
+
$seed = $oldManifestHash + [Environment]::NewLine + $newCodingHash + [Environment]::NewLine + $newAutonomousHash + [Environment]::NewLine + 'local_d=D:\'
|
|
97
|
+
$generationSuffix = (Sha256-Text $seed).Substring(0,16)
|
|
98
|
+
$newGenerationId = ([string]$pointer.release_id) + '-g' + $generationSuffix
|
|
99
|
+
$generationStore = Join-Path $stateRoot 'generations'
|
|
100
|
+
$newGeneration = Join-Path $generationStore $newGenerationId
|
|
101
|
+
if (Test-Path -LiteralPath $newGeneration) { Fail "target generation already exists: $newGeneration" }
|
|
102
|
+
New-Item -ItemType Directory -Path $newGeneration | Out-Null
|
|
103
|
+
|
|
104
|
+
$pointerBackup = Join-Path $runtimeRoot 'active-release.before-local-d.json'
|
|
105
|
+
$codingGrantPath = Join-Path $runtimeRoot 'coding-grant.json'
|
|
106
|
+
$autonomousGrantPath = Join-Path $runtimeRoot 'autonomous-grant.json'
|
|
107
|
+
$hadCodingGrant = Test-Path -LiteralPath $codingGrantPath -PathType Leaf
|
|
108
|
+
$hadAutonomousGrant = Test-Path -LiteralPath $autonomousGrantPath -PathType Leaf
|
|
109
|
+
$oldCodingGrantText = if ($hadCodingGrant) { Read-Raw $codingGrantPath } else { $null }
|
|
110
|
+
$oldAutonomousGrantText = if ($hadAutonomousGrant) { Read-Raw $autonomousGrantPath } else { $null }
|
|
111
|
+
$wasAutonomousArmed = $false
|
|
112
|
+
if ($hadCodingGrant -and $hadAutonomousGrant) {
|
|
113
|
+
try {
|
|
114
|
+
$cg = $oldCodingGrantText | ConvertFrom-Json
|
|
115
|
+
$ag = $oldAutonomousGrantText | ConvertFrom-Json
|
|
116
|
+
$wasAutonomousArmed =
|
|
117
|
+
([string]$cg.policy_sha256 -eq $oldCodingHash) -and
|
|
118
|
+
([string]$ag.policy_sha256 -eq $oldAutonomousHash) -and
|
|
119
|
+
([string]$ag.coding_policy_sha256 -eq $oldCodingHash)
|
|
120
|
+
} catch { $wasAutonomousArmed = $false }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
$switched = $false
|
|
124
|
+
try {
|
|
125
|
+
foreach ($entry in $entries) {
|
|
126
|
+
$src = Join-Path $oldGeneration $entry.Name
|
|
127
|
+
$dst = Join-Path $newGeneration $entry.Name
|
|
128
|
+
Copy-Item -LiteralPath $src -Destination $dst
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Write-Utf8NoBom (Join-Path $newGeneration 'coding-policy.json') $codingText
|
|
132
|
+
Write-Utf8NoBom (Join-Path $newGeneration 'autonomous-policy.json') $autonomousText
|
|
133
|
+
|
|
134
|
+
$configPath = Join-Path $newGeneration 'controller-config.psd1'
|
|
135
|
+
$configText = Read-Raw $configPath
|
|
136
|
+
if ($configText.IndexOf($oldGeneration,[System.StringComparison]::OrdinalIgnoreCase) -lt 0) { Fail 'controller config is not bound to old generation root' }
|
|
137
|
+
$configText = $configText.Replace($oldGeneration,$newGeneration)
|
|
138
|
+
Write-Utf8NoBom $configPath $configText
|
|
139
|
+
$cfg = Import-PowerShellDataFile -LiteralPath $configPath
|
|
140
|
+
if ([System.IO.Path]::GetFullPath([string]$cfg.CodingPolicyFile) -ne [System.IO.Path]::GetFullPath((Join-Path $newGeneration 'coding-policy.json'))) { Fail 'new controller CodingPolicyFile binding mismatch' }
|
|
141
|
+
if ([System.IO.Path]::GetFullPath([string]$cfg.AutonomousPolicyFile) -ne [System.IO.Path]::GetFullPath((Join-Path $newGeneration 'autonomous-policy.json'))) { Fail 'new controller AutonomousPolicyFile binding mismatch' }
|
|
142
|
+
|
|
143
|
+
foreach ($entry in $entries) {
|
|
144
|
+
$p = Join-Path $newGeneration $entry.Name
|
|
145
|
+
$text = $null
|
|
146
|
+
try { $text = Read-Raw $p } catch { $text = $null }
|
|
147
|
+
if ($null -ne $text -and $entry.Name -ne 'controller-config.psd1' -and $text.IndexOf($oldGeneration,[System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
|
|
148
|
+
Fail "unexpected old generation binding remains in $($entry.Name)"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
$newManifestLines = @()
|
|
153
|
+
foreach ($entry in $entries) {
|
|
154
|
+
$p = Join-Path $newGeneration $entry.Name
|
|
155
|
+
$newManifestLines += ((Sha256-File $p) + ' ' + $entry.Name)
|
|
156
|
+
}
|
|
157
|
+
$newManifestText = ($newManifestLines -join [Environment]::NewLine) + [Environment]::NewLine
|
|
158
|
+
$newManifestPath = Join-Path $newGeneration 'GENERATION.SHA256'
|
|
159
|
+
Write-Utf8NoBom $newManifestPath $newManifestText
|
|
160
|
+
$newManifestHash = Sha256-File $newManifestPath
|
|
161
|
+
|
|
162
|
+
foreach ($line in $newManifestLines) {
|
|
163
|
+
if ($line -notmatch '^([0-9a-f]{64}) ([A-Za-z0-9._-]+)$') { Fail 'new manifest internal format error' }
|
|
164
|
+
$actual = Sha256-File (Join-Path $newGeneration ([string]$Matches[2]))
|
|
165
|
+
if ($actual -ne [string]$Matches[1]) { Fail "new generation verification failed: $($Matches[2])" }
|
|
166
|
+
}
|
|
167
|
+
if ((Sha256-File (Join-Path $newGeneration 'coding-policy.json')) -ne $newCodingHash) { Fail 'new coding hash self-check failed' }
|
|
168
|
+
if ((Sha256-File (Join-Path $newGeneration 'autonomous-policy.json')) -ne $newAutonomousHash) { Fail 'new autonomous hash self-check failed' }
|
|
169
|
+
|
|
170
|
+
Write-Utf8NoBom $pointerBackup $pointerText
|
|
171
|
+
$pointer.generation_root = $newGeneration
|
|
172
|
+
$pointer.config_path = Join-Path $newGeneration 'controller-config.psd1'
|
|
173
|
+
$pointer.generation_manifest_sha256 = $newManifestHash
|
|
174
|
+
if ($pointer.PSObject.Properties.Name -contains 'generation_id') { $pointer.generation_id = $newGenerationId }
|
|
175
|
+
if ($pointer.PSObject.Properties.Name -contains 'generation_manifest_path') { $pointer.generation_manifest_path = $newManifestPath }
|
|
176
|
+
if ($pointer.PSObject.Properties.Name -contains 'updated_at_utc') { $pointer.updated_at_utc = [DateTime]::UtcNow.ToString('o') }
|
|
177
|
+
$newPointerText = ($pointer | ConvertTo-Json -Depth 32) + [Environment]::NewLine
|
|
178
|
+
$pointerTmp = $pointerPath + '.local-d.tmp'
|
|
179
|
+
Write-Utf8NoBom $pointerTmp $newPointerText
|
|
180
|
+
Move-Item -LiteralPath $pointerTmp -Destination $pointerPath -Force
|
|
181
|
+
$switched = $true
|
|
182
|
+
|
|
183
|
+
$statusText = (& $launcher status 2>&1 | Out-String)
|
|
184
|
+
if ($LASTEXITCODE -ne 0) { Fail "launcher status failed after pointer switch: $statusText" }
|
|
185
|
+
|
|
186
|
+
$armCodingText = (& $launcher armcoding 2>&1 | Out-String)
|
|
187
|
+
if ($LASTEXITCODE -ne 0) { Fail "armcoding failed after pointer switch: $armCodingText" }
|
|
188
|
+
|
|
189
|
+
$armAutonomousText = ''
|
|
190
|
+
if ($wasAutonomousArmed) {
|
|
191
|
+
$armAutonomousText = (& $launcher armautonomous 2>&1 | Out-String)
|
|
192
|
+
if ($LASTEXITCODE -ne 0) { Fail "armautonomous failed after pointer switch: $armAutonomousText" }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Write-Output 'LOCAL_D_PROMOTION=PASS'
|
|
196
|
+
Write-Output ('OLD_GENERATION=' + $oldGeneration)
|
|
197
|
+
Write-Output ('NEW_GENERATION=' + $newGeneration)
|
|
198
|
+
Write-Output ('OLD_GENERATION_MANIFEST_SHA256=' + $oldManifestHash)
|
|
199
|
+
Write-Output ('NEW_GENERATION_MANIFEST_SHA256=' + $newManifestHash)
|
|
200
|
+
Write-Output ('OLD_CODING_POLICY_SHA256=' + $oldCodingHash)
|
|
201
|
+
Write-Output ('NEW_CODING_POLICY_SHA256=' + $newCodingHash)
|
|
202
|
+
Write-Output ('NEW_AUTONOMOUS_POLICY_SHA256=' + $newAutonomousHash)
|
|
203
|
+
Write-Output ('AUTONOMOUS_REARMED=' + $wasAutonomousArmed)
|
|
204
|
+
Write-Output ('ROLLBACK_POINTER=' + $pointerBackup)
|
|
205
|
+
Write-Output 'RESTART_REQUIRED=YES'
|
|
206
|
+
} catch {
|
|
207
|
+
$message = $_.Exception.Message
|
|
208
|
+
if ($switched) {
|
|
209
|
+
try { Write-Utf8NoBom $pointerPath $pointerText } catch {}
|
|
210
|
+
if ($hadCodingGrant) { try { Write-Utf8NoBom $codingGrantPath $oldCodingGrantText } catch {} }
|
|
211
|
+
else { try { Remove-Item -LiteralPath $codingGrantPath -Force -ErrorAction SilentlyContinue } catch {} }
|
|
212
|
+
if ($hadAutonomousGrant) { try { Write-Utf8NoBom $autonomousGrantPath $oldAutonomousGrantText } catch {} }
|
|
213
|
+
else { try { Remove-Item -LiteralPath $autonomousGrantPath -Force -ErrorAction SilentlyContinue } catch {} }
|
|
214
|
+
}
|
|
215
|
+
try { if (Test-Path -LiteralPath $newGeneration) { Remove-Item -LiteralPath $newGeneration -Recurse -Force } } catch {}
|
|
216
|
+
Fail $message
|
|
217
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
$ErrorActionPreference='Stop'
|
|
2
|
+
Set-StrictMode -Version Latest
|
|
3
|
+
$Root=Split-Path -Parent $PSScriptRoot
|
|
4
|
+
Push-Location $Root
|
|
5
|
+
try {
|
|
6
|
+
& node --test --test-concurrency=1 test/r48-release-contract.test.mjs test/release-version.test.mjs test/cli-entrypoint.test.mjs test/package-boundary.test.mjs test/release-smoke-isolation.test.mjs test/full-max-catalog-r48.test.mjs
|
|
7
|
+
if($LASTEXITCODE -ne 0){throw "R48 post-bump release checks failed exit=$LASTEXITCODE"}
|
|
8
|
+
} finally { Pop-Location }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
$ErrorActionPreference='Stop'
|
|
2
|
+
Set-StrictMode -Version Latest
|
|
3
|
+
$Root=Split-Path -Parent $PSScriptRoot
|
|
4
|
+
Push-Location $Root
|
|
5
|
+
try {
|
|
6
|
+
& node --test test/machine-fs-runtime.test.mjs test/windows-observation-r48.test.mjs test/windows-observation-surface-r48.test.mjs test/hardware-observation-r45.test.mjs test/observation-mcp-tools.test.mjs test/compact-gateway-r36.test.mjs test/capability-resolver.test.mjs test/compact-catalog-r36.test.mjs test/compact-max-catalog-r45.test.mjs test/full-max-catalog-r45.test.mjs test/full-max-catalog-r46.test.mjs test/full-max-catalog-r47.test.mjs test/full-max-catalog-r48.test.mjs
|
|
7
|
+
if($LASTEXITCODE -ne 0){throw "R48 focused regression failed exit=$LASTEXITCODE"}
|
|
8
|
+
} finally { Pop-Location }
|