@mriekki/project-forge 4.6.0 → 4.8.0
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/assets/bootstrap-migrations/managed-baseline-verifier/check-managed-baselines.py +180 -0
- package/assets/bootstrap-migrations/managed-baseline-verifier/manifest.json +17 -0
- package/assets/components.json +3 -3
- package/assets/deployment/distribution.json +2 -0
- package/assets/generated/ASSEMBLY.json +22 -12
- package/assets/generated/core/core/skills/deploy/SKILL.md +124 -12
- package/assets/generated/core/core/skills/deployment-readiness/SKILL.md +89 -9
- package/assets/generated/core/core/skills/setup/SKILL.md +94 -5
- package/assets/generated/deployment/src/project_forge_deployment/access_cli.py +65 -0
- package/assets/generated/deployment/src/project_forge_deployment/access_diagnosis.py +116 -0
- package/assets/generated/deployment/src/project_forge_deployment/app_cli.py +46 -20
- package/assets/generated/deployment/src/project_forge_deployment/app_deployment.py +100 -0
- package/assets/generated/deployment/src/project_forge_deployment/entrypoint.py +4 -0
- package/assets/generated/deployment/src/project_forge_deployment/registry_access.py +135 -5
- package/assets/generated/deployment/src/project_forge_deployment/registry_cli.py +33 -4
- package/assets/generated/deployment/src/project_forge_deployment/security.py +44 -15
- package/package.json +1 -1
- package/src/bootstrap-migrations.mjs +160 -0
- package/src/cli.mjs +20 -8
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path, PurePosixPath
|
|
8
|
+
|
|
9
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def read_json(name: str) -> dict:
|
|
13
|
+
path = ROOT / name
|
|
14
|
+
try:
|
|
15
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
16
|
+
except Exception as exc:
|
|
17
|
+
raise RuntimeError(f"Unable to read {name}: {exc}") from exc
|
|
18
|
+
if not isinstance(value, dict):
|
|
19
|
+
raise RuntimeError(f"{name} must contain a JSON object")
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def safe_relative(value: object, label: str) -> str:
|
|
24
|
+
if not isinstance(value, str) or not value:
|
|
25
|
+
raise RuntimeError(f"{label} must be a non-empty relative path")
|
|
26
|
+
normalized = value.replace("\\", "/")
|
|
27
|
+
pure = PurePosixPath(normalized)
|
|
28
|
+
if pure.is_absolute() or not pure.parts or any(part in {"", ".", ".."} for part in pure.parts):
|
|
29
|
+
raise RuntimeError(f"Unsafe {label}: {value}")
|
|
30
|
+
return normalized
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sha256(path: Path) -> str:
|
|
34
|
+
digest = hashlib.sha256()
|
|
35
|
+
with path.open("rb") as handle:
|
|
36
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
37
|
+
digest.update(chunk)
|
|
38
|
+
return digest.hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def project_component_metadata(component: str, metadata: dict, schema_version: int) -> dict:
|
|
42
|
+
if schema_version == 1:
|
|
43
|
+
result = {
|
|
44
|
+
"revision": metadata.get("commit"),
|
|
45
|
+
"distribution": metadata.get("distribution"),
|
|
46
|
+
}
|
|
47
|
+
if component == "core":
|
|
48
|
+
result["version"] = metadata.get("version")
|
|
49
|
+
return result
|
|
50
|
+
if schema_version == 2:
|
|
51
|
+
result = {
|
|
52
|
+
"revision": metadata.get("revision"),
|
|
53
|
+
"distribution": metadata.get("distribution"),
|
|
54
|
+
}
|
|
55
|
+
if component == "core":
|
|
56
|
+
result["version"] = metadata.get("version")
|
|
57
|
+
return result
|
|
58
|
+
raise RuntimeError("Unsupported PROJECT_FORGE_BASELINE.json schemaVersion")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def component_baseline_metadata(component: str, baseline: dict, filename: str) -> dict:
|
|
62
|
+
schema_version = baseline.get("schemaVersion")
|
|
63
|
+
if schema_version == 1:
|
|
64
|
+
if component == "core":
|
|
65
|
+
return {
|
|
66
|
+
"revision": baseline.get("coreCommit"),
|
|
67
|
+
"version": baseline.get("coreVersion"),
|
|
68
|
+
"distribution": baseline.get("distribution"),
|
|
69
|
+
}
|
|
70
|
+
if component == "deployment":
|
|
71
|
+
return {
|
|
72
|
+
"revision": baseline.get("deploymentKitCommit"),
|
|
73
|
+
"distribution": baseline.get("distribution"),
|
|
74
|
+
}
|
|
75
|
+
elif schema_version == 2:
|
|
76
|
+
if baseline.get("component") != component:
|
|
77
|
+
raise RuntimeError(f"{filename} component does not match PROJECT_FORGE_BASELINE.json")
|
|
78
|
+
result = {
|
|
79
|
+
"revision": baseline.get("revision"),
|
|
80
|
+
"distribution": baseline.get("distribution"),
|
|
81
|
+
}
|
|
82
|
+
if component == "core":
|
|
83
|
+
result["version"] = baseline.get("version")
|
|
84
|
+
return result
|
|
85
|
+
else:
|
|
86
|
+
raise RuntimeError(f"Unsupported {filename} schemaVersion")
|
|
87
|
+
raise RuntimeError(f"Unsupported Project Forge component in generated baseline: {component}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def validate_component(
|
|
91
|
+
component: str,
|
|
92
|
+
project_forge_component: dict,
|
|
93
|
+
filename: str,
|
|
94
|
+
project_schema_version: int,
|
|
95
|
+
) -> int:
|
|
96
|
+
baseline = read_json(filename)
|
|
97
|
+
if baseline.get("schemaVersion") != project_schema_version:
|
|
98
|
+
raise RuntimeError(
|
|
99
|
+
f"{filename} schemaVersion does not match PROJECT_FORGE_BASELINE.json"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
expected = project_component_metadata(component, project_forge_component, project_schema_version)
|
|
103
|
+
actual = component_baseline_metadata(component, baseline, filename)
|
|
104
|
+
for key, expected_value in expected.items():
|
|
105
|
+
if expected_value is not None and actual.get(key) != expected_value:
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
f"{filename} {key} does not match PROJECT_FORGE_BASELINE.json"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
managed = baseline.get("managedPaths")
|
|
111
|
+
if not isinstance(managed, list) or not managed:
|
|
112
|
+
raise RuntimeError(f"{filename} managedPaths is missing or empty")
|
|
113
|
+
|
|
114
|
+
count = 0
|
|
115
|
+
seen: set[str] = set()
|
|
116
|
+
for item in managed:
|
|
117
|
+
if not isinstance(item, dict):
|
|
118
|
+
raise RuntimeError(f"Invalid managedPaths entry in {filename}")
|
|
119
|
+
relative = safe_relative(item.get("path"), f"{filename} managed path")
|
|
120
|
+
expected_hash = item.get("sha256")
|
|
121
|
+
if not isinstance(expected_hash, str) or len(expected_hash) != 64:
|
|
122
|
+
raise RuntimeError(f"Invalid managed hash in {filename}: {relative}")
|
|
123
|
+
if relative in seen:
|
|
124
|
+
raise RuntimeError(f"Duplicate managed path in {filename}: {relative}")
|
|
125
|
+
seen.add(relative)
|
|
126
|
+
path = ROOT.joinpath(*PurePosixPath(relative).parts)
|
|
127
|
+
if path.is_symlink() or not path.is_file():
|
|
128
|
+
raise RuntimeError(f"Managed file missing or unsafe: {relative}")
|
|
129
|
+
actual_hash = sha256(path)
|
|
130
|
+
if actual_hash != expected_hash:
|
|
131
|
+
raise RuntimeError(f"Managed file drift: {relative}")
|
|
132
|
+
count += 1
|
|
133
|
+
return count
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def main() -> int:
|
|
137
|
+
try:
|
|
138
|
+
manifest = read_json("PROJECT_BOOTSTRAP.json")
|
|
139
|
+
approved = (manifest.get("generation") or {}).get("project-forge") or {}
|
|
140
|
+
project_forge = read_json("PROJECT_FORGE_BASELINE.json")
|
|
141
|
+
project_schema_version = project_forge.get("schemaVersion")
|
|
142
|
+
if project_schema_version not in {1, 2}:
|
|
143
|
+
raise RuntimeError("Unsupported PROJECT_FORGE_BASELINE.json schemaVersion")
|
|
144
|
+
for key in ["package", "version", "profile"]:
|
|
145
|
+
if project_forge.get(key) != approved.get(key):
|
|
146
|
+
raise RuntimeError(
|
|
147
|
+
f"PROJECT_FORGE_BASELINE.json {key} does not match PROJECT_BOOTSTRAP.json"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
components = project_forge.get("components")
|
|
151
|
+
if not isinstance(components, dict) or not components:
|
|
152
|
+
raise RuntimeError("PROJECT_FORGE_BASELINE.json components is missing or empty")
|
|
153
|
+
|
|
154
|
+
total = 0
|
|
155
|
+
for component, metadata in sorted(components.items()):
|
|
156
|
+
if component == "core":
|
|
157
|
+
filename = "CORE_BASELINE.json"
|
|
158
|
+
elif component == "deployment":
|
|
159
|
+
filename = "DEPLOYMENT_BASELINE.json"
|
|
160
|
+
else:
|
|
161
|
+
raise RuntimeError(f"Unsupported Project Forge component: {component}")
|
|
162
|
+
if not isinstance(metadata, dict):
|
|
163
|
+
raise RuntimeError(f"Invalid Project Forge component metadata: {component}")
|
|
164
|
+
total += validate_component(
|
|
165
|
+
component,
|
|
166
|
+
metadata,
|
|
167
|
+
filename,
|
|
168
|
+
project_schema_version,
|
|
169
|
+
)
|
|
170
|
+
print(f"PASS {filename}")
|
|
171
|
+
|
|
172
|
+
print(f"PASS Project Forge managed baselines ({total} files)")
|
|
173
|
+
return 0
|
|
174
|
+
except RuntimeError as exc:
|
|
175
|
+
print(f"FAIL Project Forge managed baselines: {exc}", file=sys.stderr)
|
|
176
|
+
return 2
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "bootstrap-managed-baseline-verifier-schema2",
|
|
4
|
+
"owner": "bootstrap",
|
|
5
|
+
"destination": "tools/project-forge-package/check-managed-baselines.py",
|
|
6
|
+
"asset": "assets/bootstrap-migrations/managed-baseline-verifier/check-managed-baselines.py",
|
|
7
|
+
"source": {
|
|
8
|
+
"component": "bootstrap",
|
|
9
|
+
"revision": "b5a83729d211e0492be5368ff7f1e59b63986419",
|
|
10
|
+
"artifact": "migration-assets/package-workspace/check-managed-baselines.py"
|
|
11
|
+
},
|
|
12
|
+
"targetGitBlob": "26496854ed5d6ad47c7b6136078b2114473ac773",
|
|
13
|
+
"supportedPredecessorGitBlobs": [
|
|
14
|
+
"f7ccfc0e8efaac4d6aed6ed5d8e2130d4fb3dc9d"
|
|
15
|
+
],
|
|
16
|
+
"supportedProjectBaselineSchemas": [1, 2]
|
|
17
|
+
}
|
package/assets/components.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@mriekki/project-forge",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.8.0",
|
|
5
5
|
"defaultProfile": "greenfield-project",
|
|
6
6
|
"profiles": {
|
|
7
7
|
"greenfield-project": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"components": {
|
|
15
15
|
"core": {
|
|
16
|
-
"revision": "
|
|
16
|
+
"revision": "a78c2482f4f559ead8efbab6c25b4b05496db735",
|
|
17
17
|
"version": "4.0.0",
|
|
18
18
|
"distribution": "greenfield-workspace",
|
|
19
19
|
"descriptor": "assets/core/distribution.json",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"canonicalDescriptor": "distributions/greenfield-workspace.json"
|
|
22
22
|
},
|
|
23
23
|
"deployment": {
|
|
24
|
-
"revision": "
|
|
24
|
+
"revision": "aaf5d24c897b3d89b3d1791c79bed4979414ca40",
|
|
25
25
|
"distribution": "greenfield-project",
|
|
26
26
|
"descriptor": "assets/deployment/distribution.json",
|
|
27
27
|
"sourceRoot": "assets/generated/deployment",
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
{"source": "distribution/project-forge-deploy.py", "destination": "tools/project-forge-deployment/project-forge-deploy.py"},
|
|
7
7
|
{"source": "src/project_forge_deployment/__init__.py", "destination": "tools/project-forge-deployment/project_forge_deployment/__init__.py"},
|
|
8
8
|
{"source": "src/project_forge_deployment/__main__.py", "destination": "tools/project-forge-deployment/project_forge_deployment/__main__.py"},
|
|
9
|
+
{"source": "src/project_forge_deployment/access_cli.py", "destination": "tools/project-forge-deployment/project_forge_deployment/access_cli.py"},
|
|
10
|
+
{"source": "src/project_forge_deployment/access_diagnosis.py", "destination": "tools/project-forge-deployment/project_forge_deployment/access_diagnosis.py"},
|
|
9
11
|
{"source": "src/project_forge_deployment/app_cli.py", "destination": "tools/project-forge-deployment/project_forge_deployment/app_cli.py"},
|
|
10
12
|
{"source": "src/project_forge_deployment/app_deployment.py", "destination": "tools/project-forge-deployment/project_forge_deployment/app_deployment.py"},
|
|
11
13
|
{"source": "src/project_forge_deployment/app_runtime.py", "destination": "tools/project-forge-deployment/project_forge_deployment/app_runtime.py"},
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@mriekki/project-forge",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.8.0",
|
|
5
5
|
"components": {
|
|
6
6
|
"core": {
|
|
7
|
-
"revision": "
|
|
7
|
+
"revision": "a78c2482f4f559ead8efbab6c25b4b05496db735",
|
|
8
8
|
"version": "4.0.0",
|
|
9
9
|
"distribution": "greenfield-workspace",
|
|
10
10
|
"files": [
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
{
|
|
52
52
|
"source": "core/skills/setup/SKILL.md",
|
|
53
53
|
"destination": ".github/skills/setup/SKILL.md",
|
|
54
|
-
"sha256": "
|
|
54
|
+
"sha256": "27419e0d330abf2b6b03a696eba352ab2ece13a616c41fc0ade9aed85e8834f1"
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"source": "core/skills/build/SKILL.md",
|
|
@@ -116,12 +116,12 @@
|
|
|
116
116
|
{
|
|
117
117
|
"source": "core/skills/deployment-readiness/SKILL.md",
|
|
118
118
|
"destination": ".github/skills/deployment-readiness/SKILL.md",
|
|
119
|
-
"sha256": "
|
|
119
|
+
"sha256": "30202702cc368c872e62ce731e8ffe84a796aae243b287e3e670c8c8e1cd6531"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"source": "core/skills/deploy/SKILL.md",
|
|
123
123
|
"destination": ".github/skills/deploy/SKILL.md",
|
|
124
|
-
"sha256": "
|
|
124
|
+
"sha256": "f5d40e549ca958cb51606ced7ca482651d9a436327b6070a9a94171f22401700"
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"source": "core/skills/tear-down/SKILL.md",
|
|
@@ -326,7 +326,7 @@
|
|
|
326
326
|
]
|
|
327
327
|
},
|
|
328
328
|
"deployment": {
|
|
329
|
-
"revision": "
|
|
329
|
+
"revision": "aaf5d24c897b3d89b3d1791c79bed4979414ca40",
|
|
330
330
|
"distribution": "greenfield-project",
|
|
331
331
|
"files": [
|
|
332
332
|
{
|
|
@@ -344,15 +344,25 @@
|
|
|
344
344
|
"destination": "tools/project-forge-deployment/project_forge_deployment/__main__.py",
|
|
345
345
|
"sha256": "8742c1b766812edb07174a724d676ffeff58b71b3c192d35102a3e652e962946"
|
|
346
346
|
},
|
|
347
|
+
{
|
|
348
|
+
"source": "src/project_forge_deployment/access_cli.py",
|
|
349
|
+
"destination": "tools/project-forge-deployment/project_forge_deployment/access_cli.py",
|
|
350
|
+
"sha256": "007d01ae0c4e28e64f75fb96f9e8e7b99db5c4c8129698a78923baeac2f51953"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"source": "src/project_forge_deployment/access_diagnosis.py",
|
|
354
|
+
"destination": "tools/project-forge-deployment/project_forge_deployment/access_diagnosis.py",
|
|
355
|
+
"sha256": "92861e58a29b17758a927f7a527b089f172a1bc0c9725c79731ef07e00294af1"
|
|
356
|
+
},
|
|
347
357
|
{
|
|
348
358
|
"source": "src/project_forge_deployment/app_cli.py",
|
|
349
359
|
"destination": "tools/project-forge-deployment/project_forge_deployment/app_cli.py",
|
|
350
|
-
"sha256": "
|
|
360
|
+
"sha256": "f27704c80b1eff45888bf56745bcf8755f2fad88851147600af7056953757825"
|
|
351
361
|
},
|
|
352
362
|
{
|
|
353
363
|
"source": "src/project_forge_deployment/app_deployment.py",
|
|
354
364
|
"destination": "tools/project-forge-deployment/project_forge_deployment/app_deployment.py",
|
|
355
|
-
"sha256": "
|
|
365
|
+
"sha256": "606d6a4793ac8c8a95da4cd4c61792c69f02ad86900be1944d2ed570dd71e73e"
|
|
356
366
|
},
|
|
357
367
|
{
|
|
358
368
|
"source": "src/project_forge_deployment/app_runtime.py",
|
|
@@ -397,7 +407,7 @@
|
|
|
397
407
|
{
|
|
398
408
|
"source": "src/project_forge_deployment/entrypoint.py",
|
|
399
409
|
"destination": "tools/project-forge-deployment/project_forge_deployment/entrypoint.py",
|
|
400
|
-
"sha256": "
|
|
410
|
+
"sha256": "6b767380f502acf0763b71f46aff6efd62674e63a526ed270097efa68c320a1e"
|
|
401
411
|
},
|
|
402
412
|
{
|
|
403
413
|
"source": "src/project_forge_deployment/environment_cli.py",
|
|
@@ -492,12 +502,12 @@
|
|
|
492
502
|
{
|
|
493
503
|
"source": "src/project_forge_deployment/registry_access.py",
|
|
494
504
|
"destination": "tools/project-forge-deployment/project_forge_deployment/registry_access.py",
|
|
495
|
-
"sha256": "
|
|
505
|
+
"sha256": "1f582f1b5da656579625748b13c779f6d67ed6a6049f0ebb104ce8c3f9185a4b"
|
|
496
506
|
},
|
|
497
507
|
{
|
|
498
508
|
"source": "src/project_forge_deployment/registry_cli.py",
|
|
499
509
|
"destination": "tools/project-forge-deployment/project_forge_deployment/registry_cli.py",
|
|
500
|
-
"sha256": "
|
|
510
|
+
"sha256": "2ce977fa570580407c04a3e59d0a524f128af420b7ee20fd9706e6452378b7b3"
|
|
501
511
|
},
|
|
502
512
|
{
|
|
503
513
|
"source": "src/project_forge_deployment/route_registry.py",
|
|
@@ -512,7 +522,7 @@
|
|
|
512
522
|
{
|
|
513
523
|
"source": "src/project_forge_deployment/security.py",
|
|
514
524
|
"destination": "tools/project-forge-deployment/project_forge_deployment/security.py",
|
|
515
|
-
"sha256": "
|
|
525
|
+
"sha256": "d24d4faa0d98b2e7484e2a9e959ba4624927be61a84453ed2db150aa6132c0cf"
|
|
516
526
|
},
|
|
517
527
|
{
|
|
518
528
|
"source": "src/project_forge_deployment/sqlite_adapter.py",
|
|
@@ -44,7 +44,7 @@ Read only what is needed, including when present:
|
|
|
44
44
|
- `PROJECT_FORGE_BASELINE.json`;
|
|
45
45
|
- `DEPLOYMENT_BASELINE.json`;
|
|
46
46
|
- project topology and approved deployment/release requirements;
|
|
47
|
-
- `/deployment-readiness` evidence, including deterministic application isolation evidence when supported;
|
|
47
|
+
- `/deployment-readiness` evidence, including first-application outcome assessment and deterministic application isolation evidence when supported;
|
|
48
48
|
- fresh setup-time environment-proof evidence for this workstation;
|
|
49
49
|
- established application deployment facts.
|
|
50
50
|
|
|
@@ -62,7 +62,27 @@ Proof does not establish the real application's image/release, persistence/migra
|
|
|
62
62
|
|
|
63
63
|
A prior readiness isolation fingerprint is review evidence only. It does not authorize deployment and does not replace the deterministic deployment planner's current isolation revalidation.
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
For a first real deployment, do not collapse a target/platform `READY` result into application deployability. Confirm that readiness established an actual artifact build/publication/acquisition path when that path is material. If artifact identity or acquisition is still hypothetical, return to the missing prerequisite rather than requesting the application write gate.
|
|
66
|
+
|
|
67
|
+
## 3. Preserve execution and credential context
|
|
68
|
+
|
|
69
|
+
When installed tooling exposes structured capability evidence, preserve its semantics exactly in the deployment assessment and preview. In particular, retain fields equivalent to:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
executionLocation
|
|
73
|
+
credentialContext
|
|
74
|
+
capabilityProved
|
|
75
|
+
capabilityAttempted
|
|
76
|
+
doesNotProve
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A registry manifest check executed on a remote target proves only the capability reported for that target/context. Never describe it as workstation registry access. A workstation registry check proves only the workstation capability it reports. Neither implies Actions registry transport or package-metadata API access.
|
|
80
|
+
|
|
81
|
+
Do not infer provider-specific token scopes or error meaning from generic registry results. Provider-specific package metadata/publication behavior belongs to repository/provider tooling and its own evidence.
|
|
82
|
+
|
|
83
|
+
When a new credential is genuinely required, explain its purpose, execution context, storage location, retrievability, and rotation impact before asking the developer to create it. Credential values remain in the provider/local secret surface and never enter chat.
|
|
84
|
+
|
|
85
|
+
## 4. First real deployment asks only unresolved application questions
|
|
66
86
|
|
|
67
87
|
The first real `/deploy <environment>` after environment proof may need a small application-specific completion step. Ask only material unknowns not already answered by project evidence, such as:
|
|
68
88
|
|
|
@@ -79,7 +99,7 @@ When a bounded choice is useful, follow `INTERACTIVE_SESSION_GUIDE.md`: use host
|
|
|
79
99
|
|
|
80
100
|
Once these facts are durably established, later deployments should be routine rather than repeating onboarding.
|
|
81
101
|
|
|
82
|
-
##
|
|
102
|
+
## 5. Placement determines launcher
|
|
83
103
|
|
|
84
104
|
### Workstation container
|
|
85
105
|
|
|
@@ -114,7 +134,7 @@ Use only an installed repository/catalog provider adapter. If none exists, stop
|
|
|
114
134
|
|
|
115
135
|
Report `DEPLOYMENT NOT BOUND YET` and route to `/setup <environment>` or `/deployment-readiness <environment>`.
|
|
116
136
|
|
|
117
|
-
##
|
|
137
|
+
## 6. Preserve custom Production classification
|
|
118
138
|
|
|
119
139
|
Pass the exact configured environment name to the deterministic launcher.
|
|
120
140
|
|
|
@@ -126,7 +146,7 @@ When resolved purpose/classification is Production, use the installed tool's exp
|
|
|
126
146
|
|
|
127
147
|
Inspect installed help/version evidence. If the installed tool cannot preserve Production classification for a custom name, stop rather than downgrading safety.
|
|
128
148
|
|
|
129
|
-
##
|
|
149
|
+
## 7. Build the deterministic plan
|
|
130
150
|
|
|
131
151
|
Use repository-provided tooling to construct/inspect the exact application deployment plan. Supported operations may include:
|
|
132
152
|
|
|
@@ -149,7 +169,44 @@ The deterministic deployment tool owns the final current-state recheck immediate
|
|
|
149
169
|
|
|
150
170
|
Do not smuggle infrastructure provisioning, target/provider rebinding, destructive restore, or environment tear-down into the application plan.
|
|
151
171
|
|
|
152
|
-
|
|
172
|
+
### Remote runtime + route rollout preview
|
|
173
|
+
|
|
174
|
+
When the installed Deployment Kit exposes a deterministic read-only `app rollout` capability and the selected remote deployment has an intended route, prefer that capability for the final pre-write preview rather than independently narrating runtime and route consequences.
|
|
175
|
+
|
|
176
|
+
A current installed contract may expose a command shape similar to:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
project-forge-deploy app rollout <target>
|
|
180
|
+
--owner-repo <owner/repository>
|
|
181
|
+
--environment <exact environment>
|
|
182
|
+
--app-slug <application slug>
|
|
183
|
+
--container-name <container>
|
|
184
|
+
--runtime-port <port>
|
|
185
|
+
--persistence-mode <none|directory>
|
|
186
|
+
--image <immutable digest>
|
|
187
|
+
--health-path <path>
|
|
188
|
+
--run-as <uid[:gid]>
|
|
189
|
+
[--route-hostname <hostname> ...]
|
|
190
|
+
[--production]
|
|
191
|
+
--json
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Inspect installed help/version rather than assuming this example exactly matches every package version.
|
|
195
|
+
|
|
196
|
+
Treat the returned staged rollout as **read-only review evidence**. Preserve:
|
|
197
|
+
|
|
198
|
+
```text
|
|
199
|
+
runtime plan + isolation evidence
|
|
200
|
+
route plan + route-inventory evidence
|
|
201
|
+
separate stage authorizations
|
|
202
|
+
endpoint state before runtime
|
|
203
|
+
after-runtime / before-route endpoint state
|
|
204
|
+
after-route verification state
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
If the installed package lacks this composite preview, retain the existing safe runtime and route planners and state their sequence explicitly. Do not invent a combined write command.
|
|
208
|
+
|
|
209
|
+
## 8. Preserve exact authorization contracts
|
|
153
210
|
|
|
154
211
|
Core consequence labels do not replace stricter installed-tool phrases.
|
|
155
212
|
|
|
@@ -158,17 +215,20 @@ The standard Deployment Kit may use exact phrases such as:
|
|
|
158
215
|
```text
|
|
159
216
|
[APP DEPLOY WRITE]
|
|
160
217
|
[PRODUCTION APP DEPLOY]
|
|
218
|
+
[ROUTE WRITE]
|
|
161
219
|
```
|
|
162
220
|
|
|
163
221
|
For Production-class environments also show `[PRODUCTION WRITE]` consequence visibility where Core policy calls for it.
|
|
164
222
|
|
|
165
|
-
Resolve the exact phrase from the installed tool/version. Do not assume an example phrase when the installed contract differs.
|
|
223
|
+
Resolve the exact phrase from the installed tool/version. For a staged rollout, resolve it separately for each stage. Do not assume an example phrase when the installed contract differs.
|
|
224
|
+
|
|
225
|
+
Authorization applies only to the exact environment, resolved classification, placement/target/provider, artifact, reviewed plan/stage, current isolation evidence and route evidence, and evidence shown. Material changes require renewed review/authorization.
|
|
166
226
|
|
|
167
|
-
|
|
227
|
+
A runtime authorization never authorizes the route stage. A route authorization never retroactively authorizes a changed runtime artifact. If the rollout contains both stages, preserve the separate gates even though the Builder reviewed one coherent outcome.
|
|
168
228
|
|
|
169
|
-
##
|
|
229
|
+
## 9. Preview and execute only the approved contract
|
|
170
230
|
|
|
171
|
-
Before mutation, show concise non-secret evidence such as exact environment, purpose, placement, target/provider when applicable, proof freshness, immutable application artifact, persistence/migration, exposure/route, application isolation state/fingerprint when exposed, shared failure-domain note when relevant, planned
|
|
231
|
+
Before mutation, show concise non-secret evidence such as exact environment, purpose, placement, target/provider when applicable, proof freshness, immutable application artifact, persistence/migration, exposure/route, application isolation state/fingerprint when exposed, shared failure-domain note when relevant, planned stages, endpoint state at each stage, and the exact required authorization phrase for the next consequence only.
|
|
172
232
|
|
|
173
233
|
After authorization, invoke only the repository-provided deterministic launcher. Never redirect to another environment/host/provider, weaken trust/exposure, force through lock contention, or invent recovery writes.
|
|
174
234
|
|
|
@@ -176,7 +236,37 @@ The deterministic launcher must revalidate current isolation immediately before
|
|
|
176
236
|
|
|
177
237
|
Existing per-app/per-environment locks are normal concurrency protection when another workstation is deploying the same environment.
|
|
178
238
|
|
|
179
|
-
|
|
239
|
+
### Runtime succeeded but route is pending
|
|
240
|
+
|
|
241
|
+
A healthy application runtime is a valid intermediate outcome. When a requested route has not yet been registered, say this explicitly before showing the route gate:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
APPLICATION RUNTIME READY
|
|
245
|
+
Expected endpoint: <hostname/URL>
|
|
246
|
+
Endpoint state: NOT ACTIVE — ROUTE PENDING
|
|
247
|
+
Do not test this URL yet; the route write has not been authorized/applied.
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Then, if the reviewed route plan is still current, present the route consequence directly. Do not require the developer to invoke `/deploy` or another slash command merely to rediscover the already reviewed route stage.
|
|
251
|
+
|
|
252
|
+
The runtime success does not consume or imply route authorization. If route evidence changed, rebuild/review the route plan first.
|
|
253
|
+
|
|
254
|
+
## 10. Resume the established outcome without navigation ceremony
|
|
255
|
+
|
|
256
|
+
Within the same conversation, if a bounded engineering/delivery correction or external prerequisite completes and the developer's requested deployment outcome remains unambiguous, automatically resume safe **read-only** reconstruction of readiness/deployment evidence from the repository, GitHub, installed tools, and runtime state.
|
|
257
|
+
|
|
258
|
+
Do not require the developer to type `/deliver`, `/deployment-readiness`, or `/deploy` merely as navigation to rediscover known state after that bounded action completes.
|
|
259
|
+
|
|
260
|
+
This continuation:
|
|
261
|
+
|
|
262
|
+
- never repeats a completed write;
|
|
263
|
+
- never reuses exhausted registry/Production/route/merge/migration/destructive authority;
|
|
264
|
+
- stops when the artifact, plan, target, consequence, or other material evidence changed;
|
|
265
|
+
- does not create a parallel persistent workflow database.
|
|
266
|
+
|
|
267
|
+
If `/deploy` is invoked again after an already validated deployment, first reconstruct current deployment evidence. If the same validated artifact/runtime/route remains current and no new deployment outcome is requested, report the existing validated state rather than proposing or performing a redundant redeployment.
|
|
268
|
+
|
|
269
|
+
## 11. Validate live evidence
|
|
180
270
|
|
|
181
271
|
After execution, use repository-provided inventory/health/route/smoke/evidence commands when available.
|
|
182
272
|
|
|
@@ -191,10 +281,32 @@ Artifact: <exact immutable identity>
|
|
|
191
281
|
Artifact delivery: <workstation-transfer|remote-registry|local-runtime|provider>
|
|
192
282
|
Endpoint/Route: READY / LOOPBACK / NOT APPLICABLE
|
|
193
283
|
Application health: READY
|
|
284
|
+
Workstation/browser verification: PASSED / NOT APPLICABLE / PENDING
|
|
194
285
|
Smoke: PASSED / NOT REQUIRED
|
|
195
286
|
Deployment evidence: RECORDED / VERIFIED
|
|
196
287
|
```
|
|
197
288
|
|
|
198
|
-
Do not report success merely because a container/process started when stronger health evidence is required.
|
|
289
|
+
Do not report success merely because a container/process started when stronger health evidence is required. Do not report a route as user-accessible merely because registration returned; perform the repository-supported endpoint/workstation/browser verification when the requested outcome depends on it.
|
|
199
290
|
|
|
200
291
|
A setup-time disposable environment proof is not real application deployment evidence. A successful application deployment never authorizes a later Production write or destructive `/tear-down`.
|
|
292
|
+
|
|
293
|
+
Keep human access confirmation distinct from automated browser/health evidence. The developer saying they can reach the endpoint is useful observed human evidence; it is not automatically formal work-item acceptance.
|
|
294
|
+
|
|
295
|
+
## 12. Reconcile the durable outcome without asserting acceptance
|
|
296
|
+
|
|
297
|
+
After `DEPLOYMENT VALIDATED`, inspect the repository's configured work-item/operating authority. When an existing current-status or deployment-result section is stale and the observed deployment outcome is material, offer a bounded reconciliation containing only supported non-secret facts such as:
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
exact environment
|
|
301
|
+
release/source/artifact identity
|
|
302
|
+
artifact delivery method
|
|
303
|
+
runtime health result
|
|
304
|
+
route/endpoint
|
|
305
|
+
workstation/browser validation
|
|
306
|
+
explicit human access confirmation, when actually stated
|
|
307
|
+
known limitations and evidence boundaries
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Preserve historical approval records. Do not silently rewrite product requirements, broaden the milestone, mark formal acceptance, close the work item, or infer acceptance from browser accessibility.
|
|
311
|
+
|
|
312
|
+
If the human authorizes the bounded documentation update, use the repository's normal work-item/delivery workflow for that edit. Documentation reconciliation is not a reason to redeploy and does not create new Production write authority.
|