feat(as): P7 approvals & security — ApprovalManager + permission modes + rule persistence#448
Merged
Conversation
Cut feature/approvals-security off next (P0-P6 merged). Scope = parity-charter §3.9 — ApprovalManager + permission updates + approval rules + persistence; backs the P6 permission-toggle seam; consumes the P4 inline approval blocks. Key ADR: ApprovalRuleStorePort persistence (device-local, CHARTER-REQ-SET). Autonomous full-epic drive (P7→P12). Next: /spec:requirements. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
35 EARS REQ-AS (permission mode / rules+matching / decision flow / persistence /
status UI / a11y+additivity) + 16 NFR-AS, each mapped to a claudian path + TEST-AS
id. Permission modes = normal/plan/yolo (claudian PermissionMode, all Claude-backed).
Rule model {toolName, action-pattern?, decision allow|deny, lifetime} with claudian
matchesRulePattern semantics + explicit deny. Persistence (CLAR-AS-001→ADR-AS-001):
dedicated ApprovalRuleStorePort, device-local (ADR-PSR-002), no migration. Additive
default = P4 always-prompt path (REQ-AS-052). CLAR-AS-001..005 resolved-by-recommendation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ADR-AS-001 ApprovalRuleStorePort (store-only narrow port) + ApprovalRule DTO + pure domain matcher (claudian matchesRulePattern semantics) + device-local backing (saveLocalStorage, no data.json/vault, no migration), fail-safe-to-prompt. ADR-AS-002 additive ChatRuntimeQueryOptions.permissionMode? + TabControls.permissionMode? (folded non-normal only by P6 foldControlOptions); ToolbarCapabilities.permissionMode widens to live normal|plan|yolo; SDK mapping in the Claude runtime, no providerId branch. ADR-AS-003 application ApprovalManager decision flow (mode-gate → match → unchanged P4 prompt → persist; deny-wins; +deny-always). No-rule+normal = byte-identical P4. Components: PermissionToggle(live)/ApprovalsPanel/ApprovalRuleRow/InlineApproval. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 layer groups, 28 SPEC items. Pins PermissionMode (normal|plan|yolo), additive ChatRuntimeQueryOptions.permissionMode?/TabControls.permissionMode? (fold non-normal), ApprovalDecision +deny-always (P4 byte-identical), ToolbarCapabilities.permissionMode widen, ApprovalRule DTO + ApprovalRuleStorePort + APPROVAL_RULE_STORE_PORT (device-local), the pure matcher (claudian matchesRulePattern: bash explicit-wildcard-only, path-segment boundaries, deny-wins), ApprovalManager.decide (mode-gate→match→prompt→persist, fail-safe-to-prompt). Manual legs TEST-AS-M1/M2/M3 + plan-gate. Full coverage table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T-AS-001 baseline+guard-verify (no guard-relax needed — verified); DOMAIN 002..011, INFRA 012..015, APPLICATION 016..019, UI 020..029, STYLES 030, WIRE-IN 031..033, GATE 034..040. RED(qa)→green(dev) per contract; ToolbarCapabilities.permissionMode widen lands its implements fan-out (3 runtimes + EnqueueRuntime + ScriptedRuntime doubles) in T-AS-011 (build-green discipline). Coverage-excluded Obsidian device-local store + Claude SDK-map/setMode → manual legs T-AS-036/037/038 (TEST-AS-M1/M2/M3). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scaffold the P7 parity-screenshot matrix (baseline column from claudian-main ApprovalManager/ClaudeApprovalHandler/ClaudePermissionUpdates + permission-toggle.css/status-panel.css), the test-plan (guard-verify note + manual legs TEST-AS-M1/M2/M3 + DOMAIN-batch status), and the implementation-log. Confirms APPROVAL_RULE_STORE_PORT + the new approvals domain/app/ui paths are not guard-banned (no relaxation task). No src/ change. NFR-AS-012 NFR-AS-001 SPEC-AS-004/012/013/015/020/026. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…provalDecision Failing structural/serialisation legs: PermissionMode = 'normal'|'plan'|'yolo' (closed union + barrel surface); ChatRuntimeQueryOptions.permissionMode? appended after serviceTier with a P6-shaped query byte-identical; TabControls.permissionMode? appended; ApprovalDecision grown to the four-member union (deny-always) with the P4 members + ApprovalRequest/ApprovalOption byte-identical. vue-tsc fails RED. TEST-AS-001 TEST-AS-002 TEST-AS-016 SPEC-AS-001/002/003/021 REQ-AS-001/002/006/016/052 NFR-AS-001. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…alDecision
Add src/domain/chat/PermissionMode.ts ('normal'|'plan'|'yolo' closed union);
append permissionMode?: PermissionMode after serviceTier on ChatRuntimeQueryOptions
+ TabControls; grow the ApprovalDecision union by 'deny-always'; re-export
PermissionMode from the ports barrel. Purely additive — no implements break (the
runtimes read the optional field; the union grows additively). The P4
inlineBlockDtos union-exactness assertion is updated to the grown four-member
union (the union-grow fan-out). Greens TEST-AS-001/002/016; whole-project vue-tsc 0.
SPEC-AS-001/002/003/021 REQ-AS-001/002/006/016/052 NFR-AS-001.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Failing unit tests for getActionPattern/getActionDescription/matchesRulePattern
(@/domain/chat/approvals/ApprovalMatcher): the full SPEC-AS-026 table — per-tool
pattern/description derivation, no-rule/'*'/exact, the null-action guard (EC-AS-9),
bash explicit-wildcard only ("git *"↦"git status" yes, "git"↦"git status" no,
"npm:*"↦"npm install" yes, "git *"↦"github" no — EC-AS-7), file path-segment
boundary ("/a/b"↦"/a/b/c" yes, ↦"/a/bc" no, trailing-/ subtree, \->/ normalise —
EC-AS-8), other-tool simple prefix, and never-throws. Module missing → RED.
TEST-AS-010/011/012/013/014/015 SPEC-AS-004/026 REQ-AS-010..015 NFR-AS-009
EC-AS-7/8/9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…matchesRulePattern Add src/domain/chat/approvals/ApprovalMatcher.ts ported verbatim from claudian core/security/ApprovalManager.ts: the seven tool-name constants; getActionPattern (string|null), getActionDescription (string), matchesRulePattern (boolean) with the private isPathPrefixMatch + matchesBashPrefix helpers — \->/ normalise, no-rule/'*' match-all, exact, bash explicit-wildcard-only, file path-segment boundary, other-tool simple prefix, the null-action guard. Pure + total, never throws (NFR-AS-009); string comparison only, no eval/exec (NFR-AS-002). Barrel re-export added. Greens TEST-AS-010/011/012/013/014/015. Two targeted complexity disables (irreducible per-tool dispatch, justified). SPEC-AS-004/026 REQ-AS-010..015 NFR-AS-002/009. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…upeKey
Failing structural + dedupe-key tests: the six readonly members
(id/toolName/actionPattern?/decision:'allow'|'deny'/lifetime:'session'|'persisted'/
createdAt); ApprovalRuleInput = Omit<ApprovalRule,'id'|'createdAt'>; ruleDedupeKey
returns the `${toolName} ${actionPattern ?? ''} ${decision}` triple (absent vs ''
collapse, opposite decision distinct); no secret/token field; barrel re-export.
Module missing → RED.
TEST-AS-016 SPEC-AS-005/024 REQ-AS-016/030/031 NFR-AS-002/008.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add src/domain/chat/approvals/ApprovalRule.ts: the six readonly members
(id/toolName/actionPattern?/decision:'allow'|'deny'/lifetime:'session'|'persisted'/
createdAt), ApprovalRuleInput = Omit<ApprovalRule,'id'|'createdAt'>, and
ruleDedupeKey returning the `${toolName} ${actionPattern ?? ''} ${decision}`
triple (pure, string-only). Plain inert DTO — no secret/token field, no class, no
obsidian/node/Vue (NFR-AS-002/008). Barrel re-export added. Greens TEST-AS-016
DTO leg (6/6).
SPEC-AS-005/024 REQ-AS-016/030/031 NFR-AS-002/008.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…RT key + barrel Failing structural tests: the four Result-typed methods (loadRules: Promise<Result <readonly ApprovalRule[]>>, addRule: (input)=>Promise<Result<ApprovalRule>>, removeRule: (id)=>Promise<Result<void>>, clear: ()=>Promise<Result<void>>); the own APPROVAL_RULE_STORE_PORT InjectionKey; the @/domain/ports barrel re-exports of the port + ApprovalRule/ApprovalRuleInput/PermissionMode. vue-tsc fails RED. TEST-AS-053 SPEC-AS-006 REQ-AS-001/032/033/034/053 NFR-AS-005. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ey + barrel Add src/domain/ports/ApprovalRuleStorePort.ts (loadRules/addRule/removeRule/clear, all Promise<Result<...>>, store-only persisted lifetime, documented per-method contract: load-or-default, dedupe-by-ruleDedupeKey, idempotent remove, fail-safe-via-err); add the APPROVAL_RULE_STORE_PORT InjectionKey to bridge/ports (own key, no aggregate); re-export the port + ApprovalRule/ApprovalRuleInput from the @/domain/ports barrel (PermissionMode already re-exported in T-AS-003). Greens TEST-AS-053 port-shape leg (2/2); deleted-symbol guard green (new key/port resolve clean, no relaxation). SPEC-AS-006 REQ-AS-001/032/033/034/053 NFR-AS-005/010. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…itivity
Extend tests/domain/ports/ChatRuntimePort.ts.test.ts: assert
ToolbarCapabilities.permissionMode is WIDENED from 'default'|'plan' to the live
PermissionMode ('normal'|'plan'|'yolo'), the P6 'default' value mapping to
'normal'; the four other ToolbarCapabilities flags + the five RuntimeCapabilities
flags + the P0-P6 ChatRuntimePort members stay byte-identical; all three live modes
representable. vue-tsc fails RED (permissionMode still narrow 'default'|'plan').
TEST-AS-001 TEST-AS-021 SPEC-AS-006/021 REQ-AS-003 NFR-AS-001.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nts fan-out
Widen ToolbarCapabilities.permissionMode from 'default'|'plan' to PermissionMode
('normal'|'plan'|'yolo') in ChatRuntimePort (importing from PermissionMode; the four
other ToolbarCapabilities flags + the five RuntimeCapabilities flags + the P0-P6
members byte-identical). In the SAME commit, map the P6 'default' -> 'normal' on
every getToolbarCapabilities() impl that implements ChatRuntimePort — the three
runtimes (MockChatRuntime, FixtureChatRuntime, ClaudeCliChatRuntime) + the two
ScriptedRuntime test doubles (RunChatTurnUseCase.test/.rr.test) + the P6 capability
fixtures (buildToolbarViewModel, MockToolbarCapabilities, LocalStorageToolbar,
main.ts) — so vue-tsc + lint + the suite stay green (the P6 T-TC-008 lesson).
EnqueueRuntime forwards getToolbarCapabilities() verbatim — no change. No
providerId branch; synchronous + total. Greens TEST-AS-001 capabilities-shape +
TEST-AS-021 additivity (68/68 across the affected files).
SPEC-AS-006/021 REQ-AS-003 NFR-AS-001/005.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tate) Log T-AS-002..011 entries (RED/green commits, files, verify, deviations) + the DOMAIN-batch close-out (vue-tsc 0, lint 0, vitest tests/domain 116/116, additivity proven). Advance workflow-state to stage 7 implementation in-progress; record the DOMAIN-batch hand-off to the INFRA batch (T-AS-012..015). implementation-log.md + test-plan.md set in-progress (INFRA/APP/UI/GATE batches remain). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… gate
The P6 gate committed a stale styles.css built BEFORE the tokens.css lightningcss
comment fix — the old slash/brace-laden §4.13 comment had confused the plugin
build's CSS processor, mangling `.specorator-root {` into invalid
`.specorator-root) {` with selector text bled into the comment. This commits the
clean rebuild (from the fixed tokens.css; the P6 testvault deploy already shipped
this clean version). Corrects the artifact on next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…de SDK mode mapping + plan-exit setMode Implements SPEC-AS-007 (coverage-excluded src/infrastructure/obsidian/**): - ObsidianApprovalRuleStore backs ApprovalRuleStorePort on the device-local store under 'specorator:approval-rules' (app.saveLocalStorage/loadLocalStorage, ADR-PSR-002 pattern) — never data.json, never a vault file (NFR-AS-003, REQ-AS-034). loadRules is load-or-default (missing/unparseable -> ok([]), malformed entries dropped); addRule dedupes by ruleDedupeKey + mints id/createdAt; removeRule idempotent; clear; all Result-typed, total (never throws -> tryAsync). Exposed via get approvalRuleStore on ObsidianBridge. - ClaudeCliChatRuntime maps queryOptions.permissionMode to the SDK --permission-mode string (yolo->bypassPermissions / plan->plan / normal/absent->no flag); records the live mode so getToolbarCapabilities().permissionMode reflects it; on plan-exit syncs the session-scoped mode (parity ClaudeApprovalHandler setMode destination:session). No providerId branch (SPEC-AS-023, NG6). Behavioural gate is the MANUAL legs TEST-AS-M1/M3 (scheduled in test-plan.md); not self-claimed. REQ-AS-002/004/005/030/034/053, NFR-AS-003. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e mode + fake-ports member
Authors the failing unit tests for SPEC-AS-008:
- MockApprovalRuleStore.test.ts — the scriptable in-memory store (seedRules,
loadRules-default-ok([]), addRule mint + dedupe-by-ruleDedupeKey + opposite-
decision append, idempotent removeRule, clear, setFailMode('load'|'save'|'none')
forcing Result.err, never-throws) + the MockBridge.approvalRuleStore accessor.
- MockApprovalRuntimeMode.test.ts — MockChatRuntime records the last query's
permissionMode (getLastPermissionMode) + the scriptable
getToolbarCapabilities().permissionMode three-mode representability.
- fake-ports.test.ts — the approvalRuleStore factory member (seedable + setFailMode).
RED confirmed: MockApprovalRuleStore module + MockBridge.approvalRuleStore +
getLastPermissionMode + the fake-ports member do not yet exist.
Traces: TEST-AS-002/003/006/020/021/030/032/033/040/053/054, SPEC-AS-008,
REQ-AS-020/021/032/053/054, NFR-AS-010.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ime mode + fake-ports member
Greens the T-AS-013 RED tests (SPEC-AS-008):
- MockApprovalRuleStore — scriptable in-memory store: seedRules pre-populates;
loadRules defaults ok([]); addRule mints id/createdAt + dedupes by
ruleDedupeKey (same-triple no-op ok(existing), opposite-decision appended);
removeRule idempotent; clear; setFailMode('load'|'save'|'none') forces
Result.err for the fail-safe-to-prompt driver (TEST-AS-054); total, never
throws (NFR-AS-010). Exposed via get approvalRuleStore on MockBridge.
- MockChatRuntime records the last query's permissionMode (getLastPermissionMode,
TEST-AS-002); the scriptable getToolbarCapabilities().permissionMode covers the
three-mode representability (TEST-AS-003/006/040).
- fake-ports.ts gains the approvalRuleStore member (seedable + setFailMode) so
multi-port ApprovalManager + panel tests see it.
Runnability fix to the T-AS-013 RED fixture (no assertion change): ChatTurnRequest
has no conversationId; the drain loop no longer binds an unused chunk.
No node:*/obsidian in Mock. vitest run 32/32 green.
Traces: TEST-AS-002/003/006/020/021/030/032/033/053/054, SPEC-AS-008,
REQ-AS-020/021/032/053/054, NFR-AS-010.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ePort + inert runtime mode
Implements SPEC-AS-009 (RED leg authored first, then greened):
- LocalStorageApprovalRuleStore backs ApprovalRuleStorePort on browser
localStorage under the same key as the Obsidian device-local store
('specorator:approval-rules') so the GitHub Pages demo persists rules across a
reload with no Obsidian runtime (REQ-AS-053). loadRules is load-or-default
(missing/unparseable/corrupt -> ok([]), malformed entries dropped); addRule
dedupes by ruleDedupeKey + mints id/createdAt; removeRule idempotent; clear;
all Result-typed, never throws across the boundary (NFR-AS-010). Exposed via
get approvalRuleStore on LocalStorageBridge.
- The runtime mode is inert: FixtureChatRuntime reports permissionMode 'normal'
(T-AS-011) and fires no live setMode (no live SDK); the toggle/panel still
reflect the per-tab mode draft via the fold.
No node:*. vitest run 8/8 (store) green; full infra+fakes surface 346/346 green.
Traces: TEST-AS-053 (LocalStorage round-trip leg), SPEC-AS-009, REQ-AS-053,
NFR-AS-010.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…kflow-state) Records the INFRA batch (T-AS-012..015) in implementation-log.md (per-task entries + batch close-out), ticks the T-AS-012..015 DoD boxes in tasks.md, and updates workflow-state.md (Stage 7 row, implementation-log.md artifact status, INFRA-batch hand-off note -> APPLICATION batch T-AS-016..019). Manual legs TEST-AS-M1/M3 remain scheduled for the final epic-review gate (not self-claimed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the failing fold-leg cases: non-normal ('plan'/'yolo') folded, the
'normal'/absent guard folds nothing (EC-AS-2/13 byte-identical P6), the
P6-clause byte-identity, and never-throws.
TEST-AS-002 (fold leg). SPEC-AS-011 §3. REQ-AS-002/052. NFR-AS-001.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tions Writes folded.permissionMode only when present AND non-'normal', so a no-rule/normal tab folds nothing -> byte-identical P6 (EC-AS-2/13). The return type widens by the one optional permissionMode key; the P6 model/mode/reasoning/serviceTier clauses + behaviour stay byte-identical. Pure + total; no providerId branch. Implements SPEC-AS-011 §3. REQ-AS-002/052. NFR-AS-001. TEST-AS-002 (fold leg). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the failing use-case matrix over the scriptable Mock store + a
scripted mode: mode-gate-first (yolo auto-allow no-lookup / plan defer /
normal continue), load-await + match deny-wins, fail-safe-to-prompt on a
store err (notice, never auto-allow, no rule content logged, never throws),
applyDecision (session vs persisted, {-leading JSON-fallback stored without
actionPattern, dedupe, null cancel), listRules persisted-union-session, the
bash/path matcher edges (EC-AS-7/8), and the no-stale-snapshot re-read.
TEST-AS-003/004/020/021/023/025/030/031/032/033/052/054. SPEC-AS-010/023/027/028.
REQ-AS-004/005/020..025/030/031/052/054. NFR-AS-004/009. EC-AS-1/3/5/6/10/11/12/16/20.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
decide(action, mode): mode-gate-FIRST (yolo->ok('allow') no lookup /
plan->ok('prompt') defer to the P4 exit-plan gate / normal->continue) ->
await store.loadRules via tryAsync (err -> log no-content + storeError
notice + ok('prompt'), never auto-allow) -> match persisted-union-session
via the pure matcher (deny-wins -> ok('deny'), else allow -> ok('allow'),
else ok('prompt')). applyDecision: allow/deny -> in-memory session rule
(dedupe by ruleDedupeKey); allow-always/deny-always -> store.addRule
persisted (the {-leading JSON-fallback stored WITHOUT actionPattern); null
-> cancel. listRules -> persisted-union-session, Result-typed. No providerId
branch; never throws across the callback boundary; logs no rule content.
Also folds the lint fix to the T-AS-018 RED test (unnecessary optional
chains on non-nullish index access) so the file lints clean — no assertion
change.
Implements SPEC-AS-010/023/025/027/028. REQ-AS-004/005/020..025/030/031/052/054.
NFR-AS-002/004/009. ADR-AS-003.
TEST-AS-003/004/020/021/023/025/030/031/032/033/052/054. EC-AS-1/3/5/6/10/11/12/16/20.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-AS-053 (composable leg). SPEC-AS-018 §4. REQ-AS-040/042/053, NFR-AS-005. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-AS-018 §4 (inject-or-throw, ADR-008 one-port-one-composable). REQ-AS-040/042/053, NFR-AS-005/006. Greens TEST-AS-053 composable leg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-AS-001/002/003/006/050/051 (A legs). SPEC-AS-012 §4. REQ-AS-001/002/003/006/050/051, NFR-AS-006/013/015. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-AS-012 §4 (live normal/plan/yolo control, additive over the P6 honest-defer seam) + SPEC-AS-022 i18n (en+de). Greens TEST-AS-001/002/003/006/050/051 A legs. REQ-AS-001/002/003/006/050/051, NFR-AS-006/007/013/015. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-AS-040/041/042/043/050/051 (A legs). SPEC-AS-013/014 §4. REQ-AS-040/041/042/043/050/051, NFR-AS-006/013/015. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-AS-013/014 §4 (status/approvals surface + one-rule row, live, remove-by-id). Greens TEST-AS-040/041/042/043/050/051 A legs. SPEC-AS-022 i18n. REQ-AS-040/041/042/043/050/051, NFR-AS-006/007/013/015. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-AS-016 (option-row leg), TEST-AS-022 (four-option-row leg), TEST-AS-025 (cancel leg). SPEC-AS-015/018 §4. REQ-AS-022/025/030, NFR-AS-006/007/015. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-AS-015/018 §4 (the fourth deny-always option arrives via request.options; an additive data-decision attribute targets it; render byte-identical to P4, NG4). Greens TEST-AS-016 option-row/022/025 legs. REQ-AS-022/025/030, NFR-AS-006/007/015. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ssionMode TEST-AS-002 (store-fold leg), TEST-AS-006/020/021/022/025/040/042/043 (surface legs). SPEC-AS-016/017/023/028 §4. REQ-AS-002/004/005/006/020..025/040/043, NFR-AS-008, EC-AS-17/18. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-AS-016/017/023/028 §4. A new ApprovalGateRuntime decorator gates the
active runtime's approval callback through the per-surface ApprovalManager
(mode-gate -> match -> auto-decide OR the unchanged P4 prompt -> applyDecision);
degrades to the byte-identical P4 always-prompt path when APPROVAL_RULE_STORE_PORT is
absent. The PermissionToggle set + the ApprovalsPanel remove + the live mode thread
through ToolbarStrip/ChatComposer to tabs.setControl('permissionMode'). No providerId
branch. Greens TEST-AS-002 store-fold + TEST-AS-006/020/021/022/025/040/042/043 surface
legs. REQ-AS-002/004/005/006/020..025/040/043, NFR-AS-006/007/008, EC-AS-17/18.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ate) Tick T-AS-020..029 DoD checkboxes; record the UI-batch hand-off + Stage 7 progress in workflow-state.md (implementation-log.md remains in-progress — STYLES/WIRE-IN/GATE + manual legs remain). SPEC-AS-012..018. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mint the §4.14 approvals/security --sp-* token slice (lightningcss-safe ASCII comment): --sp-approvals-row-gap, --sp-approvals-decision-allow, --sp-approvals-decision-deny, --sp-permission-mode-active. Apply the active-mode token to PermissionToggle; extend the tokens-contract test with the §4.14 presence + leak guard. Implements SPEC-AS-020. NFR-AS-012, TEST-AS-062. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the standalone approvals-panel mount leg (RED: main.ts does not yet provide APPROVAL_RULE_STORE_PORT) + the structured-action-pattern gate leg (RED: the gate derives from req.context, not getActionPattern over input). SPEC-AS-019. TEST-AS-022/040/043/053, TEST-AS-032. REQ-AS-002/030/053. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AgentSidebarView provides ObsidianBridge.approvalRuleStore (device-local); src/ui/main.ts provides MockBridge.approvalRuleStore. The ChatSurface gate now runs live + the approvals panel mounts; absent → the P4 degrade. Greens the standalone approvals-panel mount RED leg. The action-pattern follow-up is escalated (CLAR-AS-006): threading structured input onto ApprovalRequest conflicts with the frozen SPEC-AS-003 byte-identical-shape QA test. Also records the T-AS-033 standalone smoke (deterministic leg automated; live-dev deferred-manual). Implements SPEC-AS-019. REQ-AS-002/030/053. NFR-AS-005/006. TEST-AS-022/040/043/053. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage-9 review (REVIEW-AS-001, approve-with-conditions) found en.ts (default
locale) missing the permission.mode.{normal,plan,yolo} labels PermissionToggle
renders → English UI showed raw keys (de.ts had them). Add the en labels.
Add a locale key-parity test (tests/ui/i18n/index.test.ts) asserting en and de
declare the EXACT same leaf key set — snapshotted at module-load so the sibling
i18nMerge mutation tests don't pollute it. Would have caught R-AS-001; guards the
i18n-heavy P8-P12. Also commits the Stage-9 review.md + traceability.md (CLAR-AS-006
deferred P3 per reviewer).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shipped CSS rebuilt for P7 — ApprovalsPanel/ApprovalRuleRow + the live PermissionToggle mode styling + the §4.14 status-panel/permission-toggle token slice. Gate green: typecheck 0, lint 0, build + build:web + docs:api clean, npm audit (high) clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P7 — Approvals & Security (claudian-reboot)
The approval decision/rules engine + permission management on the P1–P6 surface (charter §3.9). Spec:
specs/approvals-security/(PRD-AS-001, DESIGN-AS-001, SPEC-AS-001..028, ADR-AS-001..003, TASKS-AS-001 = 40 tasks).Delivered (T-AS-001..033 + gate)
normal/plan/yolo(claudianPermissionMode) — the P6 permission-toggle seam is now live (3-mode), per-tab, folded into the turn via the P6foldControlOptions(additiveChatRuntimeQueryOptions.permissionMode?, non-normalonly).matchesRulePatternsemantics: bash explicit-wildcard-only, path-segment boundaries) → auto-decide OR the unchanged P4 inline prompt → an*-alwaysdecision persists a rule. Fail-safe-to-prompt on store error (never silent auto-allow).ApprovalRuleStorePort+APPROVAL_RULE_STORE_PORT— device-local (saveLocalStorage, neverdata.json/vault, no migration; ADR-AS-001 / CHARTER-REQ-SET). 3 bridges (Obsidian device-local coverage-excluded / Mock scriptable / LS localStorage).PermissionToggle,ApprovalsPanel+ApprovalRuleRow,InlinePlanApproval+deny-always, theApprovalGateRuntimedecorator wiring the engine into the P4 approval path (degrades to byte-identical P4 always-prompt when the store port is absent).status-panel/permission-toggle--sp-*tokens; en+de i18n.Gate (local, green)
vue-tsc0 ·eslint0 · vitest 1574 passed (+ a new en↔de locale key-parity guard) ·build+build:web+docs:apiclean ·npm audit --audit-level=highclean.Parity self-review (Stage 9)
review.mdapprove-with-conditions. Security correctness confirmed: fail-safe-to-prompt, deny-wins, matcher safety ("git *"↛github,/a/b↛/a/bc), device-local-not-vault. Live wiring + provide verified. The one HIGH finding (R-AS-001:en.tsmissing the permission.mode labels → raw keys in English) is fixed, plus a locale key-parity test added (guards P8–P12). CLAR-AS-006 (structured action-pattern vs the frozen P4ApprovalRequest) ruled P3 deferral — the CLI transport emits no live approval_request, soreq.contextmatching is correct; carried to the interactive-transport phase.Deferred — final epic gate
Manual legs TEST-AS-M1 (real device-local store round-trip +
data.json/vault untouched), TEST-AS-M3 (real Claude SDK-mode mapping + plan-exitsetMode+ plan edit-gating), TEST-AS-M2 (parity screenshots), the livenpm run devflow, and CLAR-AS-006.🤖 Generated with Claude Code