Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
60c1337
πŸ“š docs: contract: condition deferred SDK promises
pascalandy Nov 3, 2026
4e06c32
πŸ“š docs: contract: Record the remaining verbose help copy
pascalandy Nov 4, 2026
784be76
πŸ“š docs: readme: move the extension template overview
pascalandy Nov 3, 2026
c6bd3b4
πŸ“š docs: contract: move extension version pins
pascalandy Nov 3, 2026
95e6cb9
πŸ“š docs: rules: give formal models their own page
pascalandy Nov 3, 2026
38fc439
πŸ“š docs: skill: move the bundled skill specification
pascalandy Nov 3, 2026
82b61cf
♻️ refactor: template: move repository fixture checks
pascalandy Nov 3, 2026
ef4e50e
πŸš‘ fix: docs: pin extension links to their release
pascalandy Nov 3, 2026
cfab204
πŸ“š docs: template: Complete the extension overview
pascalandy Nov 4, 2026
ab4799a
πŸš‘ fix: transcript: classify the final yt-dlp error
pascalandy Nov 3, 2026
6e38718
πŸš‘ fix: transcript: validate unnamed runs before locking
pascalandy Nov 3, 2026
25d5c91
πŸ“š docs: transcript: Describe unnamed preflight refusals
pascalandy Nov 4, 2026
7f4c7ee
πŸ§ͺ test: transcript: Preserve generated-ID collision answers
pascalandy Nov 4, 2026
3095089
πŸš‘ fix: kit: bind privacy cases to accepted limits
pascalandy Nov 3, 2026
039206c
♻️ refactor: template: Separate acceptance proof rules
pascalandy Nov 4, 2026
2509c1f
πŸš‘ fix: kit: Require reciprocal privacy limit declarations
pascalandy Nov 4, 2026
c5ff0e0
🧰 skill: template: Teach accepted privacy exceptions
pascalandy Nov 4, 2026
5887217
πŸš‘ fix: kit: Require descriptions for accepted privacy limits
pascalandy Nov 4, 2026
7718a15
πŸš‘ fix: kit: Refuse placeholders beside privacy limit text
pascalandy Nov 4, 2026
a172b1c
πŸ“š docs: kit: State the accepted privacy grammar precisely
pascalandy Nov 4, 2026
6be5949
πŸ“š docs: kit: Name the accepted output outcome
pascalandy Nov 4, 2026
ad67390
♻️ refactor: docs: Separate pinned documentation rules
pascalandy Nov 3, 2026
59edd99
πŸš‘ fix: sdk: Isolate updater Git state
pascalandy Nov 3, 2026
e1a5a5b
πŸ§ͺ test: sdk: Keep updater fixture Git config private
pascalandy Nov 4, 2026
95d54c9
πŸ“š docs: sdk: Correct the updater documentation scope
pascalandy Nov 4, 2026
54bb169
πŸ“š docs: sdk: State the shared environment filter scope
pascalandy Nov 4, 2026
314ac75
πŸ“š docs: sdk: State unconditional caller Git filtering
pascalandy Nov 4, 2026
b3e02a6
♻️ refactor: verify: Split the auth drive guide
pascalandy Nov 3, 2026
79e82f7
πŸš‘ fix: verify: Use a private Linux keyring
pascalandy Nov 3, 2026
422aa9e
πŸš‘ fix: verify: Bound abandoned private keyring sessions
pascalandy Nov 4, 2026
e81db62
πŸ“š docs: verify: Clarify platform keychain cleanup
pascalandy Nov 4, 2026
fba9948
πŸ“š docs: verify: Distinguish platform cleanup recovery
pascalandy Nov 4, 2026
c111638
πŸ› fix: verify: Keep private keyring cleanup in scope
pascalandy Nov 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions .agents/skills/verify-nicely/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,27 @@ Copy this checklist and tick each step:
- [ ] 5. Evidence: the transcripts outlive the cleanup, and the report quotes them
```

On Linux, a run also needs `dbus-run-session` and `gnome-keyring-daemon`.
Launch fails with the missing program's name; it never uses the caller's D-Bus session.
The run starts and unlocks its own keyring in one private session, shared by every later step.
Cleanup stops that keyring and its bus before removing scratch files.
The session also stops when its owned state or runtime marker disappears, and expires after six hours.
Start a new run after expiry; the wrapper never falls back to the user's session.
The Linux integration test follows this real start, auth and cleanup path.
It skips with the missing program's name when either prerequisite is absent.
[The lifecycle test](../../../cmd/ncly/keyring_lifecycle_linux_test.go) requires a normal private stop to record exit 0 without a shell diagnostic.
It also checks daemon termination and private runtime removal.
The macOS keychain path is unchanged.

## Launch

```bash
run="$(just verify)"
```

`just verify` runs `scripts/verify.sh start`. It builds `ncly` from the checkout as it is, uncommitted changes included, into `$run/build/`. It makes a scratch home in `$run/scratch/home/`, with its own XDG folders, and installs the Go fixture `hello` there as an external extension, in `.local/share/nicely/extensions/`, with its manifest, catalogs, and skill beside it. It writes the wrapper `$run/scratch/bin/ncly`, which sets `HOME`, the XDG folders, and a `PATH` without any folder that holds an `ncly-<domain>` file, and unsets `NCLY_CONFIG`, which would point the config elsewhere, so every later command reaches only the scratch folder. On macOS, the auth feature points `HOME` back at the real home, as [auth](features/auth.md) says. Its last line on stdout is the run folder, under `${XDG_CACHE_HOME:-$HOME/.cache}/nicely/verify/`. Done when that folder holds `scratch/bin/ncly`.
`just verify` runs `scripts/verify.sh start`. It builds `ncly` from the checkout as it is, uncommitted changes included, into `$run/build/`. It makes a scratch home in `$run/scratch/home/`, with its own XDG folders, and installs the Go fixture `hello` there as an external extension, in `.local/share/nicely/extensions/`, with its manifest, catalogs, and skill beside it. It writes the wrapper `$run/scratch/bin/ncly`, which sets `HOME`, the XDG folders, and a `PATH` without any folder that holds an `ncly-<domain>` file, and unsets `NCLY_CONFIG`, which would point the config elsewhere, so every later command reaches only the scratch folder. On Linux, the wrapper replaces the caller's bus address and runtime directory with the private session of this run.
A missing or stopped private session fails the call, with no fallback.
On macOS, the auth feature points `HOME` back at the real home, as [auth](features/auth.md) says. Its last line on stdout is the run folder, under `${XDG_CACHE_HOME:-$HOME/.cache}/nicely/verify/`. Done when that folder holds `scratch/bin/ncly`.

## Doctor

Expand All @@ -49,13 +63,17 @@ just verify cleanup "$run"
```

