You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The sidebar's Approve Gate button (and the gate toast, palette, and bare porch approve in a terminal) writes porch state directly. The spawning architect is never told, so its mental model of the builder goes stale: builders advance phases without the architect's knowledge, and the architect's review at the next gate starts from confusion instead of context.
All approval surfaces funnel through one place today: runPorchApprove in apps/vscode/src/commands/approve.ts shells out to porch approve <id> <gate> --a-human-explicitly-approved-this, and terminal users invoke the same CLI.
Design (v2 — post team review, decision by Amr 2026-07-18)
Treat this as an information problem, not an authority problem. Gates stay human gates; the architect is never inserted into the execution path. Emit a notification from inside porch approve immediately after the gate write succeeds, so every surface (sidebar, toast, palette, CLI, future mobile) inherits it with zero per-surface work.
Settled scope
All four gates (spec-approval, plan-approval, dev-approval, pr), uniformly. No per-gate policy. Self-adjusting: in the architect-driven path, pre-approved spec/plan frontmatter makes porch skip those phases, so notifications only fire on paths where the architect genuinely didn't know. Already-approved gates return early, so no double-fire.
Typed gate-event frame ships in v1 (decision: Amr). The notification carries machine-parseable metadata alongside the human-readable banner, typed in @cluesmith/codev-types (wire contract only). This is the first system-typed bus frame and the seed of the structured activity events web + mobile need. Draft shape (field set open for team feedback, see below):
interfaceGateEventMeta{kind: 'gate-event';gate: 'spec-approval'|'plan-approval'|'dev-approval'|'pr';action: 'approved';projectId: string;// "1191"builderId: string;// "builder-pir-1191"workspacePath: string;source?: 'sidebar'|'toast'|'cli';routedTo: string;// architect name the event was delivered tofallback?: boolean;// true when rerouted to mainartifactPath?: string;// e.g. "codev/plans/1191-....md"timestamp: string;}
Transport: HTTP via the existing shared client — tower-client.sendMessage → POST /api/send (packages/core/src/tower-client.ts), extended to carry the meta field. Forced by the typed frame: afx send --raw is plain PTY text and cannot carry metadata. This also sidesteps the one confirmed subprocess failure mode: afx send sets from = detectCurrentBuilderId() ?? 'architect' (send.ts:293), so a fallback-to-main notification sent from a builder-worktree cwd would be builder-classified and rejected by the spoofing check (verified; see comment thread). The HTTP call uses an explicit system/porch sender type, so the builder spoofing check is never implicated and consumers can key behavior off sender type rather than parsing a banner.
Routing: the actual spawning architect, not main.spawned_by_architect from global.db (the Multi-architect routing: builder→architect messages misrouted to 'main' when worktree has its own state.db #774 affinity data), read-only lookup. Fallback to main only when the column is NULL (shrinking legacy concern) or the named architect is no longer registered — stated in the message body AND logged, never silently dropped. Drop-with-log only when no architect is registered at all.
Strictly best-effort: short timeout; Tower down or send failure prints a non-fatal note and never blocks or fails the approval.
Human-readable banner stays one glanceable block, e.g.:
### [PORCH GATE EVENT | builder-pir-1191] ###
plan-approval APPROVED by human (sidebar). Builder advancing to implement.
Artifact: codev/plans/1191-recently-closed-sort.md
###
porch/notify.ts doc-comment MUST be rewritten in the same PR. It currently documents the deliberate removal of architect-bound gate notifications ("architect can't act on a gate, therefore noise"). This change reverses that decision with a new rationale (awareness for the next gate, not actionability); the comment must encode the reversal or a future maintainer re-removes the feature citing the old text.
Independence from the builder wake-up: the existing builder wake-up in approve() is suppressed when approve runs from inside the builder worktree (calledFromBuilderWorktree guard). The architect notification targets a different terminal and must fire regardless — the two calls stay independent.
Architect-side convention (role-doc addition): fire-and-forget awareness, not a request. Read it, update your notebook and expectations, do not reply, do not touch porch state. Post-hoc concerns about the approved artifact go to the human or the issue. One rule across all four gates, keyed off the system sender type.
Explicitly out of scope (deferred, unanimous)
Gate rejections: no funnel exists (rejections never call porch approve; the human types feedback straight to the builder). Separate follow-up issue to be filed so the gap stays visible.
"Request architect review" companion affordance: a different, interactive feature with exactly the latency/dead-terminal coupling this proposal avoids. Demand-gated follow-up.
Alternative considered and rejected: mediated approval
Routing the button through the spawning architect (button sends a request; architect runs porch approve) was rejected: approval latency tied to architect responsiveness, stalls on dead/busy architect terminals, a new dropped-message failure mode, no path for CLI-only workspaces, and it converts a decision the human already made into something an AI can second-guess.
Remaining questions for team feedback (before spawning)
Frame schema: is the GateEventMeta field set above right? Missing fields (e.g. phase the builder advances to, issue number, protocol)? Should action anticipate values beyond 'approved' now, or stay minimal until a reject verb exists?
Envelope placement: does meta belong on the MessageFrame itself (every message can optionally carry typed metadata — the general bus pattern) or as a gate-event-specific field on the /api/send body? MessageFrame-level seems right for the activity-feed trajectory, but it touches the shared type.
Sender type naming: 'system' vs 'porch' as the sender type for spoofing-exemption and role-doc keying. Precedent-setting for future system emitters.
--source attribution: worth plumbing a --source sidebar|toast|cli flag through runPorchApprove, or derive nothing and drop the field from v1?
packages/core/src/tower-client.ts + Tower /api/send route: carry + route meta, system sender type
Architect role doc: the no-reply handling convention
apps/vscode/src/commands/approve.ts: optional --source flag only; no behavior change
History
Original body proposed the same funnel-at-porch approve approach with transport and scope as open questions; see the comment thread for the three architect reviews (mobile, demos, reviewer), the synthesis, and the verified send.ts:293 sender-classification correction that settled transport.
Problem
The sidebar's Approve Gate button (and the gate toast, palette, and bare
porch approvein a terminal) writes porch state directly. The spawning architect is never told, so its mental model of the builder goes stale: builders advance phases without the architect's knowledge, and the architect's review at the next gate starts from confusion instead of context.All approval surfaces funnel through one place today:
runPorchApproveinapps/vscode/src/commands/approve.tsshells out toporch approve <id> <gate> --a-human-explicitly-approved-this, and terminal users invoke the same CLI.Design (v2 — post team review, decision by Amr 2026-07-18)
Treat this as an information problem, not an authority problem. Gates stay human gates; the architect is never inserted into the execution path. Emit a notification from inside
porch approveimmediately after the gate write succeeds, so every surface (sidebar, toast, palette, CLI, future mobile) inherits it with zero per-surface work.Settled scope
All four gates (spec-approval, plan-approval, dev-approval, pr), uniformly. No per-gate policy. Self-adjusting: in the architect-driven path, pre-approved spec/plan frontmatter makes porch skip those phases, so notifications only fire on paths where the architect genuinely didn't know. Already-approved gates return early, so no double-fire.
Typed gate-event frame ships in v1 (decision: Amr). The notification carries machine-parseable metadata alongside the human-readable banner, typed in
@cluesmith/codev-types(wire contract only). This is the first system-typed bus frame and the seed of the structured activity events web + mobile need. Draft shape (field set open for team feedback, see below):Transport: HTTP via the existing shared client —
tower-client.sendMessage→POST /api/send(packages/core/src/tower-client.ts), extended to carry themetafield. Forced by the typed frame:afx send --rawis plain PTY text and cannot carry metadata. This also sidesteps the one confirmed subprocess failure mode:afx sendsetsfrom = detectCurrentBuilderId() ?? 'architect'(send.ts:293), so a fallback-to-main notification sent from a builder-worktree cwd would be builder-classified and rejected by the spoofing check (verified; see comment thread). The HTTP call uses an explicit system/porch sender type, so the builder spoofing check is never implicated and consumers can key behavior off sender type rather than parsing a banner.Routing: the actual spawning architect, not main.
spawned_by_architectfrom global.db (the Multi-architect routing: builder→architect messages misrouted to 'main' when worktree has its own state.db #774 affinity data), read-only lookup. Fallback tomainonly when the column is NULL (shrinking legacy concern) or the named architect is no longer registered — stated in the message body AND logged, never silently dropped. Drop-with-log only when no architect is registered at all.Strictly best-effort: short timeout; Tower down or send failure prints a non-fatal note and never blocks or fails the approval.
Human-readable banner stays one glanceable block, e.g.:
porch/notify.tsdoc-comment MUST be rewritten in the same PR. It currently documents the deliberate removal of architect-bound gate notifications ("architect can't act on a gate, therefore noise"). This change reverses that decision with a new rationale (awareness for the next gate, not actionability); the comment must encode the reversal or a future maintainer re-removes the feature citing the old text.Independence from the builder wake-up: the existing builder wake-up in
approve()is suppressed when approve runs from inside the builder worktree (calledFromBuilderWorktreeguard). The architect notification targets a different terminal and must fire regardless — the two calls stay independent.Architect-side convention (role-doc addition): fire-and-forget awareness, not a request. Read it, update your notebook and expectations, do not reply, do not touch porch state. Post-hoc concerns about the approved artifact go to the human or the issue. One rule across all four gates, keyed off the system sender type.
Explicitly out of scope (deferred, unanimous)
porch approve; the human types feedback straight to the builder). Separate follow-up issue to be filed so the gap stays visible.Alternative considered and rejected: mediated approval
Routing the button through the spawning architect (button sends a request; architect runs
porch approve) was rejected: approval latency tied to architect responsiveness, stalls on dead/busy architect terminals, a new dropped-message failure mode, no path for CLI-only workspaces, and it converts a decision the human already made into something an AI can second-guess.Remaining questions for team feedback (before spawning)
GateEventMetafield set above right? Missing fields (e.g.phasethe builder advances to, issue number, protocol)? Shouldactionanticipate values beyond'approved'now, or stay minimal until a reject verb exists?metabelong on the MessageFrame itself (every message can optionally carry typed metadata — the general bus pattern) or as a gate-event-specific field on the/api/sendbody? MessageFrame-level seems right for the activity-feed trajectory, but it touches the shared type.'system'vs'porch'as the sender type for spoofing-exemption and role-doc keying. Precedent-setting for future system emitters.--sourceattribution: worth plumbing a--source sidebar|toast|cliflag throughrunPorchApprove, or derive nothing and drop the field from v1?Touch points
packages/codev/src/commands/porch/(approve path): emit after successful gate write; rewritenotify.tsheader commentpackages/types:GateEventMeta(wire contract)packages/core/src/tower-client.ts+ Tower/api/sendroute: carry + routemeta, system sender typeapps/vscode/src/commands/approve.ts: optional--sourceflag only; no behavior changeHistory
Original body proposed the same funnel-at-
porch approveapproach with transport and scope as open questions; see the comment thread for the three architect reviews (mobile, demos, reviewer), the synthesis, and the verifiedsend.ts:293sender-classification correction that settled transport.