diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d71ae5b..1fd81e7 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"] == "opencode-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 51017f0..dab21ca 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 52b3b2f..a861142 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -64,7 +64,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/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/crates/opencode-setup-system/src/main.rs b/crates/opencode-setup-system/src/main.rs index c98173e..1caf1d0 100644 --- a/crates/opencode-setup-system/src/main.rs +++ b/crates/opencode-setup-system/src/main.rs @@ -70,9 +70,10 @@ pub const OPENCODE: Harness = Harness { "commands", "plugins", ], - // Five names the product reads and this provider does not own, each - // measured 2026-08-31 by running the 1.18.25 binary against a - // temporary home. Declared so `status` can say what it cannot decide. + // Six names the product reads and this provider does not own -- the first + // five measured 2026-08-31 by running the 1.18.25 binary against a + // temporary home, `tui.jsonc` added when the 1.18.33 binary's loader showed + // both spellings. Declared so `status` can say what it cannot decide. shadowing_names: &[ Shadow { name: "opencode.jsonc", @@ -81,6 +82,15 @@ pub const OPENCODE: Harness = Harness { and keeps the later, so this one wins: with both present \ `debug config` returned the JSONC file's value", }, + Shadow { + name: "tui.jsonc", + over: "tui.json", + effect: "the TUI loader iterates `fileInDirectory`'s \ + [tui.json, tui.jsonc] candidates and merges each in turn, \ + so the JSONC spelling applies last and wins -- measured in \ + the pinned 1.18.33 binary, where the name is built by a \ + `${o}.jsonc` template no fixed-string search can see", + }, Shadow { name: "skill", over: "skills", diff --git a/references/opencode-baseline.json b/references/opencode-baseline.json index ae21dba..0d626b7 100644 --- a/references/opencode-baseline.json +++ b/references/opencode-baseline.json @@ -192,7 +192,7 @@ "shape": "file", "source": "https://opencode.ai/docs/tui", "evidence": "page", - "note": "keybinds, theme, attention and sounds, deliberately separate from opencode.json, which the same documentation describes as server and runtime behaviour; the setting route stays opencode.json\n\n**Searched in the product's own pinned bytes on 2026-08-29 and not found, which argues nothing either way.** Fixed-string, anchored to this product's configuration home -- the bare leaf name is in every one of these binaries and proves nothing, so only the anchored form counts. An invented path was searched in the same run and was also absent, so the search discriminates.\n\nThis row stays `page` because **a path built by joining a directory to a name at runtime never appears as a literal**, and that is the shape of every remaining one. Moving it off `page` needs the product run against a target and asked what it resolved, not a deeper grep.\n\n**There is no `tui.jsonc`, and the symmetry was asserted before it was checked.** A review of this provider filed the JSONC alias for this file as a finding of equal standing with `opencode.json`'s. The 1.18.25 binary carries `tui.json` eight times and the JSONC spelling not once. The alias is real for the main configuration and absent for this one; a neighbour's property is not this file's.\n\n**Still `page` after a pass at it on 2026-08-31, and saying so rather than promoting it on a neighbour's evidence.** What was established: the product *writes* this file. A configuration carrying `theme`, `keybinds` or `tui` that has no `tui.json` beside it gets one written at `join(dirname(), \"tui.json\")`, and the config files include the ones in a resource directory -- so a consumer bundle setting a theme can make the product create a file this provider owns. What was **not** established is the read path from a target: it was looked for in the pinned bytes and not found, and a single `debug config` run with `theme` set did not produce the file either, so the migration needs something a read-only command does not reach. Two things absent is not one thing proven." + "note": "keybinds, theme, attention and sounds, deliberately separate from opencode.json, which the same documentation describes as server and runtime behaviour; the setting route stays opencode.json\n\n**Searched in the product's own pinned bytes on 2026-08-29 and not found, which argues nothing either way.** Fixed-string, anchored to this product's configuration home -- the bare leaf name is in every one of these binaries and proves nothing, so only the anchored form counts. An invented path was searched in the same run and was also absent, so the search discriminates.\n\nThis row stays `page` because **a path built by joining a directory to a name at runtime never appears as a literal**, and that is the shape of every remaining one. Moving it off `page` needs the product run against a target and asked what it resolved, not a deeper grep.\n\n**`tui.jsonc` exists, and the earlier paragraph here that denied it measured wrong.** The 1.18.25 check counted fixed strings: `tui.json` appears, `tui.jsonc` does not -- because the binary never spells it. Re-measured 2026-09-28 against the pinned 1.18.33 artifact (`sha256:149a676b\u2026`, the digest this table pins): `ConfigPaths.projectFiles` walks `targets:[`${o}.jsonc`,`${o}.json`]` and `fileInDirectory` returns `[join(dir,`${t}.json`),join(dir,`${t}.jsonc`)]`, which is what the global `tui` load and each `.opencode`-directory load iterate. The JSONC spelling is built by the same template mechanism this note already describes for the directory half -- the search missed it for the reason given one paragraph up. The declined row carries the consequence: both spellings are real, this provider owns one.\n\n**Still `page` after a pass at it on 2026-08-31, and saying so rather than promoting it on a neighbour's evidence.** What was established: the product *writes* this file. A configuration carrying `theme`, `keybinds` or `tui` that has no `tui.json` beside it gets one written at `join(dirname(), \"tui.json\")`, and the config files include the ones in a resource directory -- so a consumer bundle setting a theme can make the product create a file this provider owns. What was **not** established is the read path from a target: it was looked for in the pinned bytes and not found, and a single `debug config` run with `theme` set did not produce the file either, so the migration needs something a read-only command does not reach. Two things absent is not one thing proven." } ], "declined": [ @@ -254,7 +254,7 @@ }, { "path": "managed-config", - "reason": "Not a path in the target, and named without an extension for that reason: the managed configuration directory is a **system** path, one per operating system, and every recorded path here is relative to the target.\n\n * Linux — `/etc/opencode/`\n * macOS — `/Library/Application Support/opencode/`\n * Windows — `%ProgramData%\\\\opencode`\n\nAll three are in the 1.18.25 bundle's own emitted source, in one switch: `case\"darwin\": return \"/Library/Application Support/opencode\"; case\"win32\": return join(process.env.ProgramData||\"C:\\\\ProgramData\",\"opencode\"); default: return \"/etc/opencode\"`. macOS additionally reads a managed plist from the `ai.opencode.managed` preference domain, deployable by MDM, which the same bundle carries as `parseManagedPlist` and `managedPreferences`.\n\n**It bears on the `full-auto` posture**, the same way the managed policy does on the harness next door. An administrator's `opencode.json` there is loaded at the highest priority tier and overrides everything, so this provider can install, verify and restore a permissive posture cleanly on a managed machine and change nothing about what the product actually permits. The setup is not wrong and the target is not wrong; a higher layer wins.\n\nRecorded and never touched: it needs root or Administrator to write, it is not under the configuration home this provider is given, and a provider that edited an organisation's policy would be doing the one thing this estate refuses everywhere else.\n\nThe user configuration home is **not** per-OS, which is why only this row is. The same bundle resolves it as `XDG_CONFIG_HOME || ~/.config` joined with the application name, with no platform branch at all.", + "reason": "Not a path in the target, and named without an extension for that reason: the managed configuration directory is a **system** path, one per operating system, and every recorded path here is relative to the target.\n\n * Linux \u2014 `/etc/opencode/`\n * macOS \u2014 `/Library/Application Support/opencode/`\n * Windows \u2014 `%ProgramData%\\\\opencode`\n\nAll three are in the 1.18.25 bundle's own emitted source, in one switch: `case\"darwin\": return \"/Library/Application Support/opencode\"; case\"win32\": return join(process.env.ProgramData||\"C:\\\\ProgramData\",\"opencode\"); default: return \"/etc/opencode\"`. macOS additionally reads a managed plist from the `ai.opencode.managed` preference domain, deployable by MDM, which the same bundle carries as `parseManagedPlist` and `managedPreferences`.\n\n**It bears on the `full-auto` posture**, the same way the managed policy does on the harness next door. An administrator's `opencode.json` there is loaded at the highest priority tier and overrides everything, so this provider can install, verify and restore a permissive posture cleanly on a managed machine and change nothing about what the product actually permits. The setup is not wrong and the target is not wrong; a higher layer wins.\n\nRecorded and never touched: it needs root or Administrator to write, it is not under the configuration home this provider is given, and a provider that edited an organisation's policy would be doing the one thing this estate refuses everywhere else.\n\nThe user configuration home is **not** per-OS, which is why only this row is. The same bundle resolves it as `XDG_CONFIG_HOME || ~/.config` joined with the application name, with no platform branch at all.", "source": "https://opencode.ai/docs/config; measured in the 1.18.25 bundle, whose bytes match this baseline's own sha256" }, { @@ -356,7 +356,7 @@ "version": "1.18.33", "verified_at": "2026-09-29T21:25:11+00:00" }, - "setup_catalogue_digest": "sha256:d0cf7ef9be83eba56d3d496069de6a5eba3e9f19cc7b27690c2c7860f59ef602", + "setup_catalogue_digest": "sha256:7af5d88d0f5d51510d0b496fa95e84cc27b308be0fdbd3e3a1efca30eaac7e2c", "previous_software_artifacts": { "command": "opencode", "shape": "gzip-tar", @@ -413,5 +413,9 @@ "control": "zzq-invented-surface-nobody-ships", "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." - } + }, + "never_touch": [ + "auth.json", + "cache" + ] } 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/skills/nddev-builder/references/authoring-agents.md b/setups/nddev-builder/home/skills/nddev-builder/references/authoring-agents.md index acef7c0..ff517c0 100644 --- a/setups/nddev-builder/home/skills/nddev-builder/references/authoring-agents.md +++ b/setups/nddev-builder/home/skills/nddev-builder/references/authoring-agents.md @@ -33,7 +33,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` | `opencode` | `antigravity` | |---|---|---|---|---| @@ -72,6 +72,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/skills/nddev-builder/references/authoring-commands.md b/setups/nddev-builder/home/skills/nddev-builder/references/authoring-commands.md index 3a7eefd..1d914af 100644 --- a/setups/nddev-builder/home/skills/nddev-builder/references/authoring-commands.md +++ b/setups/nddev-builder/home/skills/nddev-builder/references/authoring-commands.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 | `claude` | `codex` | `pi` | `opencode` | `cursor` | `antigravity` | |---|---|---|---|---|---|---| @@ -43,6 +43,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/skills/nddev-builder/references/authoring-plugins.md b/setups/nddev-builder/home/skills/nddev-builder/references/authoring-plugins.md index b673313..7415461 100644 --- a/setups/nddev-builder/home/skills/nddev-builder/references/authoring-plugins.md +++ b/setups/nddev-builder/home/skills/nddev-builder/references/authoring-plugins.md @@ -30,7 +30,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 | `pi` | `opencode` | |---|---|---| @@ -56,6 +56,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/skills/nddev-builder/references/authoring-skills.md b/setups/nddev-builder/home/skills/nddev-builder/references/authoring-skills.md index 9ff1d28..e83359a 100644 --- a/setups/nddev-builder/home/skills/nddev-builder/references/authoring-skills.md +++ b/setups/nddev-builder/home/skills/nddev-builder/references/authoring-skills.md @@ -25,7 +25,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` | |---|---|---|---|---|---|---| @@ -59,6 +59,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.