diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6967d84..b8de3cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,11 @@ on: tags: - "[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: + inputs: + tag: + description: release tag to rebuild, matching the crate version + required: true + type: string permissions: {} @@ -56,6 +61,12 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # A dispatched run releases the tag it was asked for, not whatever the + # dispatching branch happened to point at -- `github.ref_name` there + # is the branch, and `gh release create` would mint a tag named after + # it. On a tag push the input is empty and `github.ref_name` is the + # tag, so the expression is the tag either way. + ref: ${{ inputs.tag || github.ref_name }} persist-credentials: false - name: Set up Rust toolchain @@ -77,9 +88,9 @@ jobs: # -- only `python` is -- so running it on all three would fail there for a # reason that has nothing to do with the release. - name: Refuse a tag that does not match the crate version - if: matrix.os == 'ubuntu-latest' && github.event_name == 'push' + if: matrix.os == 'ubuntu-latest' env: - TAG: ${{ github.ref_name }} + TAG: ${{ inputs.tag || github.ref_name }} run: | version=$(cargo metadata --no-deps --format-version 1 \ | python3 -c 'import json,sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "cursor-setup-system"))') @@ -170,7 +181,7 @@ jobs: - name: Publish one immutable release env: GH_TOKEN: ${{ github.token }} - TAG: ${{ github.ref_name }} + TAG: ${{ inputs.tag || github.ref_name }} run: | gh release create "$TAG" staging/* \ --repo "$GITHUB_REPOSITORY" \ @@ -196,6 +207,7 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + ref: ${{ inputs.tag || github.ref_name }} persist-credentials: false - name: Collect the built binaries @@ -224,7 +236,7 @@ jobs: - name: Build and push both architectures id: push env: - TAG: ${{ github.ref_name }} + TAG: ${{ inputs.tag || github.ref_name }} run: | image="ghcr.io/$(echo "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]')" docker buildx create --use --name release >/dev/null 2>&1 || docker buildx use release diff --git a/SECURITY.md b/SECURITY.md index a8d3f54..744a6b5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -27,5 +27,5 @@ before, and reports identity digests. It does not read credentials, does not send anything over the network in its configuration lifecycle, and records no secret values in its state. -A finding that breaks any of those four statements is in scope regardless of +A finding that breaks any of those statements is in scope regardless of severity. diff --git a/SUPPORT.md b/SUPPORT.md index e74a0a3..d66fa1f 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -67,7 +67,7 @@ newer. `scoped_projection_profiles` (`ADR-0125`) is the field this applies to, and it is omitted entirely when empty -- so a build that declares no scope satisfies an older checker by accident, and a build that declares one does not. -Two versions, two different answers, both measured: +Three versions, three different answers, all measured: | checker | result | | --- | --- | diff --git a/clippy.toml b/clippy.toml index 80610a8..409a3d5 100644 --- a/clippy.toml +++ b/clippy.toml @@ -13,11 +13,12 @@ doc-valid-idents = ["NDDev", "OpenNetwork", "OpenCode", "GitHub", "macOS", "Setu # `std::net` is refused outright: no command here opens a socket, and every v3 # phase declares `network_requirement: none`. # -# `std::process::Command` is refused everywhere except two `#[allow]` sites that -# name their reason: `harness_runtime::software` starts the product for `launch`, -# which is `runtime_external` by declaration, and `harness_runtime::probe` drives -# this binary's own executable from a test. An allowlist of two, both named, is a -# boundary; an allowlist nobody counted is a habit. +# `std::process::Command` is refused everywhere except two spawn sites that +# name their reason: `harness_runtime::software` builds the product command for +# `launch` and hands it to `exec` -- one site in two `#[allow]` attributes -- +# and `harness_runtime::probe` drives this binary's own executable from a test. +# An allowlist of two, both named, is a boundary; an allowlist nobody counted is +# a habit. # # Chosen over asserting a child count at run time: counting children portably # means /proc on Linux and something else on each of the other two, so that diff --git a/crates/cursor-setup-system/src/main.rs b/crates/cursor-setup-system/src/main.rs index 9b8263a..279b18b 100644 --- a/crates/cursor-setup-system/src/main.rs +++ b/crates/cursor-setup-system/src/main.rs @@ -242,8 +242,9 @@ pub const CURSOR: Harness = Harness { // writing -- the removal refuses rather than widening when it cannot read // the record, the capture takes ours and not a neighbour's, and a restore // leaves a neighbour's file as it was. Five of the seven products read - // this root and one declared it; the reason was simply not re-read when - // the thing it described changed. + // this root. This sentence used to say one declared it; all five do now -- + // the reason had simply not been re-read when the thing it described + // changed, and the sentence kept its old answer. scoped_projections: &[ Scoped { target_scope: TargetScope::UserRoot, diff --git a/crates/harness-runtime/src/catalog.rs b/crates/harness-runtime/src/catalog.rs index 7ac6f2b..7054745 100644 --- a/crates/harness-runtime/src/catalog.rs +++ b/crates/harness-runtime/src/catalog.rs @@ -428,12 +428,14 @@ pub fn undescribed(setups: &[Setup]) -> Examined { /// Matched on the suffix so a fifth vendor works rather than being a silent /// miss. /// -/// **Two of the six that declare `plugin` have no manifest at all, and saying -/// "the rest" was wrong.** An OpenCode plugin is a JavaScript or TypeScript -/// module — `plugins/.js`, one file exporting functions — and a Pi -/// extension is a package. Neither carries `name` or `description` for a -/// product to read, so there is nothing here to check and no arm to add: a -/// module's identity is its filename and its behaviour is its exports. +/// **Two of the six plugin-kind projections have no manifest at all, and +/// saying "the rest" was wrong.** An OpenCode plugin is a JavaScript or +/// TypeScript module — `plugins/.js`, one file exporting functions — and +/// a Pi extension is a package (its `ProjectionKind::Package`, counted here +/// because it answers the same question). Neither carries `name` or +/// `description` for a product to read, so there is nothing here to check and +/// no arm to add: a module's identity is its filename and its behaviour is its +/// exports. /// /// That is a real difference rather than a gap, and it is written down because /// the first version of this comment claimed the manifest shapes covered every @@ -931,8 +933,9 @@ pub struct Setup { /// -- and those three are exactly what a consumer renders on the surface /// that precedes an install. `full-auto`'s description runs to 3312 /// characters on one harness and is safety context, not marketing copy; - /// `sources` is what `check_authored_keys.py` reads to source every key a - /// setup writes. The provenance chain ran through a file no digest held. + /// `sources` is what `check_authored_keys.py` -- not shipped in the + /// rendered trees -- reads to source every key a setup writes. The provenance + /// chain ran through a file no digest held. /// /// Proved rather than reasoned: a description was rewritten and the whole /// gate stayed clean. diff --git a/crates/harness-runtime/src/facts.rs b/crates/harness-runtime/src/facts.rs index 98eaa87..fab6c0b 100644 --- a/crates/harness-runtime/src/facts.rs +++ b/crates/harness-runtime/src/facts.rs @@ -158,7 +158,9 @@ pub struct Harness { /// /// Measured 2026-08-31 across the seven: twelve of them, in five harnesses. /// A person's keybindings under one, a plugin directory under another, and - /// a `select minimal` took both. + /// a `select minimal` took both. Recounted 2026-09-30: seventeen in the + /// same five harnesses -- claude's four further namespaces and pi's three + /// were added as the audits reached them. /// /// They stay **owned**, which is the point: a backup still captures them, /// the identity still hashes them so drift is visible, and `remove` still diff --git a/crates/harness-runtime/src/surfaces.rs b/crates/harness-runtime/src/surfaces.rs index 90d0cbe..91685d5 100644 --- a/crates/harness-runtime/src/surfaces.rs +++ b/crates/harness-runtime/src/surfaces.rs @@ -216,6 +216,22 @@ fn never_touch_is_disclaimed(harness: &Harness, baseline: &Value, found: &mut Ve if harness.native_namespaces.contains(name) { found.push(format!("{name:?} is claimed and disclaimed")); } + // When the block itself is absent, `disagreements` already names that + // one absence; repeating it once per disclaimed name adds noise, not + // information. + let recorded = baseline.get(BLOCK).is_none() + || baseline + .get("never_touch") + .and_then(Value::as_array) + .is_some_and(|list| list.iter().any(|entry| entry.as_str() == Some(name))); + if !recorded { + found.push(format!( + "{name:?} is disclaimed by {} and the baseline's never_touch \ + list does not record it -- a disclaimer without a measured \ + record is one a reader cannot check", + harness.provider_id + )); + } } } @@ -1504,7 +1520,11 @@ mod tests { "source": "this provider's own contract", }, ], - } + }, + // A disclaimer without a measured record is one a reader cannot + // check: every real baseline records the names its declaration + // disclaims at the top level, so the fixture does too. + "never_touch": TEST.never_touch, }) } diff --git a/references/cursor-baseline.json b/references/cursor-baseline.json index 11c8229..d83b6fc 100644 --- a/references/cursor-baseline.json +++ b/references/cursor-baseline.json @@ -533,7 +533,7 @@ "version": "2026.09.26-dd393fe", "verified_at": "2026-09-28T14:28:17+00:00" }, - "setup_catalogue_digest": "sha256:44b0845cb11a492cca6134357ea7b89ee31bba8b57e6b6c64e09e4e39bfc10a6", + "setup_catalogue_digest": "sha256:cc8784c867b22ff1aceda2fbc06750e0d3122f4e6efed83827ad6e2a09fb23be", "previous_pin_recovery": { "measured_at": "2026-08-31", "note": "The vendor exposes no release index, but this repository had already measured the immediately preceding release before the pin moved: exact version plus the four Unix URLs, lengths and digests. All six derived immutable URLs still answer; the two Windows archives were downloaded and hashed now, and their command-script members were read from the ZIPs. This is recovered measurement, not a second independently moving pin, and the next ordinary refresh replaces it through the same outgoing-current rotation as every other harness." @@ -563,5 +563,9 @@ "control_hits": 0, "method": "Every namespace this harness declares, global and scoped, searched as a fixed string in the artifact the current pin names, digest verified before reading; both the declared path and its last segment counted, because a product that composes a path by joining segments never spells the whole of it. A count here is a lower bound and not a routing proof -- what it answers is whether a surface vanished from the product under a version bump, which five of the seven took on 2026-09-02. The invented control was searched in the same run and found nowhere, so the search discriminates.", "count_corrected": "2026-09-29: `plugins` left the owned set after the plugins/local receipt measurement, so the declared count is 14 now; the 2026-09-02 run predates the withdrawal and counted the parent beside it. `absent` is re-derived against the current declaration rather than re-measured: the string search of this artifact was not re-run." - } + }, + "never_touch": [ + "auth.json", + "sessions" + ] } diff --git a/scripts/evidence.py b/scripts/evidence.py index 6df16e2..2744f92 100644 --- a/scripts/evidence.py +++ b/scripts/evidence.py @@ -670,7 +670,8 @@ def owned_surfaces_named_by_the_product(binary: str, prefix: Path, info: dict) - **It reports and never promotes.** Writing an `evidence` value still takes somebody recording what they measured, and `tools/derive_evidence.py --check` - refuses a value stronger than a row's own prose supports. Two instruments + -- which is not shipped in this tree -- refuses a + value stronger than a row's own prose supports. Two instruments with one opinion between them would be one instrument. **Both inputs are asked of their owner rather than copied.** The namespaces diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-commands.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-commands.md index 3357ace..a99a86c 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-commands.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-commands.md @@ -22,7 +22,7 @@ Generated from the vendor's own reference and the pinned binary. Do not edit: th ## The same file on the other harnesses -Generated from the same rows as the section above, for every harness in this estate that routes this kind. `—` means the product's own reference does not name the field, and **dropped** means it names it as one it accepts and does not act on. +Generated from the same rows as the section above, for every harness in this estate that describes this kind the same way. A harness that routes the kind another way is absent rather than approximated. `—` means the product's own reference does not name the field, and **dropped** means it names it as one it accepts and does not act on. | field | `claude` | `codex` | `pi` | `opencode` | `cursor` | `antigravity` | |---|---|---|---|---|---|---| @@ -41,6 +41,6 @@ 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. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-hooks.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-hooks.md index 5eed7fa..7ba16b2 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-hooks.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-hooks.md @@ -26,6 +26,6 @@ Generated from the vendor's own reference and the pinned binary. Do not edit: th ## 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. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-mcp.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-mcp.md index f5fa08a..cefbafb 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-mcp.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-mcp.md @@ -24,7 +24,7 @@ Generated from the vendor's own reference and the pinned binary. Do not edit: th ## The same file on the other harnesses -Generated from the same rows as the section above, for every harness in this estate that routes this kind. `—` means the product's own reference does not name the field, and **dropped** means it names it as one it accepts and does not act on. +Generated from the same rows as the section above, for every harness in this estate that describes this kind the same way. A harness that routes the kind another way is absent rather than approximated. `—` means the product's own reference does not name the field, and **dropped** means it names it as one it accepts and does not act on. | field | `cursor` | `antigravity` | |---|---|---| @@ -47,6 +47,6 @@ 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. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-plugins.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-plugins.md index 1959cd5..3cf246c 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-plugins.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-plugins.md @@ -37,7 +37,7 @@ Generated from the vendor's own reference and the pinned binary. Do not edit: th ## The same file on the other harnesses -Generated from the same rows as the section above, for every harness in this estate that routes this kind. `—` means the product's own reference does not name the field, and **dropped** means it names it as one it accepts and does not act on. +Generated from the same rows as the section above, for every harness in this estate that describes this kind the same way. A harness that routes the kind another way is absent rather than approximated. `—` means the product's own reference does not name the field, and **dropped** means it names it as one it accepts and does not act on. | field | `claude` | `grok` | `cursor` | `antigravity` | |---|---|---|---|---| @@ -71,6 +71,6 @@ 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. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-skills.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-skills.md index 42bb2af..98e24f1 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-skills.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-skills.md @@ -28,7 +28,7 @@ Generated from the vendor's own reference and the pinned binary. Do not edit: th ## The same file on the other harnesses -Generated from the same rows as the section above, for every harness in this estate that routes this kind. `—` means the product's own reference does not name the field, and **dropped** means it names it as one it accepts and does not act on. +Generated from the same rows as the section above, for every harness in this estate that describes this kind the same way. A harness that routes the kind another way is absent rather than approximated. `—` means the product's own reference does not name the field, and **dropped** means it names it as one it accepts and does not act on. | field | `claude` | `grok` | `pi` | `opencode` | `cursor` | `antigravity` | |---|---|---|---|---|---|---| @@ -62,6 +62,6 @@ 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. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/permissions-sandbox.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/permissions-sandbox.md index 6976d92..7c3850c 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/permissions-sandbox.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/permissions-sandbox.md @@ -21,10 +21,10 @@ without a reason a reader can check. In the setup that carries them, and nowhere else: -- `setups/cursor/baseline/home/cli-config.json` -- a conservative floor -- `setups/cursor/full-auto/home/cli-config.json` -- `approvalMode` unrestricted +- `setups/baseline/home/cli-config.json` -- a conservative floor +- `setups/full-auto/home/cli-config.json` -- `approvalMode` unrestricted and the sandbox disabled -- `setups/cursor/minimal/home/cli-config.json` -- the same autonomous posture +- `setups/minimal/home/cli-config.json` -- the same autonomous posture as `full-auto` here: unrestricted approval, web search auto-accepted, the sandbox disabled diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/surfaces.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/surfaces.md index 7d755ff..7c6fcda 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/surfaces.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/surfaces.md @@ -10,7 +10,7 @@ this file and the binary disagree, the binary is right -- ask it with **Configuration home**: `~/.cursor` **Environment override**: `CURSOR_CONFIG_DIR` -**And**: XDG_CONFIG_HOME moves cli-config.json to $XDG_CONFIG_HOME/cursor and moves nothing else this build owns +XDG_CONFIG_HOME moves cli-config.json to $XDG_CONFIG_HOME/cursor and moves nothing else this build owns ## The configuration file