carson 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96b7009510f859ab0892bffc34678701dc74412a4f4b9f17f1be3710cb82d0c3
4
- data.tar.gz: 236de5bb54d69845b4710b8ffe946a64534a1db5ea068e2a8e34d566d6c438c4
3
+ metadata.gz: b65ac7c43c1275299430d71f23f1da3a9896b112aa1f57e334222d06282a3a62
4
+ data.tar.gz: ff4efcd085b8cb0deb4385bfdb40d98407d463547b6e065fbfc455f516cc4dbb
5
5
  SHA512:
6
- metadata.gz: 334f80f636c8285fbb7e3bb4481b99efecf0b69700b337ad03a63cc1b9fb0fe0d1ab49765371aa57ede881958cb0d2d9f9cff9104b50d007352edd94fb1cd22e
7
- data.tar.gz: b6a92f602a9d0fdd9b22ba320616c6fe2db66d51230895b88e32962edaa5c43cfcadfe828278afbeb4111585b10d04781708c2d17dcdb03df10320fa1674e6c9
6
+ metadata.gz: b49a0b8760ad230996f6330b378fd7f9a202977c148683c4ae3eec066f2b820f6210e59a0e251ef275471d90db0d8154dee82cc5f97c3e46378cfad1fce074c2
7
+ data.tar.gz: 64cc150e7bb257ebbd6d48a603ab7eaa3e3b9794f08fcc65625bb8264628cc99e14f5603fe526d7818253ac57169de995cdc71c413c75cc6a3e2b56dbbb41f2c
data/RELEASE.md CHANGED
@@ -10,6 +10,19 @@ Release-note scope rule:
10
10
  ### Breaking
11
11
 
12
12
  - CLI grammar is now two-tier: portfolio commands (`list`, `onboard`, `offboard`, `refresh`, `version`) and repo-scoped commands (`carson <repo> <command>` or `carson <command>` from CWD)
13
+
14
+ ## 4.0.1
15
+
16
+ ### What changed
17
+
18
+ - **Workbench seal** — `carson deliver` seals the workbench after shipping. `pack!` refuses while sealed ("Shelf is sealed — parcel in flight"). `carson audit` blocks `git commit` on sealed workbenches. Delivered/held/rejected unseals; filed stays sealed.
19
+ - **Delivery progress** — `carson deliver` announces what it's doing: "Carson is delivering committed changes on branch X to Y…" and reports each poll check with count.
20
+ - **Seal guard in `carson audit`** — moved from bash hook template to Ruby. The pre-commit hook calls `carson audit` which checks for `.carson-delivering` marker.
21
+ - **`error_at_registry` is transient** — CI assessment errors no longer cause immediate rejection. The courier keeps polling.
22
+
23
+ ### Known limitation
24
+
25
+ - The workbench seal blocks `git commit` but does not block file edits (Write/Edit tools). That requires Claude Code PreToolUse hooks — a separate enforcement layer.
13
26
  - `govern` renamed to `receive` — single-repo only, no portfolio iteration
14
27
  - `repos` renamed to `list`
15
28
  - `--all` removed from all repo commands; use `carson list --json` to script batch operations
@@ -19,15 +32,14 @@ Release-note scope rule:
19
32
 
20
33
  ### Breaking
21
34
 
22
- - **Courier waits at the registry** — `carson deliver` now polls the bureau up to 6 times (default 30s interval) instead of checking once and leaving. PRs that pass CI are merged automatically without re-running `carson deliver`. Agents that relied on instant return from `carson deliver` should expect it to block for up to 3 minutes.
35
+ - **`carson deliver` waits for CI and merges automatically** — `carson deliver` now polls GitHub up to 6 times (default 30s interval) instead of checking once and returning immediately. PRs that pass CI are merged without re-running `carson deliver`. Agents that relied on instant return should expect it to block for up to 3 minutes.
23
36
  - **Hold reasons renamed** — JSON output field `hold_reason` values changed: `inspector_pending` → `pending_at_registry`, `inspector_failed` → `failed_at_registry`, `inspector_error` → `error_at_registry`. Agents parsing these values must update.