`cleanup` makes scratch and build files writable, including Go's module cache, before removal.
On Linux, it first stops the private keyring and D-Bus session, after any fake-key cleanup.
Its evidence records that stop. A stop failure keeps scratch files and fails cleanup.
If the private session already ended, cleanup discards its private keyring with the scratch folder.
It keeps `$run/evidence/` and refuses a folder that `start` did not create.
When the run prepared `auth`, cleanup first removes its fake key and checks that the keychain reports `AUTH_MISSING`.
Both calls remain in `$run/evidence/cleanup.txt`.
Its stderr says `logged the fake key of verifynicely out of the keychain` only after removal.
Otherwise, stderr says `found no fake key`.
When the key may remain, cleanup keeps scratch and exits 1.
Run cleanup again once the keychain answers.
On macOS, run cleanup again once the login keychain answers.
On Linux, rerun cleanup to discard the stopped private keyring and its scratch files.
Run cleanup after a failed drive too.
Done when cleanup lists the evidence files and both `$run/scratch` and `$run/build` are gone.

Expand Down
56 changes: 9 additions & 47 deletions .agents/skills/verify-nicely/features/auth.md

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions .agents/skills/verify-nicely/features/auth/grant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Grant and remove the key

Part of [auth](../auth.md). Follow the three guides in order.

