Compare commits
2 Commits
6cb2f932d8
...
de18f372d3
| Author | SHA1 | Date | |
|---|---|---|---|
| de18f372d3 | |||
| bdc0d4d5be |
@@ -66,6 +66,14 @@ the live herdr instance and check.
|
||||
`docker logs orchestra-api`. **Deploying a code change means rebuilding the
|
||||
compose images** (`up -d --build`) — the running image can silently predate
|
||||
recent commits, so compare its build time against `git log`.
|
||||
- **A coordinator deploy does not deploy the console.** They are two images
|
||||
built from the same repo, and the usual `up -d --no-deps orchestra-api`
|
||||
leaves `orchestra-web-ui` on whatever it was. This has bitten twice: the
|
||||
ethos console landed in the repo on 2026-08-29 02:44 and was still serving a
|
||||
2026-07-30 image hours later, through two coordinator deploys. Rebuild it
|
||||
explicitly from the same clean worktree (`docker build` in `web/`, then
|
||||
`up -d --no-deps orchestra-web-ui`), and check the served bundle rather than
|
||||
the commit: `curl -s http://127.0.0.1:19145/assets/<css> | grep 8F7AE5`.
|
||||
- `orchestra.service` was the **previous** deployment; the unit file was
|
||||
deleted from `deploy/` on 2026-07-31 along with `redeploy.sh` (which
|
||||
`sudo install`ed to `/usr/local/bin` and restarted it). If a stale copy is
|
||||
|
||||
@@ -58,18 +58,27 @@ func RequestHumanDecision(s *store.Store, project registry.Project, taskID strin
|
||||
return blockTask(s, t, domain.BlockReasonHumanDecision, req.Render(), &req)
|
||||
}
|
||||
|
||||
// fenceToLease binds a lifecycle event to the lease that is producing it.
|
||||
// Store.Append fences every lifecycle event on a leased task against the
|
||||
// current owner and epoch, so an event that omits them is a conflict rather
|
||||
// than a block. Every coordinator-side stop goes through here: two of them did
|
||||
// not, and both failed silently against a live lease (F65). A task stops for a
|
||||
// human only while some session is running, so the leased case is the only one
|
||||
// that ever mattered.
|
||||
func fenceToLease(payload map[string]any, t domain.Task) {
|
||||
if t.Lease == nil {
|
||||
return
|
||||
}
|
||||
payload["harness_id"] = t.Lease.HarnessID
|
||||
payload["lease_epoch"] = t.Lease.Epoch
|
||||
}
|
||||
|
||||
func blockTask(s *store.Store, t domain.Task, reason domain.BlockReason, blocker string, req *domain.DecisionRequest) (domain.Event, error) {
|
||||
payload := map[string]any{
|
||||
"blocker": blocker, "block_reason": string(reason),
|
||||
"lifecycle_phase": "awaiting_human",
|
||||
}
|
||||
if t.Lease != nil {
|
||||
// Store.Append fences every lifecycle event on a leased task against
|
||||
// the current owner and epoch. A question from a session that no
|
||||
// longer owns the task is a conflict, not a block.
|
||||
payload["harness_id"] = t.Lease.HarnessID
|
||||
payload["lease_epoch"] = t.Lease.Epoch
|
||||
}
|
||||
fenceToLease(payload, t)
|
||||
if req != nil {
|
||||
payload["decision_request"] = req
|
||||
}
|
||||
|
||||
@@ -154,11 +154,13 @@ func blockForPlanMismatch(s *store.Store, taskID string, m domain.PlanMismatch)
|
||||
packet += "- evidence: " + oneLine(e) + "\n"
|
||||
}
|
||||
packet += "\nReply to say how to proceed. Your reply becomes a recorded decision and outranks the plan. If it resolves the contradiction, the task resumes on the same plan; say so explicitly if you want the plan rewritten instead.\n"
|
||||
b, err := json.Marshal(map[string]any{
|
||||
payload := map[string]any{
|
||||
"blocker": packet,
|
||||
"block_reason": string(domain.BlockReasonPlanMismatch),
|
||||
"lifecycle_phase": "awaiting_human",
|
||||
})
|
||||
}
|
||||
fenceToLease(payload, t)
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ func planProject() registry.Project {
|
||||
func planWith(t *testing.T, markdown string) (*store.Store, registry.Project, string) {
|
||||
t.Helper()
|
||||
s, id := phaseStore(t)
|
||||
// Leased, because everything these tests drive comes from a live implement
|
||||
// session. Skipping it hid F65: two coordinator-side stops omitted the
|
||||
// fencing fields Store.Append requires on a leased task, and every test
|
||||
// passed because no test ever leased one.
|
||||
lease(t, s, id)
|
||||
project := planProject()
|
||||
if _, err := AdvanceWorkPhase(s, project, id, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -67,11 +67,13 @@ func raiseTrajectoryGate(s *store.Store, t domain.Task, from, to domain.WorkPhas
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(map[string]any{
|
||||
payload := map[string]any{
|
||||
"blocker": packet,
|
||||
"block_reason": string(domain.BlockReasonTrajectoryGate),
|
||||
"lifecycle_phase": "awaiting_human",
|
||||
})
|
||||
}
|
||||
fenceToLease(payload, t)
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user