24
- - **Hold messages include recovery commands** — Human output for held deliveries now shows actionable recovery steps (e.g. `→ git rebase origin/main` then `→ carson deliver`) instead of prose descriptions.
25
- - **Bureau model simplified** — No more "customs" or "inspector" in the domain model. The bureau is a registry where bureaucrats check parcels. Internal only — no user-facing impact beyond the hold_reason rename above.
37
+ - **Hold messages include recovery commands** — Human output for held deliveries now shows actionable next steps (e.g. `→ git rebase origin/main` then `→ carson deliver`) instead of prose descriptions.
26
38
 
27
39
  ### New
28
40
 
29
- - **`deliver.poll_interval_at_registry` config** — Controls how long the courier waits between registry checks (default 30 seconds). Override via config or `CARSON_POLL_INTERVAL_AT_REGISTRY` environment variable.
30
- - **`filed` outcome** — When the courier exhausts all checks without a definitive answer, it reports "filed" with `→ carson status` as the next step, instead of the old "deferred" with `→ carson deliver`.
41
+ - **`deliver.poll_interval_at_registry` config** — Controls how long `carson deliver` waits between CI checks (default 30 seconds). Override via config or `CARSON_POLL_INTERVAL_AT_REGISTRY` environment variable.
42
+ - **`filed` outcome** — When `carson deliver` exhausts all checks without a definitive answer from GitHub, it reports "filed" with `→ carson status` as the next step, instead of the old "deferred" with `→ carson deliver`.
31
43
 
32
44
  ### Migration
33
45
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.0.0
1
+ 4.0.1
@@ -15,6 +15,14 @@ module Carson
15
15
  # parcels (CI, review, mergeability) and either accept them into the
16
16
  # registry or hold them with a reason.
17
17
  #
18
+ # == Shelf seal
19
+ #
20
+ # Once the parcel ships and the waybill is filed, the warehouse seals
21
+ # the shelf. No more packing until the delivery outcome is confirmed.
22
+ # Delivered → shelf done (housekeep removes it).
23
+ # Held/rejected → courier unseals (agent can fix and re-deliver).
24
+ # Filed (checks exhausted) → shelf stays sealed (parcel still in flight).
25
+ #
18
26
  # == Situations the courier encounters
19
27
  #
20
28
  # Each numbered situation is handled by a specific guard or branch in the
@@ -45,7 +53,8 @@ module Carson
45
53
  # It polls up to MAX_CHECKS_AT_REGISTRY times, pausing between each check.
46
54
  # If the bureaucrats give a definitive answer (accepted or rejected), the
47
55
  # courier acts immediately. If the checks are exhausted without a definitive
48
- # answer, the courier reports "filed" — the parcel is still at the registry.
56
+ # answer, the courier reports "filed" — the parcel is still at the registry
57
+ # and the shelf stays sealed.
49
58
  #
50
59
  # == Future: destination modes
51
60
  #
@@ -58,18 +67,21 @@ module Carson
58
67
  ERROR = 1
59
68
  BLOCKED = 2
60
69
 
70
+ BADGE = "\u29D3".freeze
71
+
61
72
  # The courier checks the registry up to 6 times before leaving.
62
73
  MAX_CHECKS_AT_REGISTRY = 6
63
74
 
64
- def initialize( warehouse, ledger: nil, merge_method: "rebase", poll_interval_at_registry: 30 )
75
+ def initialize( warehouse, ledger: nil, merge_method: "rebase", poll_interval_at_registry: 30, output: $stdout )
65
76
  @warehouse = warehouse
66
77
  @ledger = ledger
67
78
  @merge_method = merge_method