- **Grant in a terminal.** Run `just verify tmux "$run" auth --answer '[y/N]' y auth grant verifynicely verifykeys`. Exit 0. Before Enter, the terminal shows `? Grant the key of verifynicely to verifykeys?`, then ` Path ` followed by the fixture's path, which ends in `/scratch/home/.local/share/nicely/extensions/ncly-verifykeys`, then ` [y/N] y`. After Enter, it also shows `Granted the key of verifynicely to verifykeys.`, and no `Next time:` line.
- **Fixture with the grant.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth verifykeys digest --json`. Exit 0, and stdout is `{"ok":true,"contract_version":1,"sha256":"<digest>"}`. Compute the digest of the fake key without printing the key, with `printf '%s' "$fake" | sha256sum`, or `shasum -a 256` on macOS, and check that the two match: the fixture received the fake key from the keychain.
- **Status with the grant.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth auth status --json`. Exit 0, and stdout is `{"ok":true,"contract_version":1,"keychain":"available","services":[{"service":"verifynicely","configured":true,"source":"keychain","grants":[{"domain":"verifykeys","granted":true,"optional":false}]}]}`. Then run `VERIFYNICELY_API_KEY= just verify step "$run" auth auth status`. Exit 0, and the line of `verifynicely` is `verifynicely keychain verifykeys: granted`, with no line under it.
- **Doctor with the grant.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth doctor auth --json`. Exit 0, and stdout is the one of the step Doctor without the grant, with `"ok":true` and the last check `{"id":"auth.verifynicely","component":"auth","status":"pass","message":"The key comes from the keychain."}`.
- **Grant again.** Run `just verify step "$run" auth auth grant verifynicely verifykeys --json`. Exit 0 without a terminal, and stdout is `{"ok":true,"contract_version":1,"action":"unchanged","domain":"verifykeys","path":"<path>","service":"verifynicely"}`, where `<path>` is the path that the step Grant in a terminal showed.
- **Revoke.** Run `just verify step "$run" auth auth revoke verifynicely verifykeys --force --json`. Exit 0, and stdout holds `"action":"revoke"`. Then run `VERIFYNICELY_API_KEY= just verify step "$run" auth verifykeys digest --json`. Exit 78, with `KEY_NOT_GRANTED` again.
- **Replacement.** Run `printf '%s\n' "$fake" | just verify step "$run" auth auth login verifynicely --stdin --json`. Exit 2, and stderr ends with an answer whose first error is `CONFIRMATION_REQUIRED`, with the hint `ncly auth login verifynicely --stdin --force`.
- **Login as text.** Run `printf '%s\n' "$fake" | just verify step "$run" auth auth login verifynicely --stdin --force`. Exit 0, and stdout is `Stored the key of verifynicely in the keychain.`
- **Logout dry run.** Run `just verify step "$run" auth auth logout verifynicely --dry-run --json`. Exit 0, and stdout holds what the step Login dry run names, with `"action":"logout"`.
- **Logout without --force.** Run `just verify step "$run" auth auth logout verifynicely --json`. Exit 2, and stderr ends with an answer whose first error is `CONFIRMATION_REQUIRED`, with the hint `ncly auth logout verifynicely --force` and the same command in `next`.
- **Logout.** Run `just verify step "$run" auth auth logout verifynicely --force --json`. Exit 0, and stdout is `{"ok":true,"contract_version":1,"action":"logout","service":"verifynicely"}`.
- **Key gone.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth auth status --json`. Exit 78, and the entry of `verifynicely` has `"source":"none"` and `"code":"AUTH_MISSING"`, with no other code.
26 changes: 26 additions & 0 deletions .agents/skills/verify-nicely/features/auth/prepare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Prepare and inspect the key

Part of [auth](../auth.md). Follow the three guides in order.


Preconditions:

