@namewta/speculo 1.0.20 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/package.json +1 -1
- package/template/workflows/ops/D-project-deploy/D-project-deploy.md +1 -1
- package/template/workflows/ops/H-host-manage/H-host-manage.md +4 -0
- package/template/workflows/ops/H-host-manage/references/host-profiles.md +4 -0
- package/template/workflows/ops/I-initialize/I-initialize.md +1 -1
- package/template/workflows/ops/INDEX.md +2 -0
- package/template/workflows/ops/README.md +7 -3
- package/template/workflows/ops/S-server-connect/S-server-connect.md +5 -5
- package/template/workflows/ops/V-inventory-view/V-inventory-view.md +2 -0
- package/template/workflows/ops/common/CAPABILITIES.md +2 -0
- package/template/workflows/ops/common/USAGE.md +5 -5
- package/template/workflows/ops/common/WORKSPACE-USAGE.md +29 -2
- package/template/workflows/ops/common/examples/README.md +1 -1
- package/template/workflows/ops/common/examples/register.example.json +7 -3
- package/template/workflows/ops/common/examples/server-connection.example.json +11 -0
- package/template/workflows/ops/common/rules/deployment-layout.md +1 -1
- package/template/workflows/ops/common/rules/host-root-and-onboarding.md +47 -0
- package/template/workflows/ops/common/rules/persistence-and-secrets.md +1 -1
- package/template/workflows/ops/common/rules/research-and-decisions.md +8 -0
- package/template/workflows/ops/common/rules/server-onboarding.md +3 -3
- package/template/workflows/ops/common/rules/workspace-and-authorization.md +2 -2
- package/template/workflows/ops/common/schemas/fleet-view.schema.json +64 -0
- package/template/workflows/ops/common/schemas/host.schema.json +19 -0
- package/template/workflows/ops/common/schemas/plan.schema.json +57 -0
- package/template/workflows/ops/common/schemas/root-confirmation.schema.json +118 -0
- package/template/workflows/ops/common/schemas/server-connection.schema.json +67 -0
- package/template/workflows/ops/common/schemas/server-discovery.schema.json +171 -0
- package/template/workflows/ops/common/schemas/spec.schema.json +19 -0
- package/template/workflows/ops/common/schemas/status.schema.json +19 -0
- package/template/workflows/ops/common/templates/FLEET.html +1 -1
- package/template/workflows/ops/common/templates/HOST-README.md +3 -1
- package/template/workflows/ops/common/tests/test_ops.mjs +3 -3
- package/template/workflows/ops/common/tests/test_ops_bootstrap.mjs +30 -2
- package/template/workflows/ops/common/tests/test_ops_onboarding.mjs +225 -0
- package/template/workflows/ops/common/tools/demo-local.mjs +4 -0
- package/template/workflows/ops/common/tools/opslib/agent.mjs +26 -0
- package/template/workflows/ops/common/tools/opslib/bootstrap.mjs +50 -24
- package/template/workflows/ops/common/tools/opslib/cli.mjs +1 -0
- package/template/workflows/ops/common/tools/opslib/core.mjs +1 -1
- package/template/workflows/ops/common/tools/opslib/docs.mjs +1 -0
- package/template/workflows/ops/common/tools/opslib/execution.mjs +4 -0
- package/template/workflows/ops/common/tools/opslib/fleet.mjs +26 -4
- package/template/workflows/ops/common/tools/opslib/host_recipes.mjs +34 -0
- package/template/workflows/ops/common/tools/opslib/model.mjs +6 -0
- package/template/workflows/ops/common/tools/opslib/onboarding.mjs +215 -0
- package/template/workflows/ops/common/tools/opslib/planner.mjs +3 -0
- package/template/workflows/ops/common/tools/opslib/transport.mjs +7 -3
- package/template/workflows/ops/common/tools/opslib/workspace_cli.mjs +20 -1
- package/template/workflows/ops/common/tools/server-discover.sh +65 -0
- package/template/workflows/ops/common/tools/validate-ops.mjs +1 -1
- package/template/workflows/ops/manifest.json +12 -5
- package/template/skills/optimize-codex-config/SKILL.md +0 -81
- package/template/skills/optimize-codex-config/references/configuration-contract.md +0 -103
- package/template/skills/optimize-codex-config/references/troubleshooting.md +0 -79
- package/template/skills/optimize-codex-config/scripts/audit-codex-config.mjs +0 -747
|
@@ -242,6 +242,25 @@
|
|
|
242
242
|
],
|
|
243
243
|
"additionalProperties": false
|
|
244
244
|
}
|
|
245
|
+
},
|
|
246
|
+
"root_confirmation": {
|
|
247
|
+
"type": "object",
|
|
248
|
+
"properties": {
|
|
249
|
+
"receipt_id": {
|
|
250
|
+
"type": "string",
|
|
251
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
252
|
+
"maxLength": 80
|
|
253
|
+
},
|
|
254
|
+
"digest": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
"required": [
|
|
260
|
+
"receipt_id",
|
|
261
|
+
"digest"
|
|
262
|
+
],
|
|
263
|
+
"additionalProperties": false
|
|
245
264
|
}
|
|
246
265
|
},
|
|
247
266
|
"required": [
|
|
@@ -717,6 +736,25 @@
|
|
|
717
736
|
],
|
|
718
737
|
"additionalProperties": false
|
|
719
738
|
}
|
|
739
|
+
},
|
|
740
|
+
"root_confirmation": {
|
|
741
|
+
"type": "object",
|
|
742
|
+
"properties": {
|
|
743
|
+
"receipt_id": {
|
|
744
|
+
"type": "string",
|
|
745
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
746
|
+
"maxLength": 80
|
|
747
|
+
},
|
|
748
|
+
"digest": {
|
|
749
|
+
"type": "string",
|
|
750
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
"required": [
|
|
754
|
+
"receipt_id",
|
|
755
|
+
"digest"
|
|
756
|
+
],
|
|
757
|
+
"additionalProperties": false
|
|
720
758
|
}
|
|
721
759
|
},
|
|
722
760
|
"required": [
|
|
@@ -1408,6 +1446,25 @@
|
|
|
1408
1446
|
],
|
|
1409
1447
|
"additionalProperties": false
|
|
1410
1448
|
}
|
|
1449
|
+
},
|
|
1450
|
+
"root_confirmation": {
|
|
1451
|
+
"type": "object",
|
|
1452
|
+
"properties": {
|
|
1453
|
+
"receipt_id": {
|
|
1454
|
+
"type": "string",
|
|
1455
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
1456
|
+
"maxLength": 80
|
|
1457
|
+
},
|
|
1458
|
+
"digest": {
|
|
1459
|
+
"type": "string",
|
|
1460
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
"required": [
|
|
1464
|
+
"receipt_id",
|
|
1465
|
+
"digest"
|
|
1466
|
+
],
|
|
1467
|
+
"additionalProperties": false
|
|
1411
1468
|
}
|
|
1412
1469
|
},
|
|
1413
1470
|
"required": [
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:speculo:ops:root-confirmation:v1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"schema_version": {
|
|
7
|
+
"const": 1
|
|
8
|
+
},
|
|
9
|
+
"artifact": {
|
|
10
|
+
"const": "ops-root-confirmation"
|
|
11
|
+
},
|
|
12
|
+
"receipt_id": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
15
|
+
"maxLength": 80
|
|
16
|
+
},
|
|
17
|
+
"host_id": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
20
|
+
"maxLength": 80
|
|
21
|
+
},
|
|
22
|
+
"platform": {
|
|
23
|
+
"enum": [
|
|
24
|
+
"linux",
|
|
25
|
+
"windows",
|
|
26
|
+
"darwin"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"transport": {
|
|
30
|
+
"enum": [
|
|
31
|
+
"local",
|
|
32
|
+
"ssh"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"identity": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
38
|
+
},
|
|
39
|
+
"root": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1
|
|
42
|
+
},
|
|
43
|
+
"account": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"username": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"minLength": 1
|
|
49
|
+
},
|
|
50
|
+
"uid": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
},
|
|
53
|
+
"home": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"minLength": 1
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"required": [
|
|
59
|
+
"username",
|
|
60
|
+
"uid",
|
|
61
|
+
"home"
|
|
62
|
+
],
|
|
63
|
+
"additionalProperties": false
|
|
64
|
+
},
|
|
65
|
+
"endpoint_digest": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
68
|
+
},
|
|
69
|
+
"discovery": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"properties": {
|
|
72
|
+
"receipt_id": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
75
|
+
"maxLength": 80
|
|
76
|
+
},
|
|
77
|
+
"digest": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": [
|
|
83
|
+
"receipt_id",
|
|
84
|
+
"digest"
|
|
85
|
+
],
|
|
86
|
+
"additionalProperties": false
|
|
87
|
+
},
|
|
88
|
+
"confirmed_at": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"minLength": 1
|
|
91
|
+
},
|
|
92
|
+
"by": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"minLength": 1
|
|
95
|
+
},
|
|
96
|
+
"statement": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"minLength": 1
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"required": [
|
|
102
|
+
"schema_version",
|
|
103
|
+
"artifact",
|
|
104
|
+
"receipt_id",
|
|
105
|
+
"host_id",
|
|
106
|
+
"platform",
|
|
107
|
+
"transport",
|
|
108
|
+
"identity",
|
|
109
|
+
"root",
|
|
110
|
+
"account",
|
|
111
|
+
"endpoint_digest",
|
|
112
|
+
"discovery",
|
|
113
|
+
"confirmed_at",
|
|
114
|
+
"by",
|
|
115
|
+
"statement"
|
|
116
|
+
],
|
|
117
|
+
"additionalProperties": false
|
|
118
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:speculo:ops:server-connection:v1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"platform": {
|
|
7
|
+
"enum": [
|
|
8
|
+
"linux",
|
|
9
|
+
"windows",
|
|
10
|
+
"darwin"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"transport": {
|
|
14
|
+
"enum": [
|
|
15
|
+
"local",
|
|
16
|
+
"ssh"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"connection": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"hostname": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"minLength": 1
|
|
25
|
+
},
|
|
26
|
+
"username": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"minLength": 1
|
|
29
|
+
},
|
|
30
|
+
"port": {
|
|
31
|
+
"type": "integer",
|
|
32
|
+
"minimum": 1,
|
|
33
|
+
"maximum": 65535
|
|
34
|
+
},
|
|
35
|
+
"known_hosts": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"minLength": 1
|
|
38
|
+
},
|
|
39
|
+
"identity_file": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1
|
|
42
|
+
},
|
|
43
|
+
"node": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 1
|
|
46
|
+
},
|
|
47
|
+
"sudo": {
|
|
48
|
+
"type": "boolean"
|
|
49
|
+
},
|
|
50
|
+
"shell": {
|
|
51
|
+
"enum": [
|
|
52
|
+
"posix",
|
|
53
|
+
"powershell"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"required": [],
|
|
58
|
+
"additionalProperties": false
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"required": [
|
|
62
|
+
"platform",
|
|
63
|
+
"transport",
|
|
64
|
+
"connection"
|
|
65
|
+
],
|
|
66
|
+
"additionalProperties": false
|
|
67
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:speculo:ops:server-discovery:v1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"schema_version": {
|
|
7
|
+
"const": 1
|
|
8
|
+
},
|
|
9
|
+
"artifact": {
|
|
10
|
+
"const": "ops-server-discovery"
|
|
11
|
+
},
|
|
12
|
+
"discovery_id": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
15
|
+
"maxLength": 80
|
|
16
|
+
},
|
|
17
|
+
"host_id": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
20
|
+
"maxLength": 80
|
|
21
|
+
},
|
|
22
|
+
"discovered_at": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"minLength": 1
|
|
25
|
+
},
|
|
26
|
+
"endpoint": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"host_id": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
32
|
+
"maxLength": 80
|
|
33
|
+
},
|
|
34
|
+
"platform": {
|
|
35
|
+
"enum": [
|
|
36
|
+
"linux",
|
|
37
|
+
"windows",
|
|
38
|
+
"darwin"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"transport": {
|
|
42
|
+
"enum": [
|
|
43
|
+
"local",
|
|
44
|
+
"ssh"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"connection": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"hostname": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"minLength": 1
|
|
53
|
+
},
|
|
54
|
+
"username": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"minLength": 1
|
|
57
|
+
},
|
|
58
|
+
"port": {
|
|
59
|
+
"type": "integer",
|
|
60
|
+
"minimum": 1,
|
|
61
|
+
"maximum": 65535
|
|
62
|
+
},
|
|
63
|
+
"known_hosts": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"minLength": 1
|
|
66
|
+
},
|
|
67
|
+
"identity_file": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"minLength": 1
|
|
70
|
+
},
|
|
71
|
+
"node": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"minLength": 1
|
|
74
|
+
},
|
|
75
|
+
"sudo": {
|
|
76
|
+
"type": "boolean"
|
|
77
|
+
},
|
|
78
|
+
"shell": {
|
|
79
|
+
"enum": [
|
|
80
|
+
"posix",
|
|
81
|
+
"powershell"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"required": [],
|
|
86
|
+
"additionalProperties": false
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"required": [
|
|
90
|
+
"host_id",
|
|
91
|
+
"platform",
|
|
92
|
+
"transport",
|
|
93
|
+
"connection"
|
|
94
|
+
],
|
|
95
|
+
"additionalProperties": false
|
|
96
|
+
},
|
|
97
|
+
"endpoint_digest": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
100
|
+
},
|
|
101
|
+
"facts": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"properties": {
|
|
104
|
+
"identity": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
107
|
+
},
|
|
108
|
+
"platform": {
|
|
109
|
+
"enum": [
|
|
110
|
+
"linux",
|
|
111
|
+
"windows",
|
|
112
|
+
"darwin"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
"username": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"minLength": 1
|
|
118
|
+
},
|
|
119
|
+
"uid": {
|
|
120
|
+
"type": "string"
|
|
121
|
+
},
|
|
122
|
+
"home": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"minLength": 1
|
|
125
|
+
},
|
|
126
|
+
"node": {
|
|
127
|
+
"type": [
|
|
128
|
+
"string",
|
|
129
|
+
"null"
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"node_version": {
|
|
133
|
+
"type": [
|
|
134
|
+
"string",
|
|
135
|
+
"null"
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"required": [
|
|
140
|
+
"identity",
|
|
141
|
+
"platform",
|
|
142
|
+
"username",
|
|
143
|
+
"uid",
|
|
144
|
+
"home",
|
|
145
|
+
"node",
|
|
146
|
+
"node_version"
|
|
147
|
+
],
|
|
148
|
+
"additionalProperties": false
|
|
149
|
+
},
|
|
150
|
+
"suggested_root": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"minLength": 1
|
|
153
|
+
},
|
|
154
|
+
"registered": {
|
|
155
|
+
"type": "boolean"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"required": [
|
|
159
|
+
"schema_version",
|
|
160
|
+
"artifact",
|
|
161
|
+
"discovery_id",
|
|
162
|
+
"host_id",
|
|
163
|
+
"discovered_at",
|
|
164
|
+
"endpoint",
|
|
165
|
+
"endpoint_digest",
|
|
166
|
+
"facts",
|
|
167
|
+
"suggested_root",
|
|
168
|
+
"registered"
|
|
169
|
+
],
|
|
170
|
+
"additionalProperties": false
|
|
171
|
+
}
|
|
@@ -160,6 +160,25 @@
|
|
|
160
160
|
],
|
|
161
161
|
"additionalProperties": false
|
|
162
162
|
}
|
|
163
|
+
},
|
|
164
|
+
"root_confirmation": {
|
|
165
|
+
"type": "object",
|
|
166
|
+
"properties": {
|
|
167
|
+
"receipt_id": {
|
|
168
|
+
"type": "string",
|
|
169
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
170
|
+
"maxLength": 80
|
|
171
|
+
},
|
|
172
|
+
"digest": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"required": [
|
|
178
|
+
"receipt_id",
|
|
179
|
+
"digest"
|
|
180
|
+
],
|
|
181
|
+
"additionalProperties": false
|
|
163
182
|
}
|
|
164
183
|
},
|
|
165
184
|
"required": [
|
|
@@ -362,6 +362,25 @@
|
|
|
362
362
|
],
|
|
363
363
|
"additionalProperties": false
|
|
364
364
|
}
|
|
365
|
+
},
|
|
366
|
+
"root_confirmation": {
|
|
367
|
+
"type": "object",
|
|
368
|
+
"properties": {
|
|
369
|
+
"receipt_id": {
|
|
370
|
+
"type": "string",
|
|
371
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
372
|
+
"maxLength": 80
|
|
373
|
+
},
|
|
374
|
+
"digest": {
|
|
375
|
+
"type": "string",
|
|
376
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
"required": [
|
|
380
|
+
"receipt_id",
|
|
381
|
+
"digest"
|
|
382
|
+
],
|
|
383
|
+
"additionalProperties": false
|
|
365
384
|
}
|
|
366
385
|
},
|
|
367
386
|
"required": [
|
|
@@ -47,7 +47,7 @@ function render(){const server=$("server").value,project=$("project").value,st=$
|
|
|
47
47
|
const ds=data.deployments.filter(d=>(!server||d.server_id===server)&&(!project||d.project_id===project)&&(!st||d.status===st)&&match(d));
|
|
48
48
|
const ss=data.servers.filter(s=>(!server||s.server_id===server)&&(!project||s.deployment_ids.some(id=>byDeployment.get(id)?.project_id===project))&&(!st||ds.some(d=>d.server_id===s.server_id))&&(match(s)||ds.some(d=>d.server_id===s.server_id)));
|
|
49
49
|
const ps=data.projects.filter(p=>(!project||p.project_id===project)&&(!server||p.deployment_ids.some(id=>byDeployment.get(id)?.server_id===server))&&(!st||ds.some(d=>d.project_id===p.project_id))&&(match(p)||ds.some(d=>d.project_id===p.project_id)));
|
|
50
|
-
table("servers",["服务器 / 名称","地址 / 系统","检查结果","检查详情(* 必需项)","项目","
|
|
50
|
+
table("servers",["服务器 / 名称","地址 / 系统","检查结果","检查详情(* 必需项)","项目","账号 / 持久化根 / 确认 / 文档"],ss.map(s=>[links("server",[s.server_id]),`${s.name} · ${s.address}${s.port?":"+s.port:""} / ${s.platform}`,badge(s.readiness),checks(s),links("project",s.project_ids),`${s.login_account} / ${s.root} / ${s.root_confirmation.status} / ${s.initialization.documents}`]));
|
|
51
51
|
table("projects",["项目","名称","类别 / 服务类型","所在服务器","部署数量"],ps.map(p=>[link("project",p.project_id),p.name,`${p.kind} / ${p.service_type||"—"}`,links("server",p.server_ids),p.deployment_ids.length]));
|
|
52
52
|
table("deployments",["部署实例","项目 / 服务器","环境 / 方法","记录状态","计划版本","验证版本","根目录 / 更新时间"],ds.map(d=>{const where=node("div");where.append(link("project",d.project_id),node("div"),link("server",d.server_id));return[d.deployment_id,where,`${d.environment} / ${d.instance} / ${d.method}`,badge(d.status),d.desired_version,d.observed_version,`${d.root} / ${d.updated_at||"未验证"}`];}));
|
|
53
53
|
table("services",["服务器","主机级服务","类型","systemd 单元","监听端口"],ss.flatMap(s=>s.host_services.map(v=>[link("server",s.server_id),v.service_id,v.kind,v.unit,v.port])));
|
|
@@ -67,7 +67,7 @@ function setupFixture() {
|
|
|
67
67
|
project_id: "app-a", display_name: "APP A", kind: "app", service_type: null,
|
|
68
68
|
source: { type: "local", location: join(tmp, "source"), revision: "v1" },
|
|
69
69
|
};
|
|
70
|
-
model.
|
|
70
|
+
const legacy = model.load(state); legacy.hosts[host.host_id] = host; legacy.projects[project.project_id] = project; model.save(state, legacy); // pre-upgrade v3 fixture
|
|
71
71
|
model.putCredential(state, { credential_id: "app-auth", version: 1, purpose: "Synthetic test only", values: { username: "app-user", password: PASSWORD } });
|
|
72
72
|
const spec = (version = "v1", operation = "deploy", program = null) => {
|
|
73
73
|
if (program == null) {
|
|
@@ -440,7 +440,7 @@ test("planner contracts", async (t) => {
|
|
|
440
440
|
});
|
|
441
441
|
await each("unrelated host registration does not block approval", (fx) => {
|
|
442
442
|
const [info] = fx.plan();
|
|
443
|
-
model.
|
|
443
|
+
const legacy = model.load(fx.state); legacy.hosts["node-b"] = { ...fx.host, host_id: "node-b", display_name: "Node B", root: join(fx.tmp, "server-b") }; model.save(fx.state, legacy); // preserve legacy multi-root executor coverage
|
|
444
444
|
fx.approve(info);
|
|
445
445
|
});
|
|
446
446
|
await each("touched host catalog drift blocks approval", (fx) => {
|
|
@@ -888,7 +888,7 @@ test("control file helpers", () => {
|
|
|
888
888
|
});
|
|
889
889
|
|
|
890
890
|
test("disjoint host plans apply sequentially", () => runFx((fx) => {
|
|
891
|
-
model.
|
|
891
|
+
const legacy = model.load(fx.state); legacy.hosts["node-b"] = { ...fx.host, host_id: "node-b", display_name: "Node B", root: join(fx.tmp, "server-b") }; model.save(fx.state, legacy); // preserve legacy multi-root executor coverage
|
|
892
892
|
const specA = {
|
|
893
893
|
schema_version: 1, worker: "H", operation: "maintain", hosts: ["node-a"], reason: "Host A cache fixture",
|
|
894
894
|
rollback_note: "Keep isolated cache until a separate purge is approved.",
|
|
@@ -14,6 +14,7 @@ import * as bootstrap from "../tools/opslib/bootstrap.mjs";
|
|
|
14
14
|
import { initialize, main, cliHooks } from "../tools/opslib/cli.mjs";
|
|
15
15
|
import * as agent from "../tools/opslib/agent.mjs";
|
|
16
16
|
import * as model from "../tools/opslib/model.mjs";
|
|
17
|
+
import { onboardingHooks, discoverServer, confirmServerRoot } from "../tools/opslib/onboarding.mjs";
|
|
17
18
|
|
|
18
19
|
const toolsDir = join(dirname(fileURLToPath(import.meta.url)), "../tools");
|
|
19
20
|
const scriptPath = join(toolsDir, "bootstrap-volta.sh");
|
|
@@ -258,9 +259,13 @@ function cliFixture({ executeLinux = false } = {}) {
|
|
|
258
259
|
const archives = makeArchives(tmp);
|
|
259
260
|
const prevPin = bootstrap.bootstrapHooks.loadPin;
|
|
260
261
|
bootstrap.bootstrapHooks.loadPin = () => pinFor(archives);
|
|
262
|
+
const previousObserve = onboardingHooks.observe;
|
|
263
|
+
onboardingHooks.observe = (h, root) => ({ identity: executeLinux ? agent.fingerprint() : "a".repeat(64), platform: "linux", username: h.connection.username, uid: "1000", home: "/home/ops", node: null, node_version: null, ...(root ? {root_check: {exists: false, writable: true, nonempty: false, owner: null}} : {}) });
|
|
261
264
|
return {
|
|
262
265
|
tmp, state, target, kh, archives,
|
|
266
|
+
confirm(host) { const d = discoverServer(state, host.host_id, host); const r = confirmServerRoot(state, host.host_id, d.discovery.receipt_id, host.root, "fixture-user", "Confirm only this isolated simulated target root"); host.root_confirmation = r.root_confirmation; },
|
|
263
267
|
cleanup() {
|
|
268
|
+
onboardingHooks.observe = previousObserve;
|
|
264
269
|
bootstrap.bootstrapHooks.loadPin = prevPin;
|
|
265
270
|
rmSync(tmp, { recursive: true, force: true });
|
|
266
271
|
},
|
|
@@ -279,6 +284,7 @@ test("Linux SSH bootstrap and enrollment preserve remote paths on every controll
|
|
|
279
284
|
const mock = mockTransport({ probeJson: missingProbe, applyJson: { status: "installed", connection_node: connectionNode, profile_unchanged: true } });
|
|
280
285
|
try {
|
|
281
286
|
const host = sshHost({ root: fx.target, kh: fx.kh, node: "node" });
|
|
287
|
+
fx.confirm(host);
|
|
282
288
|
const conn = writeConn(fx.tmp, host);
|
|
283
289
|
const r = captureMain(["bootstrap-node", "--state", fx.state, "--host-id", host.host_id, "--connection-file", conn, "--apply", "--account", "ops", "--host-root", fx.target, "--ack", ACK,
|
|
284
290
|
"--volta-archive", fx.archives.voltaTar, "--volta-sha256", fx.archives.voltaSha, "--node-archive", fx.archives.nodeTar, "--node-sha256", fx.archives.nodeSha]);
|
|
@@ -290,6 +296,15 @@ test("Linux SSH bootstrap and enrollment preserve remote paths on every controll
|
|
|
290
296
|
assert.deepEqual(applyArgs.slice(applyArgs.indexOf("--apply") + 1), [paths[0], fx.archives.voltaSha, paths[1], fx.archives.nodeSha, posix.dirname(posix.dirname(connectionNode)), fx.archives.nodeVersion, ACK]);
|
|
291
297
|
assert.equal(JSON.parse(readFileSync(boot.receipt, "utf8")).connection_node, connectionNode);
|
|
292
298
|
assert.deepEqual(Object.keys(model.load(fx.state).hosts), []);
|
|
299
|
+
// A new confirmation cannot reset an interrupted bootstrap's machine/root guard.
|
|
300
|
+
fx.confirm(host); writeConn(fx.tmp, host);
|
|
301
|
+
const retryArgs = ["bootstrap-node", "--state", fx.state, "--host-id", host.host_id, "--connection-file", conn, "--apply", "--account", "ops", "--host-root", fx.target, "--ack", ACK,
|
|
302
|
+
"--volta-archive", fx.archives.voltaTar, "--node-archive", fx.archives.nodeTar];
|
|
303
|
+
const blockedRetry = captureMain(retryArgs); assert.equal(blockedRetry.code, 2); assert.match(blockedRetry.stderr, /already started/);
|
|
304
|
+
assert.equal(mock.log.filter(e => e.cmd === "scp").length, 2);
|
|
305
|
+
const recovered = captureMain([...retryArgs, "--recover-ack", "I-VERIFIED-BOOTSTRAP-IS-STOPPED", "--recovery-statement", "Fixture only: the simulated old process is stopped; retry exactly the same pinned inputs"]);
|
|
306
|
+
assert.equal(recovered.code, 0, recovered.stderr);
|
|
307
|
+
assert.ok(readdirSync(join(fx.state, "hosts", host.host_id, "bootstrap")).some(n => n.startsWith("recovery-")));
|
|
293
308
|
mock.restore();
|
|
294
309
|
host.connection.node = connectionNode;
|
|
295
310
|
const enrollFile = join(fx.tmp, "register.json"); writeFileSync(enrollFile, JSON.stringify({ hosts: [host], projects: [] }));
|
|
@@ -355,6 +370,18 @@ test("bootstrap-node apply without ack blocked", () => {
|
|
|
355
370
|
} finally { mock.restore(); fx.cleanup(); }
|
|
356
371
|
});
|
|
357
372
|
|
|
373
|
+
test("bootstrap with valid archives but no root confirmation cannot write or upload", () => {
|
|
374
|
+
const fx = cliFixture(), mock = mockTransport({ probeJson: missingProbe });
|
|
375
|
+
try {
|
|
376
|
+
const host = sshHost({ root: fx.target, kh: fx.kh, node: "node" }), conn = writeConn(fx.tmp, host);
|
|
377
|
+
const r = captureMain(["bootstrap-node", "--state", fx.state, "--host-id", host.host_id, "--connection-file", conn, "--apply", "--account", "ops", "--host-root", fx.target, "--ack", ACK,
|
|
378
|
+
"--volta-archive", fx.archives.voltaTar, "--node-archive", fx.archives.nodeTar]);
|
|
379
|
+
assert.equal(r.code, 2); assert.match(r.stderr, /root-confirm/);
|
|
380
|
+
assert.equal(mock.log.filter(e => e.cmd === "scp").length, 0);
|
|
381
|
+
assert.ok(mock.log.every(e => e.args.at(-1).includes("--probe")));
|
|
382
|
+
} finally { mock.restore(); fx.cleanup(); }
|
|
383
|
+
});
|
|
384
|
+
|
|
358
385
|
test("bootstrap-node sha mismatch blocked", () => {
|
|
359
386
|
const fx = cliFixture();
|
|
360
387
|
const mock = mockTransport({ probeJson: missingProbe });
|
|
@@ -428,6 +455,7 @@ test("bootstrap-node apply then enroll writes inventory and status", linuxExecut
|
|
|
428
455
|
const mock = mockTransport({ probeJson: missingProbe });
|
|
429
456
|
try {
|
|
430
457
|
const host = sshHost({ root: fx.target, kh: fx.kh, node: "node" });
|
|
458
|
+
fx.confirm(host);
|
|
431
459
|
const conn = writeConn(fx.tmp, host);
|
|
432
460
|
const applied = captureMain([
|
|
433
461
|
"bootstrap-node", "--state", fx.state, "--host-id", "wsl-a",
|
|
@@ -518,7 +546,7 @@ test("environmentSpec uses managed volta path", () => {
|
|
|
518
546
|
host_id: "node-a", display_name: "Node A", platform: process.platform === "win32" ? "windows" : "linux",
|
|
519
547
|
transport: "local", root: target, identity: agent.fingerprint(), connection: {},
|
|
520
548
|
};
|
|
521
|
-
model.
|
|
549
|
+
const legacy = model.load(state); legacy.hosts[host.host_id] = host; model.save(state, legacy); // existing runtime, no fabricated confirmation
|
|
522
550
|
const prev = recipes.hostRecipesHooks.call;
|
|
523
551
|
const voltaBin = core.targetJoin(host, "_host/toolchains/ops/volta/bin/volta");
|
|
524
552
|
recipes.hostRecipesHooks.call = (_h, req) => {
|
|
@@ -554,7 +582,7 @@ test("environmentSpec still throws when volta truly absent", () => {
|
|
|
554
582
|
host_id: "node-a", display_name: "Node A", platform: process.platform === "win32" ? "windows" : "linux",
|
|
555
583
|
transport: "local", root: target, identity: agent.fingerprint(), connection: {},
|
|
556
584
|
};
|
|
557
|
-
model.
|
|
585
|
+
const legacy = model.load(state); legacy.hosts[host.host_id] = host; model.save(state, legacy); // existing runtime, no fabricated confirmation
|
|
558
586
|
const prev = recipes.hostRecipesHooks.call;
|
|
559
587
|
recipes.hostRecipesHooks.call = (_h, req) => {
|
|
560
588
|
if (req.action === "snapshot") return { paths: { [req.paths[0]]: { kind: "absent" } } };
|