68
79
  @poll_interval_at_registry = poll_interval_at_registry
80
+ @output = output
69
81
  end
70
82
 
71
83
  # Deliver a parcel to the registry.
72
- # Ships it, files a waybill, waits at the registry for the bureaucrats.
84
+ # Ships it, files a waybill, seals the shelf, waits at the registry.
73
85
  def deliver( parcel, title: nil, body_file: nil, commit_message: nil )
74
86
  result = {
75
87
  command: "deliver",
@@ -121,6 +133,9 @@ module Carson
121
133
  recovery: "git rebase #{remote_main}, then carson deliver" )
122
134
  end
123
135
 
136
+ # Announce the delivery.
137
+ say "Carson is delivering committed changes on branch #{parcel.label} to #{result[ :remote_main ]}..."
138
+
124
139
  # The courier picks up the parcel — start tracking.
125
140
  record( parcel, status: "preparing", summary: "delivery accepted" )
126
141
 
@@ -144,11 +159,20 @@ module Carson
144
159
  result[ :tracking_number ] = waybill.tracking_number
145
160
  result[ :url ] = waybill.url
146
161
 
162
+ # Seal the shelf — no more packing until the outcome is confirmed.
163
+ @warehouse.seal_shelf!( tracking_number: waybill.tracking_number )
164
+
147
165
  # Wait at the registry while the bureaucrats check the parcel.
148
166
  wait_and_poll_at_registry( waybill, result )
149
167
 
168
+ # Unseal based on outcome:
169
+ # delivered/held/rejected → unseal (shelf done or parcel returned)
170
+ # filed → stay sealed (parcel still in flight)
171
+ outcome = result[ :outcome ]
172
+ @warehouse.unseal_shelf! if outcome == "delivered" || outcome == "held" || outcome == "rejected"
173
+
150
174
  # Update the ledger with the final outcome.
151
- record( parcel, status: result[ :outcome ] || "filed", summary: result[ :hold_reason ] )
175
+ record( parcel, status: outcome || "filed", summary: result[ :hold_reason ] )
152
176
 
153
177
  result[ :exit ] ||= OK
154
178
  result
@@ -196,6 +220,9 @@ module Carson
196
220
  return
197
221
  end
198
222
 
223
+ # Report progress — the courier tells what the bureaucrats said.
224
+ say "#{waybill.hold_summary} (#{check + 1}/#{MAX_CHECKS_AT_REGISTRY})..."
225
+
199
226
  # Still waiting — pause before the next check.
200
227
  pause_between_polls unless check == MAX_CHECKS_AT_REGISTRY - 1
201
228
  end
@@ -215,6 +242,11 @@ module Carson
215
242
  "behind_registry", "policy_block", "draft" ].include?( reason )
216
243
  end
217
244
 
245
+ # The courier speaks — reports progress to whoever is listening.
246
+ def say( message )
247
+ @output&.puts "#{BADGE} #{message}"
248
+ end
249
+
218
250
  # Pause between poll checks. Overridable for test isolation.
219
251
  def pause_between_polls
220
252
  sleep @poll_interval_at_registry
@@ -7,6 +7,12 @@ module Carson
7
7
  class Runtime
8
8
  module Audit
9
9
  def audit!( json_output: false )
10
+ # Sealed workbench guard — hard block before anything else.
11
+ # The warehouse seals the workbench when a parcel ships.
12
+ # No commits allowed until the delivery outcome is confirmed.
13
+ sealed_result = audit_sealed_workbench( json_output: json_output )
14
+ return sealed_result unless sealed_result.nil?
15
+
10
16
  fingerprint_status = block_if_outsider_fingerprints!
11
17
  return fingerprint_status unless fingerprint_status.nil?
12
18
  unless head_exists?
@@ -177,6 +183,30 @@ module Carson
177
183
  end
178
184
  end
179
185
 
