From 424b4ff600e824b755bb97713633ce88a6f2e2eb Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:05:53 -0500 Subject: [PATCH 01/10] docs: plan v1 stabilization --- tasks/plan.md | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++ tasks/todo.md | 200 +++++++++++++++++++++++++++ 2 files changed, 576 insertions(+) create mode 100644 tasks/plan.md create mode 100644 tasks/todo.md diff --git a/tasks/plan.md b/tasks/plan.md new file mode 100644 index 0000000..152db15 --- /dev/null +++ b/tasks/plan.md @@ -0,0 +1,376 @@ +# Implementation Plan: Tink v1 Stabilization and Release Readiness + +## Overview + +Treat the current Tink feature set as complete and prepare it for an honest +`v1.0.0` decision. The work closes or precisely narrows the three proof gaps +declared in `ACCEPTANCE.md`, preserves the existing command and on-disk +contracts, verifies the full supported-platform release gate, and makes the +major-version promotion safe under the existing automatic release workflow. +No new user capability belongs in this plan. + +## Outcome and Scope Boundary + +The outcome is a release candidate whose current behavior is demonstrably safe +to label `v1.0.0`, followed by a human go/no-go decision and, only after explicit +approval, a verified public release. + +In scope: + +- Automated or explicitly bounded proof for acceptance rows C4, S1, and S2. +- Current local, CI, and supported-platform release verification. +- A safe one-time transition from the `0.3.x` patch train to `1.0.0`. +- Minimal version and user-facing maturity documentation changes. +- Post-release install and update smoke tests. + +Out of scope: + +- New commands, flags, storage formats, sources, or lifecycle behavior. +- Windows, private GitHub authentication, concurrency, or other items already + listed as out of v1. +- A new release framework, roadmap system, governance document, or agent team. +- Refactors that are not required to establish the named proof. + +## Architecture Decisions + +- **Freeze the v1 behavior boundary.** `ACCEPTANCE.md` remains the evaluator; + this plan improves evidence without expanding the product contract. +- **Test only claims Tink owns.** C4 and S1 are Tink process guarantees. S2 must + be phrased as a testable Tink boundary—library-only skills are not treated as + project-live skills—rather than claiming control over every external agent + harness. +- **Guard Git at the centralized boundary.** Tink may use read-only Git + operations and temporary clones, but its Git process owner should make the + forbidden project mutations (`init`, `add`, `commit`, and `push`) impossible + to introduce accidentally. +- **Promote versions through existing automation.** Extend the current release + workflow only enough to publish an intentionally pre-bumped, untagged version. + Do not add a second release workflow or manually race the patch bumper. +- **Separate readiness from publication.** Passing tests and CI creates a + release candidate; publishing `v1.0.0` remains a distinct human-approved + action. + +## Dependency Graph + +```text +Task 1: Establish baseline evidence + | + +--> Task 2: Prove check is read-only + | | + +--> Task 3: Guard forbidden Git mutations + | | + +--> Task 4: Prove library/live isolation + | + +--> Checkpoint A: v1 contract proof + | + +--> Task 5: Make major promotion safe + | + +--> Checkpoint B: release mechanism + | + +--> Task 6: Prepare v1 candidate + | + +--> Task 7: Publish and verify +``` + +Tasks 2–4 are conceptually independent, but they share `ACCEPTANCE.md` and +`tests/acceptance.rs`; implement them sequentially to keep review and rollback +small. There is no useful multi-agent parallelism for this plan. + +## Task 1: Establish the frozen baseline + +**Description:** Prove that the clean `v0.3.20` checkout satisfies the current +local quality gate before changing its evidence or release machinery. Record +failures in the implementation session rather than weakening acceptance rows. + +**Acceptance criteria:** + +- [ ] The worktree is clean and `main` is reconciled with `origin/main`. +- [ ] Every command in the `ACCEPTANCE.md` proof block passes locally where the + host supports it. +- [ ] Current GitHub CI and the `v0.3.20` release are confirmed healthy; any + environmental limitation is identified separately from a product defect. + +**Verification:** + +- [ ] `git status --short --branch` +- [ ] `cargo fmt --all -- --check` +- [ ] `cargo check --workspace --all-targets --locked` +- [ ] `cargo clippy --workspace --all-targets --locked -- -D warnings` +- [ ] `cargo test --workspace --all-targets --locked` +- [ ] `cargo test --workspace --locked --doc` +- [ ] `cargo build --workspace --release --locked` +- [ ] `cargo audit --file Cargo.lock` +- [ ] Inspect the current GitHub Actions and release result without mutating it. + +**Dependencies:** None + +**Files likely touched:** None + +**Estimated scope:** XS (verification only) + +## Task 2: Prove `skill check` is read-only + +**Description:** Replace C4's manual marker with an automated acceptance sensor. +Run `skill check` against a valid fixture while external command lookup is +unavailable, and compare the relevant project and Tink-home trees before and +after execution. The sensor must detect content, entry-type, executable-mode, +or path changes without relying on access times. + +**Acceptance criteria:** + +- [ ] C4 fails if `skill check` creates, removes, or changes project or home + state. +- [ ] C4 succeeds without Git, curl, or other external commands available on + `PATH`, establishing the owned no-network/no-child-process boundary. +- [ ] `ACCEPTANCE.md` names C4 as an automated sensor without broadening its + claim beyond observable Tink behavior. + +**Verification:** + +- [ ] `cargo test --test acceptance c4_` +- [ ] `cargo test --test acceptance_traceability` +- [ ] `cargo test --workspace --all-targets --locked` + +**Dependencies:** Task 1 + +**Files likely touched:** + +- `tests/acceptance.rs` +- `ACCEPTANCE.md` + +**Estimated scope:** S (2 files) + +## Task 3: Guard forbidden Git mutations + +**Description:** Turn S1 from an init-only observation into an enforceable +process-boundary guarantee. Keep all Tink Git execution behind `src/git.rs`, +reject the forbidden project-mutating verbs before process spawn, and exercise +representative successful local and remote command paths through acceptance +coverage. Temporary clone/fetch behavior remains allowed. + +**Acceptance criteria:** + +- [ ] The centralized Git boundary refuses `init`, `add`, `commit`, and `push` + before spawning Git. +- [ ] Legitimate inspection/import/refresh Git operations continue to pass. +- [ ] S1's acceptance wording and sensor describe the proven boundary and no + longer claim broader coverage than the test provides. + +**Verification:** + +- [ ] Focused unit tests for the Git argument guard pass. +- [ ] `cargo test --test acceptance s1_` +- [ ] `cargo test --test acceptance_traceability` +- [ ] `cargo test --workspace --all-targets --locked` + +**Dependencies:** Task 1; perform after Task 2 to avoid shared-file conflicts + +**Files likely touched:** + +- `src/git.rs` +- `tests/acceptance.rs` +- `ACCEPTANCE.md` + +**Estimated scope:** M (3 files) + +## Task 4: Prove library and live-skill isolation + +**Description:** Replace S2's untestable external-harness phrasing with the +strongest claim Tink owns: a library-only skill never appears in project skill +listing or validation and becomes live only through explicit promotion. Keep +the README's distinction between inventory and discovery consistent. + +**Acceptance criteria:** + +- [ ] A library-only fixture is absent from `tink skill list` project output and + does not affect `tink skill check`. +- [ ] Explicit `tink skill add ` makes that skill project-live, + after which list and check observe it normally. +- [ ] S2 and the README state the owned isolation guarantee without claiming + control over third-party agent discovery configuration. + +**Verification:** + +- [ ] `cargo test --test acceptance s2_` +- [ ] `cargo test --test acceptance_traceability` +- [ ] `cargo test --workspace --all-targets --locked` +- [ ] Manually compare the revised wording in `README.md` and `ACCEPTANCE.md`. + +**Dependencies:** Task 1; perform after Task 3 to avoid shared-file conflicts + +**Files likely touched:** + +- `tests/acceptance.rs` +- `ACCEPTANCE.md` +- `README.md` + +**Estimated scope:** M (3 files) + +## Checkpoint A: v1 contract proof + +- [ ] Tasks 1–4 acceptance criteria are satisfied. +- [ ] `ACCEPTANCE.md` has no unexplained manual or partial sensor marker. +- [ ] The full local proof block passes. +- [ ] The diff contains no new user-facing capability or storage contract. +- [ ] Human review confirms the original v1 boundary is still intact. + +Stop here if closing a proof gap requires new product machinery. Reclassify the +claim precisely or seek a revised scope instead of expanding Tink. + +## Task 5: Make intentional major-version promotion safe + +**Description:** Teach the existing `bump-release` workflow to distinguish an +intentional untagged version already present on `main` from the normal +tagged-current-version patch path. The intended `1.0.0` commit should be tagged +and dispatched as `v1.0.0`, not automatically transformed into `1.0.1`. + +**Acceptance criteria:** + +- [ ] A tagged current version still produces the next patch release exactly as + it does today. +- [ ] An untagged, higher, manifest/lock-consistent version is published at that + exact version after all gates pass. +- [ ] Existing, non-ancestor, mismatched, lower, or malformed tags/versions fail + closed before any push or dispatch. + +**Verification:** + +- [ ] Focused workflow-contract tests cover normal patching and intentional + pre-bumped promotion. +- [ ] `cargo test --test workflow_contract` +- [ ] Review the workflow's atomic push, tag validation, and dispatch paths. +- [ ] Full local proof block passes. + +**Dependencies:** Checkpoint A + +**Files likely touched:** + +- `.github/workflows/bump-release.yml` +- `tests/workflow_contract.rs` + +**Estimated scope:** S (2 files) + +## Checkpoint B: release mechanism + +- [ ] Task 5 passes focused and full verification. +- [ ] A dry review proves `1.0.0` will not become `1.0.1` accidentally. +- [ ] Failure paths cannot publish a partial main/tag pair. +- [ ] Human approval is obtained before preparing the version candidate. + +## Task 6: Prepare the `v1.0.0` candidate + +**Description:** Change only the package maturity markers and concise +user-facing positioning needed for the major release. Do not change commands, +formats, dependencies, or behavior in the version-preparation increment. + +**Acceptance criteria:** + +- [ ] `Cargo.toml` and the Tink entry in `Cargo.lock` both declare `1.0.0`. +- [ ] README installation and usage remain accurate and identify the accepted + v1 support boundary without adding a roadmap subsystem. +- [ ] The candidate diff contains no runtime behavior change. + +**Verification:** + +- [ ] `cargo metadata --locked --no-deps` reports package version `1.0.0`. +- [ ] `git diff --check` +- [ ] Full local proof block passes. +- [ ] Pull-request CI passes on all four supported target runners. + +**Dependencies:** Checkpoint B + +**Files likely touched:** + +- `Cargo.toml` +- `Cargo.lock` +- `README.md` + +**Estimated scope:** M (3 files) + +## Task 7: Publish and verify `v1.0.0` + +**Description:** After an explicit human go decision, merge the focused +candidate, allow the existing automation to atomically tag and dispatch the +release, and prove the public artifacts and real install/update paths. This is +an external mutation and requires its own approval at execution time. + +**Acceptance criteria:** + +- [ ] GitHub publishes non-draft `v1.0.0` with exactly four expected regular + archives and matching SHA-256 asset digests. +- [ ] A clean install reports `tink 1.0.0`; updating the prior public version + reaches `1.0.0` without damaging the existing binary on failure. +- [ ] `main`, tag, Cargo version, GitHub release, and installed binary reconcile + to the same version. + +**Verification:** + +- [ ] Required PR checks pass before merge. +- [ ] Release workflow quality, audit, build, and publish jobs pass. +- [ ] Run the installer in a temporary destination and execute `tink --version`. +- [ ] Run the supported update smoke path from the preceding public version in + an isolated temporary destination. +- [ ] Confirm `git status`, `git tag`, release metadata, asset inventory, and + checksums agree. + +**Dependencies:** Task 6 and an explicit human go decision + +**Files likely touched:** None beyond Task 6; external GitHub state changes + +**Estimated scope:** M (release operation and live verification) + +## Checkpoint C: Final go/no-go + +Before publication: + +- [ ] No unresolved correctness, security, or compatibility finding remains. +- [ ] All task acceptance criteria and the standing Definition of Done pass. +- [ ] CI is green on macOS/Linux and x86_64/arm64. +- [ ] The candidate adds no post-freeze feature. +- [ ] Rollback is understood: do not move/delete a published tag; fix a failed + draft before publication, or ship a subsequent patch after publication. +- [ ] Human explicitly approves public release. + +After publication: + +- [ ] Install and update smoke tests pass against public release infrastructure. +- [ ] Public documentation resolves to the released behavior. +- [ ] Tink enters maintenance mode: bug fixes, security, compatibility, + onboarding friction, and documentation only unless observed user evidence + justifies reopening the feature boundary. + +## Definition of Done + +Every implementation task must satisfy its acceptance criteria plus the +repository-wide bar: + +- Correctness is exercised at runtime, including failure paths. +- Focused tests fail without the proof/change and pass with it. +- The full existing suite, formatting, Clippy, docs, build, and audit pass. +- Public behavior and compatibility remain documented accurately. +- Security and rollback implications are reviewed. +- No unrelated refactor, dead code, debug output, or process artifact remains. +- A human reviews each release-boundary increment before merge/publication. + +## Risks and Mitigations + +| Risk | Impact | Mitigation | +|---|---|---| +| A test overclaims control over third-party agent discovery | High | Rewrite S2 around observable Tink project/library behavior only | +| Read-only testing observes access-time noise as a write | Medium | Snapshot content, paths, kinds, modes, and stable metadata; exclude atime | +| Git guard blocks required clone/inspection operations | High | Deny only named forbidden verbs and retain focused positive-path tests | +| Major bump is auto-incremented to `1.0.1` | High | Complete Task 5 and its checkpoint before changing package versions | +| Automatic release partially publishes | High | Preserve quality dependencies, atomic main/tag push, draft-first assets, and digest checks | +| “v1 cleanup” becomes feature development | High | Reject new behavior during this plan; require a separate evidence-backed proposal | +| Local green checks hide platform failure | Medium | Require the four-target GitHub matrix before the go decision | +| Public smoke test damages an installed binary | High | Use isolated temporary destinations and verify rollback behavior | + +## Open Questions Requiring Human Decisions + +- At Checkpoint A: Are the narrowed, automated S2 semantics strong enough to + retire the manual claim, or should the external-harness statement remain an + explicitly accepted manual assertion? +- At Checkpoint B: Approve preparing the `1.0.0` candidate only after reviewing + the release-automation change. +- At Checkpoint C: Approve or decline the public `v1.0.0` release based on the + complete evidence package. diff --git a/tasks/todo.md b/tasks/todo.md new file mode 100644 index 0000000..6345017 --- /dev/null +++ b/tasks/todo.md @@ -0,0 +1,200 @@ +# Tink v1 Stabilization Checklist + +This checklist executes `tasks/plan.md`. No new commands, formats, or product +capabilities are part of the v1 stabilization boundary. + +## Phase 1: Establish the baseline + +### Task 1: Establish the frozen baseline + +**Dependencies:** None + +**Estimated scope:** XS; verification only + +**Files likely touched:** None + +**Acceptance criteria:** + +- [ ] Clean `main` is reconciled with `origin/main` at `v0.3.20`. +- [ ] The complete local proof block passes on the current host. +- [ ] Current CI and release health are confirmed; environmental limitations + are separated from product defects. + +**Verification:** + +- [ ] `git status --short --branch` +- [ ] `cargo fmt --all -- --check` +- [ ] `cargo check --workspace --all-targets --locked` +- [ ] `cargo clippy --workspace --all-targets --locked -- -D warnings` +- [ ] `cargo test --workspace --all-targets --locked` +- [ ] `cargo test --workspace --locked --doc` +- [ ] `cargo build --workspace --release --locked` +- [ ] `cargo audit --file Cargo.lock` +- [ ] Inspect GitHub CI and `v0.3.20` release state read-only. + +## Phase 2: Close declared proof gaps + +### Task 2: Prove `skill check` is read-only + +**Dependencies:** Task 1 + +**Estimated scope:** S; `tests/acceptance.rs`, `ACCEPTANCE.md` + +**Acceptance criteria:** + +- [ ] C4 detects project/home path, content, kind, or mode changes. +- [ ] C4 passes with external commands unavailable. +- [ ] C4 is an automated, accurately bounded acceptance sensor. + +**Verification:** + +- [ ] `cargo test --test acceptance c4_` +- [ ] `cargo test --test acceptance_traceability` +- [ ] `cargo test --workspace --all-targets --locked` + +### Task 3: Guard forbidden Git mutations + +**Dependencies:** Task 1; run after Task 2 because files overlap + +**Estimated scope:** M; `src/git.rs`, `tests/acceptance.rs`, `ACCEPTANCE.md` + +**Acceptance criteria:** + +- [ ] The centralized Git boundary rejects `init`, `add`, `commit`, and `push` + before process spawn. +- [ ] Required clone, inspect, and refresh paths remain functional. +- [ ] S1 states and tests the precise enforced boundary. + +**Verification:** + +- [ ] Focused Git-boundary unit tests pass. +- [ ] `cargo test --test acceptance s1_` +- [ ] `cargo test --test acceptance_traceability` +- [ ] `cargo test --workspace --all-targets --locked` + +### Task 4: Prove library and live-skill isolation + +**Dependencies:** Task 1; run after Task 3 because files overlap + +**Estimated scope:** M; `tests/acceptance.rs`, `ACCEPTANCE.md`, `README.md` + +**Acceptance criteria:** + +- [ ] A library-only skill is absent from project list/check behavior. +- [ ] Explicit promotion makes the skill project-live and observable. +- [ ] Documentation claims only the isolation behavior Tink owns. + +**Verification:** + +- [ ] `cargo test --test acceptance s2_` +- [ ] `cargo test --test acceptance_traceability` +- [ ] `cargo test --workspace --all-targets --locked` +- [ ] Manually compare README and acceptance wording. + +## Checkpoint A: v1 contract proof + +- [ ] Tasks 1–4 satisfy their acceptance criteria. +- [ ] No unexplained manual or partial sensor remains. +- [ ] Full local proof passes. +- [ ] No new feature or storage contract entered the diff. +- [ ] Human confirms the v1 behavior boundary remains intact. + +## Phase 3: Make the major promotion safe + +### Task 5: Support intentional major-version promotion + +**Dependencies:** Checkpoint A + +**Estimated scope:** S; `.github/workflows/bump-release.yml`, +`tests/workflow_contract.rs` + +**Acceptance criteria:** + +- [ ] Tagged current versions retain normal patch-bump behavior. +- [ ] An intentional higher untagged version publishes at that exact version. +- [ ] Invalid, lower, conflicting, or unsafe release states fail before push or + dispatch. + +**Verification:** + +- [ ] Focused workflow-contract tests cover both version paths. +- [ ] `cargo test --test workflow_contract` +- [ ] Atomic push, validation, and dispatch paths are reviewed manually. +- [ ] Full local proof passes. + +## Checkpoint B: release mechanism + +- [ ] Task 5 passes focused and full verification. +- [ ] Dry review proves `1.0.0` cannot become `1.0.1` accidentally. +- [ ] No partial main/tag publication path is introduced. +- [ ] Human approves preparation of the version candidate. + +## Phase 4: Prepare the candidate + +### Task 6: Prepare `v1.0.0` + +**Dependencies:** Checkpoint B + +**Estimated scope:** M; `Cargo.toml`, `Cargo.lock`, `README.md` + +**Acceptance criteria:** + +- [ ] Manifest and lockfile both declare `1.0.0`. +- [ ] README accurately states the accepted v1 support boundary. +- [ ] The candidate contains no runtime behavior change. + +**Verification:** + +- [ ] `cargo metadata --locked --no-deps` reports `1.0.0`. +- [ ] `git diff --check` +- [ ] Full local proof passes. +- [ ] PR CI passes on all four supported targets. + +## Checkpoint C: Final go/no-go + +- [ ] All acceptance criteria and the standing Definition of Done pass. +- [ ] No unresolved correctness, security, or compatibility finding remains. +- [ ] Four-target CI is green. +- [ ] Candidate contains no post-freeze feature. +- [ ] Release failure and post-publication rollback paths are understood. +- [ ] Human explicitly approves public release. + +## Phase 5: Publish and verify + +### Task 7: Publish `v1.0.0` + +**Dependencies:** Task 6 and explicit human go approval + +**Estimated scope:** M; external GitHub release state only + +**Acceptance criteria:** + +- [ ] Non-draft `v1.0.0` contains exactly four expected archives with matching + GitHub SHA-256 digests. +- [ ] Clean install and prior-version update both reach `tink 1.0.0` in isolated + temporary destinations. +- [ ] `main`, tag, manifest, release, and installed binary agree on `1.0.0`. + +**Verification:** + +- [ ] Required PR checks pass before merge. +- [ ] Release quality, audit, build, and publish jobs pass. +- [ ] Temporary clean-install smoke test passes. +- [ ] Temporary prior-version update smoke test passes. +- [ ] Git and GitHub release metadata reconcile. + +## Completion + +- [ ] Public documentation matches released behavior. +- [ ] Tink enters maintenance mode: bugs, security, compatibility, onboarding, + and documentation unless observed evidence justifies new capability. +- [ ] No temporary verification artifact or unrelated change remains. + +## Risks to re-check at every checkpoint + +- [ ] Tests do not claim control over third-party agent harnesses. +- [ ] S1 protection does not block legitimate Git reads or temporary clones. +- [ ] Release automation cannot turn `1.0.0` into `1.0.1`. +- [ ] Automatic publication remains atomic and draft-first. +- [ ] Platform CI, not local tests alone, supports the release decision. +- [ ] Public smoke tests use isolated destinations and preserve existing tools. From de6fe77726200da5794a0090cd34193604f96065 Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:07:54 -0500 Subject: [PATCH 02/10] test: prove skill check preserves managed state --- ACCEPTANCE.md | 2 +- tests/acceptance.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/ACCEPTANCE.md b/ACCEPTANCE.md index ff39cd5..9c9ea5f 100644 --- a/ACCEPTANCE.md +++ b/ACCEPTANCE.md @@ -178,7 +178,7 @@ Ids are stable. Tests must name or comment the id they prove. | C1 | `skill check` after valid `init` + `skill add` | Exit 0 | | C2 | `skill check` without `.agents/skills` | Exit ≠ 0 | | C3 | `skill check` when `.agents` is a symlink | Exit ≠ 0; refuse | -| C4 | `skill check` | Performs no network I/O and no filesystem writes. Sensor: manual. | +| C4 | `skill check` | Leaves the project and Tink home trees byte-for-byte and mode-for-mode unchanged; succeeds without external commands on `PATH` | | C5 | `skill check` after an installed skill's frontmatter name is corrupted | Exit ≠ 0; reports the skill-name mismatch | | C6 | `skill check` after an installed `SKILL.md` loses its YAML frontmatter | Exit ≠ 0; reports that YAML frontmatter is required | | C7 | `skill check` after an installed `SKILL.md` has unclosed frontmatter | Exit ≠ 0; reports that the frontmatter is not closed | diff --git a/tests/acceptance.rs b/tests/acceptance.rs index 1b4f35b..b31798d 100644 --- a/tests/acceptance.rs +++ b/tests/acceptance.rs @@ -4,6 +4,7 @@ use assert_cmd::Command; use assert_cmd::cargo::cargo_bin_cmd; use predicates::prelude::*; use std::fs; +use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::path::{Path, PathBuf}; use std::process::Command as StdCommand; use tempfile::TempDir; @@ -198,6 +199,59 @@ fn github_redirect(local_repo: &Path, public_url: &str) -> Vec<(String, String)> ] } +#[derive(Debug, Eq, PartialEq)] +struct TreeEntry { + path: PathBuf, + kind: &'static str, + mode: u32, + contents: Vec, +} + +fn snapshot_tree(root: &Path) -> Vec { + fn visit(root: &Path, path: &Path, entries: &mut Vec) { + let mut children: Vec<_> = fs::read_dir(path) + .expect("read snapshot directory") + .map(|entry| entry.expect("snapshot entry").path()) + .collect(); + children.sort(); + for child in children { + let metadata = fs::symlink_metadata(&child).expect("snapshot metadata"); + let file_type = metadata.file_type(); + let kind = if file_type.is_dir() { + "directory" + } else if file_type.is_file() { + "file" + } else if file_type.is_symlink() { + "symlink" + } else if file_type.is_socket() { + "socket" + } else { + "special" + }; + entries.push(TreeEntry { + path: child + .strip_prefix(root) + .expect("snapshot path under root") + .to_path_buf(), + kind, + mode: metadata.permissions().mode(), + contents: if file_type.is_file() { + fs::read(&child).expect("snapshot file") + } else { + Vec::new() + }, + }); + if file_type.is_dir() { + visit(root, &child, entries); + } + } + } + + let mut entries = Vec::new(); + visit(root, root, &mut entries); + entries +} + // --- I*: init --- #[test] @@ -2030,6 +2084,24 @@ fn c3_check_refuses_agents_symlink() { .stderr(predicate::str::contains("symlink").or(predicate::str::contains("Symlink"))); } +#[test] +fn c4_check_preserves_project_and_home_trees_without_external_commands() { + let ws = Workspace::new(); + let project = ws.project("app"); + ws.cmd(&project).arg("init").assert().success(); + let project_before = snapshot_tree(&project); + let home_before = snapshot_tree(&ws.inventory); + + ws.cmd(&project) + .env("PATH", "") + .args(["skill", "check"]) + .assert() + .success(); + + assert_eq!(snapshot_tree(&project), project_before); + assert_eq!(snapshot_tree(&ws.inventory), home_before); +} + #[test] fn c5_check_fails_with_corrupt_installed_skill() { let ws = Workspace::new(); From 092f2c06fac7f2f47d7f19156838473fdc887607 Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:09:05 -0500 Subject: [PATCH 03/10] guard against project-mutating git commands --- ACCEPTANCE.md | 2 +- src/git.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/acceptance.rs | 15 ++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/ACCEPTANCE.md b/ACCEPTANCE.md index 9c9ea5f..ae39dce 100644 --- a/ACCEPTANCE.md +++ b/ACCEPTANCE.md @@ -318,7 +318,7 @@ Ids are stable. Tests must name or comment the id they prove. | Id | Action | Expect | |---|---|---| -| S1 | Any successful command | Does not `git init`, stage, commit, or push. Sensor: S1 (partial: `init` only). | +| S1 | Successful local and remote commands | Tink's centralized Git process boundary refuses root `init`, `add`, `commit`, and `push` commands; representative init and remote-add paths do not create project Git state | | S2 | Home root | Never treated as an agent discovery root. Sensor: manual. | | S3 | `skill remove` or `destroy --yes` when neither `TINK_HOME` nor `HOME` can resolve an inventory | Exit 0; completes local cleanup without creating or mutating inventory state | diff --git a/src/git.rs b/src/git.rs index 47599ce..d593661 100644 --- a/src/git.rs +++ b/src/git.rs @@ -31,6 +31,25 @@ fn git_command_args<'a>(args: &[&'a str]) -> Vec<&'a str> { full } +fn git_subcommand<'a>(args: &[&'a str]) -> Option<&'a str> { + let mut index = 0; + while let Some(argument) = args.get(index) { + match *argument { + "-C" | "-c" | "--git-dir" | "--work-tree" | "--namespace" => index += 2, + value if value.starts_with('-') => index += 1, + value => return Some(value), + } + } + None +} + +fn guard_git_command(args: &[&str]) -> Result<(), Error> { + if matches!(git_subcommand(args), Some("init" | "add" | "commit" | "push")) { + return Err(Error::msg("Tink refuses project-mutating Git commands")); + } + Ok(()) +} + fn run_git( args: &[&str], cwd: Option<&Path>, @@ -38,6 +57,7 @@ fn run_git( io_context: &str, missing_message: Option<&str>, ) -> Result { + guard_git_command(args)?; let mut command = Command::new("git"); command.args(git_command_args(args)); if non_interactive { @@ -236,4 +256,26 @@ mod tests { ] ); } + + #[test] + fn git_guard_rejects_project_mutations_and_allows_owned_reads() { + for args in [ + &["init"][..], + &["add", "."][..], + &["commit", "-m", "message"][..], + &["push", "origin", "main"][..], + &["-C", "/tmp/repo", "commit"][..], + ] { + assert!(guard_git_command(args).is_err(), "must refuse {args:?}"); + } + + for args in [ + &["ls-remote", "https://example.test", "HEAD"][..], + &["clone", "https://example.test/repo.git", "/tmp/repo"][..], + &["rev-parse", "HEAD"][..], + &["-C", "/tmp/repo", "worktree", "add", "/tmp/tree"][..], + ] { + assert!(guard_git_command(args).is_ok(), "must allow {args:?}"); + } + } } diff --git a/tests/acceptance.rs b/tests/acceptance.rs index b31798d..4b89a84 100644 --- a/tests/acceptance.rs +++ b/tests/acceptance.rs @@ -4502,12 +4502,25 @@ fn x7_remove_refuses_symlinked_catalog_without_external_or_project_writes() { // --- S*: safety --- #[test] -fn s1_init_does_not_create_git_repo() { +fn s1_successful_local_and_remote_commands_do_not_mutate_project_git() { let ws = Workspace::new(); let project = ws.project("app"); assert!(!project.join(".git").exists()); ws.cmd(&project).arg("init").assert().success(); assert!(!project.join(".git").exists()); + + let remote = ws.root.join("remote"); + init_repo(&remote); + write_skill(&remote, "remote-skill", "remote"); + commit_all(&remote, "initial"); + let public_url = "https://github.com/example/remote-skill.git"; + let envs = github_redirect(&remote, public_url); + ws.cmd(&project) + .envs(envs) + .args(["skill", "add", "example/remote-skill"]) + .assert() + .success(); + assert!(!project.join(".git").exists()); } #[test] From e1de197ca9b9088ba3f8dbf60bca71f7af8ea583 Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:09:55 -0500 Subject: [PATCH 04/10] test: prove library skills require promotion --- ACCEPTANCE.md | 2 +- README.md | 6 ++++-- tests/acceptance.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/ACCEPTANCE.md b/ACCEPTANCE.md index ae39dce..31fdf86 100644 --- a/ACCEPTANCE.md +++ b/ACCEPTANCE.md @@ -319,7 +319,7 @@ Ids are stable. Tests must name or comment the id they prove. | Id | Action | Expect | |---|---|---| | S1 | Successful local and remote commands | Tink's centralized Git process boundary refuses root `init`, `add`, `commit`, and `push` commands; representative init and remote-add paths do not create project Git state | -| S2 | Home root | Never treated as an agent discovery root. Sensor: manual. | +| S2 | A valid skill exists only in the home library | Project `skill list` and `skill check` do not treat it as live; explicit `skill add ` promotes it into the project, after which both commands observe it | | S3 | `skill remove` or `destroy --yes` when neither `TINK_HOME` nor `HOME` can resolve an inventory | Exit 0; completes local cleanup without creating or mutating inventory state | ## Proof diff --git a/README.md b/README.md index 9ab0bc5..94a3197 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,10 @@ flowchart LR tink -->|"add "| live ``` -Home (`~/.tink` or `$TINK_HOME`) is **not** an agent discovery root. Library holds -skill trees; catalog holds by-project **names** only. +Tink lists and validates live project skills only under `.agents/skills/`. It +never promotes a home-library entry automatically or configures an agent +harness to discover the home. Library holds skill trees; catalog holds +by-project **names** only. ## Use (everyday) diff --git a/tests/acceptance.rs b/tests/acceptance.rs index 4b89a84..b2cd171 100644 --- a/tests/acceptance.rs +++ b/tests/acceptance.rs @@ -4523,6 +4523,46 @@ fn s1_successful_local_and_remote_commands_do_not_mutate_project_git() { assert!(!project.join(".git").exists()); } +#[test] +fn s2_library_skill_is_not_project_live_until_explicitly_added() { + let ws = Workspace::new(); + let bootstrap = ws.project("bootstrap"); + ws.cmd(&bootstrap) + .args(["init", "--no-zen", "--no-tink-skills", "--no-manage-tink"]) + .assert() + .success(); + write_skill(&ws.library_skill("library-only"), "library-only", "body"); + + let project = ws.project("app"); + ws.cmd(&project) + .args(["init", "--no-zen", "--no-tink-skills", "--no-manage-tink"]) + .assert() + .success(); + ws.cmd(&project) + .args(["skill", "list"]) + .assert() + .success() + .stdout(predicate::str::contains("library-only").not()); + ws.cmd(&project) + .args(["skill", "check"]) + .assert() + .success(); + + ws.cmd(&project) + .args(["skill", "add", "library-only"]) + .assert() + .success(); + ws.cmd(&project) + .args(["skill", "list"]) + .assert() + .success() + .stdout(predicate::str::contains("library-only")); + ws.cmd(&project) + .args(["skill", "check"]) + .assert() + .success(); +} + #[test] fn s3_remove_and_destroy_complete_when_implicit_home_cannot_resolve() { let ws = Workspace::new(); From e19b2920d3227e2c2ca2ae972a982d2eb090176b Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:10:17 -0500 Subject: [PATCH 05/10] style: format v1 proof tests --- src/git.rs | 5 ++++- tests/acceptance.rs | 10 ++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/git.rs b/src/git.rs index d593661..797f2ce 100644 --- a/src/git.rs +++ b/src/git.rs @@ -44,7 +44,10 @@ fn git_subcommand<'a>(args: &[&'a str]) -> Option<&'a str> { } fn guard_git_command(args: &[&str]) -> Result<(), Error> { - if matches!(git_subcommand(args), Some("init" | "add" | "commit" | "push")) { + if matches!( + git_subcommand(args), + Some("init" | "add" | "commit" | "push") + ) { return Err(Error::msg("Tink refuses project-mutating Git commands")); } Ok(()) diff --git a/tests/acceptance.rs b/tests/acceptance.rs index b2cd171..25c088e 100644 --- a/tests/acceptance.rs +++ b/tests/acceptance.rs @@ -4543,10 +4543,7 @@ fn s2_library_skill_is_not_project_live_until_explicitly_added() { .assert() .success() .stdout(predicate::str::contains("library-only").not()); - ws.cmd(&project) - .args(["skill", "check"]) - .assert() - .success(); + ws.cmd(&project).args(["skill", "check"]).assert().success(); ws.cmd(&project) .args(["skill", "add", "library-only"]) @@ -4557,10 +4554,7 @@ fn s2_library_skill_is_not_project_live_until_explicitly_added() { .assert() .success() .stdout(predicate::str::contains("library-only")); - ws.cmd(&project) - .args(["skill", "check"]) - .assert() - .success(); + ws.cmd(&project).args(["skill", "check"]).assert().success(); } #[test] From ffed0820880264c0460adf5caf7e3a4dedd00600 Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:11:55 -0500 Subject: [PATCH 06/10] ci: support intentional major releases --- .github/workflows/bump-release.yml | 68 ++++++++++++++++++++++++++++++ tests/workflow_contract.rs | 27 ++++++++++++ 2 files changed, 95 insertions(+) diff --git a/.github/workflows/bump-release.yml b/.github/workflows/bump-release.yml index 31156e4..ff700eb 100644 --- a/.github/workflows/bump-release.yml +++ b/.github/workflows/bump-release.yml @@ -112,6 +112,21 @@ jobs: exit 1 fi + locked="$(python3 - <<'PY' + import tomllib + with open("Cargo.lock", "rb") as source: + packages = tomllib.load(source)["package"] + matches = [package["version"] for package in packages if package["name"] == "tink"] + if len(matches) != 1: + raise SystemExit("Cargo.lock must contain exactly one tink package") + print(matches[0]) + PY + )" + if [ "${locked}" != "${current}" ]; then + echo "Cargo.lock tink version ${locked} does not match Cargo.toml ${current}" >&2 + exit 1 + fi + current_tag="v${current}" if git rev-parse --verify --quiet "refs/tags/${current_tag}" >/dev/null; then current_tagged_version="$(git show "${current_tag}:Cargo.toml" | sed -n 's/^version = "\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)"/\1/p' | head -n1)" @@ -132,6 +147,59 @@ jobs: fi fi + # An intentionally pre-bumped, untagged manifest (for example 1.0.0) + # publishes that exact version instead of incrementing it again. + if ! git rev-parse --verify --quiet "refs/tags/${current_tag}" >/dev/null; then + baseline_tag="$(gh release view --json tagName --jq '.tagName' 2>/dev/null || true)" + if [ -z "${baseline_tag}" ]; then + echo "Cannot publish ${current_tag} without a published release baseline" >&2 + exit 1 + fi + baseline_version="${baseline_tag#v}" + if ! python3 - "${current}" "${baseline_version}" <<'PY' + import re + import sys + + current, baseline = sys.argv[1:] + pattern = re.compile(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)") + if pattern.fullmatch(current) is None or pattern.fullmatch(baseline) is None: + raise SystemExit(1) + raise SystemExit(0 if tuple(map(int, current.split("."))) > tuple(map(int, baseline.split("."))) else 1) + PY + then + echo "Pre-bumped version ${current} must be newer than published ${baseline_tag}" >&2 + exit 1 + fi + if ! git rev-parse --verify --quiet "refs/tags/${baseline_tag}" >/dev/null; then + echo "Published baseline tag ${baseline_tag} is missing locally" >&2 + exit 1 + fi + baseline_tagged_version="$(git show "${baseline_tag}:Cargo.toml" | sed -n 's/^version = "\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)"/\1/p' | head -n1)" + if [ "${baseline_tagged_version}" != "${baseline_version}" ]; then + echo "Baseline tag ${baseline_tag} does not contain Cargo version ${baseline_version}" >&2 + exit 1 + fi + if ! git merge-base --is-ancestor "${baseline_tag}^{commit}" origin/main; then + echo "Baseline tag ${baseline_tag} is not contained in origin/main" >&2 + exit 1 + fi + baseline_release_draft="$(gh release view "${baseline_tag}" --json isDraft --jq '.isDraft' 2>/dev/null || true)" + if [ "${baseline_release_draft}" != "false" ]; then + echo "Baseline release ${baseline_tag} is not published" >&2 + exit 1 + fi + if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]; then + echo "origin/main advanced after this workflow started; a newer run owns the release" >&2 + exit 1 + fi + + git tag "${current_tag}" + git push --atomic origin HEAD:main "refs/tags/${current_tag}:refs/tags/${current_tag}" + gh workflow run release.yml --ref "${current_tag}" + echo "Dispatched intentional release for ${current_tag}" + exit 0 + fi + IFS=. read -r major minor patch <<< "${current}" new="${major}.${minor}.$((patch + 1))" tag="v${new}" diff --git a/tests/workflow_contract.rs b/tests/workflow_contract.rs index 965802c..d015756 100644 --- a/tests/workflow_contract.rs +++ b/tests/workflow_contract.rs @@ -56,6 +56,33 @@ fn bump_gates_the_tag_on_every_platform_and_publishes_refs_atomically() { assert!(existing_tag < release_check && release_check < dispatch); } +#[test] +fn bump_publishes_an_intentional_pre_bump_without_incrementing_it() { + let marker = "# An intentionally pre-bumped, untagged manifest"; + let pre_bump = BUMP + .split_once(marker) + .expect("intentional pre-bump branch") + .1 + .split_once("IFS=. read -r major minor patch") + .expect("pre-bump must precede patch calculation") + .0; + + assert!(BUMP.find("locked=\"").unwrap() < BUMP.find(marker).unwrap()); + assert!(pre_bump.contains("baseline_tag=")); + assert!(pre_bump.contains("must be newer than published")); + assert!(pre_bump.contains("baseline_tagged_version=")); + assert!(pre_bump.contains("git merge-base --is-ancestor")); + assert!(pre_bump.contains("baseline_release_draft=")); + assert!(pre_bump.contains("git tag \"${current_tag}\"")); + assert!(pre_bump.contains( + "git push --atomic origin HEAD:main \"refs/tags/${current_tag}:refs/tags/${current_tag}\"" + )); + assert!(pre_bump.contains("gh workflow run release.yml --ref \"${current_tag}\"")); + assert!(pre_bump.contains("exit 0")); + assert!(!pre_bump.contains("sed -i")); + assert!(!pre_bump.contains("git commit")); +} + #[test] fn release_uploads_an_exact_asset_set_to_a_draft_before_publication() { let create = position(RELEASE, "gh release create \"${tag}\""); From 7a357468ca538ad6703641cb863ae8b0fa4692bc Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:13:12 -0500 Subject: [PATCH 07/10] release: prepare tink 1.0.0 candidate --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3a530e..819bea8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,7 +570,7 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "tink" -version = "0.3.20" +version = "1.0.0" dependencies = [ "anstyle", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index 581500f..44b93d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tink" -version = "0.3.20" +version = "1.0.0" edition = "2024" rust-version = "1.95" description = "Install Agent Skills into a project's .agents/skills/" diff --git a/README.md b/README.md index 94a3197..444339c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ Skill manager that makes sense to you, and your agent. +Tink 1.0 is feature-complete for the v1 acceptance boundary in +[`ACCEPTANCE.md`](ACCEPTANCE.md). Maintenance prioritizes correctness, +security, compatibility, and a simpler everyday experience over new lifecycle +machinery. + Live skills live only under a project’s `.agents/skills//`. Grouped skillsets use one canonical nested root at `.agents/skills/-skillset//`. There From d8895cdb321c5670102903c70eabd69404f43fa2 Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:15:17 -0500 Subject: [PATCH 08/10] fix: keep release checks compatible with runners --- .github/workflows/bump-release.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/bump-release.yml b/.github/workflows/bump-release.yml index ff700eb..e9b2301 100644 --- a/.github/workflows/bump-release.yml +++ b/.github/workflows/bump-release.yml @@ -112,16 +112,21 @@ jobs: exit 1 fi - locked="$(python3 - <<'PY' - import tomllib - with open("Cargo.lock", "rb") as source: - packages = tomllib.load(source)["package"] - matches = [package["version"] for package in packages if package["name"] == "tink"] - if len(matches) != 1: - raise SystemExit("Cargo.lock must contain exactly one tink package") - print(matches[0]) - PY - )" + locked="$(awk ' + /^\[\[package\]\]$/ { package = ""; next } + /^name = "tink"$/ { package = "tink"; next } + package == "tink" && /^version = "/ { + version = $0 + sub(/^version = "/, "", version) + sub(/"$/, "", version) + print version + exit + } + ' Cargo.lock)" + if [ -z "${locked}" ]; then + echo "Could not read tink version from Cargo.lock" >&2 + exit 1 + fi if [ "${locked}" != "${current}" ]; then echo "Cargo.lock tink version ${locked} does not match Cargo.toml ${current}" >&2 exit 1 From 784f6f52370ba6c4a921f02257e77d3fec98bf86 Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 18:16:07 -0500 Subject: [PATCH 09/10] test: enforce release guard ordering --- tests/workflow_contract.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/workflow_contract.rs b/tests/workflow_contract.rs index d015756..5d2efee 100644 --- a/tests/workflow_contract.rs +++ b/tests/workflow_contract.rs @@ -81,6 +81,27 @@ fn bump_publishes_an_intentional_pre_bump_without_incrementing_it() { assert!(pre_bump.contains("exit 0")); assert!(!pre_bump.contains("sed -i")); assert!(!pre_bump.contains("git commit")); + + let tag = pre_bump.find("git tag \"${current_tag}\"").unwrap(); + let push = pre_bump.find("git push --atomic origin HEAD:main").unwrap(); + let dispatch = pre_bump.find("gh workflow run release.yml").unwrap(); + for guard in [ + "if ! git rev-parse --verify --quiet \"refs/tags/${current_tag}\"", + "if [ -z \"${baseline_tag}\" ]", + "if ! python3 - \"${current}\" \"${baseline_version}\"", + "if ! git rev-parse --verify --quiet \"refs/tags/${baseline_tag}\"", + "if [ \"${baseline_tagged_version}\" != \"${baseline_version}\" ]", + "if ! git merge-base --is-ancestor", + "if [ \"${baseline_release_draft}\" != \"false\" ]", + "if [ \"$(git rev-parse HEAD)\" != \"$(git rev-parse origin/main)\" ]", + ] { + assert!( + pre_bump.find(guard).unwrap() < tag, + "guard must precede tag creation: {guard}" + ); + } + assert!(tag < push, "tag must be created before atomic publication"); + assert!(push < dispatch, "publication must precede release dispatch"); } #[test] From 2e27569df4052fb469c13e4d6071662fedd603c9 Mon Sep 17 00:00:00 2001 From: jon-devlapaz Date: Wed, 12 Aug 2026 19:14:19 -0500 Subject: [PATCH 10/10] docs: remove completed v1 planning artifacts --- tasks/plan.md | 376 -------------------------------------------------- tasks/todo.md | 200 --------------------------- 2 files changed, 576 deletions(-) delete mode 100644 tasks/plan.md delete mode 100644 tasks/todo.md diff --git a/tasks/plan.md b/tasks/plan.md deleted file mode 100644 index 152db15..0000000 --- a/tasks/plan.md +++ /dev/null @@ -1,376 +0,0 @@ -# Implementation Plan: Tink v1 Stabilization and Release Readiness - -## Overview - -Treat the current Tink feature set as complete and prepare it for an honest -`v1.0.0` decision. The work closes or precisely narrows the three proof gaps -declared in `ACCEPTANCE.md`, preserves the existing command and on-disk -contracts, verifies the full supported-platform release gate, and makes the -major-version promotion safe under the existing automatic release workflow. -No new user capability belongs in this plan. - -## Outcome and Scope Boundary - -The outcome is a release candidate whose current behavior is demonstrably safe -to label `v1.0.0`, followed by a human go/no-go decision and, only after explicit -approval, a verified public release. - -In scope: - -- Automated or explicitly bounded proof for acceptance rows C4, S1, and S2. -- Current local, CI, and supported-platform release verification. -- A safe one-time transition from the `0.3.x` patch train to `1.0.0`. -- Minimal version and user-facing maturity documentation changes. -- Post-release install and update smoke tests. - -Out of scope: - -- New commands, flags, storage formats, sources, or lifecycle behavior. -- Windows, private GitHub authentication, concurrency, or other items already - listed as out of v1. -- A new release framework, roadmap system, governance document, or agent team. -- Refactors that are not required to establish the named proof. - -## Architecture Decisions - -- **Freeze the v1 behavior boundary.** `ACCEPTANCE.md` remains the evaluator; - this plan improves evidence without expanding the product contract. -- **Test only claims Tink owns.** C4 and S1 are Tink process guarantees. S2 must - be phrased as a testable Tink boundary—library-only skills are not treated as - project-live skills—rather than claiming control over every external agent - harness. -- **Guard Git at the centralized boundary.** Tink may use read-only Git - operations and temporary clones, but its Git process owner should make the - forbidden project mutations (`init`, `add`, `commit`, and `push`) impossible - to introduce accidentally. -- **Promote versions through existing automation.** Extend the current release - workflow only enough to publish an intentionally pre-bumped, untagged version. - Do not add a second release workflow or manually race the patch bumper. -- **Separate readiness from publication.** Passing tests and CI creates a - release candidate; publishing `v1.0.0` remains a distinct human-approved - action. - -## Dependency Graph - -```text -Task 1: Establish baseline evidence - | - +--> Task 2: Prove check is read-only - | | - +--> Task 3: Guard forbidden Git mutations - | | - +--> Task 4: Prove library/live isolation - | - +--> Checkpoint A: v1 contract proof - | - +--> Task 5: Make major promotion safe - | - +--> Checkpoint B: release mechanism - | - +--> Task 6: Prepare v1 candidate - | - +--> Task 7: Publish and verify -``` - -Tasks 2–4 are conceptually independent, but they share `ACCEPTANCE.md` and -`tests/acceptance.rs`; implement them sequentially to keep review and rollback -small. There is no useful multi-agent parallelism for this plan. - -## Task 1: Establish the frozen baseline - -**Description:** Prove that the clean `v0.3.20` checkout satisfies the current -local quality gate before changing its evidence or release machinery. Record -failures in the implementation session rather than weakening acceptance rows. - -**Acceptance criteria:** - -- [ ] The worktree is clean and `main` is reconciled with `origin/main`. -- [ ] Every command in the `ACCEPTANCE.md` proof block passes locally where the - host supports it. -- [ ] Current GitHub CI and the `v0.3.20` release are confirmed healthy; any - environmental limitation is identified separately from a product defect. - -**Verification:** - -- [ ] `git status --short --branch` -- [ ] `cargo fmt --all -- --check` -- [ ] `cargo check --workspace --all-targets --locked` -- [ ] `cargo clippy --workspace --all-targets --locked -- -D warnings` -- [ ] `cargo test --workspace --all-targets --locked` -- [ ] `cargo test --workspace --locked --doc` -- [ ] `cargo build --workspace --release --locked` -- [ ] `cargo audit --file Cargo.lock` -- [ ] Inspect the current GitHub Actions and release result without mutating it. - -**Dependencies:** None - -**Files likely touched:** None - -**Estimated scope:** XS (verification only) - -## Task 2: Prove `skill check` is read-only - -**Description:** Replace C4's manual marker with an automated acceptance sensor. -Run `skill check` against a valid fixture while external command lookup is -unavailable, and compare the relevant project and Tink-home trees before and -after execution. The sensor must detect content, entry-type, executable-mode, -or path changes without relying on access times. - -**Acceptance criteria:** - -- [ ] C4 fails if `skill check` creates, removes, or changes project or home - state. -- [ ] C4 succeeds without Git, curl, or other external commands available on - `PATH`, establishing the owned no-network/no-child-process boundary. -- [ ] `ACCEPTANCE.md` names C4 as an automated sensor without broadening its - claim beyond observable Tink behavior. - -**Verification:** - -- [ ] `cargo test --test acceptance c4_` -- [ ] `cargo test --test acceptance_traceability` -- [ ] `cargo test --workspace --all-targets --locked` - -**Dependencies:** Task 1 - -**Files likely touched:** - -- `tests/acceptance.rs` -- `ACCEPTANCE.md` - -**Estimated scope:** S (2 files) - -## Task 3: Guard forbidden Git mutations - -**Description:** Turn S1 from an init-only observation into an enforceable -process-boundary guarantee. Keep all Tink Git execution behind `src/git.rs`, -reject the forbidden project-mutating verbs before process spawn, and exercise -representative successful local and remote command paths through acceptance -coverage. Temporary clone/fetch behavior remains allowed. - -**Acceptance criteria:** - -- [ ] The centralized Git boundary refuses `init`, `add`, `commit`, and `push` - before spawning Git. -- [ ] Legitimate inspection/import/refresh Git operations continue to pass. -- [ ] S1's acceptance wording and sensor describe the proven boundary and no - longer claim broader coverage than the test provides. - -**Verification:** - -- [ ] Focused unit tests for the Git argument guard pass. -- [ ] `cargo test --test acceptance s1_` -- [ ] `cargo test --test acceptance_traceability` -- [ ] `cargo test --workspace --all-targets --locked` - -**Dependencies:** Task 1; perform after Task 2 to avoid shared-file conflicts - -**Files likely touched:** - -- `src/git.rs` -- `tests/acceptance.rs` -- `ACCEPTANCE.md` - -**Estimated scope:** M (3 files) - -## Task 4: Prove library and live-skill isolation - -**Description:** Replace S2's untestable external-harness phrasing with the -strongest claim Tink owns: a library-only skill never appears in project skill -listing or validation and becomes live only through explicit promotion. Keep -the README's distinction between inventory and discovery consistent. - -**Acceptance criteria:** - -- [ ] A library-only fixture is absent from `tink skill list` project output and - does not affect `tink skill check`. -- [ ] Explicit `tink skill add ` makes that skill project-live, - after which list and check observe it normally. -- [ ] S2 and the README state the owned isolation guarantee without claiming - control over third-party agent discovery configuration. - -**Verification:** - -- [ ] `cargo test --test acceptance s2_` -- [ ] `cargo test --test acceptance_traceability` -- [ ] `cargo test --workspace --all-targets --locked` -- [ ] Manually compare the revised wording in `README.md` and `ACCEPTANCE.md`. - -**Dependencies:** Task 1; perform after Task 3 to avoid shared-file conflicts - -**Files likely touched:** - -- `tests/acceptance.rs` -- `ACCEPTANCE.md` -- `README.md` - -**Estimated scope:** M (3 files) - -## Checkpoint A: v1 contract proof - -- [ ] Tasks 1–4 acceptance criteria are satisfied. -- [ ] `ACCEPTANCE.md` has no unexplained manual or partial sensor marker. -- [ ] The full local proof block passes. -- [ ] The diff contains no new user-facing capability or storage contract. -- [ ] Human review confirms the original v1 boundary is still intact. - -Stop here if closing a proof gap requires new product machinery. Reclassify the -claim precisely or seek a revised scope instead of expanding Tink. - -## Task 5: Make intentional major-version promotion safe - -**Description:** Teach the existing `bump-release` workflow to distinguish an -intentional untagged version already present on `main` from the normal -tagged-current-version patch path. The intended `1.0.0` commit should be tagged -and dispatched as `v1.0.0`, not automatically transformed into `1.0.1`. - -**Acceptance criteria:** - -- [ ] A tagged current version still produces the next patch release exactly as - it does today. -- [ ] An untagged, higher, manifest/lock-consistent version is published at that - exact version after all gates pass. -- [ ] Existing, non-ancestor, mismatched, lower, or malformed tags/versions fail - closed before any push or dispatch. - -**Verification:** - -- [ ] Focused workflow-contract tests cover normal patching and intentional - pre-bumped promotion. -- [ ] `cargo test --test workflow_contract` -- [ ] Review the workflow's atomic push, tag validation, and dispatch paths. -- [ ] Full local proof block passes. - -**Dependencies:** Checkpoint A - -**Files likely touched:** - -- `.github/workflows/bump-release.yml` -- `tests/workflow_contract.rs` - -**Estimated scope:** S (2 files) - -## Checkpoint B: release mechanism - -- [ ] Task 5 passes focused and full verification. -- [ ] A dry review proves `1.0.0` will not become `1.0.1` accidentally. -- [ ] Failure paths cannot publish a partial main/tag pair. -- [ ] Human approval is obtained before preparing the version candidate. - -## Task 6: Prepare the `v1.0.0` candidate - -**Description:** Change only the package maturity markers and concise -user-facing positioning needed for the major release. Do not change commands, -formats, dependencies, or behavior in the version-preparation increment. - -**Acceptance criteria:** - -- [ ] `Cargo.toml` and the Tink entry in `Cargo.lock` both declare `1.0.0`. -- [ ] README installation and usage remain accurate and identify the accepted - v1 support boundary without adding a roadmap subsystem. -- [ ] The candidate diff contains no runtime behavior change. - -**Verification:** - -- [ ] `cargo metadata --locked --no-deps` reports package version `1.0.0`. -- [ ] `git diff --check` -- [ ] Full local proof block passes. -- [ ] Pull-request CI passes on all four supported target runners. - -**Dependencies:** Checkpoint B - -**Files likely touched:** - -- `Cargo.toml` -- `Cargo.lock` -- `README.md` - -**Estimated scope:** M (3 files) - -## Task 7: Publish and verify `v1.0.0` - -**Description:** After an explicit human go decision, merge the focused -candidate, allow the existing automation to atomically tag and dispatch the -release, and prove the public artifacts and real install/update paths. This is -an external mutation and requires its own approval at execution time. - -**Acceptance criteria:** - -- [ ] GitHub publishes non-draft `v1.0.0` with exactly four expected regular - archives and matching SHA-256 asset digests. -- [ ] A clean install reports `tink 1.0.0`; updating the prior public version - reaches `1.0.0` without damaging the existing binary on failure. -- [ ] `main`, tag, Cargo version, GitHub release, and installed binary reconcile - to the same version. - -**Verification:** - -- [ ] Required PR checks pass before merge. -- [ ] Release workflow quality, audit, build, and publish jobs pass. -- [ ] Run the installer in a temporary destination and execute `tink --version`. -- [ ] Run the supported update smoke path from the preceding public version in - an isolated temporary destination. -- [ ] Confirm `git status`, `git tag`, release metadata, asset inventory, and - checksums agree. - -**Dependencies:** Task 6 and an explicit human go decision - -**Files likely touched:** None beyond Task 6; external GitHub state changes - -**Estimated scope:** M (release operation and live verification) - -## Checkpoint C: Final go/no-go - -Before publication: - -- [ ] No unresolved correctness, security, or compatibility finding remains. -- [ ] All task acceptance criteria and the standing Definition of Done pass. -- [ ] CI is green on macOS/Linux and x86_64/arm64. -- [ ] The candidate adds no post-freeze feature. -- [ ] Rollback is understood: do not move/delete a published tag; fix a failed - draft before publication, or ship a subsequent patch after publication. -- [ ] Human explicitly approves public release. - -After publication: - -- [ ] Install and update smoke tests pass against public release infrastructure. -- [ ] Public documentation resolves to the released behavior. -- [ ] Tink enters maintenance mode: bug fixes, security, compatibility, - onboarding friction, and documentation only unless observed user evidence - justifies reopening the feature boundary. - -## Definition of Done - -Every implementation task must satisfy its acceptance criteria plus the -repository-wide bar: - -- Correctness is exercised at runtime, including failure paths. -- Focused tests fail without the proof/change and pass with it. -- The full existing suite, formatting, Clippy, docs, build, and audit pass. -- Public behavior and compatibility remain documented accurately. -- Security and rollback implications are reviewed. -- No unrelated refactor, dead code, debug output, or process artifact remains. -- A human reviews each release-boundary increment before merge/publication. - -## Risks and Mitigations - -| Risk | Impact | Mitigation | -|---|---|---| -| A test overclaims control over third-party agent discovery | High | Rewrite S2 around observable Tink project/library behavior only | -| Read-only testing observes access-time noise as a write | Medium | Snapshot content, paths, kinds, modes, and stable metadata; exclude atime | -| Git guard blocks required clone/inspection operations | High | Deny only named forbidden verbs and retain focused positive-path tests | -| Major bump is auto-incremented to `1.0.1` | High | Complete Task 5 and its checkpoint before changing package versions | -| Automatic release partially publishes | High | Preserve quality dependencies, atomic main/tag push, draft-first assets, and digest checks | -| “v1 cleanup” becomes feature development | High | Reject new behavior during this plan; require a separate evidence-backed proposal | -| Local green checks hide platform failure | Medium | Require the four-target GitHub matrix before the go decision | -| Public smoke test damages an installed binary | High | Use isolated temporary destinations and verify rollback behavior | - -## Open Questions Requiring Human Decisions - -- At Checkpoint A: Are the narrowed, automated S2 semantics strong enough to - retire the manual claim, or should the external-harness statement remain an - explicitly accepted manual assertion? -- At Checkpoint B: Approve preparing the `1.0.0` candidate only after reviewing - the release-automation change. -- At Checkpoint C: Approve or decline the public `v1.0.0` release based on the - complete evidence package. diff --git a/tasks/todo.md b/tasks/todo.md deleted file mode 100644 index 6345017..0000000 --- a/tasks/todo.md +++ /dev/null @@ -1,200 +0,0 @@ -# Tink v1 Stabilization Checklist - -This checklist executes `tasks/plan.md`. No new commands, formats, or product -capabilities are part of the v1 stabilization boundary. - -## Phase 1: Establish the baseline - -### Task 1: Establish the frozen baseline - -**Dependencies:** None - -**Estimated scope:** XS; verification only - -**Files likely touched:** None - -**Acceptance criteria:** - -- [ ] Clean `main` is reconciled with `origin/main` at `v0.3.20`. -- [ ] The complete local proof block passes on the current host. -- [ ] Current CI and release health are confirmed; environmental limitations - are separated from product defects. - -**Verification:** - -- [ ] `git status --short --branch` -- [ ] `cargo fmt --all -- --check` -- [ ] `cargo check --workspace --all-targets --locked` -- [ ] `cargo clippy --workspace --all-targets --locked -- -D warnings` -- [ ] `cargo test --workspace --all-targets --locked` -- [ ] `cargo test --workspace --locked --doc` -- [ ] `cargo build --workspace --release --locked` -- [ ] `cargo audit --file Cargo.lock` -- [ ] Inspect GitHub CI and `v0.3.20` release state read-only. - -## Phase 2: Close declared proof gaps - -### Task 2: Prove `skill check` is read-only - -**Dependencies:** Task 1 - -**Estimated scope:** S; `tests/acceptance.rs`, `ACCEPTANCE.md` - -**Acceptance criteria:** - -- [ ] C4 detects project/home path, content, kind, or mode changes. -- [ ] C4 passes with external commands unavailable. -- [ ] C4 is an automated, accurately bounded acceptance sensor. - -**Verification:** - -- [ ] `cargo test --test acceptance c4_` -- [ ] `cargo test --test acceptance_traceability` -- [ ] `cargo test --workspace --all-targets --locked` - -### Task 3: Guard forbidden Git mutations - -**Dependencies:** Task 1; run after Task 2 because files overlap - -**Estimated scope:** M; `src/git.rs`, `tests/acceptance.rs`, `ACCEPTANCE.md` - -**Acceptance criteria:** - -- [ ] The centralized Git boundary rejects `init`, `add`, `commit`, and `push` - before process spawn. -- [ ] Required clone, inspect, and refresh paths remain functional. -- [ ] S1 states and tests the precise enforced boundary. - -**Verification:** - -- [ ] Focused Git-boundary unit tests pass. -- [ ] `cargo test --test acceptance s1_` -- [ ] `cargo test --test acceptance_traceability` -- [ ] `cargo test --workspace --all-targets --locked` - -### Task 4: Prove library and live-skill isolation - -**Dependencies:** Task 1; run after Task 3 because files overlap - -**Estimated scope:** M; `tests/acceptance.rs`, `ACCEPTANCE.md`, `README.md` - -**Acceptance criteria:** - -- [ ] A library-only skill is absent from project list/check behavior. -- [ ] Explicit promotion makes the skill project-live and observable. -- [ ] Documentation claims only the isolation behavior Tink owns. - -**Verification:** - -- [ ] `cargo test --test acceptance s2_` -- [ ] `cargo test --test acceptance_traceability` -- [ ] `cargo test --workspace --all-targets --locked` -- [ ] Manually compare README and acceptance wording. - -## Checkpoint A: v1 contract proof - -- [ ] Tasks 1–4 satisfy their acceptance criteria. -- [ ] No unexplained manual or partial sensor remains. -- [ ] Full local proof passes. -- [ ] No new feature or storage contract entered the diff. -- [ ] Human confirms the v1 behavior boundary remains intact. - -## Phase 3: Make the major promotion safe - -### Task 5: Support intentional major-version promotion - -**Dependencies:** Checkpoint A - -**Estimated scope:** S; `.github/workflows/bump-release.yml`, -`tests/workflow_contract.rs` - -**Acceptance criteria:** - -- [ ] Tagged current versions retain normal patch-bump behavior. -- [ ] An intentional higher untagged version publishes at that exact version. -- [ ] Invalid, lower, conflicting, or unsafe release states fail before push or - dispatch. - -**Verification:** - -- [ ] Focused workflow-contract tests cover both version paths. -- [ ] `cargo test --test workflow_contract` -- [ ] Atomic push, validation, and dispatch paths are reviewed manually. -- [ ] Full local proof passes. - -## Checkpoint B: release mechanism - -- [ ] Task 5 passes focused and full verification. -- [ ] Dry review proves `1.0.0` cannot become `1.0.1` accidentally. -- [ ] No partial main/tag publication path is introduced. -- [ ] Human approves preparation of the version candidate. - -## Phase 4: Prepare the candidate - -### Task 6: Prepare `v1.0.0` - -**Dependencies:** Checkpoint B - -**Estimated scope:** M; `Cargo.toml`, `Cargo.lock`, `README.md` - -**Acceptance criteria:** - -- [ ] Manifest and lockfile both declare `1.0.0`. -- [ ] README accurately states the accepted v1 support boundary. -- [ ] The candidate contains no runtime behavior change. - -**Verification:** - -- [ ] `cargo metadata --locked --no-deps` reports `1.0.0`. -- [ ] `git diff --check` -- [ ] Full local proof passes. -- [ ] PR CI passes on all four supported targets. - -## Checkpoint C: Final go/no-go - -- [ ] All acceptance criteria and the standing Definition of Done pass. -- [ ] No unresolved correctness, security, or compatibility finding remains. -- [ ] Four-target CI is green. -- [ ] Candidate contains no post-freeze feature. -- [ ] Release failure and post-publication rollback paths are understood. -- [ ] Human explicitly approves public release. - -## Phase 5: Publish and verify - -### Task 7: Publish `v1.0.0` - -**Dependencies:** Task 6 and explicit human go approval - -**Estimated scope:** M; external GitHub release state only - -**Acceptance criteria:** - -- [ ] Non-draft `v1.0.0` contains exactly four expected archives with matching - GitHub SHA-256 digests. -- [ ] Clean install and prior-version update both reach `tink 1.0.0` in isolated - temporary destinations. -- [ ] `main`, tag, manifest, release, and installed binary agree on `1.0.0`. - -**Verification:** - -- [ ] Required PR checks pass before merge. -- [ ] Release quality, audit, build, and publish jobs pass. -- [ ] Temporary clean-install smoke test passes. -- [ ] Temporary prior-version update smoke test passes. -- [ ] Git and GitHub release metadata reconcile. - -## Completion - -- [ ] Public documentation matches released behavior. -- [ ] Tink enters maintenance mode: bugs, security, compatibility, onboarding, - and documentation unless observed evidence justifies new capability. -- [ ] No temporary verification artifact or unrelated change remains. - -## Risks to re-check at every checkpoint - -- [ ] Tests do not claim control over third-party agent harnesses. -- [ ] S1 protection does not block legitimate Git reads or temporary clones. -- [ ] Release automation cannot turn `1.0.0` into `1.0.1`. -- [ ] Automatic publication remains atomic and draft-first. -- [ ] Platform CI, not local tests alone, supports the release decision. -- [ ] Public smoke tests use isolated destinations and preserve existing tools.