- A new run from `just verify`, whose Doctor step passed. On macOS, an agent runs it as Gotchas says
- `tmux -V` answers, for the steps in a terminal, as [terminal.md](terminal.md) describes `just verify tmux`
- On macOS, the login keychain can hold `verifynicely` from an earlier run. Linux starts with a new private keyring
- The fake key of this run: `fake=verify-nicely-fake-$(date +%s)`

- **No default keychain.** On macOS, before the step Fixture, run `just verify step "$run" auth auth status --json`. Exit 0 with `"keychain":"unavailable"`: the scratch home holds no keychain. Then `printf '%s\n' "$fake" | just verify step "$run" auth auth login verifynicely --stdin --json` exits 78 with `KEYRING_UNAVAILABLE`, and no window opens.
- **Fixture.** Run `just verify prepare "$run" auth`, with `--mac-keychain` on macOS. Exit 0, and stderr names the service `verifynicely` and its variable `VERIFYNICELY_API_KEY`, and on macOS that the wrapper keeps the real `HOME`.
- **Help.** Run `just verify step "$run" auth auth --help`. Exit 0, and `COMMANDS` lists `grant`, `login`, `logout`, `revoke`, and `status`.
- **Grant dry run.** Run `just verify step "$run" auth auth grant verifynicely verifykeys --dry-run --json`. Exit 0, and stdout is `{"ok":true,"contract_version":1,"dry_run":true,"pending_checks":[{"id":"auth.confirmation","message":"Whether a human at a terminal confirms the grant of the key of verifynicely to verifykeys."}],"plan":[{"key":"verifynicely","action":"grant","domain":"verifykeys","effects":{"user_writes":true,"network":false,"paid":false}}]}`.
- **Grant without a terminal.** Run `just verify step "$run" auth auth grant verifynicely verifykeys --json`. Exit 78, stdout is empty, and stderr ends with an answer whose first error is `TERMINAL_REQUIRED`, with the hint ``Run `ncly auth grant verifynicely verifykeys` in a terminal`` and the `next` `["ncly","auth","grant","verifynicely","verifykeys"]`.
- **No grant to revoke.** Run `just verify step "$run" auth auth revoke verifynicely verifykeys --force --json`. Exit 2, and stderr ends with an answer whose first error is `NOT_FOUND`, with the hint `ncly auth status` and the same command in `next`.
- **Missing key.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth auth status --json`. Exit 78, stderr is empty, and stdout is one line with `"ok":false`, `"keychain":"available"`, and one entry, whose `grants` is `[{"domain":"verifykeys","granted":false,"optional":false}]`. Without the fake key of an earlier run, the entry is `{"service":"verifynicely","configured":false,"source":"none","code":"AUTH_MISSING", ...}`, whose `hint` is `ncly auth login verifynicely` and whose `next` is `["ncly","auth","login","verifynicely"]`: a missing key reports its own code alone, without its grant. With it, the entry is `{"service":"verifynicely","configured":true,"source":"keychain","code":"KEY_NOT_GRANTED", ...}`, as in the step Key from the keychain: report which one you saw.
- **Missing key as text.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth auth status`. Exit 78. The first line is `keychain available`, then an empty line. Without the fake key of an earlier run, the line of `verifynicely` is `verifynicely none verifykeys: not granted`. Two lines follow it, in the column of `none`: `fail[AUTH_MISSING] No key for verifynicely in VERIFYNICELY_API_KEY or the keychain.`, then `hint: ncly auth login verifynicely`. With the key, the line is `verifynicely keychain verifykeys: not granted`, and the two lines are `fail[KEY_NOT_GRANTED] The key of verifynicely is not granted to verifykeys.`, then `hint: ncly auth grant verifynicely verifykeys`.
- **Key from the variable.** Run `VERIFYNICELY_API_KEY="$fake" just verify step "$run" auth auth status --json`. Exit 78, and stdout is `{"ok":false,"contract_version":1,"keychain":"available","services":[{"service":"verifynicely","configured":true,"source":"env","code":"KEY_NOT_GRANTED","message":"The key of verifynicely is not granted to verifykeys.","hint":"ncly auth grant verifynicely verifykeys","next":["ncly","auth","grant","verifynicely","verifykeys"],"grants":[{"domain":"verifykeys","granted":false,"optional":false}]}]}`: the key is there, and no human granted it to the fixture yet.
- **Login dry run.** Run `just verify step "$run" auth auth login verifynicely --dry-run --json`. Exit 0, and stdout holds `"dry_run":true`, the plan `{"key":"verifynicely","action":"login","effects":{"user_writes":true,"network":false,"paid":false}}`, and the pending check `auth.keychain`.
- **Login.** Run `printf '%s\n' "$fake" | just verify step "$run" auth auth login verifynicely --stdin --force --json`. Exit 0, and stdout is `{"ok":true,"contract_version":1,"action":"login","service":"verifynicely"}`.
- **Key from the keychain.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth auth status --json`. Exit 78, and stdout is the one of the step Key from the variable, with `"source":"keychain"` instead of `"source":"env"`.
- **Doctor without the grant.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth doctor auth --json`. Exit 78, stderr is empty, and stdout is `{"ok":false,"contract_version":1,"checks":[{"id":"auth.keychain","component":"auth","status":"pass","message":"The keychain answers."},{"id":"auth.grants","component":"auth","status":"pass","message":"Every grant is in use."},{"id":"auth.verifynicely","component":"auth","status":"fail","code":"KEY_NOT_GRANTED","message":"The key of verifynicely is not granted to verifykeys.","hint":"ncly auth grant verifynicely verifykeys","next":["ncly","auth","grant","verifynicely","verifykeys"]}]}`.
- **Fixture without a grant.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth verifykeys digest --json`. Exit 78, stdout is empty, and stderr is one line, an answer whose first error is `KEY_NOT_GRANTED`, with the hint `ncly auth grant verifynicely verifykeys` and the same command in `next`: the keychain holds the fake key, and still it does not reach the fixture.
16 changes: 16 additions & 0 deletions .agents/skills/verify-nicely/features/auth/terminal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Terminal steps and cleanup

Part of [auth](../auth.md). Follow the three guides in order.

- **Login in a terminal.** Run `just verify tmux "$run" auth --answer 'verifynicely key' "$fake" auth login verifynicely`. Exit 0. Before Enter, the terminal shows the masked field: `┃ verifynicely key`, `┃ It goes to the keychain and never shows.`, `┃ > ` followed by one `*` per character of the fake key, and `enter submit`. After Enter, the field is gone, and the terminal shows only `Stored the key of verifynicely in the keychain.`, with no `Next time:` line.
- **Key from the terminal.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth auth status --json`. Exit 78, and stdout is the one of the step Key from the keychain: the key comes from the keychain again, and the step Revoke took its grant back.
- **Replacement in a terminal.** Run `just verify tmux "$run" auth --answer '[y/N]' n auth login verifynicely`. Exit 2. The terminal shows `? The keychain already holds a key for verifynicely. Replace it? [y/N] n`, then `error[CONFIRMATION_REQUIRED]: The key of verifynicely stays as it was.`, and no masked field.
- **Ctrl-C at the field.** Run `just verify tmux "$run" auth --answer 'verifynicely key' '^C' auth login verifynicely --force`. Exit 130. The terminal shows only `error[INTERRUPTED]: Stopped by Ctrl-C. Finished work is kept.` and its hint, because the field erased itself.
- **Logout in a terminal.** Run `just verify tmux "$run" auth --answer '[y/N]' y auth logout verifynicely`. Exit 0. The terminal shows `? Remove the key of verifynicely from the keychain? [y/N] y`, then `Removed the key of verifynicely from the keychain.`
- **Doctor without the key.** Run `VERIFYNICELY_API_KEY= just verify step "$run" auth doctor --json`. Exit 78, stderr is empty, and stdout lists `core.config`, then `auth.keychain` and `auth.grants` as a `pass`, then `{"id":"auth.verifynicely","component":"auth","status":"fail","code":"AUTH_MISSING", ...}` with the hint and `next` of the status entry, then the `extensions` checks, each a `pass`.
- **Nothing to remove.** Run `just verify step "$run" auth auth logout verifynicely --force --json`. Exit 2, and stderr ends with an answer whose first error is `NOT_FOUND`, with the hint and `next` `ncly auth status`.
- **Missing service.** Run `just verify step "$run" auth auth login`. Exit 2, stdout is empty, and stderr starts with `error[USAGE_INVALID]: The argument <service> is missing.`, with the hint `ncly auth login <service>`.
- **Login from the form.** Run `just verify tmux "$run" auth --answer 'Service of the key' verifynicely --answer 'verifynicely key' "$fake" auth login`. Exit 0. Before the first Enter, the terminal shows the form for missing values: `┃ service`, `┃ Service of the key, such as deepgram`, and `┃ > verifynicely`. Before the second, it shows the masked field of the step Login in a terminal. After it, both are gone, and the terminal shows `Stored the key of verifynicely in the keychain.`, then `Next time:` and ` ncly auth login verifynicely`, never the key.
- **Login for the cleanup.** Run `printf '%s\n' "$fake" | just verify step "$run" auth auth login verifynicely --stdin --force --json`. Exit 0, so the cleanup has a key to log out.
- **Proof.** Run `grep -c "$fake" "$run/evidence/auth.txt"` after the steps. It prints `0` and exits 1, grep's code for no match: no transcript holds the fake key, not even the one that the terminal steps typed.
- **Cleanup.** Run `just verify cleanup "$run"`, as SKILL.md says. Exit 0, stderr says `logged the fake key of verifynicely out of the keychain`, and `$run/evidence/cleanup.txt` holds an `ncly auth logout verifynicely --force --json` that exits 0, then an `ncly auth status --json` with `"keychain":"available"`, whose entry of `verifynicely` has `"source":"none"` and `"code":"AUTH_MISSING"`.
3 changes: 3 additions & 0 deletions .agents/skills/verify-nicely/features/extensions-skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- `template-command` gives the command that creates an extension repository, in the `nicely-extensions` skill, with the VERSION of a development build
- `template-install-command` gives the commands that install an extension repository at a release tag, in the `nicely-extensions` skill
- `revision-links` use the release tag for bundled documentation, or main in a development build
- `template-remove-command` gives the steps that remove an extension, in the `nicely-extensions` skill