186
+ # Check if the workbench is sealed (parcel in flight).
187
+ # Returns EXIT_BLOCK if sealed, nil otherwise.
188
+ def audit_sealed_workbench( json_output: )
189
+ marker_path = File.join( work_dir, ".carson-delivering" )
190
+ return nil unless File.exist?( marker_path )
191
+
192
+ tracking_number = File.read( marker_path ).strip rescue "unknown"
193
+ if json_output
194
+ require "json"
195
+ output.puts JSON.pretty_generate( {
196
+ command: "audit",
197
+ status: "block",
198
+ reason: "workbench_sealed",
199
+ tracking_number: tracking_number,
200
+ recovery: "carson worktree create <name>",
201
+ exit_code: EXIT_BLOCK
202
+ } )
203
+ else
204
+ puts_line "Workbench is sealed — parcel in flight (PR ##{tracking_number})."
205
+ puts_line " \u2192 carson worktree create <name>"
206
+ end
207
+ EXIT_BLOCK
208
+ end
209
+
180
210
  def audit_working_tree_report
181
211
  dirty_reason = dirty_worktree_reason
182
212
  return { dirty: false, context: nil, status: "ok" } if dirty_reason.nil?
@@ -22,7 +22,8 @@ module Carson
22
22
  courier = Courier.new( warehouse,
23
23
  ledger: ledger,
24
24
  merge_method: config.govern_merge_method,
25
- poll_interval_at_registry: config.poll_interval_at_registry
25
+ poll_interval_at_registry: config.poll_interval_at_registry,
26
+ output: output
26
27
  )
27
28
 
28
29
  result = courier.deliver( parcel,
@@ -91,13 +91,43 @@ module Carson
91
91
  end
92
92
 
93
93
  # Pack a parcel — stage all changes and commit.
94
+ # Refuses if the shelf is sealed (parcel already in flight).
94
95
  # Returns true on success, false on failure.
95
96
  def pack!( message: )
97
+ if sealed?
98
+ raise "Shelf is sealed — parcel in flight (PR ##{sealed_tracking_number}). " \
99
+ "Create a new worktree to continue working."
100
+ end
96
101
  git( "add", "-A" )
97
102
  _, _, status = git( "commit", "-m", message )
98
103
  status.success?
99
104
  end
100
105
 
106
+ # --- Shelf seal ---
107
+
108
+ # Seal the shelf — no more packing until the delivery outcome is confirmed.
109
+ # The courier seals the shelf after shipping and filing the waybill.
110
+ def seal_shelf!( tracking_number: )
111
+ File.write( delivering_marker_path, tracking_number.to_s )
112
+ end
113
+
114
+ # Unseal the shelf — the courier brought back the parcel.
115
+ # Called when the delivery outcome is held or rejected.
116
+ def unseal_shelf!
117
+ File.delete( delivering_marker_path ) if File.exist?( delivering_marker_path )
118
+ end
119
+
120
+ # Is this shelf sealed for a delivery in flight?
121
+ def sealed?
122
+ File.exist?( delivering_marker_path )
123
+ end
124
+
125
+ # The tracking number of the in-flight delivery (nil if not sealed).
126
+ def sealed_tracking_number
127
+ return nil unless sealed?
128
+ File.read( delivering_marker_path ).strip
129
+ end
130
+
101
131
  # Receive the latest standard from the registry after a parcel is accepted.
102
132
  # Fast-forwards local main without switching branches.
103
133
  # Returns true on success, false on failure.
@@ -142,6 +172,11 @@ module Carson
142
172
 
143
173
  private
144
174
 
175
+ # Path to the delivery marker file — signals the shelf is sealed.
176
+ def delivering_marker_path
177
+ File.join( path, ".carson-delivering" )
178
+ end
179
+
145
180
  # All git commands go through this single gateway.
146
181
  # Returns [stdout, stderr, status].
147
182
  def git( *arguments )
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carson
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang