From 133620ed65b5b94e2555ab09233586bc9a9d0bc6 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 7 Sep 2026 19:57:38 -0400 Subject: [PATCH] projects: required assistant field, the domain assistant a project's work enters through (PyAutoBrain#362) Every projects.yaml row now declares assistant: a workspace-relative repo name such as autolens_assistant, or none. check accepts none or a bare directory name and stats nothing. subhalo_validation and euclid_dr1_prelim declare autolens_assistant; every dormant and retired row declares none. The Cortex names the assistant and never reads it. note stays the one optional field; retire is unaffected. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01AuStnf9YpNvDVdwkyaG81J --- REFERENCE.md | 7 ++++++- projects.yaml | 14 ++++++++++++++ scripts/cortex.py | 7 ++++++- tests/fixtures/skeleton/projects.yaml | 1 + tests/test_cortex.py | 25 +++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/REFERENCE.md b/REFERENCE.md index 8448a8e..16daf7b 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -393,9 +393,14 @@ listed here. A document PyYAML cannot read is reported as one problem. projects; a personal remote is recorded as a fact with a `note:`), `local_path` (absolute laptop path — the Cortex-only exception to the workspace-paths rule), `ral_root`, `mirror` (path | `none`), `sync_cli`, - `sync_verbs`, `ledger`, `witness_file` (glob), `partition` + `sync_verbs`, `ledger`, `assistant`, `witness_file` (glob), `partition` (`gpu | ral | both`), `status` (`active | dormant | planned | retired`). +- `assistant` is the domain assistant this project's work enters through — a + workspace-relative repo name such as `autolens_assistant`, or `none`; the + Cortex names it, never reads it. `check` accepts `none` or a bare directory + name (`^[A-Za-z0-9_.-]+$`: no `/`, never absolute) and stats nothing, so the + render does not need an assistant checkout. - `note` (free text) is the **one optional field**: a row may omit it, an empty `note:` is drift, and any other field is still an unknown-field error. A note holding `:` or `#` is quoted like any other scalar. diff --git a/projects.yaml b/projects.yaml index 0791ccc..d6891e0 100644 --- a/projects.yaml +++ b/projects.yaml @@ -22,6 +22,9 @@ # sync_cli: # the project's sync CLI, relative to local_path # sync_verbs: [a, b, c] # the verbs that CLI has — a flow list of bare words, the only list # ledger: # the project's own commentary ledger, relative to local_path +# assistant: # the domain assistant this project's work enters through — a +# # workspace-relative repo name such as `autolens_assistant`, or +# # `none`; the Cortex names it, never reads it # witness_file: # where a run's witness lands, relative to the mirror # partition: gpu | ral | both # which SLURM partition(s) the project runs on # status: active | dormant | planned | retired # `cortex.py retire` writes the last @@ -50,6 +53,7 @@ inference_programme: sync_cli: hpc/sync sync_verbs: [pull, logs, status, submit, jobs, sacct, cancel, tail, du, check] ledger: results/notes/inference/PROGRAMME.md + assistant: none witness_file: results/searches/**/*.json partition: both status: retired @@ -63,6 +67,7 @@ subhalo_validation: sync_cli: hpc/sync sync_verbs: [push, pull, logs, sync, push-data-init, pull-full, status, submit, push-submit, jobs, sacct, cancel, wait-and-pull, tail, du, check, clear-logs] ledger: wiki/project/state.md + assistant: autolens_assistant witness_file: results/**/*.json partition: ral status: active @@ -76,6 +81,7 @@ euclid: sync_cli: hpc/sync sync_verbs: [push, pull, sync, status] ledger: CLAUDE.md + assistant: none witness_file: catalogue/**/*.csv partition: both status: dormant @@ -89,6 +95,7 @@ euclid_dr1_prelim: sync_cli: hpc/sync sync_verbs: [push, pull, sync, status] ledger: wiki/project/state.md + assistant: autolens_assistant witness_file: results/**/*.json partition: both status: active @@ -108,6 +115,7 @@ profiling: sync_cli: hpc/sync sync_verbs: [pull] ledger: README.md + assistant: none witness_file: results/**/*.json partition: both status: dormant @@ -121,6 +129,7 @@ cowls_diana: sync_cli: hpc/sync sync_verbs: [pull] ledger: README.md + assistant: none witness_file: results/**/*.json partition: both status: dormant @@ -134,6 +143,7 @@ subhalo_simulations: sync_cli: hpc/sync sync_verbs: [pull] ledger: README.md + assistant: none witness_file: results/**/*.json partition: both status: dormant @@ -147,6 +157,7 @@ slope_hierarchy: sync_cli: hpc/sync sync_verbs: [pull] ledger: README.md + assistant: none witness_file: results/**/*.json partition: both status: dormant @@ -160,6 +171,7 @@ pj011646: sync_cli: hpc/sync sync_verbs: [pull] ledger: README.md + assistant: none witness_file: results/**/*.json partition: both status: dormant @@ -173,6 +185,7 @@ concr: sync_cli: hpc/sync sync_verbs: [pull] ledger: README.md + assistant: none witness_file: results/**/*.json partition: both status: dormant @@ -186,6 +199,7 @@ ic50_workspace: sync_cli: hpc/sync sync_verbs: [pull] ledger: README.md + assistant: none witness_file: results/**/*.json partition: both status: dormant diff --git a/scripts/cortex.py b/scripts/cortex.py index 688321d..c83e823 100644 --- a/scripts/cortex.py +++ b/scripts/cortex.py @@ -104,7 +104,8 @@ RULING_SECTIONS = ("Ruling", "Evidence") PROJECT_FIELDS = ("remote", "local_path", "ral_root", "mirror", "sync_cli", - "sync_verbs", "ledger", "witness_file", "partition", "status") + "sync_verbs", "ledger", "assistant", "witness_file", "partition", + "status") #: the one optional field — free text about the row (why a remote is `none`, #: what a verb does). A row may omit it; an empty `note:` is still drift, and #: any field outside these two tuples is still an error. @@ -367,6 +368,10 @@ def _finish_row(key: str, row: dict, problems: "list[str]") -> None: problems.append(f"projects.yaml: {key}.{f} must be an absolute path") if row.get("mirror") and row["mirror"] != "none" and not row["mirror"].startswith("/"): problems.append(f"projects.yaml: {key}.mirror must be an absolute path or none") + assistant = row.get("assistant") + if assistant and assistant != "none" and not re.match(r"^[A-Za-z0-9_.-]+$", assistant): + problems.append(f"projects.yaml: {key}.assistant must be 'none' or a bare " + f"workspace-relative directory name (got {assistant!r})") def load_projects(root: Path) -> "tuple[dict[str, dict], list[str]]": diff --git a/tests/fixtures/skeleton/projects.yaml b/tests/fixtures/skeleton/projects.yaml index 879255e..eb2e69b 100644 --- a/tests/fixtures/skeleton/projects.yaml +++ b/tests/fixtures/skeleton/projects.yaml @@ -9,6 +9,7 @@ example: sync_cli: hpc/sync sync_verbs: [pull, submit, jobs, tail] ledger: wiki/project/state.md + assistant: none witness_file: output/**/checkpoint.hdf5 partition: gpu status: active diff --git a/tests/test_cortex.py b/tests/test_cortex.py index ffb9f0a..4c6d94d 100644 --- a/tests/test_cortex.py +++ b/tests/test_cortex.py @@ -203,6 +203,8 @@ def test_the_live_body_map_parses_clean(): assert rows["euclid_dr1_prelim"]["status"] == "active" assert rows["euclid"]["remote"] == "none" assert "PyAutoLabs remote" in rows["euclid"]["note"] + assert rows["subhalo_validation"]["assistant"] == "autolens_assistant" + assert rows["inference_programme"]["assistant"] == "none" def test_a_block_list_and_a_quoted_scalar_are_ordinary_yaml(tmp_path): @@ -279,6 +281,29 @@ def test_note_is_optional_but_never_empty_and_the_field_set_still_closes(tmp_pat _assert_drift(root, "example.note is empty") +def test_assistant_is_required_and_names_a_bare_workspace_directory(tmp_path): + """`assistant` is required like every field but `note`. Its value is + exactly `none` or a bare workspace-relative directory name — the Cortex + names the assistant, it never reads it, so no path and no stat.""" + root = _copy(tmp_path) + assert _problems(root) == [], "the fixture carries `assistant: none`" + _edit(root, "projects.yaml", " assistant: none\n", + " assistant: autolens_assistant\n") + assert _problems(root) == [] + rows, problems = cortex.parse_projects((root / "projects.yaml").read_text()) + assert problems == [] and rows["example"]["assistant"] == "autolens_assistant" + _edit(root, "projects.yaml", " assistant: autolens_assistant\n", + " assistant:\n") + _assert_drift(root, "example.assistant is empty") + _edit(root, "projects.yaml", " assistant:\n", "") + _assert_drift(root, "example is missing assistant") + for bad in ("../autolens_assistant", "/home/jammy/Code/PyAutoLabs/autolens_assistant"): + root = _copy(tmp_path / bad.replace("/", "_")) + _edit(root, "projects.yaml", " assistant: none\n", f" assistant: {bad}\n") + _assert_drift(root, "example.assistant must be 'none' or a bare " + "workspace-relative directory name") + + # --------------------------------------------------------------------------- # # retire # --------------------------------------------------------------------------- #