diff --git a/.github/ISSUE_TEMPLATE/defect.md b/.github/ISSUE_TEMPLATE/defect.md index cf6c652..678dae9 100644 --- a/.github/ISSUE_TEMPLATE/defect.md +++ b/.github/ISSUE_TEMPLATE/defect.md @@ -18,7 +18,7 @@ labels: [] ## Environment - Operating system and architecture: -- `-setup-system --version`: +- `codex-setup-system --version`: - Product version being configured: ## Target state, if relevant diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4aa7a4e..d7adde3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: contents: read uses: NDDev-OpenNetwork/ci-workflows/.github/workflows/rust-ci.yml@d92026cf31a10a0eeaa532e1f323c7cfdfbfac5b # 0.1.29 with: - toolchain: '1.98.0' + toolchain: '1.98.1' # The three-OS matrix is the evidence ADR-0113 asks for, and standard # hosted runners are free with unlimited minutes on a public repository. test_matrix_os: '["ubuntu-latest", "macos-latest", "windows-latest"]' @@ -99,7 +99,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 with: - toolchain: '1.98.0' + toolchain: '1.98.1' # The same reason the release job gives. This job's whole subject is # the bytes that come out of the build, so restoring someone else's # `target/` and then reading its import table would answer a question @@ -181,9 +181,13 @@ jobs: # bypassed, or read by someone with no reason to trust it. # # Measured on Linux across all seven binaries, then on macOS by - # `0.0.8`'s own run, and the two agree exactly: the six that declare - # `launch` import `execvp` and nothing else; antigravity, which - # declares none, imports no spawn symbol at all, on either platform. + # `0.0.8`'s own run, and the two agree exactly: every build that + # declares `launch` imports `execvp` and nothing else. `0.0.8` still + # carried the era when antigravity declared none and imported no + # spawn symbol at all -- all seven declare it now (antigravity's is + # a documented-home binding), so today the negative arm is enforced + # on nobody, and stays because the day a launch slips out of a + # declaration is the day the spawn symbol must fail here. # So the linker drops what nothing calls and the absence is real # rather than incidental -- which is what had to be true before this # could be enforced anywhere. diff --git a/.github/workflows/evidence.yml b/.github/workflows/evidence.yml index 7707bbe..8431a37 100644 --- a/.github/workflows/evidence.yml +++ b/.github/workflows/evidence.yml @@ -67,7 +67,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 with: - toolchain: '1.98.0' + toolchain: '1.98.1' - name: Prove this is the native architecture, not emulation env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a6e3b6..6f08511 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 with: - toolchain: '1.98.0' + toolchain: '1.98.1' # This action caches by default, and a release must not build on top # of a cache. A run with weaker trust than this one can write the # cache a release would restore, so a poisoned entry would be baked diff --git a/README.md b/README.md index fce1187..2f5a84d 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,15 @@ instructions, agents, commands, hooks and settings together, in one step. ```bash codex-setup-system list -codex-setup-system install baseline --target ~/.tool-config -codex-setup-system status --target ~/.tool-config -codex-setup-system select full-auto --target ~/.tool-config -codex-setup-system diff --target ~/.tool-config -codex-setup-system reinstall --target ~/.tool-config -codex-setup-system backups --target ~/.tool-config -codex-setup-system hold --backup slot-000000000001 --reason "before the experiment" --target ~/.tool-config -codex-setup-system restore --backup slot-000000000001 --target ~/.tool-config -codex-setup-system remove --target ~/.tool-config +codex-setup-system install baseline --target ~/.codex +codex-setup-system status --target ~/.codex +codex-setup-system select full-auto --target ~/.codex +codex-setup-system diff --target ~/.codex +codex-setup-system reinstall --target ~/.codex +codex-setup-system backups --target ~/.codex +codex-setup-system hold --backup slot-000000000001 --reason "before the experiment" --target ~/.codex +codex-setup-system restore --backup slot-000000000001 --target ~/.codex +codex-setup-system remove --target ~/.codex ``` Every command takes an explicit `--target`. There is no default and no fallback @@ -47,8 +47,8 @@ seven setup systems, expressed in each product's own format: | | | | --- | --- | -| `baseline` | a working floor: instructions plus a conservative configuration | -| `minimal` | the product's own defaults, and the state a restore proves it can reach | +| `baseline` | a working floor: instructions plus the shared autonomous posture | +| `minimal` | instructions plus the shared autonomous posture, and nothing else | | `full-auto` | nothing asked and nothing sandboxed, in this product's own keys | | `nddev-builder` | the full-auto posture plus the product-native NDDev authoring toolkit | diff --git a/SUPPORT.md b/SUPPORT.md index fee8d99..70dfdd6 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -34,23 +34,18 @@ A provider that advertised an operation it cannot perform would let a caller ask for something that cannot be honoured, which is worse than not offering it. All five core operations do work: `backup`, `restore`, `remove`, `install` and -`replace`, both from the local setup catalog and from an `ai-stp-bundle/1` +`replace`, both from the local setup catalog and from an `ai-stp-bundle/2` arriving over the wire. ## Using this against a home you already have -**An owned namespace is removed whole.** The table below says what this build -owns; `remove` deletes each of those paths entirely, and a backup slot holds -what was there first. That includes content this build never wrote -- if the -product itself put a key in a configuration file this provider owns, `remove` -takes the file, not the keys this provider added to it. - -Measured, with the real product: launching Codex through `launch` and running -`mcp add` writes `~/.codex/config.toml` with an `[mcp_servers.*]` entry; a -later `install` captures that file into a slot and replaces it; a later -`remove` deletes it. The entry is not lost -- `backups` lists the slot as -*before install, setup none*, and restoring it returns the file byte for byte --- but it is not in the target either. +**Removal follows receipts, not namespaces.** The table below says what this +build owns; `remove` withdraws the files this provider recorded writing, and +in a JSON file it owns it strips the keys it added rather than taking the +file. Anything under those paths this build never wrote stays. Emptying every +owned namespace is a separate, explicitly named operation: `reset`. + +Measured, with the real product: launching Codex CLI through `launch` and running `mcp add` writes `~/.codex/config.toml` with an `[mcp_servers.*]` entry; a later `install` captures that file into a slot and replaces it; a later `remove` withdraws it. The entry is not lost -- `backups` lists the slot as *before install, setup none*, and restoring it returns the file byte for byte -- but it is not in the target either. So: point `--target` at a home you are willing to have managed. `backups --target ` names every earlier state and which setup each preceded, and diff --git a/crates/codex-setup-system/src/main.rs b/crates/codex-setup-system/src/main.rs index 1053e94..59828b9 100644 --- a/crates/codex-setup-system/src/main.rs +++ b/crates/codex-setup-system/src/main.rs @@ -184,10 +184,10 @@ pub const CODEX: Harness = Harness { // `developer_instructions` is refused by name when absent. The consumer // reproduced it against the same binary before either side moved. // - // The stanza form still works and excludes its own file from the scan, - // so this setup's builder role stays the pair -- a setup owning two - // files it declares, which is a different thing from a component, and - // was the half of the old reasoning that was true. + // The stanza form still works and excludes its own file from the scan; + // this setup's builder role ships as the standalone + // `agents/nddev-builder.toml` -- one file, declared like any other + // payload member. ComponentKind::Agent, ], projection_kinds: &[ @@ -671,10 +671,10 @@ mod tests { "{}", examined.problems.join("\n ") ); - // codex ships no skill and no agent file: its skills are `user_root` only and its agent is a role declared in `config.toml`. **Zero is the right number and it is the reason this count exists** -- the assertion below it was green here while examining nothing, and nobody could tell that from the six harnesses where it examined something. + // Codex ships no SKILL.md and no markdown agent: its skills are `user_root` only and its agent is the standalone `agents/nddev-builder.toml`. **One is the right number and it is the reason this count exists** -- the assertion below it was green on every harness while the guard examined nothing at all here, and the count is what makes the subject visible. assert_eq!( - examined.entry_points, 0, - "the description guard examined {} entry points, not 0", + examined.entry_points, 1, + "the description guard examined {} entry points, not 1", examined.entry_points ); } @@ -708,7 +708,7 @@ mod tests { } /// Three postures, on every one of the seven. /// - /// `baseline` is a working floor, `minimal` is the product's own defaults, + /// `baseline` is a working floor, `minimal` is the shared autonomous posture and nothing else, /// and `full-auto` asks nothing and sandboxes nothing. A caller who learns /// them on one product knows them on all seven, which is the whole reason /// the names are the estate's rather than each harness's. diff --git a/crates/harness-runtime/src/catalog.rs b/crates/harness-runtime/src/catalog.rs index e7a9a43..2bb51c0 100644 --- a/crates/harness-runtime/src/catalog.rs +++ b/crates/harness-runtime/src/catalog.rs @@ -324,7 +324,10 @@ pub struct Examined { /// half is the part that took the measuring: /// /// * `SKILL.md`, and a file directly under `agents/`, are entry points. Cursor's -/// own generator writes `{name, description}` for exactly these. +/// own generator writes `{name, description}` for exactly these. A codex +/// `agents/.toml` is the same obligation in TOML: the product refuses +/// the file by name when `name` or `description` is absent, so the check +/// reads the keys instead of frontmatter. /// * A file under `references/` is **not** -- it is a document a skill links to, /// and requiring frontmatter there would be inventing a rule. /// * A file under `commands/` is **not**. Cursor's loader builds @@ -357,10 +360,18 @@ pub fn undescribed(setups: &[Setup]) -> Examined { found.push(format!("{} cannot read {name:?}", setup.manifest.id)); continue; }; + let named = if std::path::Path::new(&name) + .extension() + .is_some_and(|extension| extension.eq_ignore_ascii_case("toml")) + { + toml_names + } else { + frontmatter_names + }; for key in ["name", "description"] { - if !frontmatter_names(&text, key) { + if !named(&text, key) { found.push(format!( - "{} ships {name:?} with no `{key}` in its frontmatter, and a component \ + "{} ships {name:?} with no `{key}` the product reads, and a component \ the product cannot describe is one the model cannot choose", setup.manifest.id )); @@ -767,13 +778,34 @@ fn is_entry_point(relative: &str) -> bool { if leaf.eq_ignore_ascii_case("SKILL.md") { return true; } - // `agents/.md`, and only directly under it. + // `agents/.md` and `agents/.toml`, and only directly under it. + // The `.toml` form is how codex declares a role file; it is measured there + // to carry the same two keys in TOML spelling. parts.len() >= 2 && parts[parts.len() - 2] == "agents" && std::path::Path::new(leaf) .extension() .and_then(std::ffi::OsStr::to_str) - .is_some_and(|extension| extension.eq_ignore_ascii_case("md")) + .is_some_and(|extension| { + extension.eq_ignore_ascii_case("md") || extension.eq_ignore_ascii_case("toml") + }) +} + +/// Whether a TOML entry point assigns `key` at the top level. +/// +/// Read by structure rather than by searching the whole file: only lines +/// before the first `[section]` header count, because a `description` three +/// tables down belongs to that table and not to the agent the file names. +fn toml_names(text: &str, key: &str) -> bool { + text.lines() + .take_while(|line| !line.trim_start().starts_with('[')) + .any(|line| { + let line = line.trim_start(); + line.starts_with(key) + && line[key.len()..] + .trim_start_matches([' ', '\t']) + .starts_with('=') + }) } /// Whether a file opens with YAML frontmatter naming `key`. diff --git a/crates/harness-runtime/src/facts.rs b/crates/harness-runtime/src/facts.rs index 35984fa..e5a2dff 100644 --- a/crates/harness-runtime/src/facts.rs +++ b/crates/harness-runtime/src/facts.rs @@ -6,7 +6,7 @@ //! *facts about a product*, verified against its official documentation and //! recorded in a baseline — not behaviour, and not code. //! -//! Holding them as data rather than as five copies of a dispatcher means a +//! Holding them as data rather than as seven copies of a dispatcher means a //! change to the shared logic lands in one place, and a change to a product's //! surface lands in exactly one struct with a test binding it to that product's //! baseline. @@ -137,7 +137,7 @@ pub struct Harness { /// makes the name *visible*, which is the part that was missing: the /// answer was true about what it examined and silent about what decides. /// - /// Empty for six of the seven, and empty because they were asked -- a + /// Empty for three of the seven, and empty because they were asked -- a /// product whose alternate spellings nobody has measured belongs here as /// nothing rather than as a guess. pub shadowing_names: &'static [Shadow], @@ -203,9 +203,9 @@ pub struct Harness { pub projection_kinds: &'static [ProjectionKind], /// Second targets this provider owns, if any. /// - /// Empty for six of the seven. Antigravity is the exception because the - /// product genuinely keeps a workspace copy of five of its surfaces, and - /// `ai_stp#424`/`#425` are the consumer asking for exactly that route. + /// No build leaves this empty today: each declares a `user_root` scope for + /// products that read the shared `~/.agents` convention, a `project` + /// scope for surfaces the product reads from a workspace, or both. pub scoped_projections: &'static [Scoped], /// The largest file count a bundle may carry. pub max_files: u64, @@ -234,12 +234,6 @@ pub struct Harness { /// [`Delivery::Manager`], which is a different statement -- the product is /// installable, but not by fetching bytes whose digest was fixed in advance /// -- and the refusal says which. - /// How the product's own software is installed, when this build can do it. - /// - /// `None` means the software lifecycle is not offered at all. So does a - /// [`Delivery::Manager`], which is a different statement -- the product is - /// installable, but not by fetching bytes whose digest was fixed in advance - /// -- and the refusal says which. pub software: Option, /// Target-relative path of the user-global instruction attachment. /// diff --git a/crates/harness-runtime/src/lib.rs b/crates/harness-runtime/src/lib.rs index a7b945f..4a583d7 100644 --- a/crates/harness-runtime/src/lib.rs +++ b/crates/harness-runtime/src/lib.rs @@ -1,12 +1,12 @@ //! The provider command runtime every NDDev setup system shares. //! -//! Five products, one set of commands. What differs between them is not +//! Seven products, one set of commands. What differs between them is not //! behaviour but *facts*: which directory a product configures, which files //! inside it this provider owns, and which files belong to the product and must //! be left alone. [`Harness`] holds those facts; [`wire::dispatch`] performs the //! commands over them. //! -//! Written this way, a change to the shared logic lands once instead of five +//! Written this way, a change to the shared logic lands once instead of seven //! times, and a change to one product's surface lands in exactly one struct that //! a test binds to that product's verified baseline. //! @@ -14,7 +14,7 @@ //! //! It performs all five core operations. `backup`, `restore` and `remove` read //! the target, a backup slot, or the provider's own state. `install` and -//! `replace` materialize an `ai-stp-bundle/1` the consumer sends, or a complete +//! `replace` materialize an `ai-stp-bundle/2` the consumer sends, or a complete //! setup from the local catalog when the owner asks for one by name. //! //! The software lifecycle is optional in the contract, and a harness declares diff --git a/crates/provider-v3/src/bundle.rs b/crates/provider-v3/src/bundle.rs index 18bdf74..3f322b4 100644 --- a/crates/provider-v3/src/bundle.rs +++ b/crates/provider-v3/src/bundle.rs @@ -34,10 +34,10 @@ use crate::zip; /// The digest domain for a bundle manifest. pub const BUNDLE_DOMAIN: &str = "ai-stp:bundle:v1"; -/// The original format tag, kept byte-identical during the v2 rollout. +/// The adaptation-bound format, the only tag a production bundle carries. pub const BUNDLE_FORMAT: &str = "ai-stp-bundle/2"; -/// The adaptation-bound format. +/// The original format tag, retired by the v2 rollout and refused on read. #[cfg(test)] const RETIRED_BUNDLE_FORMAT_V1: &str = "ai-stp-bundle/1"; @@ -237,7 +237,7 @@ pub struct ComponentAdaptationBinding { pub struct Manifest { /// Schema of this manifest. pub schema_version: u32, - /// Always `ai-stp-bundle/1`. + /// Always `ai-stp-bundle/2`. pub bundle_format: String, /// The *bundle* protocol, which is 1. Not the provider protocol. pub protocol_version: u32, diff --git a/crates/provider-v3/src/vocabulary.rs b/crates/provider-v3/src/vocabulary.rs index 1c1b0fc..12204b4 100644 --- a/crates/provider-v3/src/vocabulary.rs +++ b/crates/provider-v3/src/vocabulary.rs @@ -399,8 +399,8 @@ impl ProjectionKind { /// A target a projection profile owns, other than the product's own home. /// -/// The kit's schema enumerates exactly one value today, and the global scope is -/// deliberately not among them: the global profile is declared by +/// The kit's schema enumerates exactly two values today, and the global scope +/// is deliberately not among them: the global profile is declared by /// `projection_profile` itself, and two statements about one fact are a defect /// even while they agree. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] diff --git a/install.ps1 b/install.ps1 index 33d4665..1878f31 100644 --- a/install.ps1 +++ b/install.ps1 @@ -8,7 +8,7 @@ [CmdletBinding()] param( [string]$Version = "0.0.81", - [string]$InstallDir = "$env:LOCALAPPDATA\Programs\codex-setup-system" + [string]$InstallDir = $(if ($env:CODEX_INSTALL_DIR) { $env:CODEX_INSTALL_DIR } else { "$env:LOCALAPPDATA\Programs\codex-setup-system" }) ) $ErrorActionPreference = "Stop" diff --git a/references/codex-baseline.json b/references/codex-baseline.json index 197bb0c..918a0b1 100644 --- a/references/codex-baseline.json +++ b/references/codex-baseline.json @@ -281,7 +281,7 @@ "version": "0.158.0", "verified_at": "2026-09-28T14:26:56+00:00" }, - "setup_catalogue_digest": "sha256:911bd311e0797e8222218ba12d80dfc0d0a8399419447fb86edf37d54137cff4", + "setup_catalogue_digest": "sha256:38ea677a1b5ebbb65bd1347ac5e3ae22778eee45a2a3f8361f943c08500319a4", "previous_software_artifacts": { "command": "codex", "shape": "gzip-tar", diff --git a/scripts/evidence.py b/scripts/evidence.py index c450a6c..36eb96e 100644 --- a/scripts/evidence.py +++ b/scripts/evidence.py @@ -172,10 +172,12 @@ def plan( ] ) if answer.get("reason") == "unsupported_platform": - # An honest answer, not a failure. Cursor publishes no Windows build, - # and the provider says so by name rather than planning something it - # could not apply. Treating that as a red would make this job report a - # vendor's product range as a defect of ours. + # An honest answer, not a failure. When a vendor's published manifest + # carries no build for a declared platform, the provider says so by + # name rather than planning something it could not apply. Treating + # that as a red would make this job report a vendor's product range + # as a defect of ours. No harness answers it today; the path stays + # wired because the reason is contract, not decoration. raise NothingToProve(str(answer.get("detail", ""))) if answer.get("state") != "planned": raise Failed( diff --git a/setups/full-auto/setup.json b/setups/full-auto/setup.json index d00739b..11ef2f0 100644 --- a/setups/full-auto/setup.json +++ b/setups/full-auto/setup.json @@ -1,7 +1,7 @@ { "schema_version": 1, "id": "full-auto", - "description": "Full auto: nothing is asked, nothing is sandboxed, and every capability this build ships as stable-but-off is on. `approval_policy` is never, `sandbox_mode` is danger-full-access, `web_search` is live -- the most capable of the four modes -- and `features.memories`, `features.multi_agent_v2` and `features.recommended_plugins` are enabled, which is all three stable features the 0.151.0 registry leaves off. Measured against that artifact: `config.toml parse ok`, 49 enabled and 3 overridden where the product's own default is 46 and 0. This is a setup posture -- keys in this product's own configuration file. It is not an execution profile and it grants no environment: what it changes is what the product asks you and which of its own tools exist, not what anyone is permitted to run. Higher-authority layers can still clamp it: this file is one layer of ten, and managed requirements constrain rather than merge.", + "description": "Full auto: nothing is asked, nothing is sandboxed, and every capability this build ships as stable-but-off is on. `approval_policy` is never, `sandbox_mode` is danger-full-access, `web_search` is live -- the most capable of the four modes -- and `features.memories`, `features.multi_agent_v2` and `features.recommended_plugins` are enabled -- three of the four stable features the 0.157.1 registry leaves off; the fourth, `secret_auth_storage`, stays off deliberately because it moves CLI auth into an encrypted local-secrets backend. Measured against that artifact on 2026-09-27: `config.toml parse ok`, and `codex doctor` reads `55 enabled · 3 overridden`. This is a setup posture -- keys in this product's own configuration file. It is not an execution profile and it grants no environment: what it changes is what the product asks you and which of its own tools exist, not what anyone is permitted to run. Higher-authority layers can still clamp it: this file is one layer of ten, and managed requirements constrain rather than merge.", "sources": [ "https://learn.chatgpt.com/docs/config-file/config-reference" ], diff --git a/setups/nddev-builder/home/agents/nddev-builder.toml b/setups/nddev-builder/home/agents/nddev-builder.toml index 1b24682..25d68cf 100644 --- a/setups/nddev-builder/home/agents/nddev-builder.toml +++ b/setups/nddev-builder/home/agents/nddev-builder.toml @@ -69,9 +69,9 @@ what it owns, ask the binary: `codex-setup-system provider-info`. ## Before you ship one -- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. -- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. -- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. +- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. +- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. +- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. ## Command @@ -115,9 +115,9 @@ Generated from the same rows as the section above, for every harness in this est ## Before you ship one -- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. -- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. -- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. +- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. +- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. +- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. ## Hook @@ -178,9 +178,9 @@ Generated from the same rows as the section above, for every harness in this est ## Before you ship one -- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. -- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. -- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. +- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. +- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. +- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. # The Commands This Program Answers @@ -202,14 +202,15 @@ restore [--backup ] --target the last backup, or a named one hold --backup [--reason ] --target release --backup --target remove --target the files this program recorded writing +adopt --target take over a target the earlier provider left software --prefix which product versions a prefix holds rollback --to --prefix point the command at one already there ``` There is no `--json` on these. JSON is the **provider** surface -- `provider-info`, `status --target --json`, `validate-bundle`, -`plan-operation`, `apply-operation`, `recover-operation` -- and a consumer calls -those. +`plan-operation`, `apply-operation`, `recover-operation`, `launch` -- and a +consumer calls those. ## Invariants worth knowing before changing anything @@ -352,8 +353,8 @@ When changing provider implementation, run that checkout's CI checks: ```bash cargo fmt --all --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace +cargo clippy --locked --all-targets -- -D warnings +cargo test --locked --all-targets ``` Report each result and any unavailable check. The cargo commands apply only diff --git a/setups/nddev-builder/home/prompts/nddev-setup.md b/setups/nddev-builder/home/prompts/nddev-setup.md index 227d21f..c098f2e 100644 --- a/setups/nddev-builder/home/prompts/nddev-setup.md +++ b/setups/nddev-builder/home/prompts/nddev-setup.md @@ -28,8 +28,8 @@ If changing provider implementation, also run this tree's checks: ```bash cargo fmt --all --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace +cargo clippy --locked --all-targets -- -D warnings +cargo test --locked --all-targets ``` If a command is not present, say so rather than working around it. diff --git a/setups/nddev-builder/home/prompts/nddev-validate.md b/setups/nddev-builder/home/prompts/nddev-validate.md index 13d4ff1..db126fa 100644 --- a/setups/nddev-builder/home/prompts/nddev-validate.md +++ b/setups/nddev-builder/home/prompts/nddev-validate.md @@ -8,8 +8,8 @@ For provider implementation changes, also run this checkout's checks: ```bash cargo fmt --all --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace +cargo clippy --locked --all-targets -- -D warnings +cargo test --locked --all-targets ``` If a command here is not present, say so rather than working around it. diff --git a/tools/build_crates_io.py b/tools/build_crates_io.py index 30e85b4..d292da6 100644 --- a/tools/build_crates_io.py +++ b/tools/build_crates_io.py @@ -172,7 +172,17 @@ def main() -> int: if args.self_check: with tempfile.TemporaryDirectory(prefix="nddev-crates-io-") as temporary: root = Path(temporary) - for harness in HARNESSES: + # This workspace carries all seven crates; a rendered public tree + # carries exactly one. The check walks whichever exist rather than + # asserting the shared layout in a tree that never had it. + harnesses = [ + harness + for harness in HARNESSES + if (ROOT / "crates" / f"{harness}-setup-system").is_dir() + ] + if not harnesses: + raise SystemExit("no harness crate under crates/ -- nothing to self-check") + for harness in harnesses: package = build(harness, root, args.version) document = package.joinpath("Cargo.toml").read_text(encoding="utf-8") expected = f'name = "{harness}-setup-system"' @@ -184,23 +194,29 @@ def main() -> int: stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) - codex = root / "codex-setup-system" + first = harnesses[0] + built = root / f"{first}-setup-system" subprocess.run( - ["cargo", "build", "--quiet", "--manifest-path", str(codex / "Cargo.toml")], + ["cargo", "build", "--quiet", "--manifest-path", str(built / "Cargo.toml")], check=True, ) answer = subprocess.run( - [codex / "target/debug/codex-setup-system", "provider-info"], + [built / "target/debug" / f"{first}-setup-system", "provider-info"], check=True, capture_output=True, text=True, ) info = json.loads(answer.stdout) - if info["provider_id"] != "codex-setup-system" or info["projection_profile"][ + if info["provider_id"] != f"{first}-setup-system" or info["projection_profile"][ "bundle_formats" ] != ["ai-stp-bundle/2"]: - raise SystemExit("the installed-shape provider-info is not the v2-only Codex provider") - print("crates.io: seven same-name packages; all package, and a standalone provider runs") + raise SystemExit( + f"the installed-shape provider-info is not the v2-only {first} provider" + ) + print( + f"crates.io: {len(harnesses)} same-name package(s); all package, " + "and a standalone provider runs" + ) return 0 if args.out is None: parser.error("--out is required unless --self-check is used") diff --git a/tools/build_wheels.py b/tools/build_wheels.py index 4301695..433d0a9 100644 --- a/tools/build_wheels.py +++ b/tools/build_wheels.py @@ -34,7 +34,7 @@ probe wheel without repairing it first. Usage: - python3 tools/build_wheels.py --harness claude --version 0.0.58 \ + python3 tools/build_wheels.py --harness --version \ --assets --out python3 tools/build_wheels.py --self-check """