@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
|
@@ -78,7 +78,78 @@ It does not prove final application artifact identity, persistence/migration beh
|
|
|
78
78
|
|
|
79
79
|
If topology, target binding, host trust, platform state, or another material fact changes, treat the proof as stale and reverify the affected prerequisite.
|
|
80
80
|
|
|
81
|
-
## 3.
|
|
81
|
+
## 3. First-application outcome assessment
|
|
82
|
+
|
|
83
|
+
For the first real application deployment to an environment, or whenever the developer's stated goal is a usable endpoint rather than only machine readiness, keep that requested outcome visible and assess the whole material path before declaring the application deploy-ready.
|
|
84
|
+
|
|
85
|
+
Inspect available non-secret repository, package/tool, workstation, target, and GitHub/CI evidence before asking for facts. Do not require a release workflow to fail merely to discover a prerequisite that can be identified read-only.
|
|
86
|
+
|
|
87
|
+
Build a compact prerequisite matrix with rows such as:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
requested outcome / expected endpoint
|
|
91
|
+
workstation architecture
|
|
92
|
+
required artifact/target architecture
|
|
93
|
+
immutable artifact or release identity policy
|
|
94
|
+
artifact destination / publication state when relevant
|
|
95
|
+
credential capability + execution location for each required operation
|
|
96
|
+
workstation artifact acquisition capability
|
|
97
|
+
target delivery method
|
|
98
|
+
current application isolation / footprint
|
|
99
|
+
runtime health contract
|
|
100
|
+
route / exposure mechanism
|
|
101
|
+
final workstation/browser verification
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For each material row distinguish:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
evidence source
|
|
108
|
+
execution location / credential context
|
|
109
|
+
PROVEN / UNKNOWN / BLOCKED / NOT APPLICABLE
|
|
110
|
+
missing fact or prerequisite
|
|
111
|
+
exact consequential gate, if any
|
|
112
|
+
effect on the requested endpoint
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Architecture is a portability fact, not an operating-system stereotype. For example, an ARM64 workstation targeting a Linux/x86-64 environment must surface the required artifact platform explicitly; do not label that generically as a macOS defect.
|
|
116
|
+
|
|
117
|
+
Do not report `ENVIRONMENT READY TO DEPLOY` for the first real application while its actual artifact build/publication/acquisition path is still hypothetical. Target/platform readiness and application-deployment readiness are different dimensions.
|
|
118
|
+
|
|
119
|
+
Keep these evidence dimensions separate in status language:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
static/review correctness
|
|
123
|
+
live external-contract confidence
|
|
124
|
+
credential capability in the exact execution context
|
|
125
|
+
immutable artifact provenance
|
|
126
|
+
environment/runtime readiness
|
|
127
|
+
route state
|
|
128
|
+
workstation/browser validation
|
|
129
|
+
human access confirmation
|
|
130
|
+
formal work-item acceptance
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A green result in one dimension never proves another.
|
|
134
|
+
|
|
135
|
+
### Credential capability and lifecycle
|
|
136
|
+
|
|
137
|
+
Reason from **operation + execution context**, not from the existence of a generic token or successful access somewhere else. When installed tooling exposes structured registry capability evidence, preserve fields such as execution location, credential context, capability proved, and `doesNotProve` rather than paraphrasing a target result as workstation access.
|
|
138
|
+
|
|
139
|
+
Before asking the developer to create or rotate a credential, explain non-secret lifecycle facts first:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
purpose / capability
|
|
143
|
+
machine, workflow, or process where it is used
|
|
144
|
+
where the credential will be stored
|
|
145
|
+
whether its value can be retrieved again
|
|
146
|
+
rotation/regeneration impact on existing consumers
|
|
147
|
+
value must remain in the local/provider secret surface, never chat
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Do not assume registry transport access proves package-metadata API access, that target registry access proves workstation access, or that a workstation login proves an Actions workflow capability. Provider-specific token types, scopes, and error-body strings belong to installed/provider-owned adapters or Project evidence, not generic Core invention.
|
|
151
|
+
|
|
152
|
+
## 4. Placement branches
|
|
82
153
|
|
|
83
154
|
### A. Workstation placement
|
|
84
155
|
|
|
@@ -188,7 +259,7 @@ DEPLOYMENT INTENTIONALLY DEFERRED
|
|
|
188
259
|
|
|
189
260
|
Do not mark workspace readiness failed. Route to `/setup <environment>` or `/setup remote` if the developer later wants to bind it.
|
|
190
261
|
|
|
191
|
-
##
|
|
262
|
+
## 5. Shared failure domains / colocated environments
|
|
192
263
|
|
|
193
264
|
Multiple logical environments may share physical failure domains, but they remain independently targetable and independently authorized. If environments share physical failure, state it explicitly.
|
|
194
265
|
|
|
@@ -196,7 +267,7 @@ A clear application isolation preflight means the current Deployment Kit-managed
|
|
|
196
267
|
|
|
197
268
|
Colocation does not demonstrate host-level Production isolation. Never infer readiness for one logical environment from another merely because they share hardware.
|
|
198
269
|
|
|
199
|
-
##
|
|
270
|
+
## 6. State-driven cadence
|
|
200
271
|
|
|
201
272
|
Normal cadence is:
|
|
202
273
|
|
|
@@ -205,6 +276,7 @@ select exact project-defined environment
|
|
|
205
276
|
→ read current topology
|
|
206
277
|
→ reuse fresh environment-proof evidence when valid
|
|
207
278
|
→ rediscover placement-relevant local state
|
|
279
|
+
→ for first real deployment, assess the complete artifact/credential/delivery/route/endpoint path
|
|
208
280
|
→ verify current application isolation when the installed adapter supports it
|
|
209
281
|
→ verify/repair only missing prerequisites under exact gates
|
|
210
282
|
→ ENVIRONMENT READY TO DEPLOY
|
|
@@ -213,7 +285,9 @@ select exact project-defined environment
|
|
|
213
285
|
|
|
214
286
|
For remote placement, reuse valid existing target access rather than creating duplicate credentials simply because another Project/environment uses the same machine.
|
|
215
287
|
|
|
216
|
-
|
|
288
|
+
When a bounded engineering/delivery correction completes in the same conversation and the developer's deployment outcome is still clear, automatically resume safe read-only readiness reconstruction from current repository/tool evidence. Do not require the developer to type `/deployment-readiness` again merely as navigation. This continuation does not reuse an exhausted write authorization and does not authorize a new registry, Production, route, migration, merge, or other consequential write.
|
|
289
|
+
|
|
290
|
+
## 7. Read-only status
|
|
217
291
|
|
|
218
292
|
`/deployment-readiness status [environment]` performs no remote writes. Without a specific environment, summarize all known environments from project topology, including Placement and deferred state where evidenced.
|
|
219
293
|
|
|
@@ -229,7 +303,7 @@ Archive demo deferred NOT APPLICABLE DEFERRED
|
|
|
229
303
|
|
|
230
304
|
Do not manufacture target/provider facts for deferred environments.
|
|
231
305
|
|
|
232
|
-
##
|
|
306
|
+
## 8. Bounded readiness writes
|
|
233
307
|
|
|
234
308
|
A missing prerequisite is not authorization to change it. Use the repository/tool's exact authorization phrase for key/trust/platform/provider mutations.
|
|
235
309
|
|
|
@@ -237,21 +311,23 @@ Do not hide the real application deployment inside readiness. Readiness may rely
|
|
|
237
311
|
|
|
238
312
|
The application isolation preflight itself is read-only and does not authorize later deployment, route, persistence, migration, Production, or destructive writes.
|
|
239
313
|
|
|
240
|
-
##
|
|
314
|
+
## 9. Pre-deploy versus post-deploy evidence
|
|
241
315
|
|
|
242
316
|
Readiness asks whether `/deploy <name>` can safely perform the real application deployment now.
|
|
243
317
|
|
|
244
|
-
Pre-deploy evidence may include topology, adapter availability, binding, local/remote identity and access, target platform, fresh proof evidence, artifact policy, deterministic application isolation/collision evidence, route/port availability, persistence/backup/migration prerequisites, lock/recovery capability, and Production-specific safeguards.
|
|
318
|
+
Pre-deploy evidence may include topology, adapter availability, binding, local/remote identity and access, target platform, fresh proof evidence, artifact policy, actual artifact acquisition/publication capability when material, deterministic application isolation/collision evidence, route/port availability, persistence/backup/migration prerequisites, lock/recovery capability, and Production-specific safeguards.
|
|
245
319
|
|
|
246
320
|
Do not require first-deployment live route, deployed-release identity, application health, or smoke evidence before the application has ever been deployed. Those are post-deploy validation evidence owned by `/deploy`.
|
|
247
321
|
|
|
248
|
-
|
|
322
|
+
The intended endpoint may still be known before deployment. When the route does not yet exist, label it explicitly `NOT ACTIVE — ROUTE PENDING`; do not invite the developer to test it as though readiness had made it reachable.
|
|
323
|
+
|
|
324
|
+
## 10. Production policy
|
|
249
325
|
|
|
250
326
|
Production is logical policy, not hardware count and not the literal name `production`. A named environment such as `prod-east` with purpose `production` must retain Production readiness and later deployment gates.
|
|
251
327
|
|
|
252
328
|
Readiness never authorizes Production application deployment.
|
|
253
329
|
|
|
254
|
-
##
|
|
330
|
+
## 11. Final handoff
|
|
255
331
|
|
|
256
332
|
When pre-deploy requirements are satisfied:
|
|
257
333
|
|
|
@@ -263,10 +339,14 @@ Purpose: <purpose>
|
|
|
263
339
|
Placement: <...>
|
|
264
340
|
Binding: VERIFIED / LOCAL / provider-specific
|
|
265
341
|
This-workstation environment proof: PROVEN / NOT REQUIRED / NOT AVAILABLE
|
|
342
|
+
Artifact path: PROVEN / NOT APPLICABLE
|
|
343
|
+
Credential contexts: PROVEN / NOT APPLICABLE
|
|
266
344
|
Application isolation: CLEAR / NOT APPLICABLE
|
|
267
345
|
Isolation evidence: <evidenceSha256 when exposed by installed tooling>
|
|
268
346
|
Platform prerequisites: READY
|
|
269
347
|
Application-specific prerequisites: READY / <remaining items>
|
|
348
|
+
Expected endpoint: <URL/route when known>
|
|
349
|
+
Endpoint state: NOT ACTIVE — ROUTE PENDING / LOOPBACK PENDING / provider-specific
|
|
270
350
|
Security blockers: NONE
|
|
271
351
|
|
|
272
352
|
POST-DEPLOY VALIDATION PENDING
|
|
@@ -65,6 +65,14 @@ Do not require another workstation to be online and do not treat another compute
|
|
|
65
65
|
|
|
66
66
|
Detect the current OS automatically. Do not ask the developer which setup launcher to use when the platform can be observed.
|
|
67
67
|
|
|
68
|
+
If the observed workstation OS is not declared in mutable `DEV_ENVIRONMENT.json`, surface that explicitly before claiming setup support:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
NEW WORKSTATION — PLATFORM NOT DECLARED
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Do not silently reinterpret Bootstrap provenance or append a platform merely because tools happen to run. If current repository/catalog evidence supports the stack on the observed OS, offer one reviewed **consumer-owned** platform expansion to `DEV_ENVIRONMENT.json`; otherwise report the concrete unsupported/capability gap. Preserve original `PROJECT_BOOTSTRAP.json` provenance.
|
|
75
|
+
|
|
68
76
|
A workspace instance owns machine-local state such as local runtime/data, SSH private keys/trust, credential-manager entries, Docker state, and repository-scoped readiness evidence. Git/GitHub is the shared source-code coordination boundary. Never invent a central workstation registry merely to coordinate another computer.
|
|
69
77
|
|
|
70
78
|
## 2. `status` is read-only
|
|
@@ -112,6 +120,10 @@ setup --check
|
|
|
112
120
|
|
|
113
121
|
A manually invoked `/setup` authorizes routine, bounded, non-destructive local setup that the generated script already owns: supported tool installation, VS Code extension installation, dependency restore, personal Project Forge profile initialization, and other declared workstation preparation.
|
|
114
122
|
|
|
123
|
+
Before a large workstation install run, surface all currently knowable prerequisites together when the generated tooling exposes them: missing tools, runtime/version pins, editor extensions, Docker operational state, interactive elevation/terminal requirements, and durable fresh-shell discovery. Group unavoidable sudo/administrator or secret-bearing prompts into clear terminal handoffs instead of discovering them one installer at a time.
|
|
124
|
+
|
|
125
|
+
A temporary process `PATH` adjustment may prove a diagnosis but does not by itself prove durable tool discovery. Re-check important runtime commands in the environment a fresh terminal/editor will use before reporting final readiness.
|
|
126
|
+
|
|
115
127
|
Stop before destructive replacement, unrelated machine mutation, secret disclosure, or repair outside the declared setup contract.
|
|
116
128
|
|
|
117
129
|
## 4. `/setup local` converges verified local development readiness
|
|
@@ -132,7 +144,7 @@ When application source/runtime exists, converge only the repository-owned local
|
|
|
132
144
|
|
|
133
145
|
Verify supported tools/versions using repository setup/doctor tooling when present. Run repository-defined dependency restore/setup commands when missing or stale. Preserve pinned versions and project conventions.
|
|
134
146
|
|
|
135
|
-
Treat **installation**, **version compatibility**, and **operational readiness** as separate evidence when tooling exposes those distinctions. A successful CLI/version probe does not prove that the backing service/runtime is operational. For Docker-based setup, do not call the runtime ready merely because `docker --version` succeeds; require the repository/doctor operational probe before relying on Docker Engine capability.
|
|
147
|
+
Treat **installation**, **version compatibility**, and **operational readiness** as separate evidence when tooling exposes those distinctions. A successful CLI/version probe does not prove that the backing service/runtime is operational for the required Project Forge action. For Docker-based setup, do not call the runtime ready merely because `docker --version` succeeds; require the repository/doctor operational probe before relying on Docker Engine capability.
|
|
136
148
|
|
|
137
149
|
### B. Local persistence
|
|
138
150
|
|
|
@@ -266,6 +278,26 @@ For remote SSH preserve strict host identity. Unknown/changed host identity is a
|
|
|
266
278
|
|
|
267
279
|
Remote readiness does not require configuring Production when the selected environment is non-Production. Production classification remains project topology/policy, not a literal-name check.
|
|
268
280
|
|
|
281
|
+
### One read-only remote preflight before recovery
|
|
282
|
+
|
|
283
|
+
Before asking the developer to repair one SSH prerequisite at a time, collect all currently knowable target-access facts through supported deterministic status/diagnosis surfaces. When installed Deployment Kit exposes the access diagnosis command, prefer:
|
|
284
|
+
|
|
285
|
+
```text
|
|
286
|
+
project-forge-deploy access diagnose <target> --json
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
If stable name resolution is unavailable and the developer supplies an exact address independently observed at the trusted target console, carry that address through read-only diagnosis without persisting it:
|
|
290
|
+
|
|
291
|
+
```text
|
|
292
|
+
project-forge-deploy access diagnose <target> --address <exact-ip> --json
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The exact IP is address-path evidence, not a replacement SSH identity. Preserve the configured `HostKeyAlias`, target-specific trust, and configured identity. Do not store the address merely because it made diagnosis possible. Persist it only through the separately gated verified-address operation after strict SSH succeeds.
|
|
296
|
+
|
|
297
|
+
When diagnosis exposes route-source evidence, use the actual selected local source address/interface for any source-policy discussion. Never assume Wi-Fi, Ethernet, the first interface, or a subnet merely from workstation configuration.
|
|
298
|
+
|
|
299
|
+
If the Deployment Kit version does not expose the supported diagnosis surface, use its documented structured environment status/plan commands. Do not substitute ad hoc Python imports or unbounded raw `ssh -vvv` transcripts as normal orchestration.
|
|
300
|
+
|
|
269
301
|
### Guided remote failure recovery is part of `/setup`
|
|
270
302
|
|
|
271
303
|
A supported deterministic setup/readiness failure is not, by itself, the end of `/setup`. When installed Deployment Kit tooling returns structured diagnosis or a bounded next action, continue through that diagnosis automatically until one of these boundaries is reached:
|
|
@@ -278,7 +310,7 @@ A supported deterministic setup/readiness failure is not, by itself, the end of
|
|
|
278
310
|
|
|
279
311
|
Do **not** merely repeat an opaque subprocess exit code and tell the developer to fix access independently when deterministic diagnosis is available.
|
|
280
312
|
|
|
281
|
-
|
|
313
|
+
Preserve distinct categories such as:
|
|
282
314
|
|
|
283
315
|
```text
|
|
284
316
|
name resolution / address path
|
|
@@ -293,8 +325,45 @@ remote account/source-policy rejection
|
|
|
293
325
|
|
|
294
326
|
Keep credentials, private keys, raw secure-prompt content, and unbounded SSH transcripts out of conversational diagnostics and journals.
|
|
295
327
|
|
|
328
|
+
For fingerprint input, show the expected public form such as `SHA256:<fingerprint>` and validate that shape before progressing. Distinguish the **server host fingerprint**, the **client public-key fingerprint**, and passwords/passphrases. If input looks credential-like rather than fingerprint-shaped, do not repeat it into commands, reports, journals, or upstream issues.
|
|
329
|
+
|
|
330
|
+
If a local key/agent operation requires a passphrase or another terminal-owned secret prompt, follow the interactive-terminal handoff contract: collect any distinct bounded authorization once, then immediately provide the exact standalone copyable command, correct machine/working directory, expected local prompt, and the non-secret completion evidence to report. Do not ask for the passphrase through chat or ask a second ceremonial question merely to repeat the same authorized command.
|
|
331
|
+
|
|
296
332
|
If client-side evidence shows that the configured key reached the server but access is still rejected, stop reinstalling the same key. Route to a trusted target console or already-authorized administrative session to inspect effective account/source policy. Do not guess that `AllowUsers`, `AllowGroups`, PAM, or another server rule is the exact cause until server-side evidence establishes it.
|
|
297
333
|
|
|
334
|
+
### One bounded trusted-console diagnostic bundle
|
|
335
|
+
|
|
336
|
+
When client evidence reaches the server but cannot establish why the account/session is rejected, request one compact server-side evidence bundle instead of a chain of one-fact-at-a-time questions. Limit the bundle to the facts needed for the selected account and actual route-selected source:
|
|
337
|
+
|
|
338
|
+
- intended SSH account;
|
|
339
|
+
- whether the expected **client public-key fingerprint** is present for that account, without dumping unrelated authorized keys;
|
|
340
|
+
- ownership/modes for the account's `.ssh` directory and `authorized_keys`;
|
|
341
|
+
- a narrowly scoped recent authentication reason for the attempted source/account;
|
|
342
|
+
- effective SSH allow/deny policy relevant to that account and source;
|
|
343
|
+
- the file/include/`Match` location that owns any relevant directive.
|
|
344
|
+
|
|
345
|
+
State exactly where to run each console command and what non-secret output to return. Never request full SSH configs/logs, passwords, private keys, tokens, or unrelated authorized-key material when the focused bundle is enough.
|
|
346
|
+
|
|
347
|
+
Client evidence may classify `remote-authentication-rejected` or `remote-account-policy-rejected`, but only trusted server-side evidence may establish a concrete cause such as `AllowUsers`.
|
|
348
|
+
|
|
349
|
+
### One exact server-policy repair after evidence
|
|
350
|
+
|
|
351
|
+
If the trusted-console evidence establishes that a server-side account/source policy change is required, prepare **one exact repair plan** before any write. The plan must show:
|
|
352
|
+
|
|
353
|
+
- exact affected file/directive and relevant before/after state;
|
|
354
|
+
- the actual durable source address/interface assumption being authorized;
|
|
355
|
+
- preservation of existing allow/deny entries unless the operator explicitly chooses otherwise;
|
|
356
|
+
- an exact backup of the affected file before editing;
|
|
357
|
+
- syntax/config validation before reload;
|
|
358
|
+
- preservation of the current trusted administrative session until new strict access is proven;
|
|
359
|
+
- reload/restart scope, rollback command, and explicit stop conditions.
|
|
360
|
+
|
|
361
|
+
Do not present configuration text as if it were a shell command. Give either one copyable bounded command or explicit editor instructions on the **target host**, with the file path stated. Do not assume that adding a new drop-in overrides or composes safely with existing includes/`Match` blocks. Never broaden a specific source restriction to a subnet merely for convenience.
|
|
362
|
+
|
|
363
|
+
A server-policy write remains a distinct operator/administrative authorization. Once the exact reviewed repair is authorized and the human reports it completed, do not ask the same authorization again: automatically re-run strict structured diagnosis/verification, then continue through separately gated address persistence, host-platform readiness, and proof as applicable.
|
|
364
|
+
|
|
365
|
+
If the human says stop, done, or pause at any point, stop modifying state. Read-only reporting may summarize what is already known, but newly observed prerequisite changes do not override the expressed pause.
|
|
366
|
+
|
|
298
367
|
### Gate password-assisted bootstrap on server capability
|
|
299
368
|
|
|
300
369
|
Before recommending a password-assisted/Home Lab bootstrap against a reachable server, use deterministic evidence of the server-advertised authentication methods when the installed Deployment Kit supports that probe.
|
|
@@ -355,6 +424,8 @@ For a Production-class environment, keep the proof explicitly labeled as a **PRO
|
|
|
355
424
|
|
|
356
425
|
If cleanup fails, report `PROOF PASSED — CLEANUP ACTION NEEDED`; do not claim a clean setup.
|
|
357
426
|
|
|
427
|
+
When proof output distinguishes `transferState: transferred` from `transferState: already-present`, report that distinction. A proof that reused an exact cached image may still prove runtime/health/cleanup, but it must not be described as an observed fresh workstation-to-target image transfer. If fresh transport itself must be tested, plan that separately without deleting or pruning a shared image cache merely to manufacture evidence.
|
|
428
|
+
|
|
358
429
|
When installed Deployment Kit provides a durable local proof receipt or equivalent discoverable evidence, record/reuse it rather than relying on chat history. Reuse only while its target binding, host identity, workstation identity, adapter/provenance, logical environment, and cleanup result remain applicable. A new chat alone must not invalidate valid machine evidence.
|
|
359
430
|
|
|
360
431
|
## 9. `repair` is bounded and fail-closed
|
|
@@ -382,9 +453,27 @@ Stop for a separate explicit decision before:
|
|
|
382
453
|
|
|
383
454
|
## 11. Completion and handoff
|
|
384
455
|
|
|
385
|
-
After setup, report evidence rather than inferring human acceptance.
|
|
456
|
+
After setup, report evidence rather than inferring human acceptance. Keep independent readiness dimensions independent instead of collapsing them into one `READY` label.
|
|
457
|
+
|
|
458
|
+
Prefer a compact final shape such as:
|
|
459
|
+
|
|
460
|
+
```text
|
|
461
|
+
SETUP RESULT
|
|
462
|
+
|
|
463
|
+
Local tools/runtime ......... VERIFIED / BLOCKED / NOT APPLICABLE
|
|
464
|
+
Fresh-shell discovery ....... VERIFIED / CAVEAT / BLOCKED
|
|
465
|
+
Build/tests ................. VERIFIED / BLOCKED / NOT RUN
|
|
466
|
+
Remote target access ........ VERIFIED / BLOCKED / NOT CONFIGURED
|
|
467
|
+
Host platform ............... READY / BLOCKED / NOT APPLICABLE
|
|
468
|
+
Deployment path ............. PROVEN / NOT PROVEN / BLOCKED
|
|
469
|
+
Proof image transfer ........ TRANSFERRED / CACHE REUSED / NOT RUN
|
|
470
|
+
Proof cleanup ............... CLEAN / ACTION NEEDED / NOT RUN
|
|
471
|
+
Project application ......... NOT DEPLOYED / <actual evidence>
|
|
472
|
+
Repository CI ............... PASS / BLOCKED / NOT CHECKED
|
|
473
|
+
Human workspace acceptance .. CONFIRMED / PENDING
|
|
474
|
+
```
|
|
386
475
|
|
|
387
|
-
A generated setup/doctor/proof success is machine evidence. Human workspace acceptance remains distinct.
|
|
476
|
+
A generated setup/doctor/proof success is machine evidence. Human workspace acceptance remains distinct. A continuation/override for a documented Project Forge defect is not a CI waiver and is not human workspace acceptance; preserve its exact scope so resuming setup does not repeatedly ask the same decision.
|
|
388
477
|
|
|
389
478
|
Useful next actions include:
|
|
390
479
|
- `/next-step` — one sensible next action after current evidence is known;
|
|
@@ -393,4 +482,4 @@ Useful next actions include:
|
|
|
393
482
|
- `/deploy <environment>` — only after readiness and its own authorization contract;
|
|
394
483
|
- `/tear-down <scope>` — destructive cleanup under its separate gate.
|
|
395
484
|
|
|
396
|
-
Do not create a second top-level developer-environment-mode workflow. Local/remote workstation convergence belongs here.
|
|
485
|
+
Do not create a second top-level developer-environment-mode workflow. Local/remote workstation convergence belongs here.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""CLI for strict target-access diagnosis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .access_diagnosis import diagnose_target_access
|
|
11
|
+
from .errors import DeploymentKitError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _print(value: Any, *, as_json: bool) -> None:
|
|
15
|
+
if as_json:
|
|
16
|
+
print(json.dumps(value, indent=2, sort_keys=True))
|
|
17
|
+
return
|
|
18
|
+
if isinstance(value, dict):
|
|
19
|
+
for key, item in value.items():
|
|
20
|
+
if isinstance(item, (dict, list)):
|
|
21
|
+
print(f"{key}: {json.dumps(item, sort_keys=True)}")
|
|
22
|
+
else:
|
|
23
|
+
print(f"{key}: {item}")
|
|
24
|
+
return
|
|
25
|
+
print(value)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
29
|
+
parser = argparse.ArgumentParser(
|
|
30
|
+
prog="project-forge-deploy access",
|
|
31
|
+
description="Read-only strict SSH target-access diagnosis.",
|
|
32
|
+
)
|
|
33
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
34
|
+
|
|
35
|
+
diagnose = subparsers.add_parser(
|
|
36
|
+
"diagnose",
|
|
37
|
+
help="Diagnose one configured target without mutating target or workstation state",
|
|
38
|
+
)
|
|
39
|
+
diagnose.add_argument("name")
|
|
40
|
+
diagnose.add_argument(
|
|
41
|
+
"--address",
|
|
42
|
+
help=(
|
|
43
|
+
"Exact temporary IPv4/IPv6 address independently confirmed by the operator. "
|
|
44
|
+
"Used only for this read-only diagnosis and never persisted."
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
diagnose.add_argument("--json", action="store_true")
|
|
48
|
+
return parser
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main(argv: list[str] | None = None) -> int:
|
|
52
|
+
args = build_parser().parse_args(argv)
|
|
53
|
+
try:
|
|
54
|
+
if args.command == "diagnose":
|
|
55
|
+
result = diagnose_target_access(args.name, temporary_address=args.address)
|
|
56
|
+
_print(result, as_json=args.json)
|
|
57
|
+
return 0 if result.get("state") == "verified" else 2
|
|
58
|
+
raise DeploymentKitError("Unsupported access command.")
|
|
59
|
+
except DeploymentKitError as error:
|
|
60
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
61
|
+
return 2
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Read-only strict target-access diagnosis with optional temporary address evidence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ipaddress
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import socket
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .errors import DeploymentKitError
|
|
15
|
+
from .profiles import load_profile
|
|
16
|
+
from .ssh import diagnose_access
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _temporary_address(value: str | None) -> str | None:
|
|
20
|
+
if value is None:
|
|
21
|
+
return None
|
|
22
|
+
try:
|
|
23
|
+
parsed = ipaddress.ip_address(value)
|
|
24
|
+
except ValueError as error:
|
|
25
|
+
raise DeploymentKitError("Temporary target address must be an exact IPv4 or IPv6 address.") from error
|
|
26
|
+
if isinstance(parsed, ipaddress.IPv6Address) and parsed.scope_id is not None:
|
|
27
|
+
raise DeploymentKitError("Temporary target address must not include an IPv6 scope identifier.")
|
|
28
|
+
return str(parsed)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _route_interface(address: str) -> str | None:
|
|
32
|
+
if sys.platform == "darwin":
|
|
33
|
+
route = shutil.which("route") or ("/sbin/route" if os.path.exists("/sbin/route") else None)
|
|
34
|
+
if route:
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
[route, "-n", "get", address],
|
|
37
|
+
check=False,
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
encoding="utf-8",
|
|
41
|
+
errors="replace",
|
|
42
|
+
)
|
|
43
|
+
if result.returncode == 0:
|
|
44
|
+
match = re.search(r"^\s*interface:\s*(\S+)\s*$", result.stdout, re.MULTILINE)
|
|
45
|
+
if match:
|
|
46
|
+
return match.group(1)
|
|
47
|
+
elif os.name != "nt":
|
|
48
|
+
ip = shutil.which("ip")
|
|
49
|
+
if ip:
|
|
50
|
+
result = subprocess.run(
|
|
51
|
+
[ip, "route", "get", address],
|
|
52
|
+
check=False,
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True,
|
|
55
|
+
encoding="utf-8",
|
|
56
|
+
errors="replace",
|
|
57
|
+
)
|
|
58
|
+
if result.returncode == 0:
|
|
59
|
+
match = re.search(r"\bdev\s+(\S+)", result.stdout)
|
|
60
|
+
if match:
|
|
61
|
+
return match.group(1)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def route_source_evidence(address: str, port: int) -> dict[str, Any]:
|
|
66
|
+
"""Return the local route-selected source for one exact target address without scanning."""
|
|
67
|
+
parsed = ipaddress.ip_address(address)
|
|
68
|
+
family = socket.AF_INET6 if isinstance(parsed, ipaddress.IPv6Address) else socket.AF_INET
|
|
69
|
+
evidence: dict[str, Any] = {
|
|
70
|
+
"targetAddress": str(parsed),
|
|
71
|
+
"targetPort": int(port),
|
|
72
|
+
"sourceAddress": None,
|
|
73
|
+
"interface": _route_interface(str(parsed)),
|
|
74
|
+
}
|
|
75
|
+
try:
|
|
76
|
+
with socket.socket(family, socket.SOCK_DGRAM) as probe:
|
|
77
|
+
probe.connect((str(parsed), int(port)))
|
|
78
|
+
evidence["sourceAddress"] = probe.getsockname()[0]
|
|
79
|
+
except OSError as error:
|
|
80
|
+
evidence["sourceProbeError"] = str(error)
|
|
81
|
+
return evidence
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def diagnose_target_access(name: str, *, temporary_address: str | None = None) -> dict[str, Any]:
|
|
85
|
+
"""Diagnose strict SSH access without persisting a temporary address override."""
|
|
86
|
+
profile = load_profile(name)
|
|
87
|
+
address = _temporary_address(temporary_address)
|
|
88
|
+
effective = dict(profile)
|
|
89
|
+
if address is not None:
|
|
90
|
+
effective["addressOverride"] = address
|
|
91
|
+
|
|
92
|
+
diagnosis = diagnose_access(effective)
|
|
93
|
+
used_address = diagnosis.get("address")
|
|
94
|
+
if used_address is None and address is not None:
|
|
95
|
+
used_address = address
|
|
96
|
+
|
|
97
|
+
result: dict[str, Any] = {
|
|
98
|
+
**diagnosis,
|
|
99
|
+
"addressInput": (
|
|
100
|
+
{
|
|
101
|
+
"source": "temporary-operator-confirmed",
|
|
102
|
+
"address": address,
|
|
103
|
+
"persisted": False,
|
|
104
|
+
}
|
|
105
|
+
if address is not None
|
|
106
|
+
else {
|
|
107
|
+
"source": "profile",
|
|
108
|
+
"address": profile.get("addressOverride"),
|
|
109
|
+
"persisted": profile.get("addressOverride") is not None,
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
"mutated": False,
|
|
113
|
+
}
|
|
114
|
+
if used_address is not None:
|
|
115
|
+
result["routeSource"] = route_source_evidence(used_address, int(profile["port"]))
|
|
116
|
+
return result
|
|
@@ -12,6 +12,7 @@ from .app_deployment import (
|
|
|
12
12
|
apply_app_deploy,
|
|
13
13
|
apply_app_deploy_from_workstation,
|
|
14
14
|
plan_app_deploy,
|
|
15
|
+
plan_app_rollout,
|
|
15
16
|
)
|
|
16
17
|
from .app_runtime import (
|
|
17
18
|
APP_DEPLOY_WRITE_PHRASE,
|
|
@@ -85,6 +86,29 @@ def _add_isolation_footprint(parser: argparse.ArgumentParser) -> None:
|
|
|
85
86
|
)
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
def _add_runtime_contract(parser: argparse.ArgumentParser) -> None:
|
|
90
|
+
parser.add_argument("--image", required=True)
|
|
91
|
+
parser.add_argument("--health-path", required=True)
|
|
92
|
+
parser.add_argument("--run-as", required=True)
|
|
93
|
+
parser.add_argument("--data-container-path")
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
"--env",
|
|
96
|
+
action="append",
|
|
97
|
+
default=[],
|
|
98
|
+
help="Non-secret runtime environment value in NAME=value form; repeat as needed",
|
|
99
|
+
)
|
|
100
|
+
parser.add_argument(
|
|
101
|
+
"--image-source",
|
|
102
|
+
choices=("remote-registry", "workstation"),
|
|
103
|
+
default="remote-registry",
|
|
104
|
+
help=(
|
|
105
|
+
"remote-registry preserves the existing target-side pull path; workstation streams an exact local image "
|
|
106
|
+
"over verified SSH so the target does not need registry credentials"
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
parser.add_argument("--json", action="store_true")
|
|
110
|
+
|
|
111
|
+
|
|
88
112
|
def build_parser() -> argparse.ArgumentParser:
|
|
89
113
|
parser = argparse.ArgumentParser(prog="project-forge-deploy app")
|
|
90
114
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
@@ -108,31 +132,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
108
132
|
)
|
|
109
133
|
isolation.add_argument("--json", action="store_true")
|
|
110
134
|
|
|
135
|
+
rollout = sub.add_parser(
|
|
136
|
+
"rollout",
|
|
137
|
+
help="Preview application runtime and owned route as one read-only staged rollout",
|
|
138
|
+
)
|
|
139
|
+
_add_isolation_footprint(rollout)
|
|
140
|
+
_add_runtime_contract(rollout)
|
|
141
|
+
|
|
111
142
|
deploy = sub.add_parser(
|
|
112
143
|
"deploy",
|
|
113
144
|
help="Deploy one immutable application runtime without modifying routes",
|
|
114
145
|
)
|
|
115
146
|
_add_isolation_footprint(deploy)
|
|
116
|
-
deploy
|
|
117
|
-
deploy.add_argument("--health-path", required=True)
|
|
118
|
-
deploy.add_argument("--run-as", required=True)
|
|
119
|
-
deploy.add_argument("--data-container-path")
|
|
120
|
-
deploy.add_argument(
|
|
121
|
-
"--env",
|
|
122
|
-
action="append",
|
|
123
|
-
default=[],
|
|
124
|
-
help="Non-secret runtime environment value in NAME=value form; repeat as needed",
|
|
125
|
-
)
|
|
126
|
-
deploy.add_argument(
|
|
127
|
-
"--image-source",
|
|
128
|
-
choices=("remote-registry", "workstation"),
|
|
129
|
-
default="remote-registry",
|
|
130
|
-
help=(
|
|
131
|
-
"remote-registry preserves the existing target-side pull path; workstation streams an exact local image "
|
|
132
|
-
"over verified SSH so the target does not need registry credentials"
|
|
133
|
-
),
|
|
134
|
-
)
|
|
135
|
-
deploy.add_argument("--json", action="store_true")
|
|
147
|
+
_add_runtime_contract(deploy)
|
|
136
148
|
deploy.add_argument("--yes", action="store_true")
|
|
137
149
|
return parser
|
|
138
150
|
|
|
@@ -175,6 +187,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
175
187
|
environment_variables=_parse_env(args.env),
|
|
176
188
|
)
|
|
177
189
|
assert_app_spec_matches_persistence(args.name, spec)
|
|
190
|
+
|
|
191
|
+
if args.command == "rollout":
|
|
192
|
+
rollout_plan = plan_app_rollout(
|
|
193
|
+
args.name,
|
|
194
|
+
spec,
|
|
195
|
+
production=production_classified,
|
|
196
|
+
database_file=args.database_file,
|
|
197
|
+
route_hostnames=args.route_hostname,
|
|
198
|
+
)
|
|
199
|
+
rollout_plan["imageSource"] = args.image_source
|
|
200
|
+
rollout_plan["targetRegistryCredentialRequired"] = args.image_source == "remote-registry"
|
|
201
|
+
_print(rollout_plan, as_json=args.json)
|
|
202
|
+
return 0
|
|
203
|
+
|
|
178
204
|
plan = plan_app_deploy(
|
|
179
205
|
args.name,
|
|
180
206
|
spec,
|