@mriekki/project-forge 4.3.0 → 4.4.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@mriekki/project-forge",
4
- "version": "4.3.0",
4
+ "version": "4.4.0",
5
5
  "defaultProfile": "greenfield-project",
6
6
  "profiles": {
7
7
  "greenfield-project": {
@@ -14,7 +14,7 @@
14
14
  "components": {
15
15
  "core": {
16
16
  "repository": "mriekki/project-forge-core",
17
- "commit": "c35a42d4a87e59b3729797a9a2aace8499a93953",
17
+ "commit": "45139179c3d10ebd89702ac3aedec231364d8a34",
18
18
  "version": "4.0.0",
19
19
  "distribution": "greenfield-workspace",
20
20
  "descriptor": "assets/core/distribution.json",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "deployment": {
25
25
  "repository": "mriekki/project-forge-deployment-kit",
26
- "commit": "e5fe3333cad04714d714445866639680378eb23d",
26
+ "commit": "eb157c8ae2c638ae1cd08bf27446d430835adc80",
27
27
  "distribution": "greenfield-project",
28
28
  "descriptor": "assets/deployment/distribution.json",
29
29
  "sourceRoot": "assets/generated/deployment",
@@ -7,6 +7,7 @@
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
9
  {"source": "src/project_forge_deployment/app_cli.py", "destination": "tools/project-forge-deployment/project_forge_deployment/app_cli.py"},
10
+ {"source": "src/project_forge_deployment/app_deployment.py", "destination": "tools/project-forge-deployment/project_forge_deployment/app_deployment.py"},
10
11
  {"source": "src/project_forge_deployment/app_runtime.py", "destination": "tools/project-forge-deployment/project_forge_deployment/app_runtime.py"},
11
12
  {"source": "src/project_forge_deployment/app_runtime_remote.py", "destination": "tools/project-forge-deployment/project_forge_deployment/app_runtime_remote.py"},
12
13
  {"source": "src/project_forge_deployment/app_runtime_workstation.py", "destination": "tools/project-forge-deployment/project_forge_deployment/app_runtime_workstation.py"},
@@ -17,6 +18,7 @@
17
18
  {"source": "src/project_forge_deployment/doctor.py", "destination": "tools/project-forge-deployment/project_forge_deployment/doctor.py"},
18
19
  {"source": "src/project_forge_deployment/entrypoint.py", "destination": "tools/project-forge-deployment/project_forge_deployment/entrypoint.py"},
19
20
  {"source": "src/project_forge_deployment/environment_cli.py", "destination": "tools/project-forge-deployment/project_forge_deployment/environment_cli.py"},
21
+ {"source": "src/project_forge_deployment/environment_isolation.py", "destination": "tools/project-forge-deployment/project_forge_deployment/environment_isolation.py"},
20
22
  {"source": "src/project_forge_deployment/environment_probe.py", "destination": "tools/project-forge-deployment/project_forge_deployment/environment_probe.py"},
21
23
  {"source": "src/project_forge_deployment/environment_setup.py", "destination": "tools/project-forge-deployment/project_forge_deployment/environment_setup.py"},
22
24
  {"source": "src/project_forge_deployment/errors.py", "destination": "tools/project-forge-deployment/project_forge_deployment/errors.py"},
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@mriekki/project-forge",
4
- "version": "4.3.0",
4
+ "version": "4.4.0",
5
5
  "components": {
6
6
  "core": {
7
7
  "repository": "mriekki/project-forge-core",
8
- "commit": "c35a42d4a87e59b3729797a9a2aace8499a93953",
8
+ "commit": "45139179c3d10ebd89702ac3aedec231364d8a34",
9
9
  "version": "4.0.0",
10
10
  "distribution": "greenfield-workspace",
11
11
  "files": [
@@ -117,12 +117,12 @@
117
117
  {
118
118
  "source": "core/skills/deployment-readiness/SKILL.md",
119
119
  "destination": ".github/skills/deployment-readiness/SKILL.md",
120
- "sha256": "82eaf37f8acc4e3d69871a02ad38ab9bf94caccaa0b3a40784dbb622621e112c"
120
+ "sha256": "33be75e856b1b16378bb2a456d8418f8ae0b03edee75fb826d8f7ef3824d2f6a"
121
121
  },
122
122
  {
123
123
  "source": "core/skills/deploy/SKILL.md",
124
124
  "destination": ".github/skills/deploy/SKILL.md",
125
- "sha256": "8296d5cdd166dc5e0125df5d8ebc83d901a04f71caad8563ec67e9d02761955a"
125
+ "sha256": "21b8209bc756ef2981f9e44f19ee80e79c1c5c7d5be4c03c9b528fdadd7b8eca"
126
126
  },
127
127
  {
128
128
  "source": "core/skills/tear-down/SKILL.md",
@@ -172,7 +172,7 @@
172
172
  {
173
173
  "source": "core/process/DEPLOYMENT_ENABLEMENT.md",
174
174
  "destination": "docs/process/DEPLOYMENT_ENABLEMENT.md",
175
- "sha256": "fa4f0bf3929d7d07b0061191232d39a7d03ae5e1e334e2aeafd27bad83b845fc"
175
+ "sha256": "f45b84f64975f5f90f639ed1f4f8709d0153d48d109198764c0ec13218956188"
176
176
  },
177
177
  {
178
178
  "source": "core/process/HUMAN_GATES.md",
@@ -328,7 +328,7 @@
328
328
  },
329
329
  "deployment": {
330
330
  "repository": "mriekki/project-forge-deployment-kit",
331
- "commit": "e5fe3333cad04714d714445866639680378eb23d",
331
+ "commit": "eb157c8ae2c638ae1cd08bf27446d430835adc80",
332
332
  "distribution": "greenfield-project",
333
333
  "files": [
334
334
  {
@@ -349,7 +349,12 @@
349
349
  {
350
350
  "source": "src/project_forge_deployment/app_cli.py",
351
351
  "destination": "tools/project-forge-deployment/project_forge_deployment/app_cli.py",
352
- "sha256": "405c5b4ceb08885b3f789dad5af5bacc5ccd6913b73a6304f562229f29196338"
352
+ "sha256": "9bf8eb7337c5b506311bf718de71b9a2c9ac445eb94cb7dfdeb63a09e5f0f899"
353
+ },
354
+ {
355
+ "source": "src/project_forge_deployment/app_deployment.py",
356
+ "destination": "tools/project-forge-deployment/project_forge_deployment/app_deployment.py",
357
+ "sha256": "e58158e48c9fbd51b51f7af90f2dd178987ad3936e44507523cfc41b96fcd9f9"
353
358
  },
354
359
  {
355
360
  "source": "src/project_forge_deployment/app_runtime.py",
@@ -399,17 +404,22 @@
399
404
  {
400
405
  "source": "src/project_forge_deployment/environment_cli.py",
401
406
  "destination": "tools/project-forge-deployment/project_forge_deployment/environment_cli.py",
402
- "sha256": "13d41913fd91ad2461bc28e1af0343dd81ba34141c0db8b30f20d89423b64582"
407
+ "sha256": "ff4883a491dc4b4fab4e4c2afbb2eb3f65ddce6ae7ff22df7ab355ded5cc4f6b"
408
+ },
409
+ {
410
+ "source": "src/project_forge_deployment/environment_isolation.py",
411
+ "destination": "tools/project-forge-deployment/project_forge_deployment/environment_isolation.py",
412
+ "sha256": "aa2f380c7cbf474084375460c9d9168390c608a6566559eab778764ad5cf094d"
403
413
  },
404
414
  {
405
415
  "source": "src/project_forge_deployment/environment_probe.py",
406
416
  "destination": "tools/project-forge-deployment/project_forge_deployment/environment_probe.py",
407
- "sha256": "4e9a49a7cf0427dcdef1e28f955297569ec8cd0ee9d071404ef876e3ce036313"
417
+ "sha256": "0a7b7a939fa38172abf85a036e2e8e9e97fa2c890f63a2e4d9304b4d62ebfa2d"
408
418
  },
409
419
  {
410
420
  "source": "src/project_forge_deployment/environment_setup.py",
411
421
  "destination": "tools/project-forge-deployment/project_forge_deployment/environment_setup.py",
412
- "sha256": "a79919da8655a6c42fded3224373f6381d15c8c68869b70c31d6edb7702fd348"
422
+ "sha256": "47ce6af4ab3b03c441725ec8cc1b356212ef7a1706635fe191affd959b09a5f5"
413
423
  },
414
424
  {
415
425
  "source": "src/project_forge_deployment/errors.py",
@@ -78,6 +78,24 @@ If existing configuration is present, show known facts for confirmation. If part
78
78
 
79
79
  Remote probing requires explicit read-only authorization. Address discovery never substitutes for cryptographic host identity. Unknown/changed SSH identity fails closed.
80
80
 
81
+ ### Real application isolation readiness
82
+
83
+ For supported remote application deployment, readiness should use the repository-distributed Deployment Kit launcher rather than infer collision safety from prose:
84
+
85
+ ```text
86
+ tools/project-forge-deployment/project-forge-deploy.py app isolation ... --require-clear --json
87
+ ```
88
+
89
+ The read-only check should be built from the exact known application footprint: target binding, owner repository, exact logical environment, resolved Production classification, app slug, container name, runtime port, persistence mode, optional SQLite database file, and intended route hostnames when applicable.
90
+
91
+ The deterministic result may prove the current managed application footprint is non-colliding across ownership, runtime/container identity, route hostnames/upstream ownership, SQLite identity, persistence root, and per-app/per-environment lock identity. It may also report colocated sibling environments and a shared physical failure domain.
92
+
93
+ A clear result is pre-deploy evidence only. It does not authorize application, route, persistence, migration, Production, or destructive writes. A blocked result means readiness must stop before the real application write gate and surface the structured conflict evidence.
94
+
95
+ If installed package tooling lacks the required isolation capability, require a Project Forge capability/update path rather than reconstructing equivalent checks from model knowledge.
96
+
97
+ When available, retain the isolation evidence fingerprint for human review. `/deploy` must still use the deterministic planner and current-state revalidation before mutation; a readiness fingerprint is not a durable write authorization.
98
+
81
99
  ## Deferred readiness
82
100
 
83
101
  A deferred placement is intentional configuration state:
@@ -108,6 +126,8 @@ failure domain. This may fit Project cost/learning goals, but it does not
108
126
  demonstrate host-level Production isolation.
109
127
  ```
110
128
 
129
+ A clear application isolation preflight confirms only the currently requested Deployment Kit-managed footprint is non-colliding. It does not demonstrate host-level Production isolation.
130
+
111
131
  Production backup/recovery/security/deploy/destructive gates remain stronger regardless of colocation.
112
132
 
113
133
  ## Readiness cadence
@@ -118,6 +138,7 @@ select environment
118
138
  → inspect placement-relevant local state
119
139
  → show/confirm known facts
120
140
  → authorize relevant read-only verification
141
+ → run deterministic application isolation preflight when supported
121
142
  → repair only missing prerequisites under explicit write gates
122
143
  → ENVIRONMENT READY TO DEPLOY
123
144
  → /deploy <environment>
@@ -133,6 +154,8 @@ Readiness does **not** hide application deployment inside setup.
133
154
 
134
155
  Application runtime deployment, release-specific migration, route/port publication, live health/readiness, smoke validation, and release-specific evidence belong to `/deploy` when the installed adapter defines them.
135
156
 
157
+ The application isolation preflight is a remote read-only inspection and does not itself authorize any later write.
158
+
136
159
  ## Pre-deploy versus post-deploy evidence
137
160
 
138
161
  Readiness proves only facts that can exist before deployment, such as:
@@ -142,6 +165,7 @@ Readiness proves only facts that can exist before deployment, such as:
142
165
  - local Docker readiness or verified remote target/identity depending on placement;
143
166
  - target OS/runtime when applicable;
144
167
  - artifact identity/access;
168
+ - deterministic application isolation/collision evidence when supported;
145
169
  - route/port ownership availability;
146
170
  - persistence/backup/migration prerequisites;
147
171
  - deployment lock/recovery capability;
@@ -163,4 +187,4 @@ Once ready:
163
187
  /deploy <environment>
164
188
  ```
165
189
 
166
- `/deploy` verifies current topology/target/artifact evidence, presents the exact installed-tool authorization gate, delegates deterministic mechanics, and verifies placement-relevant live evidence.
190
+ `/deploy` verifies current topology/target/artifact evidence, presents the exact installed-tool authorization gate, delegates deterministic mechanics, requires current isolation evidence when the installed adapter defines it, and verifies placement-relevant live evidence.
@@ -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;
47
+ - `/deployment-readiness` evidence, including 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
 
@@ -60,6 +60,8 @@ When a fresh setup-time proof already demonstrated target access, platform readi
60
60
 
61
61
  Proof does not establish the real application's image/release, persistence/migration contract, route/exposure, secrets, health contract, or release policy.
62
62
 
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
+
63
65
  ## 3. First real deployment asks only unresolved application questions
64
66
 
65
67
  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:
@@ -102,6 +104,8 @@ exact image acquired/built locally
102
104
 
103
105
  Do not copy a developer GitHub PAT/GHCR credential to the target as normal deployment behavior.
104
106
 
107
+ For installed Deployment Kit versions that expose real application isolation planning, require that capability rather than reconstructing collision checks from model knowledge. If the installed package lacks the capability required by current Core policy, stop with a Project Forge update/capability requirement before requesting the application write authorization.
108
+
105
109
  ### Managed service
106
110
 
107
111
  Use only an installed repository/catalog provider adapter. If none exists, stop with `CATALOG GAP / PLANNED` rather than constructing provider commands from general knowledge.
@@ -137,6 +141,12 @@ optional backup/safety check
137
141
  → deployment evidence capture
138
142
  ```
139
143
 
144
+ For the standard remote Deployment Kit path, pass the intended application footprint needed by the installed planner's isolation contract, including exact target, owner repository, environment, Production classification, app slug, container name, runtime port, persistence mode, optional SQLite database file, and intended route hostnames when applicable.
145
+
146
+ When the deterministic application plan exposes reviewed isolation evidence (for example `isolationReview`), require a clear state and retain its evidence fingerprint for the human preview. A blocked plan is not deployable. Do not edit names/ports/routes/persistence identities automatically to make a blocked plan pass.
147
+
148
+ The deterministic deployment tool owns the final current-state recheck immediately before mutation. Core must not substitute a stale readiness fingerprint or prose-only reasoning for that revalidation.
149
+
140
150
  Do not smuggle infrastructure provisioning, target/provider rebinding, destructive restore, or environment tear-down into the application plan.
141
151
 
142
152
  ## 7. Preserve exact authorization contracts
@@ -154,14 +164,16 @@ For Production-class environments also show `[PRODUCTION WRITE]` consequence vis
154
164
 
155
165
  Resolve the exact phrase from the installed tool/version. Do not assume an example phrase when the installed contract differs.
156
166
 
157
- Authorization applies only to the exact environment, resolved classification, placement/target/provider, artifact, reviewed plan, and evidence shown. Material changes require renewed review/authorization.
167
+ Authorization applies only to the exact environment, resolved classification, placement/target/provider, artifact, reviewed plan, current isolation evidence, and evidence shown. Material changes require renewed review/authorization.
158
168
 
159
169
  ## 8. Preview and execute only the approved contract
160
170
 
161
- 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, planned operations, and exact required authorization phrase.
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 operations, and exact required authorization phrase.
162
172
 
163
173
  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.
164
174
 
175
+ The deterministic launcher must revalidate current isolation immediately before real application mutation when the installed contract provides that capability. If current isolation changed or became blocked after review, stop and create/review a fresh plan rather than continuing under the old authorization.
176
+
165
177
  Existing per-app/per-environment locks are normal concurrency protection when another workstation is deploying the same environment.
166
178
 
167
179
  ## 9. Validate live evidence
@@ -129,6 +129,43 @@ Before asking questions, inspect existing non-secret profile/target evidence. Cl
129
129
 
130
130
  Only after explicit `[REMOTE READ ONLY]` authorization under the installed/repository contract may new remote checks occur. Never scan/select another host or weaken host-key verification merely to get a green result.
131
131
 
132
+ #### Real application isolation preflight
133
+
134
+ For a supported remote application deployment, prefer the repository-distributed launcher:
135
+
136
+ ```text
137
+ tools/project-forge-deployment/project-forge-deploy.py
138
+ ```
139
+
140
+ Inspect installed help/version evidence before relying on a specific subcommand. When the installed contract exposes `app isolation`, use that deterministic read-only capability before declaring the application footprint ready. Do not reconstruct equivalent collision logic from model knowledge.
141
+
142
+ Build the check from exact project/topology/application evidence only:
143
+
144
+ ```text
145
+ app isolation <target>
146
+ --owner-repo <owner/repository>
147
+ --environment <exact logical environment>
148
+ --app-slug <application slug>
149
+ --container-name <managed container name>
150
+ --runtime-port <container-scoped runtime port>
151
+ --persistence-mode <none|directory>
152
+ [--database-file <SQLite file when applicable>]
153
+ [--route-hostname <intended hostname> ...]
154
+ [--production when resolved purpose/classification is Production]
155
+ --require-clear
156
+ --json
157
+ ```
158
+
159
+ Pass `--production` for custom Production names such as `prod-east`; never downgrade Production policy because the literal environment name differs.
160
+
161
+ The isolation check is remote read-only evidence. A clear result may establish that the requested Project/environment footprint does not collide with current Deployment Kit-managed application ownership, container identity, route hostnames/upstream ownership, SQLite identity, persistence root, or deployment-lock identity. It may also report a shared physical failure domain and sibling logical environments.
162
+
163
+ A blocked result or nonzero `--require-clear` outcome means the environment is **not ready to deploy**. Surface the structured collision codes/evidence and stop before any application write gate. Do not auto-rename the environment, container, route, database, target, or app merely to obtain a clear result.
164
+
165
+ If current installed tooling does not expose the required isolation capability, report an installed-capability / Project Forge update requirement. Do not silently fall back to prose-only collision reasoning.
166
+
167
+ Keep the returned isolation `evidenceSha256` with readiness evidence when available. It is review evidence, not a reusable write authorization; `/deploy` and the deterministic deployment tool must re-evaluate current state before mutation.
168
+
132
169
  ### C. Managed-service placement
133
170
 
134
171
  Use only a repository/catalog-provided provider adapter. If none exists, report:
@@ -155,6 +192,8 @@ Do not mark workspace readiness failed. Route to `/setup <environment>` or `/set
155
192
 
156
193
  Multiple logical environments may share physical failure domains, but they remain independently targetable and independently authorized. If environments share physical failure, state it explicitly.
157
194
 
195
+ A clear application isolation preflight means the current Deployment Kit-managed footprint is non-colliding; it does **not** demonstrate host-level Production isolation.
196
+
158
197
  Colocation does not demonstrate host-level Production isolation. Never infer readiness for one logical environment from another merely because they share hardware.
159
198
 
160
199
  ## 5. State-driven cadence
@@ -166,6 +205,7 @@ select exact project-defined environment
166
205
  → read current topology
167
206
  → reuse fresh environment-proof evidence when valid
168
207
  → rediscover placement-relevant local state
208
+ → verify current application isolation when the installed adapter supports it
169
209
  → verify/repair only missing prerequisites under exact gates
170
210
  → ENVIRONMENT READY TO DEPLOY
171
211
  → /deploy <environment>
@@ -195,11 +235,13 @@ A missing prerequisite is not authorization to change it. Use the repository/too
195
235
 
196
236
  Do not hide the real application deployment inside readiness. Readiness may rely on the disposable setup-time proof, but it must not start/replace the real project runtime, register the final application route, perform release-specific migration, or claim project-release smoke validation.
197
237
 
238
+ The application isolation preflight itself is read-only and does not authorize later deployment, route, persistence, migration, Production, or destructive writes.
239
+
198
240
  ## 8. Pre-deploy versus post-deploy evidence
199
241
 
200
242
  Readiness asks whether `/deploy <name>` can safely perform the real application deployment now.
201
243
 
202
- Pre-deploy evidence may include topology, adapter availability, binding, local/remote identity and access, target platform, fresh proof evidence, artifact policy, route/port availability, persistence/backup/migration prerequisites, lock/recovery capability, and Production-specific safeguards.
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.
203
245
 
204
246
  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`.
205
247
 
@@ -221,6 +263,8 @@ Purpose: <purpose>
221
263
  Placement: <...>
222
264
  Binding: VERIFIED / LOCAL / provider-specific
223
265
  This-workstation environment proof: PROVEN / NOT REQUIRED / NOT AVAILABLE
266
+ Application isolation: CLEAR / NOT APPLICABLE
267
+ Isolation evidence: <evidenceSha256 when exposed by installed tooling>
224
268
  Platform prerequisites: READY
225
269
  Application-specific prerequisites: READY / <remaining items>
226
270
  Security blockers: NONE
@@ -7,16 +7,19 @@ import json
7
7
  import sys
8
8
  from typing import Any
9
9
 
10
+ from .app_deployment import (
11
+ app_isolation_check,
12
+ apply_app_deploy,
13
+ apply_app_deploy_from_workstation,
14
+ plan_app_deploy,
15
+ )
10
16
  from .app_runtime import (
11
17
  APP_DEPLOY_WRITE_PHRASE,
12
18
  PRODUCTION_APP_DEPLOY_WRITE_PHRASE,
13
19
  app_inventory,
14
- apply_app_deploy,
15
20
  deploy_write_phrase,
16
21
  normalize_app_spec,
17
- plan_app_deploy,
18
22
  )
19
- from .app_runtime_workstation import apply_app_deploy_from_workstation
20
23
  from .errors import DeploymentKitError
21
24
  from .sqlite_adapter import assert_app_spec_matches_persistence
22
25
 
@@ -53,6 +56,35 @@ def _confirm(args: argparse.Namespace, phrase: str, action: str) -> None:
53
56
  raise DeploymentKitError(f"{action.capitalize()} was not authorized.")
54
57
 
55
58
 
59
+ def _add_isolation_footprint(parser: argparse.ArgumentParser) -> None:
60
+ parser.add_argument("name")
61
+ parser.add_argument("--owner-repo", required=True)
62
+ parser.add_argument("--environment", required=True)
63
+ parser.add_argument(
64
+ "--production",
65
+ action="store_true",
66
+ help="Classify this named environment as Production even when its name is not literally 'production'",
67
+ )
68
+ parser.add_argument("--app-slug", required=True)
69
+ parser.add_argument("--container-name", required=True)
70
+ parser.add_argument("--runtime-port", type=int, required=True)
71
+ parser.add_argument(
72
+ "--persistence-mode",
73
+ choices=("none", "directory"),
74
+ required=True,
75
+ )
76
+ parser.add_argument(
77
+ "--database-file",
78
+ help="Optional intended SQLite database file used only for read-only collision/isolation checks",
79
+ )
80
+ parser.add_argument(
81
+ "--route-hostname",
82
+ action="append",
83
+ default=[],
84
+ help="Optional intended route hostname used only for read-only collision/isolation checks; repeat as needed",
85
+ )
86
+
87
+
56
88
  def build_parser() -> argparse.ArgumentParser:
57
89
  parser = argparse.ArgumentParser(prog="project-forge-deploy app")
58
90
  sub = parser.add_subparsers(dest="command", required=True)
@@ -64,29 +96,26 @@ def build_parser() -> argparse.ArgumentParser:
64
96
  inventory.add_argument("name")
65
97
  inventory.add_argument("--json", action="store_true")
66
98
 
99
+ isolation = sub.add_parser(
100
+ "isolation",
101
+ help="Read current managed ownership/runtime/route/persistence isolation for an application footprint",
102
+ )
103
+ _add_isolation_footprint(isolation)
104
+ isolation.add_argument(
105
+ "--require-clear",
106
+ action="store_true",
107
+ help="Exit nonzero when the read-only isolation result is blocked",
108
+ )
109
+ isolation.add_argument("--json", action="store_true")
110
+
67
111
  deploy = sub.add_parser(
68
112
  "deploy",
69
113
  help="Deploy one immutable application runtime without modifying routes",
70
114
  )
71
- deploy.add_argument("name")
72
- deploy.add_argument("--owner-repo", required=True)
73
- deploy.add_argument("--environment", required=True)
74
- deploy.add_argument(
75
- "--production",
76
- action="store_true",
77
- help="Classify this named environment as Production even when its name is not literally 'production'",
78
- )
79
- deploy.add_argument("--app-slug", required=True)
80
- deploy.add_argument("--container-name", required=True)
115
+ _add_isolation_footprint(deploy)
81
116
  deploy.add_argument("--image", required=True)
82
- deploy.add_argument("--runtime-port", type=int, required=True)
83
117
  deploy.add_argument("--health-path", required=True)
84
118
  deploy.add_argument("--run-as", required=True)
85
- deploy.add_argument(
86
- "--persistence-mode",
87
- choices=("none", "directory"),
88
- required=True,
89
- )
90
119
  deploy.add_argument("--data-container-path")
91
120
  deploy.add_argument(
92
121
  "--env",
@@ -115,6 +144,23 @@ def main(argv: list[str] | None = None) -> int:
115
144
  _print(app_inventory(args.name), as_json=args.json)
116
145
  return 0
117
146
 
147
+ production_classified = args.production or args.environment == "production"
148
+ if args.command == "isolation":
149
+ result = app_isolation_check(
150
+ args.name,
151
+ owner_repo=args.owner_repo,
152
+ environment=args.environment,
153
+ production=production_classified,
154
+ app_slug=args.app_slug,
155
+ container_name=args.container_name,
156
+ runtime_port=args.runtime_port,
157
+ persistence_mode=args.persistence_mode,
158
+ database_file=args.database_file,
159
+ route_hostnames=args.route_hostname,
160
+ )
161
+ _print(result, as_json=args.json)
162
+ return 2 if args.require_clear and result["state"] != "clear" else 0
163
+
118
164
  spec = normalize_app_spec(
119
165
  owner_repo=args.owner_repo,
120
166
  environment=args.environment,
@@ -129,10 +175,15 @@ def main(argv: list[str] | None = None) -> int:
129
175
  environment_variables=_parse_env(args.env),
130
176
  )
131
177
  assert_app_spec_matches_persistence(args.name, spec)
132
- plan = plan_app_deploy(args.name, spec)
178
+ plan = plan_app_deploy(
179
+ args.name,
180
+ spec,
181
+ production=production_classified,
182
+ database_file=args.database_file,
183
+ route_hostnames=args.route_hostname,
184
+ )
133
185
  plan_view = {key: value for key, value in plan.items() if key != "spec"}
134
186
  plan_view["imageSource"] = args.image_source
135
- production_classified = args.production or plan["environment"] == "production"
136
187
  plan_view["productionClassified"] = production_classified
137
188
  if args.image_source == "workstation":
138
189
  plan_view["targetRegistryCredentialRequired"] = False