Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/exlab_wizard/api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class TemplateBlock(
# (legal for project / equipment templates per Spec §5.2). Persisted as
# an omitted field thanks to ``omit_defaults=True``.
run_scope: RunScope | None = None
# Path (relative to the instance dir, POSIX) of the frozen verbatim copy
# of the template source written under ``.exlab-wizard/templates/...`` at
# creation time (added in schema 1.10). Empty when the provenance copy was
# not made; omitted on serialize thanks to ``omit_defaults=True``.
provenance_path: str = ""


class PluginIsolation(
Expand Down
6 changes: 6 additions & 0 deletions src/exlab_wizard/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
SERVER_STATE_FILE,
SYNC_QUEUE_DB_NAME,
SYNC_STATE_FILENAME,
TEMPLATES_SUBDIR,
TEST_RUNS_JSON_NAME,
)

Expand Down Expand Up @@ -89,6 +90,8 @@
QUIT_DRAIN_TIMEOUT_SECONDS,
SESSION_GC_AFTER_SECONDS,
SIGTERM_DRAIN_TIMEOUT_SECONDS,
TEMPLATE_MAX_FILES,
TEMPLATE_UPLOAD_MAX_BYTES,
TRAY_STATUS_REFRESH_SECONDS,
VALIDATOR_BINARY_DETECT_BYTES,
WINDOW_DEFAULT_HEIGHT,
Expand Down Expand Up @@ -215,8 +218,11 @@
"SYNC_QUEUE_DB_NAME",
"SYNC_STATE_FILENAME",
"SYNC_STATE_JSON_VERSION",
"TEMPLATES_SUBDIR",
"TEMPLATE_MAX_FILES",
"TEMPLATE_QUESTION_ID_PATTERN",
"TEMPLATE_QUESTION_ID_REGEX",
"TEMPLATE_UPLOAD_MAX_BYTES",
"TEST_MODE_ENV",
"TEST_MODE_PREFIX",
"TEST_RUNS_DIR_NAME",
Expand Down
3 changes: 3 additions & 0 deletions src/exlab_wizard/constants/filenames.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
# wizard against an existing directory. Backend Spec §5.3.
ANSWERS_FILE_NAME: str = ".exlab-answers.yml"

# Subdirectory name (under a cache dir) holding per-instance template stores. Spec §5.0.
TEMPLATES_SUBDIR: str = "templates"

# Per-host log filename template inside the central log dir. Backend Spec §4.5.
# Format with ``LOG_FILE_TEMPLATE.format(hostname=...)``.
LOG_FILE_TEMPLATE: str = "wizard.{hostname}.log"
Expand Down
6 changes: 6 additions & 0 deletions src/exlab_wizard/constants/limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,9 @@
# Pre-flight free-disk-space requirement, in MiB, on the run/project target
# volume before the wizard will start a creation. Frontend Spec §4.6.
DISK_SPACE_PREFLIGHT_MIB: int = 100

# Max size (bytes) of a single file uploaded into a template via the GUI. Spec §5.3.
TEMPLATE_UPLOAD_MAX_BYTES: int = 25 * 1024 * 1024

# Max number of files a single template directory may hold (GUI upload guard).
TEMPLATE_MAX_FILES: int = 500
2 changes: 1 addition & 1 deletion src/exlab_wizard/constants/schema_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

# Version of the per-run ``creation.json`` cache file. See Backend Spec
# section 11.3 (history table) for the schema and migration rules.
CREATION_JSON_VERSION: str = "1.9"
CREATION_JSON_VERSION: str = "1.10"

# Version of the per-equipment ``readme_fields.json`` cache. Backend Spec §11.4.
README_FIELDS_JSON_VERSION: str = "1.1"
Expand Down
19 changes: 19 additions & 0 deletions src/exlab_wizard/controller/creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
ResolvedTemplate,
TemplateEngine,
)
from exlab_wizard.template.provenance import copy_template_into_instance
from exlab_wizard.utils.time import utc_now, utc_now_iso
from exlab_wizard.validator.engine import CreationValidationInput, Validator
from exlab_wizard.validator.findings import Finding
Expand Down Expand Up @@ -1002,6 +1003,17 @@ async def _write_cache(
for entry in plugin_result.applied
]

# Freeze a verbatim copy of the template source into the instance's
# own typed provenance store (.exlab-wizard/templates/<type>/<name>/)
# and record its instance-relative path in creation.json. Best-effort:
# a provenance-copy failure must NOT fail the creation.
own_type = "run" if isinstance(req, RunCreateRequest) else "project"
try:
provenance_path = copy_template_into_instance(resolved, dst, own_type)
except Exception as exc: # best-effort: a copy failure is never fatal
_log.warning("provenance copy failed for %s: %s", dst, exc)
provenance_path = ""

# Redesign §3.1/§3.3: the orchestrator block, template/paths blocks,
# and the CreationJson assembly are shared with the sample-data
# seeder via ``build_creation_json`` so the two can never drift.
Expand All @@ -1010,6 +1022,7 @@ async def _write_cache(
version=resolved.exlab_version,
source_path=str(resolved.path),
run_scope=resolved.run_scope,
provenance_path=provenance_path,
extra_readme_fields=resolved.extra_readme_fields,
plugin_order=resolved.plugin_order,
)
Expand Down Expand Up @@ -1090,6 +1103,12 @@ def _post_validate(
file_names: list[str] = []
file_contents: dict[str, str] = {}
for path in dst.rglob("*"):
# Skip the ``.exlab-wizard/`` cache subtree -- it holds the frozen
# provenance template copy (copier.yml / .jinja / placeholder
# files) and other wizard metadata, none of which is run output to
# be scanned for residual placeholders.
if CACHE_DIR_NAME in path.parts:
continue
if not path.is_file():
continue
file_names.append(path.name)
Expand Down
6 changes: 6 additions & 0 deletions src/exlab_wizard/controller/metadata_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ class TemplateDesc:
``str(ResolvedTemplate.path)``.
run_scope: The run-scope tag persisted on ``creation.json``'s
template block; ``None`` for project/equipment templates.
provenance_path: Path (relative to the instance dir, POSIX) of the
frozen verbatim template copy written under
``.exlab-wizard/templates/...`` at creation time; empty when no
copy was made.
extra_readme_fields: ``_exlab_readme.fields`` entries (free-form
dicts) used to build the template-layer field declarations.
plugin_order: Plugin slug ordering (unused by these helpers but
Expand All @@ -89,6 +93,7 @@ class TemplateDesc:
version: str
source_path: str
run_scope: RunScope | None = None
provenance_path: str = ""
extra_readme_fields: list[dict[str, Any]] = field(default_factory=list)
plugin_order: list[str] = field(default_factory=list)

Expand Down Expand Up @@ -210,6 +215,7 @@ def build_creation_json(
version=template.version,
source_path=template.source_path,
run_scope=template.run_scope,
provenance_path=template.provenance_path,
),
variables=dict(variables),
paths=PathsBlock(
Expand Down
Loading
Loading