721ba656a3a3

pin now fails loudly with 'not implemented yet' instead of silently no-op'ing through the editFlakeLock stub and reporting fake success; unpin (which genuinely runs nix flake update) replaces pin in help/man since pin needs real flake.lock input-source resolution out of scope for a mechanical fix (NINA-008)

time
2026-07-02 13:41 UTC · 3 months ago
machine
koh2
agent
claude-sonnet-5
session
fable audit fix — commands.zig
parent
a2c2b67bd83a
5 files src/commands.zig+17 −4src/errors.zig+2 −0src/exec.zig+0 −7src/man.zig+1 −1src/output.zig+1 −5
src/commands.zig+17 −4
@@ -2007,17 +2007,30 @@20072007 20082008 // --- Pin / Unpin ---20092009 2010+// `nina pin` is not implemented — editFlakeLock (exec.zig) was a stub that2011+// silently did nothing while this printed a success line implying an input2012+// had been pinned (NINA-008). Pinning an input to a commit requires resolving2013+// that input's existing source (github/gitlab/generic git, each with its own2014+// flake-ref shape) from flake.lock before nix can override it, which is real2015+// scope beyond a mechanical fix — so this fails loudly instead of lying.20102016 pub fn pin(ctx: Ctx) !void {2011-    const input = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;2012-    const commit = if (ctx.args.len > 1) ctx.args[1] else return error.Internal;2013-    exec.editFlakeLock(ctx.gpa, input, commit) catch return error.ProcessFailed;2014-    output.pinDone(input, commit);2017+    _ = ctx;2018+    errors.error_info.setMessage("nina pin is not implemented yet", .{});2019+    errors.error_info.setSuggestion("override manually: nix flake lock --override-input <input> <flake-ref>", .{});2020+    return error.NotImplemented;20152021 }20162022 20172023 pub fn unpin(ctx: Ctx) !void {20182024     const input = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;20192025     _ = try exec.flakeUpdate(ctx.io, &ctx.machine, ctx.gpa, input, &.{});20202026     output.unpinDone(input);2027+}2028+2029+test "pin reports NotImplemented instead of a fake success" {2030+    // pin() ignores every field of ctx before returning, so an undefined2031+    // value is safe here and avoids constructing a real std.Io/machine/config.2032+    const ctx: Ctx = undefined;2033+    try std.testing.expectError(error.NotImplemented, pin(ctx));20212034 }20222035 20232036 // --- Hash / Fetch ---

view file

src/errors.zig+2 −0
@@ -19,6 +19,7 @@1919     AccessDenied,2020     SystemResources,2121     Unexpected,22+    NotImplemented,2223 };2324 2425 // ─── ErrorInfo (used for non-build errors) ───────────────────────────────────@@ -83,6 +84,7 @@8384         error.PackageNotFound => "package not found",8485         error.GenerationNotFound => "generation not found",8586         error.BuildFailed => "build failed",87+        error.NotImplemented => "not implemented yet",8688         else => "internal error",8789     };8890 }

view file

src/exec.zig+0 −7
@@ -1572,13 +1572,6 @@15721572     return r.stdout;15731573 }15741574 1575-// --- Pin ---1576-1577-pub fn editFlakeLock(_: std.mem.Allocator, _: []const u8, _: []const u8) !void {1578-    // Stub: full implementation would parse and edit flake.lock JSON1579-    // The pin/unpin flow works via nix flake update which regenerates flake.lock1580-}1581-15821575 pub fn openEditor(io: std.Io, gpa: std.mem.Allocator, editor: []const u8, path: []const u8, line: ?u32) !u8 {15831576     if (line) |l| {15841577         const line_arg = try std.fmt.allocPrint(gpa, "+{d}", .{l});

view file

src/man.zig+1 −1
@@ -185,7 +185,7 @@185185             .{ .cmd_block = "$ nina install neovim\n:: neovim  0.10.4\n\n   [i] profile install    instant  no rebuild\n   [s] system install     opens editor  requires apply\n   [t] try now            exits when done\n\n   > i\n:: installing neovim  profile\n   -> nix profile install nixpkgs#neovim\n:: neovim installed  available now" },186186             .{ .para = "explain why a package is installed:" },187187             .{ .cmd_block = "$ nina why ripgrep\n:: why ripgrep  kyoshi\n\n   ripgrep is a direct profile package" },188-            .{ .para = "pin or unpin a flake input to a specific commit. `nina unpin` runs `nix flake update` on that input to release the pin." },188+            .{ .para = "`nina unpin <input>` releases a pinned flake input by running `nix flake update` on it." },189189         },190190     },191191     .{

view file

src/output.zig+1 −5
@@ -975,7 +975,7 @@975975         .{ "option <q>", "search nixos options" },976976         .{ "pkg", "inspect packages" },977977         .{ "why <pkg>", "what pulled a package in" },978-        .{ "pin", "pin flake inputs" },978+        .{ "unpin <input>", "release a pinned flake input (nix flake update)" },979979         .{ "hash", "compute nix hash" },980980         .{ "fetch", "fetch and hash a url" },981981         .{ "goodbye", "uninstall nina" },@@ -1283,10 +1283,6 @@12831283 pub fn setupNixosNeedsRebuild(conf_path: []const u8) void {12841284     p("{s}already declared in {s}\n", .{ hdr(), conf_path });12851285     p("   just run {s}sudo nixos-rebuild switch{s} to activate it\n", .{ c(BOLD), c(RESET) });1286-}1287-1288-pub fn pinDone(input: []const u8, commit: []const u8) void {1289-    p("{s}pinned {s} -> {s}\n", .{ hdr(), input, commit });12901286 }12911287 12921288 pub fn unpinDone(input: []const u8) void {

view file