Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ARCHITECTURE.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"id": "RENT_HUMAN_CONSENT_GATE",
"state": "pos-conv",
"value": "a GPU-pod rent (`hexa cloud rent|up`) is a real-money action gated on a fresh HUMAN consent token (`!sidecar sign rent`) minted only via the TUI bang — the agent's own `sidecar sign` mint and any write into ~/.sidecar/signs/ are code-blocked (SIGN-SELF-MINT/SIGN-FORGE); one-shot, consumed on admission, TTL/one-shot are code constants (no config opt-out). Gate rests on 'the agent cannot emit a `!` bang', not on the text parser being airtight",
"threshold": "renting a pod costs money but nothing forced a human in the loop — the agent could `hexa cloud rent` unilaterally; ports the retired hexa `_sign_guard.hexa` consent gate (old `! sidecar sign commons` pattern). Indirect rents (bash -c/eval/ssh/pool wrapper) are blocked 'run it directly'; structural closure (hexa itself requiring the token) is a documented phase-2",
"threshold": "renting a pod costs money but nothing forced a human in the loop — the agent could `hexa cloud rent` unilaterally; ports the retired hexa `_sign_guard.hexa` consent gate (old `! sidecar sign commons` pattern). Indirect rents (bash -c/eval/ssh/pool wrapper) are blocked 'run it directly'; structural closure (hexa itself requiring the token) is a documented phase-2. FALSE-POSITIVE fix: detectSelfMint read the token after `sidecar sign` as a mint key even when it was a shell redirect (`sidecar sign 2>/dev/null` → rest[1]=`2>` since segments() splits on unquoted `&`), false-blocking a bare list+redirect — a mint key must now match /^[A-Za-z][\\w-]*/ so operators/empty fall through to ALLOW",
"source": "modules/sign.ts"
},
{
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.jsonl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{"ts":"2026-07-24","title":"fix(sign): detectSelfMint false-positive — redirect token after 'sidecar sign' misread as mint key","body":"Fix a false-positive in the sign self-mint guard (detectSelfMint): a shell redirect token after 'sidecar sign' was misread as a mint key, so a routine 'sidecar sign 2>/dev/null' / 'sidecar sign >out' (bare list with a redirect) was wrongly BLOCKED as SIGN-SELF-MINT (caught live on a diagnostic 'sidecar sign 2>&1'). Root: segments() splits on unquoted & so '2>&1' left rest[1]='2>', and any rest[1] not in the read-verb allowlist was treated as a mint key. Fix: a mint key must match /^[A-Za-z][\\w-]*/ (a real word like rent/commons), so redirects/operators/empty fall through to ALLOW (bare list). Real mints ('sidecar sign rent', 'sudo sidecar sign rent') still block; 'sidecar sign check rent 2>&1' still allows (rest[1]=check). Follow-up to #432."}
{"ts":"2026-07-24","title":"feat(sign): GPU-pod-rent consent gate — !sidecar sign rent (human-only one-shot token)","body":"New sidecar sign command + GPU-pod-rent consent gate: renting a pod (hexa cloud rent/up) is a real-money action, so pre bash now BLOCKS it (CLOUD-RENT-UNSIGNED) until the HUMAN mints a one-shot consent token via the TUI bang '!sidecar sign rent' — ported from the retired hexa _sign_guard.hexa (old '! sidecar sign commons' pattern). modules/sign.ts: host-global plain-epoch token store ~/.sidecar/signs/<key>.sign, TTL rent=600s, one-shot (consumed atomically on admission via rename-then-unlink; pre-consume stops parallel double-spend); CLI verbs list/mint/check/clear; helpers signFresh/consumeSign/detectSelfMint/signForgeBashTarget/isSignsPath. cloud-guard.ts: detectCloudRent (direct head-match gated; indirect bash-c/eval/ssh/pool wrapper blocked); fixed pre-existing segment-local weakness — isHexaBuiltin exempted the WHOLE command string so 'hexa cloud pods; raw-create' leaked; now sanctioned per-SEGMENT. pre.ts: SIGN-SELF-MINT + SIGN-FORGE(bash) in preBash, rent gate LAST (never burns the one-shot token), SIGN-FORGE(write) in preWrite. TTL/one-shot are CODE constants (no config opt-out). No new hook wiring: both surfaces already route bash and write. Docs: commands/sign.md, templates/sign.md. Skipped as over-eng (sol): repo-scope hash, JSON/nonce token, MCP pre-tool deny — phase-2. Smoke: unsigned/self-mint/forge block; mint->rent allow+consume->re-block; echo/grep allow; indirect block; seg-local block; tsc+lint+ci green."}
{"ts":"2026-07-19","title":"commons: add no-bare-jargon slug","body":"User-facing prose must gloss every jargon/acronym/hyphenated/backtick term at first use (plain-word swap or term(=plain meaning)); elevates the easy-style advisory into an always-on governance hard rule. Also trimmed the lab ARCHITECTURE node under the 300-char cell cap."}
{"ts":"2026-07-18","title":"fix(lab): guard child.stdin null (TS18047) — origin/main tsc RED 복구","body":"child.stdin 은 spawn 타입상 Writable|null 인데 널가드 없이 .write/.end 호출 → strict tsc TS18047(2곳: fable·sol 백엔드). origin/main 이 이 상태로 머지돼 sidecar 전체 ci(npx tsc --noEmit)가 RED. optional chaining(child.stdin?.write/?.end)으로 가드 — stdin 이 null 이면 spawn 실패라 error 핸들러가 이미 settle(127) 처리하므로 안전. tsc exit 0 복구."}
Expand Down Expand Up @@ -27,4 +28,3 @@
{"ts":"2026-07-08","title":"feat(frontier): single north-star objective command — 지정/진행/교체/해제 on a dedicated frontier git ref","body":"신규 sidecar frontier 명령 — 세션/repo 의 단일 north-star 목표(최전선) 추적기. ing(다건 진행보드)과 달리 SINGLE-SLOT. 저장은 ing ref 패턴 미러: 전용 refs/heads/frontier 에 FRONTIER.jsonl(1줄) plumbing(hash-object→mktree→commit-tree→update-ref)+best-effort push origin frontier → branch-switch/reset 무관·공유·protected-main-safe. verb: set(지정·기존 있으면 clobber 거부→swap 유도) · go(진행·push-now 지시 출력+선택 인자를 진행노트 append) · swap(교체·기존 은퇴 id 증가→CHANGELOG+신규 지정, 없으면 set) · clear(해제) · show · inject. 한글 별칭(지정/진행/교체/해제)+designate/advance/replace/release 허용. 셸 특수문자 텍스트는 --stdin. inject 는 SessionStart+per-turn 로 최전선 표면화(미지정=0B 침묵 · git-context/worktree 선례 · advisory only, Stop 강제게이트 없음). 배선 wire-to-prod 양쪽: setup.ts(UserPromptSubmit/SessionStart/PreCompact/PostCompact)+hooks/hooks.json(UserPromptSubmit/SessionStart)+pi/sidecar.ts PER_TURN + pi/CLAUDE.md 매핑 lockstep. 등록 cli/index.ts import+case+HELP · commands/frontier.md 델리게이터 · toolkit write(82→83) · ARCHITECTURE module-frontier 노드. QA 14케이스 ALL PASS(show empty·go-unset warn·set·clobber refuse·go+note·swap id증가·inject set/unset(0B)·한글별칭 지정/진행/해제·usage) · help 파싱 스모크 green."}
{"ts":"2026-07-08","title":"feat(worktree+ing): ING↔stranded task linkage (#7) + worktree stop-check last-Stop nudge (#5)","body":"stranded-work 가시성 마무리 2건(Fable 랭킹 #5·#7). (#7 ING↔stranded 링크) ing Item 에 branch? 필드 추가 + ing add/next 가 현재 branch(BOARD_ROOT 기준·≠main/master/HEAD)를 스탬프 → stranded 워크트리/브랜치를 그 태스크로 역링크. 신규 export findIngForBranch(branch,items): Tier1=branch 정확 스탬프 매치(최신), Tier2=무스탬프 구엔트리용 보수적 fallback(전체 branch명 리터럴이 text 에 포함 AND 이름이 distinctive(len≥8||\"/\") AND 유일매치일 때만), 모호/무매치→null(잘못된 매치가 no-match보다 해로우므로 FP-averse). worktree inject + sidecar end 6/6b 의 각 stranded 라인에 ` — 🔄 ING#<id>: <text>` 인라인(readItems 1콜·stranded 있을 때만·clean=0B). (#5 Stop-time 넛지) 신규 `sidecar worktree stop-check`(warn-only·never block): prompt-scan(다음 프롬프트)·worktree inject(다음 세션)·shipStopCheck(미커밋만)이 모두 못 보는 유일 창=세션 마지막 Stop 에 worktree 에 committed-but-unpushed 남기고 터미널 닫는 경우를 warm-context 에서 상기. stop_hook_active anti-wedge + 0-linked fast-exit(classify per-wt git콜 회피) + git-common-dir keyed dedup(branch@tip: 같은 tip 침묵·새 커밋 재경고·해소된 키 prune) 로 긴 세션서 매 Stop 잔소리 방지. 배선 setup.ts Stop[] + hooks/hooks.json Stop 양쪽(setup-ts-1) + install-hooks --global. Stop 훅은 CC 전용이라 CC만 배선(warn-only → wire-to-prod 위반 아님). help/toolkit/ARCHITECTURE(surface-stop·detail-worktree·module-ing) lockstep. QA: findIngForBranch 5케이스(Tier1/Tier2/short-blocked/no-match/empty) PASS · ing add worktree 내 branch 스탬프 확인 · worktree inject 가 `🔄 ING#N` 링크 표시 · stop-check WARN→dedup 침묵→anti-wedge 침묵→new tip 재경고 전부 출력검증 · ci/help/architecture lint green."}
{"ts":"2026-07-08","title":"feat(load+end): active stranded-work gauges — wt N(M⚠)🔴 in load line (TTL-cached) + end classify() upgrade + stranded-branch check","body":"stranded-work 가시성 후속 2건(Fable 랭킹 #4·#6). (#4) load 라인 wt 게이지가 stranded 워크트리를 능동 표시: `wt N(M⚠)` + M>0면 무조건 🔴(개수 3/10 임계 무시) — 매 턴 항상 보이는 표면이라 방치작업이 즉시 눈에 띔. classify()는 워크트리당 ~4 git콜이라 매 UserPromptSubmit sync 핫패스엔 과함 → readOpenPRs 와 동일한 git-common-dir TTL캐시(60s) sync 헬퍼 readStrandedWt(포slain 재사용·stranded=비-main 워크트리 dirty|unpushed 정의 동일), cold 턴만 git콜 지불. (#6) `sidecar end` 세션종료 점검을 classify() 로 업그레이드: 방치(⚠ dirty/unpushed)와 정리대기(○ clean merged)를 구분(기존 raw count는 구분 불가), stranded 워크트리는 warn++ 하고 항목별 resume/폐기 명령 출력 + 신규 6b 항목으로 워크트리없는 stranded 로컬브랜치(strandedBranches 재사용 — 소유자의 'stale 브랜치' 케이스)도 flag. 둘 다 기존 워크트리 감지(classify/strandedWorktrees/strandedBranches) 재사용, 발명 0. help 무변경(내부 게이지·기존 verb). ARCHITECTURE detail-3(wt 게이지 stranded 🔴) lockstep. QA: end 가 라이브 stranded 브랜치(ing) flag·load 가 임시 stranded 워크트리에 `wt 1(1⚠)🔴`·clean시 기존 표시 유지·ci green."}
{"ts":"2026-07-08","title":"feat(worktree+prompt-scan): surface stranded worktrees/branches at session-start & first-action (stderr→emitInject bugfix + worktree inject verb)","body":"세션이 stale 브랜치/워크트리에 방치되는 걸 세션 시작·첫 행동 시점에 잡는다. Fable investigate-mode 조사로 근본원인 확정: 신호는 이미 계산되는데 stderr로 나가 에이전트 context에 안 닿음(배선 결함, detection 결함 아님). (A) prompt-scan.ts 버그픽스: strandedWorktrees() 블록을 stderr→emitInject(stdout additionalContext, Claude Code 프로토콜)로 라우팅 — 매 UserPromptSubmit(첫 행동+지속) 노출, clean이면 0바이트(self-limiting). 헤더 주석은 \"additionalContext로 주입\"이라 주장했는데 실제 stderr였던 문서-코드 불일치를 정정. keyword/hint는 per-turn bloat 회피 위해 stderr 유지. (B) 신규 `sidecar worktree inject` verb(SessionStart+PreCompact+PostCompact WARN·gc 직후): strandedWorktrees()+신규 strandedBranches()(워크트리없는 stranded 로컬브랜치 — classify() 사각지대, 라이브 `ing` 브랜치가 증거)+refs/reaped tip 카운트를 이전세션 이월 방치작업으로 항목별 resume/보존/폐기 명령과 함께 surface, clean이면 0바이트. config worktree.branchAllow 로 의도적 장수브랜치 예외. WARN(SessionStart는 block 불가·FP-averse·persistence가 teeth). 배선: setup.ts SessionStart/Pre/PostCompact[] + hooks/hooks.json SessionStart 양쪽(setup-ts-1) + install-hooks --global + grep 검증. classify()는 isAgent뿐 아니라 모든 linked worktree 열거라 WORKTREE_GONE_ONLY_PILEUP 의 isAgent-only 사각지대 미반복(단 cross-repo는 여전히 repo-local·별개과제). help/toolkit/ARCHITECTURE(detail-worktree·surface-precompact) lockstep. QA: prompt-scan stranded-wt→stdout inject·worktree inject 라이브 ing 브랜치+임시 stranded wt 둘 다 emit·clean=0바이트·ci/help/architecture lint green."}
9 changes: 7 additions & 2 deletions modules/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,13 @@ export function detectSelfMint(rawCmd: string): string | null {
rest = rest.slice(1);
}
const base = head.slice(head.lastIndexOf("/") + 1); // ./bin/sidecar, abs paths → sidecar
if (base === "sidecar" && rest[0] === "sign" && !MINT_ALLOWED.has(rest[1] ?? "")) {
return `sidecar sign ${rest[1]}`;
// A mint key is a real WORD token (`rent`, `commons`), not a shell redirect/operator:
// `sidecar sign 2>&1` / `sidecar sign >out` / bare `sidecar sign` are LISTs, not mints,
// so rest[1] like `2>`/`>out`/undefined must NOT be read as a key (that false-blocked a
// routine `sidecar sign 2>/dev/null`). Only a `[A-Za-z]…` key that isn't a read verb mints.
const key = rest[1] ?? "";
if (base === "sidecar" && rest[0] === "sign" && /^[A-Za-z][\w-]*$/.test(key) && !MINT_ALLOWED.has(key)) {
return `sidecar sign ${key}`;
}
// shell -c / eval indirection: the inner mint is quoted (masked from the head scan)
// → scan the raw segment for a non-read `sidecar sign …`.
Expand Down
Loading