## How to get to it (user POV)
Expand All @@ -23,6 +24,8 @@ Preconditions:
- **Install command.** In the output of `skill view nicely-extensions`, the skill holds the section `## Install an extension`. It quotes the install rule, in a line that starts with `> A user installs a fixed version with`. Its code block holds the lines `git clone --branch TAG https://github.com/OWNER/REPO` and `cd REPO && just install`, and the text names `ncly describe ROOT --json`.
- **Remove command.** In the output of `skill view nicely-extensions`, the skill holds the section `## Remove an extension`. It names `ncly doctor extensions --json`, the four files `ncly-SLUG`, `ncly-SLUG.toml`, `ncly-SLUG.locales/`, and `ncly-SLUG.skill/`, `just uninstall`, `NOT_FOUND`, and `GRANT_UNUSED`.

- **Revision links.** In a development or pseudo-version build, each Nicely `blob/` link in `skill view nicely-extensions` uses `main`. For a release-version check, build the same source into `$run/build/ncly` with `go build -ldflags '-X github.com/pascalandy/nicely/internal/cli.version=v0.26.0' -o "$run/build/ncly" ./cmd/ncly`. Run `just verify step "$run" extensions-skill --version`, then `just verify step "$run" extensions-skill skill view nicely-extensions`. Every Nicely `blob/` link now uses `v0.26.0`. Quote both outputs and the build flag; this is a test build, not a published release.

## Gotchas

- The skill comes with the `ncly` of the run, so this feature needs no `prepare`
Loading
Loading