-
Notifications
You must be signed in to change notification settings - Fork 0
Clean Room Spec
Host doc role: Normative delta against the Claude Code reference product (SPEC_AUTHORING_RULES.md Framing). Operator authentication and credential UX are enumerated in §2.7–2.8 (closed list)—implementers must ship those capabilities; they must not infer them from memory or informal “parity.” Other operator chrome not listed in §2.7–2.8 or elsewhere in this doc defaults to reference parity per SPEC_AUTHORING_RULES.md. This document binds persistence, hook plane, orchestration, hooks, tools, and cited contracts for a one-shot implementer pass.
Authoring rules: SPEC_AUTHORING_RULES.md. Closed decisions: §17 (binding; no external phase purchases required to implement).
Reference traceability (informative): Hook stdin order cross-checked against entrypoints/sdk/coreSchemas.ts → HookInputSchema union (lines 767–796). Normative authority: this document, including §17. Forks from reference are labeled fork policy below.
One-shot (binding): Every normative section is complete as written—no guessing shapes, enums, DDL, or algorithms unless labeled implementation-defined with a closed set.
- Modality: In this document, must / shall / required / forbidden denote hard conformance: implementers must be able to demonstrate each with an observable test (CLI action, stored row, API outcome, or logged hook row). May denotes optional behavior only in that sentence’s scope; absence of may on a capability does not make that capability optional if elsewhere required.
-
No silent subsetting: Per
SPEC_AUTHORING_RULES.mdSpec as diff, any UX, CLI flow, tool, or provider not explicitly forked in this document or §17 defaults to reference Claude Code parity. Forbidden: treating spec silence as permission to ship a smaller command set, fewer §7 tools, or fewer §2.1 providers than reference unless §17 records an explicit omit or replace row with a closed substitute. - Forbidden delivery patterns: Forbidden merging a one-shot labeled “complete” while any normative path returns not implemented, TODO, permanent stub throws, or unreachable code for a must in this spec. Forbidden “documentation-only” features (described in operator docs but not wired in the binary).
- Contracts are closed: Where this spec cites “reference behavior,” acceptance requires behavioral match to the reference tree for that surface unless a fork policy paragraph or §17 row states a closed delta. Forbidden ambiguous phrases such as “similar to reference,” “best effort,” or “eventually” for normative requirements.
- DELIVERED is binary: The product state DELIVERED is true iff Appendix F is satisfied in full. Forbidden partial credit, “mostly done,” or “usable except …” for one-shot sign-off. If Appendix F is false, the build is not DELIVERED—regardless of subjective review.
- No post-shot construction: Forbidden declaring DELIVERED if the operator must edit application source (runtime code paths), add missing modules, or follow undocumented steps to obtain behavior that this spec marks must. After DELIVERED, work is defect repair, dependency updates, or explicit spec revision—not completing normative features left unfinished at sign-off.
-
Contracts complete: Per
SPEC_AUTHORING_RULES.md§2 and §3; §2.7–2.8 operator auth; §2.9–2.10 REPL + providers; unified DDL §4.3–§4.8; hook I/O §§5.11–5.12, §6, §§11–12; tools §7 (including §7.2); algorithms §5–8; permissions Appendix E. - Hook plane: Sequential, total order, deterministic merge (fork policy vs reference parallel/async patterns where applicable).
- Transactional SQLite only: All transactional data per §4; forbidden authoritative JSONL transcripts, authoritative plan directory scans, secrets in DB.
- Functional completeness: Every must in this document is live in the shipped binary for the one-shot; §10 and §18 are the minimum acceptance bar—not aspirational.
- Operator usability: The operator can log in, select or change model in-session, run every §2.1 provider that remains non-forked, and invoke every §7 built-in tool contract without undocumented manual file rituals as the sole path (§2.9, §7.2).
-
Traceability — machine closure: Appendix F artefacts must exist in the implementation root (§9). CI must execute
scripts/sdlc-acceptance.sh(or the closed equivalent named in the matrix) on every change intended for release; forbidden merge to the delivery branch if exit code ≠ 0. Forbidden subjective sign-off (“LGTM”) as a substitute for Appendix F.
| Goal | Mechanism |
|---|---|
| Model orchestration (spine) | §2: provider enums, per-provider model enums, env-only credentials, canonical turn loop (internal/orchestration/); §2.7–2.8 auth + storage (internal/auth/) |
| Enforceable SDLC phases | Workflow KV + plans + PreToolUse / policy hooks |
| Auditability |
hook_invocations, transcript_entries, events
|
| Determinism | Sequential hooks, enumerated merges, single DB |
Explicit fork (normative): Session transcript authority is SQLite (transcript_entries), not filesystem JSONL. Explicit fork: Hook stdin base omits transcript_path; adds conversation_id and runtime_db_path (§6). Explicit fork: Valid stdout {"async":true} is rejected as control flow for SDLC profile (§5.8). Explicit fork: PreToolUse multi-hook exit code 2 and permission merge — §5.6 (not reference last-wins race semantics).
Orchestration must implement: resolve provider → load context from transcript_entries → model call → assistant output → tools through §5 → append results. Subagents: distinct session_id; no parallel model calls per session_id.
| Value | Meaning |
|---|---|
claude_code_subscription |
Anthropic Messages API (or documented successor) with subscription/API credentials; official-class endpoints unless ANTHROPIC_BASE_URL overrides. |
openai_compatible |
Remote OpenAI Chat Completions–compatible HTTP API (tool-capable). |
lm_studio_local |
Local OpenAI-compatible server; LM Studio–style defaults in §2.3. |
{
"provider": "<§2.1>",
"model_id": "<enum for that provider>"
}Forbidden keys in model_config: any secret, token, or Authorization material. Allowed: provider, model_id only.
claude_code_subscription — ClaudeSubscriptionModelId:
model_id |
Wire model
|
|---|---|
claude_opus_4 |
claude-opus-4-20250514 |
claude_sonnet_4 |
claude-sonnet-4-20250514 |
claude_3_5_haiku |
claude-3-5-haiku-20241022 |
openai_compatible — OpenAICompatModelId:
model_id |
Wire model
|
|---|---|
gpt_4o |
gpt-4o |
gpt_4o_mini |
gpt-4o-mini |
o3 |
o3 |
o3_mini |
o3-mini |
lm_studio_local — LmStudioModelId:
model_id |
Wire model
|
|---|---|
lm_studio_server_routed |
Value of env LM_STUDIO_MODEL (required non-empty when selected). |
Invalid model_id for provider → configuration error before network I/O.
| Provider | Variable | Required | Default |
|---|---|---|---|
claude_code_subscription |
ANTHROPIC_API_KEY |
yes* | — |
claude_code_subscription |
ANTHROPIC_AUTH_TOKEN |
yes* | — |
claude_code_subscription |
ANTHROPIC_BASE_URL |
no | https://api.anthropic.com |
*At least one of ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN non-empty.
| Provider | Variable | Required | Default |
|---|---|---|---|
openai_compatible |
OPENAI_API_KEY |
yes | — |
openai_compatible |
OPENAI_BASE_URL |
yes | — |
| Provider | Variable | Required | Default |
|---|---|---|---|
lm_studio_local |
LM_STUDIO_BASE_URL |
no | http://127.0.0.1:1234/v1 |
lm_studio_local |
LM_STUDIO_API_KEY |
no | lm-studio |
lm_studio_local |
LM_STUDIO_MODEL |
yes if model_id is lm_studio_server_routed
|
— |
- Env supplies endpoints and credential values per §2.3.
-
settings_snapshot.effectivesuppliesmodel_config.providerandmodel_config.model_id. - Missing required env → no model call; record failure (
StopFailure-class semantics /events).
- Load snapshot + env; validate §2.2–2.3.
- Build provider messages from
transcript_entriesforsession_idordered bysequence(deterministic mapping). - On user submit:
UserPromptSubmithooks (§5); appenduserrows (Appendix C). - Call model (streaming allowed); parse assistant content and tool calls.
- Append
assistantrows (Appendix C); preserve tool declaration order. - For each tool use in order: PreToolUse → permission → execute → PostToolUse (§5); append
tool_result. - If more tool results feed the model, repeat from step 4; else end turn.
- Commit
transcript_entriesandhook_invocationsin SQLite transactions consistent with §4.
Fork policy: No Promise.all / parallel pools for model response or PreToolUse outcomes on the same session_id.
Should echo resolved wire model string (§2.2) for audit.
The product must expose the following without the implementer guessing. Reference trace (informative, for behavior parity tests): utils/auth.ts (getAuthTokenSource, getAnthropicApiKeyWithSource), commands/login/, commands/logout/, services/oauth/*, utils/secureStorage/*, utils/authPortable.ts.
| ID | Capability | Normative requirement |
|---|---|---|
| A1 | API key via environment | Operator can run the product with ANTHROPIC_API_KEY set in the process environment before startup (CI, shell export, process manager). Runtime must use it for Messages API auth when no higher-precedence bearer source is active per §2.7.4. |
| A2 | Bearer via environment | Operator can run with ANTHROPIC_AUTH_TOKEN set (OAuth access token or other bearer accepted by Anthropic’s API for the configured endpoint). |
| A3 | Interactive Claude.ai OAuth | Operator can invoke a login flow (reference: slash command /login) that completes browser- or device-style OAuth against Claude.ai / Anthropic’s OAuth endpoints, obtains tokens, and activates subscription-class API access without writing those secrets into SQLite or settings_snapshot. Tokens may reside in OS secure storage or equivalent (keychain, credential manager, encrypted file outside AGENT_SDLC_DB). |
| A4 | Logout | Operator can invoke logout (reference: /logout) that clears Claude.ai–sourced OAuth session material from secure storage / process caches so the next run does not silently reuse the prior account unless re-authenticated. |
| A5 | Auth status | Operator can inspect whether the process has usable Anthropic-class credentials and which class (e.g. env API key vs OAuth vs none)—reference: /status or equivalent must exist and must not print full secrets. |
| A6 | apiKey helper script | If settings support apiKeyHelper (reference: configured helper that prints a key to stdout), runtime may invoke it only before model calls, must not persist returned material to SQLite, and must treat failure as authentication_failed / configuration error. Forbidden running helper before workspace trust if reference forbids it. |
| A7 | Bare / hermetic mode | If product supports --bare (reference), in that mode only ANTHROPIC_API_KEY and/or apiKeyHelper (from designated settings path) may supply Anthropic credentials; OAuth and keychain paths must be disabled as in reference isBareMode() semantics. |
| ID | Capability | Normative requirement |
|---|---|---|
| O1 | API key + base URL | Operator can set OPENAI_API_KEY and OPENAI_BASE_URL via environment (or platform equivalent that injects env before turn boundary). Forbidden storing values in SQLite. |
| ID | Capability | Normative requirement |
|---|---|---|
| L1 | Local endpoint + model id | Operator can set LM_STUDIO_BASE_URL, LM_STUDIO_API_KEY (optional), LM_STUDIO_MODEL per §2.3 via environment (or env injection only). |
When multiple sources could apply, resolution must match reference getAuthTokenSource() / getAnthropicApiKeyWithSource() ordering unless this document explicitly forks a step. SDLC fork: whatever source wins must expose the effective credential to the HTTP client only via memory / env for the request; never persist the secret into AGENT_SDLC_DB.
If the product ships teleport / bridge / claude.ai web session features (reference: utils/teleport/*), those code paths must refuse to proceed with API-key-only auth where reference requires OAuth—error text must direct the operator to /login (or product’s equivalent of A3).
-
Forbidden:
INSERTorUPDATEof API keys, OAuth access/refresh tokens, rawAuthorizationheaders, orapiKeyHelperoutput intoAGENT_SDLC_DB,settings_snapshot,transcript_entries, hook stdin, or hook stdout persistence. -
Allowed: OS secure storage, env vars, short-lived process memory, file-descriptor handoff from parent (reference:
CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR), and managed-launcher injection (CLAUDE_CODE_OAUTH_TOKEN,ANTHROPIC_UNIX_SOCKETproxy)—provided secrets never land in §4 transactional tables. - Refresh: If OAuth refresh is implemented, refresh tokens must stay in secure storage only; refreshed access token may replace process/session memory until exit.
- Logging: Telemetry must not emit full keys or bearer tokens (reference safeguards apply).
Per SPEC_AUTHORING_RULES.md, operator chrome not enumerated in §2.7–2.8 defaults to reference parity with Claude Code. The interactive REPL must implement the built-in slash commands from reference (trace: code.claude.com/docs/en/commands and reference commands/ / dispatch), including discovery (/ menu, filter-as-you-type if reference does), unless a command is explicitly forked or omitted in §17 with a closed row (omit | replace with …).
Minimum commands that must be fully functional on first delivery (non-exhaustive; full set = reference parity minus §17 forks):
| Command | Requirement |
|---|---|
/help |
Lists or filters available commands per reference. |
/model [model] |
Changes active wire model (and provider when applicable) without process restart; effect timing matches reference (immediate). |
/login, /logout, /status
|
Satisfy §2.7 A3–A5; must not print full secrets (A5). |
/config and/or /settings
|
Operator can change model and other session-relevant preferences per reference (alias rules per reference). |
/clear (and aliases /reset, /new if reference) |
Clears conversation per reference; emits hooks (SessionEnd / SessionStart or reference-equivalent) consistent with §3. |
Forbidden: Project JSON under a dot-directory as the only supported way to switch model or provider when reference exposes /model or equivalent in-session control.
Each provider value in §2.1 must be fully operational in the one-shot binary: with valid §2.3 env (and §2.2 model_id), the operator completes at least one full turn (user → model → optional tools → persisted transcript) without undocumented side channels.
Forbidden: shipping a §2.1 enum value that cannot be selected and exercised end-to-end.
Set of event names (same as reference HOOK_EVENTS in coreSchemas.ts; order in source array may differ):
PreToolUse | PostToolUse | PostToolUseFailure | PermissionDenied | Notification | UserPromptSubmit | SessionStart | SessionEnd | Stop | StopFailure | SubagentStart | SubagentStop | PreCompact | PostCompact | PermissionRequest | Setup | TeammateIdle | TaskCreated | TaskCompleted | Elicitation | ElicitationResult | ConfigChange | InstructionsLoaded | WorktreeCreate | WorktreeRemove | CwdChanged | FileChanged
Normative HookInputSchema union order (Appendix A, stdin, and scripts/verify-sdlc-hook-order.mjs) is the z.union([...]) order under HookInputSchema, not the HOOK_EVENTS array order.
-
Env:
AGENT_SDLC_DB— absolute path to the SQLite file. -
Required:
PRAGMA foreign_keys = ON;on every connection. -
Journal:
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS schema_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
-- Init: INSERT INTO schema_meta(key,value) VALUES ('schema_version','1');PRAGMA journal_mode = WAL;
CREATE TABLE schema_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE conversations (
id TEXT PRIMARY KEY,
project_dir TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
last_active TEXT NOT NULL DEFAULT (datetime('now')),
phase TEXT NOT NULL DEFAULT 'idle'
);
CREATE TABLE sessions (
session_id TEXT PRIMARY KEY,
conversation_id TEXT NOT NULL REFERENCES conversations(id),
started_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE state (
conversation_id TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT,
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (conversation_id, key)
);
CREATE TABLE plans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
conversation_id TEXT NOT NULL,
file_path TEXT,
content TEXT NOT NULL,
hash TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
created_at TEXT NOT NULL DEFAULT (datetime('now')),
approved_at TEXT,
completed_at TEXT
);
CREATE TABLE summaries (
conversation_id TEXT PRIMARY KEY REFERENCES conversations(id),
content TEXT NOT NULL,
word_count INTEGER NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
conversation_id TEXT NOT NULL,
session_id TEXT,
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
event_type TEXT NOT NULL,
detail TEXT
);
CREATE TABLE task_ratings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
conversation_id TEXT NOT NULL,
rating INTEGER NOT NULL,
objective TEXT,
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE memories (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
title TEXT NOT NULL,
content TEXT,
keywords TEXT,
anticipated_queries TEXT,
concept_tags TEXT,
project_scope TEXT,
correction_count INTEGER DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
last_accessed INTEGER,
access_count INTEGER DEFAULT 0,
attention_score REAL DEFAULT 0.5
);
CREATE VIRTUAL TABLE memories_fts USING fts5(
title, content, keywords, anticipated_queries,
tokenize='porter unicode61'
);
CREATE TABLE transcript_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL REFERENCES sessions(session_id),
sequence INTEGER NOT NULL,
parent_entry_id INTEGER REFERENCES transcript_entries(id),
entry_type TEXT NOT NULL,
payload_json TEXT NOT NULL,
tool_use_id TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(session_id, sequence)
);
CREATE INDEX idx_transcript_session ON transcript_entries(session_id, sequence);
CREATE TABLE hook_invocations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
conversation_id TEXT NOT NULL,
hook_event TEXT NOT NULL,
hook_ordinal INTEGER NOT NULL,
matcher TEXT NOT NULL,
command TEXT NOT NULL,
tool_use_id TEXT,
tool_name TEXT,
input_json TEXT NOT NULL,
exit_code INTEGER,
stdout_text TEXT,
stderr_text TEXT,
started_at TEXT NOT NULL,
completed_at TEXT,
skipped_reason TEXT
);
CREATE INDEX idx_hook_inv_session ON hook_invocations(session_id, hook_ordinal);
CREATE TABLE tool_permission_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
tool_use_id TEXT NOT NULL,
tool_name TEXT NOT NULL,
decision TEXT NOT NULL,
reason_json TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE settings_snapshot (
scope TEXT PRIMARY KEY,
json TEXT NOT NULL,
updated_at TEXT NOT NULL
);FTS (normative): After any memories write in a transaction:
DELETE FROM memories_fts;
INSERT INTO memories_fts(title, content, keywords, anticipated_queries)
SELECT title, content, keywords, anticipated_queries FROM memories;At process start only, may read settings.json (or equivalent) once, then INSERT OR REPLACE INTO settings_snapshot(scope,json,updated_at) VALUES ('effective', :json, datetime('now')). Effective JSON may include model_config (§2.2) without secrets. Forbidden: mid-turn re-read of project settings file. During a turn, hooks and permissions read settings_snapshot only.
- One row per logical message/record in
transcript_entries. -
sequence: integer ≥ 0, strictly increasing persession_id, step 1. -
entry_typeclosed set:user|assistant|system|tool_use|tool_result|progress|attachment|internal_hook|content_replacement|collapse_commit|file_history_snapshot|attribution_snapshot|queue_operation|speculation_accept|ai_title -
payload_json: UTF-8 JSON per Appendix C.
Authoritative plan body: plans.content. Forbidden: authoritative plan text from filesystem glob of plans/ or ~/.claude/plans/. plans.file_path is non-authoritative hint only.
plans.status closed enum: draft | approved | completed | superseded. §8 transition to implement requires status = 'approved'.
Ranking/import rules: align with operator ARCHITECTURE.md where cited. Forbidden: runtime authoritative reads from shared-memory/*.md; offline import into memories allowed.
Writer model: At most one in-process writer transaction against AGENT_SDLC_DB at a time (mutex or single connection with exclusive write). Forbidden: two concurrent writers without explicit locking.
Reader connections: May be multiple read connections only if writers use BEGIN IMMEDIATE (or equivalent) so readers see consistent snapshots; recommended single connection for simplicity.
Pragmas: Every connection: PRAGMA foreign_keys=ON;, PRAGMA journal_mode=WAL;, PRAGMA busy_timeout=30000; (milliseconds, closed default).
Normative COMMIT groupings:
| Operation | Must be atomic in one transaction |
|---|---|
| Single hook tool turn | All hook_invocations rows for that tool fire + related transcript_entries (tool_result, etc.) the runtime appends for that tool_use_id before any other session observes the next model step. |
| Orchestration iteration §2.5 | After step 6–7, commit before step 4’s next model call all transcript rows and hook rows from that iteration. |
memories + FTS |
§4.3 rebuild in same transaction as triggering memories write. |
| Phase change §8 |
UPDATE conversations for phase + any related events / plans rows required by the transition rule, one COMMIT. |
Isolation: Rely on SQLite single-writer + WAL; no BEGIN nesting required beyond savepoints for internal helpers.
hook_event_name |
Match query source |
|---|---|
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied
|
tool_name |
SessionStart |
source |
Setup, PreCompact, PostCompact
|
trigger |
Notification |
notification_type |
SessionEnd |
reason |
StopFailure |
string form of error
|
SubagentStart, SubagentStop
|
agent_type |
Elicitation, ElicitationResult
|
mcp_server_name |
ConfigChange |
source |
InstructionsLoaded |
load_reason |
FileChanged |
basename of file_path
|
TeammateIdle, TaskCreated, TaskCompleted
|
(empty — only "" / * matchers) |
Matcher rules: "" or * → any. If matcher matches ^[a-zA-Z0-9_|]+$ and contains | → split on |, exact match set. Else ECMAScript RegExp (not PCRE); invalid regex → matches nothing.
Starting at 0: (1) snapshot matchers — JSON array order in settings_snapshot; per matcher, hooks[] order. (2) Plugins — plugin_id ascending Unicode; same inner order. (3) Session-scoped — insertion order with monotonic counter.
Dedup: Collapse adjacent identical (hook_event, matcher, command, shell, if_condition) keeping first. shell default bash; if_condition default "".
Hooks run one at a time ascending hook_ordinal. Fork policy: no parallel hook outcome aggregation for PreToolUse blocking semantics.
| Code | Meaning |
|---|---|
0 |
Success; if stdout starts with {, parse JSON; else plain success |
2 |
Blocking (PreToolUse / UserPromptSubmit where applicable) — §5.6 |
| other | Non-blocking failure — log, continue chain |
Must be one JSON value matching Appendix B. async: true → invalid control for SDLC (§5.8).
agg_permission ∈ unset | allow | ask | deny — merge: deny > ask > allow > unset.
agg_blocks: list of { ordinal, stderr_text } for exit 2.
- Apply
hookSpecificOutput.permissionDecisionforPreToolUseintoagg_permission. - On exit
2, appendagg_blocks. -
PreToolUse: if
agg_permission == denyoragg_blocksnon-empty → skip remaining hooks for this tool;skipped_reason = 'prior_block_or_deny'. -
Final: if
agg_blocksnon-empty → deny; message = lines"[ordinal] " + stderrsorted by ordinal. Ifdenywithout block → use hookreasonor empty. - PostToolUse / PostToolUseFailure: never skip remainder; concatenate exit-2 messages in ordinal order.
- PreToolUse chain §5.6.
-
§5.12 permission rules from
settings_snapshotonly. - On
ask, human dialog +tool_permission_logrow.
Forbidden: treating {"async":true} as valid control flow. Log validation error; exit 2 still blocks where applicable.
Default 600000 ms for PreToolUse / PostToolUse / PostToolUseFailure / UserPromptSubmit; 1500 ms for SessionEnd. Overrides: SDLC_HOOK_TIMEOUT_MS, SDLC_SESSIONEND_HOOK_TIMEOUT_MS. Timeout on PreToolUse/UserPromptSubmit → treat as exit 2; PostToolUse timeout → non-blocking, log.
SDLC_DISABLE_ALL_HOOKS=1 → skip hooks. Untrusted workspace / managed-only rules per operator policy.
shell field (closed): bash (default per §5.2 dedup) | sh | powershell.
Working directory: Child process cwd must equal hook stdin cwd (§6). Missing directory → hook non-blocking failure (log, exit_code null, stderr_text explains); do not spawn.
Invocation:
shell |
Spawn shape (POSIX) |
|---|---|
bash |
argv = ['/bin/bash', '-lc', <commandString>] — if /bin/bash missing, try /usr/bin/bash, then invoke bash from PATH. <commandString> = hook command after any implementation-defined template substitution. |
sh |
argv = ['/bin/sh', '-c', <commandString>] with same fallbacks mutatis mutandis. |
powershell |
argv = [<pwshOrPowershell>, '-NoProfile', '-NonInteractive', '-Command', <commandString>] where executable is first found of pwsh, powershell on PATH. Missing → non-blocking hook failure. |
Windows: Implementations must either (a) document and implement reference spawn rules in utils/hooks.ts (execCommandHook, Git Bash / PowerShell split) for parity, or (b) refuse bash hooks on Windows with a clear startup error (closed declaration in operator docs).
Stdin: UTF-8, no BOM. Payload = JSON object for hook (common prefix §6 + event fields) serialized without extra whitespace variants requirement — must append exactly one ASCII newline \n after the closing } before stdin.end() (reference: jsonInput + '\n').
Stdout/stderr: Decode as UTF-8; invalid bytes → U+FFFD replacement per WHATWG UTF-8 decode (closed). Max capture per stream: 4194304 bytes; if exceeded, stop reading, append \n[SDLC_OUTPUT_TRUNCATED]\n to stored stream text and complete process.
Environment: Copy entire host process environment at spawn. Set or override: AGENT_SDLC_DB (absolute path), SDLC_HOOK=1. If LANG or LC_ALL unset, set LANG=C.UTF-8 (or implementation-defined closed UTF-8 locale string). Optional reference-parity: CLAUDE_PROJECT_DIR when project root is known (absolute path).
Timeouts: §5.9.
Config shape: settings_snapshot JSON for scope effective must include optional key permissions conforming to Appendix E. If absent, treat as { "defaultMode": "default" }.
When evaluated: After §5.6 PreToolUse hook chain yields allow (no deny, no exit-2 block), and after §8.3 planning hard gate passes, before starting tool execution and before showing the human permission dialog.
Matching semantics (reference behavior): Rule strings in allow, deny, ask must be validated at ingest with the same rules as utils/settings/validation.ts filterInvalidPermissionRules / validatePermissionRule. A candidate tool call ruleMatches(rule, tool_name, tool_input) must match reference outcomes for built-ins in §7 and mcp__* names when compared against utils/permissions/permissions.ts + per-tool checkPermissions in services/tools/ (same permissions object, same parsed input), with ML/auto classifier paths disabled (defaultMode must not rely on auto — not in SDLC defaultMode enum).
Aggregate precedence (normative fork — tie-break): If reference and this clause disagree, this order applies: (1) any deny list match → deny; (2) else any ask list match → ask; (3) else any allow list match → allow; (4) else defaultMode: bypassPermissions→allow; dontAsk→deny; default→ask; acceptEdits→allow for Read|Write|Edit only, else ask; plan→deny for Write|Edit|Bash|NotebookEdit, else follow default behavior for others. (Implementers should diff against reference for acceptEdits/plan edge cases; report as fork if changed.)
Persistence: Any ask → user decision → row in tool_permission_log (§4.3).
Every stdin object must include:
| Field | Type | Notes |
|---|---|---|
hook_event_name |
string | §3 |
session_id |
string | |
conversation_id |
string | FK conversations.id
|
runtime_db_path |
string | equals AGENT_SDLC_DB
|
cwd |
string | |
permission_mode |
string? | |
agent_id |
string? | subagent |
agent_type |
string? |
Reference field omitted (fork): transcript_path — must not be sent or required. Reference: BaseHookInputSchema in coreSchemas.ts.
Read | Write | Edit | NotebookEdit | Bash | Glob | Grep | WebFetch | WebSearch | AskUserQuestion | TodoWrite | TaskOutput | Agent | Skill | EnterPlanMode | ExitPlanMode | ListMcpResources | ReadMcpResource | ToolSearch | Brief | TaskStop
MCP: mcp__<server>__<tool> (lowercase server slug).
Inputs: Each tool receives tool_name (§7) and tool_input (JSON object). Schemas reference behavior: same required/optional keys as reference tools/* Zod/schema for that tool unless this spec lists a fork.
Outputs: Map to transcript_entries tool_result (Appendix C): content UTF-8 string (model-visible), is_error boolean.
Error classification (closed):
| Class | is_error |
When |
|---|---|---|
success |
false |
Tool completed its contract (file read OK, exit 0 for Bash, HTTP 2xx for fetch where applicable). |
tool_rejected |
true |
Validation failed, path missing, HTTP 4xx/5xx, Bash exit code ≠ 0, MCP error response, subprocess signal. |
internal_failure |
true |
Host crash, timeout, IPC broken — content must include prefix [SDLC_INTERNAL]. |
Streams (fork caps): For Bash, capture stdout and stderr separately; concatenate for content as stdout + (stderr ? "\n--- stderr ---\n" + stderr : "") capped at 1048576 bytes each; if truncated suffix [SDLC_TRUNCATED].
Read/Write/Edit: content = short summary (path, line count, or error text). Binary files — reference behavior (encoding detection / hex).
Glob/Grep: content = UTF-8 listing; empty result is_error: false.
WebFetch/WebSearch: is_error: true on transport failure; success body capped at 1048576 bytes in content with [SDLC_TRUNCATED] if cut.
MCP mcp__*: JSON-RPC to server; timeout 120000 ms default (closed); is_error on timeout or error payload.
NotebookEdit, AskUserQuestion, TodoWrite, TaskOutput, Agent, Skill, EnterPlanMode, ExitPlanMode, ListMcpResources, ReadMcpResource, ToolSearch, Brief, TaskStop: Reference behavior for payloads and side effects; transcript content = user-visible summary string.
Logging: Tool internal debug must not write authoritative state outside §4; may append events rows.
Every name in the §7 built-in list (pipe-separated) must have a complete implementation: for valid tool_input per reference schema, the runtime must execute the full contract and return tool_result per Appendix C and §7.1 error classes—not not implemented, not empty stub, not permanent TODO.
MCP mcp__*: For each MCP server registered in effective settings (reference configuration shape), invocations must complete JSON-RPC per §7.1 (timeout 120000 ms default) and map success/failure to is_error per §7.1.
Interpretation of “reference behavior” in §7.1: For a given tool, outputs and side effects must match reference services/tools/* (and related) for the same parsed input unless §17 states a fork for that tool. Forbidden using “reference behavior” to justify partial parity.
Reference Claude Code plan/implement flows default for UX; this section binds persistence and hard gates so implementers do not invent policy.
conversations.phase must be exactly one of:
idle | planning | implement | test | verify | done
Authoritative field: conversations.phase only. Forbidden: treating state KV phase / sdlc_phase as authoritative unless kept strictly in sync; SDLC profile does not require a duplicate phase key in state.
Persistence mechanism: Every transition must execute UPDATE conversations SET phase = :new, last_active = datetime('now') WHERE id = :conversation_id (same column names as §4.3) in the same COMMIT as any rows that transition depends on (e.g. plans.status, events).
| From | To | Condition |
|---|---|---|
| any | planning |
Successful EnterPlanMode (or reference-parity equivalent). |
planning |
implement |
ExitPlanMode (or equivalent) and EXISTS plans row for this conversation_id with status = 'approved'. |
implement |
test |
Implementation complete; behavioral tests generated per §8.5 and executed per §8.6. Must be same transaction as any events row recording the “implementation complete” signal. |
test |
verify |
All plan-traced behavioral tests pass (§8.6 coverage gate satisfied). |
verify |
done |
Operator approves coverage report and test results (§8.7). |
done / idle
|
planning |
New plan cycle allowed per operator policy. |
Forbidden: setting implement from planning without an approved plan row.
Forbidden: transitioning directly from implement to verify or done — the test phase must not be bypassed.
When conversations.phase = 'planning' and there is no plans row for this conversation_id with status = 'approved', the runtime must deny (before tool execution, same user-visible class as hook deny) the following built-in tools:
Write | Edit | Bash | NotebookEdit
All other built-in names in §7 and all mcp__* tools may run subject to hooks and permission rules. (Hooks may further restrict; this clause is the minimum hard gate.)
Policy shell hooks should read/write only AGENT_SDLC_DB for workflow state (fork vs reference MEMORY.md as authoritative store).
When the product enters the test phase, it must generate behavioral tests that verify the implementation against the approved plan. The test generation process is mechanically constrained to prevent smoke tests, mock-the-implementation tests, and tautological assertions.
The test generator must receive only the following inputs when constructing test cases:
| Allowed | Source |
|---|---|
| Approved plan text |
plans.content where status = 'approved' for the active conversation_id
|
| Plan success criteria | Extracted from the plan's success criteria section |
| Public interface contracts | CLI entry points, tool contracts (§7), DB schema (§4.3) |
| Project public API | Entry point signatures, documented CLI flags, published schemas |
Forbidden inputs for test generation context:
- Implementation source files (contents of files created or modified during
implementphase) - Internal function signatures, module structure, or import paths
- Git diffs, code review context, or implementation commit messages
- Runtime debug output or intermediate state from the implementation process
Enforcement: The test generation context must be constructed without implementation source. This is a mechanical requirement — the product must not feed implementation file contents into the prompt, context, or retrieval scope used to generate test cases. Forbidden: generating tests in the same agent context that wrote the implementation, unless that context is provably stripped of implementation source before test generation begins.
Every test assertion must target one of these observable surfaces (closed set):
| Surface | Example assertion |
|---|---|
| DB state | SQL query against AGENT_SDLC_DB returns expected rows/values |
| File state | File exists at expected path, contains expected content, has expected permissions |
| Process output | stdout/stderr of CLI invocation matches expected patterns; exit code equals expected value |
| Tool result contract |
{content, is_error} per §7.1 for a given tool_name + tool_input
|
| Hook invocation record |
hook_invocations row exists with expected hook_event, exit_code, tool_name fields |
Forbidden assertion targets:
- Internal function return values (requires importing implementation modules)
- Object shapes or types defined in implementation source
- Private state not observable through DB, filesystem, or process output
- In-memory runtime state (variable values, object properties, closure captures)
The system under test must run its real code paths. Tests may:
- Set up controlled input fixtures (files, env vars, DB seed data)
- Invoke the product through its public entry points (CLI, tool dispatch)
- Inspect output through observable surfaces (§8.5.2)
Tests must not:
- Replace, mock, stub, or monkey-patch implementation modules or functions
- Intercept internal function calls or inject test doubles
- Use dependency injection to swap real behavior for fake behavior within the product
- Override or shadow runtime modules with test-specific replacements
External dependency exception: Network APIs and third-party services may use recorded fixtures or local test servers, but the product's own code paths must execute without substitution.
Each behavioral test case must reference a specific requirement from the approved plan's success criteria. The product must record this mapping (implementation-defined persistence — events rows, dedicated table, or structured test output).
Untraceable tests (setup utilities, teardown helpers, infrastructure checks) are permitted but do not count toward the coverage gate.
The transition from test to verify must be blocked until all of the following are satisfied:
- Every success criterion in the approved plan has ≥ 1 plan-traced test case.
- All plan-traced test cases have been executed and pass (exit code 0).
- Test results are persisted for the
verifyphase to display (§8.7).
Missing coverage for any plan requirement blocks the transition. Forbidden: transitioning to verify with uncovered plan requirements.
The verify phase is a reporting surface, not a testing gate. Test quality was enforced mechanically by §8.5; the operator reviews results, not test source.
The product must display to the operator:
- Coverage matrix: plan requirement → test case(s) → pass/fail status per case.
- Test output: stdout/stderr for each test case (actual results, not summaries).
- Uncovered requirements: any plan success criteria without a passing traced test (should be zero if §8.6.2 gate passed; shown for transparency).
- Aggregate pass rate: total traced tests, passed, failed.
Transition verify → done requires operator approval of the report.
These templates demonstrate the difference between compliant and non-compliant tests. Implementers must use these as reference patterns for the test generator's output.
Compliant — behavioral, observable-only:
# Setup: create a known file
echo -e "line1\nline2\nline3" > /tmp/sdlc_test_read_input.txt
# Exercise: invoke Read through the product's public CLI interface
ona --eval '{"tool": "Read", "input": {"file_path": "/tmp/sdlc_test_read_input.txt"}}'
# Assert against observable outcomes:
# 1. transcript_entries row exists with entry_type='tool_result', is_error=false
RESULT=$(sqlite3 "$AGENT_SDLC_DB" \
"SELECT payload_json FROM transcript_entries
WHERE entry_type='tool_result' ORDER BY sequence DESC LIMIT 1")
echo "$RESULT" | grep '"is_error":false' || exit 1
# 2. Tool content references the file content
echo "$RESULT" | grep 'line1' || exit 1
echo "$RESULT" | grep '3 lines' || exit 1Non-compliant — mocks implementation (FORBIDDEN):
// FORBIDDEN: imports implementation module directly
import { toolRead } from '../lib/executeTools.mjs'
// FORBIDDEN: calls internal function, not public interface
const result = await toolRead('/tmp/test', { file_path: 'foo.txt' })
// FORBIDDEN: asserts against internal return shape
assert.equal(result.is_error, false)Non-compliant — smoke test (FORBIDDEN):
# FORBIDDEN: only checks "doesn't crash", not behavioral contract
ona --eval '{"tool": "Read", "input": {"file_path": "/tmp/anything.txt"}}'
test $? -eq 0 # passes even if tool returned wrong contentCompliant — behavioral, DB-observable:
# Setup: conversation in implement phase
sqlite3 "$AGENT_SDLC_DB" \
"INSERT INTO conversations(id, project_dir, phase) VALUES ('test-conv-1', '/tmp', 'implement')"
# Exercise: attempt direct transition to verify (must fail — test phase required)
ona --transition verify --conversation test-conv-1 2>&1
PHASE=$(sqlite3 "$AGENT_SDLC_DB" "SELECT phase FROM conversations WHERE id='test-conv-1'")
test "$PHASE" = "implement" || exit 1 # still implement, not verify
# Exercise: transition to test (should succeed)
ona --transition test --conversation test-conv-1
PHASE=$(sqlite3 "$AGENT_SDLC_DB" "SELECT phase FROM conversations WHERE id='test-conv-1'")
test "$PHASE" = "test" || exit 1Compliant — behavioral, DB-observable:
# Setup: configure a PreToolUse hook that exits 2 (blocking)
sqlite3 "$AGENT_SDLC_DB" \
"INSERT OR REPLACE INTO settings_snapshot(scope, json, updated_at)
VALUES ('effective',
'{\"hooks\":[{\"hook_event_name\":\"PreToolUse\",\"matcher\":\"Bash\",\"command\":\"exit 2\"}]}',
datetime('now'))"
# Exercise: invoke Bash tool
ona --eval '{"tool": "Bash", "input": {"command": "echo hello"}}'
# Assert: hook_invocations row with exit_code=2
sqlite3 "$AGENT_SDLC_DB" \
"SELECT exit_code FROM hook_invocations
WHERE hook_event='PreToolUse' AND tool_name='Bash'
ORDER BY id DESC LIMIT 1" | grep '^2$' || exit 1
# Assert: transcript has tool_result with is_error=true (denied)
sqlite3 "$AGENT_SDLC_DB" \
"SELECT payload_json FROM transcript_entries
WHERE entry_type='tool_result' ORDER BY sequence DESC LIMIT 1" \
| grep '"is_error":true' || exit 1agent-sdlc-runtime/
├── cmd/agent/
├── internal/
│ ├── store/
│ ├── orchestration/ # §2 turn loop
│ ├── auth/ # §2.7–2.8 login/logout/status, secure storage adapters
│ ├── hookplane/ # §5
│ ├── transcript/
│ ├── trust/
│ ├── permissions/
│ ├── workflow/ # §8 phase transitions + planning gate
│ └── session/
├── pkg/api/
├── schema.sql # canonical DDL copy of §4.3 (optional but recommended)
└── docs/
Implementation root (this repo): agent-sdlc-runtime/ must exist as the working tree for the SDLC runtime. Forbidden: checking in or labeling a release complete while the tree contains stub implementations for any must in this spec (§0, §7.2, §18). The leaked claude-code tree remains reference only for traceability per SPEC_AUTHORING_RULES.md—not a substitute for shipping the runtime.
- Single DB; §4.3 tables present.
- No authoritative session
.jsonl. - No authoritative plan directory scan.
- Hooks sequential; ordinals + skip reasons logged.
- No valid
async:truecontrol path (§5.8). - stdin/stdout match §§6, 11–12.
- §2 model enums + env-only secrets.
- §2.7 A1–A7, O1, L1 (login/logout/status/OAuth + env keys) implemented; §2.8 never persists secrets in SQLite.
-
HookInputSchemaunion order matches Appendix A event order. - §8 phase transitions and planning-phase mutating-tool denial enforced.
- §8.5 behavioral test generation: epistemic isolation enforced (test generator cannot access implementation source).
- §8.5.2 observable-only assertions: tests assert against DB state, file state, process output, tool results, or hook records only.
- §8.5.3 anti-mock: tests exercise real code paths through public interfaces; no mocking, stubbing, or patching of implementation internals.
- §8.6.1 plan traceability: every test case traces to a plan success criterion.
- §8.6.2 coverage gate:
test → verifyblocked until all plan requirements have ≥1 passing traced test. - §8.7 verify-as-reporting: coverage matrix, test output, uncovered requirements, and aggregate pass rate displayed before
done. - §8.2 no bypass: direct
implement → verifytransition forbidden;testphase mandatory. -
pnpm exec node ../scripts/verify-sdlc-hook-order.mjspasses (or equivalent invocation from repo root). - §5.11 hook spawn, stdin newline, UTF-8, env, 4 MiB cap.
- §5.12 + Appendix E permission evaluation order.
- §7.1 tool outcomes + caps.
- §4.8 transaction groupings + single-writer rule.
- §0 — no stub/TODO/
not implementedon any normative must; no silent subsetting vs reference. - §2.9 —
/model,/help,/login,/logout,/status,/configor/settings,/clear(and reference aliases) functional; not model-switch-via-hand-edited JSON only. - §2.10 — every §2.1 provider value end-to-end invokable with §2.2–2.3.
- §7.2 — every §7 built-in tool + configured MCP tools functional per §7.1.
- Appendix F —
docs/ACCEPTANCE_MATRIX.md,docs/OPERATOR_COLD_START.md,scripts/sdlc-acceptance.shpresent; all matrix rows PASS; CI runs acceptance script on delivery branch.
Common prefix (all events): §6 fields, plus event-specific fields below.
Union order matches HookInputSchema in coreSchemas.ts.
tool_name string; tool_input unknown JSON; tool_use_id string.
tool_name; tool_input; tool_response unknown; tool_use_id.
tool_name; tool_input; tool_use_id; error string; is_interrupt boolean optional.
tool_name; tool_input; tool_use_id; reason string.
message string; title string optional; notification_type string.
prompt string.
source enum: startup | resume | clear | compact; agent_type string optional; model string optional.
reason enum: clear | resume | logout | prompt_input_exit | other | bypass_permissions_disabled.
stop_hook_active boolean; last_assistant_message string optional.
error enum: authentication_failed | billing_error | rate_limit | invalid_request | server_error | unknown | max_output_tokens; error_details string optional; last_assistant_message string optional.
agent_id string; agent_type string.
stop_hook_active boolean; agent_id string; agent_transcript_path string — SDLC: send "" (empty); reference required path; runtime ignores for authority. agent_type string; last_assistant_message string optional.
trigger enum: manual | auto; custom_instructions string nullable.
trigger enum: manual | auto; compact_summary string.
tool_name string; tool_input unknown; permission_suggestions array optional (elements: PermissionUpdate, Appendix B).
trigger enum: init | maintenance.
teammate_name string; team_name string.
task_id string; task_subject string; task_description string optional; teammate_name string optional; team_name string optional.
Same fields as TaskCreated.
mcp_server_name string; message string; mode enum optional: form | url; url string optional; elicitation_id string optional; requested_schema object optional (string keys, JSON values).
mcp_server_name; elicitation_id optional; mode optional form|url; action enum: accept|decline|cancel; content object optional (string keys, JSON values).
source enum: user_settings|project_settings|local_settings|policy_settings|skills; file_path string optional.
file_path string; memory_type enum: User|Project|Local|Managed; load_reason enum: session_start|nested_traversal|path_glob_match|include|compact; globs string array optional; trigger_file_path string optional; parent_file_path string optional.
name string.
worktree_path string.
old_cwd string; new_cwd string.
file_path string; event enum: change|add|unlink.
Top-level union:
-
Async stub (reference shape; SDLC invalid as control):
{ "async": true, "asyncTimeout": <number optional> }— must be rejected for SDLC control flow (§5.8). -
Sync object — optional fields:
continueboolean;suppressOutputboolean;stopReasonstring;decisionenumapprove|block;systemMessagestring;reasonstring;hookSpecificOutputobject.
hookSpecificOutput union order (reference SyncHookJSONOutputSchema in coreSchemas.ts) — exactly one arm or omit:
hookEventName: PreToolUse; permissionDecision optional enum allow|deny|ask; permissionDecisionReason string optional; updatedInput object optional; additionalContext string optional.
hookEventName: UserPromptSubmit; additionalContext string optional.
hookEventName: SessionStart; additionalContext string optional; initialUserMessage string optional; watchPaths string array optional.
hookEventName: Setup; additionalContext string optional.
hookEventName: SubagentStart; additionalContext string optional.
hookEventName: PostToolUse; additionalContext string optional; updatedMCPToolOutput any optional.
hookEventName: PostToolUseFailure; additionalContext string optional.
hookEventName: PermissionDenied; retry boolean optional.
hookEventName: Notification; additionalContext string optional.
hookEventName: PermissionRequest; decision object — either { "behavior": "allow", "updatedInput": {...}?, "updatedPermissions": [ PermissionUpdate, ... ]? } or { "behavior": "deny", "message"?: string, "interrupt"?: boolean }.
hookEventName: Elicitation; action optional accept|decline|cancel; content object optional.
Same discriminator fields as Elicitation.
hookEventName: CwdChanged; watchPaths string array optional.
hookEventName: FileChanged; watchPaths string array optional.
hookEventName: WorktreeCreate; worktreePath string required.
hookSpecificOutput omitted; only top-level sync fields apply.
destination enum: userSettings | projectSettings | localSettings | session | cliArg
{ "type": "addRules", "rules": [ { "toolName": string, "ruleContent"?: string } ], "behavior": "allow"|"deny"|"ask", "destination": <destination> }{ "type": "replaceRules", "rules": [...], "behavior": "allow"|"deny"|"ask", "destination": <destination> }{ "type": "removeRules", "rules": [...], "behavior": "allow"|"deny"|"ask", "destination": <destination> }{ "type": "setMode", "mode": "acceptEdits"|"bypassPermissions"|"default"|"dontAsk"|"plan", "destination": <destination> }{ "type": "addDirectories", "directories": string[], "destination": <destination> }{ "type": "removeDirectories", "directories": string[], "destination": <destination> }
(Aligned with PermissionUpdateSchema in coreSchemas.ts / permissionUpdateSchema in utils/permissions/PermissionUpdateSchema.ts.)
Include discriminator "_t" equal to entry_type (recommended).
entry_type |
Shape |
|---|---|
user |
{ "_t":"user", "uuid", "content": [ blocks ] } |
assistant |
{ "_t":"assistant", "uuid", "content": [ blocks ] } |
tool_use |
{ "_t":"tool_use", "id", "name", "input": {...} } |
tool_result |
{ "_t":"tool_result", "tool_use_id", "content", "is_error": boolean } |
system |
{ "_t":"system", "subtype", ... } |
progress |
{ "_t":"progress", "data": {...} } |
attachment |
{ "_t":"attachment", "attachment": {...} } |
| others | { "_t": <entry_type>, ... } |
Content blocks (closed):
{ "type": "text", "text": string }{ "type": "tool_use", "id": string, "name": string, "input": object }{ "type": "tool_result", "tool_use_id": string, "content": string, "is_error": boolean }
Leaked tree locations (informative): utils/hooks.ts, entrypoints/sdk/coreSchemas.ts, services/tools/toolExecution.ts, types/hooks.ts. Do not reintroduce parallel hook races, JSONL transcript authority, or async hook completion as SDLC normative behavior.
Embedded under settings_snapshot.json for scope effective, key permissions. All listed fields optional unless noted. Extra keys may be ignored (forward compatibility) or rejected — implementation-defined closed choice documented in operator runbook.
{
"allow": [ "<ruleString>", "..." ],
"deny": [ "<ruleString>", "..." ],
"ask": [ "<ruleString>", "..." ],
"defaultMode": "default | acceptEdits | bypassPermissions | plan | dontAsk",
"additionalDirectories": [ "<absoluteOrProjectRelativePath>", "..." ],
"disableBypassPermissionsMode": "disable",
"allowManagedPermissionRulesOnly": true
}ruleString: Non-empty UTF-8 string. Validation: Each element must pass reference validatePermissionRule in utils/settings/permissionValidation.ts at load time; invalid entries dropped (same spirit as filterInvalidPermissionRules in utils/settings/validation.ts).
defaultMode (closed): default | acceptEdits | bypassPermissions | plan | dontAsk. Forbidden in SDLC profile: auto (classifier mode — not supported normatively).
additionalDirectories: Used with reference path permission logic when resolving filesystem tools — reference behavior for interpretation.
Evaluation: §5.12.
Edits follow SPEC_AUTHORING_RULES.md and SEP-027 Phase 0 (process for future changes only). §17 is already closed—implementers do not wait on SEP rows to build.
These replace any “open decision” placeholders elsewhere. Implementers SHALL follow this table.
| ID | Decision | Binding choice |
|---|---|---|
| D1 | Authority for hook JSON vs reference Zod |
CLEAN_ROOM_SPEC.md is canonical for the SDLC profile. entrypoints/sdk/coreSchemas.ts and types/hooks.ts in this repository are reference trace only. On conflict, this spec wins on items marked fork policy; on all other hook fields, this spec matches reference union order and shapes. CI should run scripts/verify-sdlc-hook-order.mjs. |
| D2 | Multi-hook PreToolUse exit 2 / permission merge vs reference |
Fork: §5.6 (skip remainder on deny/block; concatenate block messages; dominance merge). Not reference race / last-hook-wins. |
| D3 | Transcript one-shot level |
Fork: Authoritative transcript is transcript_entries + Appendix C only. No authoritative session JSONL. entry_type and content blocks listed in Appendix C are the closed persistence set for those rows; additional entry_type values in the table are opaque envelope payload_json with _t discriminator. |
| D4 | Workflow policy document |
Fork: §8 is the normative workflow and planning gate for this profile. There is no separate required ARCHITECTURE.md import. Operator hooks may add stricter policy via DB only. |
| D5 | One-shot = usable product |
Binding: A one-shot pass must yield an operator-usable agent (login, in-session model change, full §7 tool surface, all §2.1 providers live). Forbidden claiming “spec complete” with partial REPL commands, missing /model, or stub tools (§0, §2.9–2.10, §7.2). |
| D6 | Ambiguity & silence | Forbidden using undefined “MVP,” “phase 2,” or spec silence to omit reference UX or §7 tools. Only explicit fork policy paragraphs or §17 rows may waive parity; waiver must name the closed replacement or omit with testable acceptance. |
| D7 | DELIVERED vs interpretation | DELIVERED iff Appendix F is satisfied. Forbidden claiming delivery when any matrix row fails, when cold-start steps exceed §F.3, or when open spec-must items remain (§F.4). Reviewer discretion must not override failed automated acceptance. |
Environment: AGENT_SDLC_DB (required), provider env per §2.3, optional SDLC_* timeouts §5.9.
Build order (all in agent-sdlc-runtime/ unless noted):
-
store— apply §4.3 DDL (schema.sqlcopy optional); migrations +schema_meta; §4.8 pragmas + writer mutex. -
transcript— append/readtranscript_entriesper §4.5, Appendix C. -
session/workflow— conversations, sessions, §8UPDATE conversations.phase+ planning gate §8.3 before mutating tools. -
hookplane— §5 through §5.11 + persisthook_invocations. -
orchestration— §2 turn loop + §7.1 tool runner. -
permissions/trust— Appendix E ingest, §5.12, §5.7, §5.10. -
cmd/agent— wire reference-parity interactive REPL: §2.9 slash-command surface (including/model,/help,/clear,/config//settings) plus §2.7–2.8 auth; forbidden shipping only a minimal slash subset. Substrate above must be live. - Acceptance artefacts — implement Appendix F files under the implementation root; wire CI per Hard requirements item 6.
Definition of done: DELIVERED per §0.5 and Appendix F. Concretely: §10 100% checked; scripts/sdlc-acceptance.sh exits 0; operator can follow docs/OPERATOR_COLD_START.md (≤ 12 steps, §F.3) from clean checkout to first successful model turn without patching runtime source. Forbidden: calling the build “done” if any matrix row fails, any must is stubbed, or continued implementation is required for normative behavior.
This appendix defines the only admissible sign-off for one-shot delivery. If any clause below is unsatisfied, DELIVERED is false.
| Artefact | Purpose |
|---|---|
docs/ACCEPTANCE_MATRIX.md |
Closed mapping: spec obligation → objective PASS criterion (script + args + expected exit 0, or exact SQL/file assertion). Forbidden cells: “manual review,” “TBD,” “smoke test,” “nightly only.” |
docs/OPERATOR_COLD_START.md |
Numbered steps only (see §F.3). Each step is one shell command or one UI action—forbidden “configure your environment” without the exact env keys listed. |
scripts/sdlc-acceptance.sh |
Runs every matrix row in a documented order; exits 0 iff all PASS; exits non-zero on first FAIL with stderr naming the matrix row id. |
docs/ACCEPTANCE_MATRIX.md must contain at least one PASS row (each with unique row id) for:
- Every item in §10 (checklist id = matrix row or explicit closed rollup row referencing sub-scripts).
- Every built-in name in §7 (one row per tool).
-
Every
providerenum in §2.1. -
Every minimum slash command in §2.9 table (including
/model). - Every capability id A1–A7, O1, L1 in §2.7.
- Every forbidden in §0 and §2.8 that is machine-checkable (e.g. grep for secrets in DB dumps)—or a closed row stating N/A with objective justification (e.g. “static analysis script X proves no INSERT into transcript for api key pattern”).
-
Every behavioral test requirement in §8.5–8.7: one row proving epistemic isolation (test generator context excludes implementation source), one row proving anti-mock (tests use no internal imports/stubs), one row proving plan traceability (each test maps to a plan requirement), one row proving the
test → verifygate blocks on failure, one row provingimplement → verifydirect transition is rejected. - Additional rule: Any must / shall / forbidden added to this spec in future revisions must gain a matrix row before DELIVERED may be claimed for that revision.
docs/OPERATOR_COLD_START.md must contain ≤ 12 numbered steps from clean clone of the implementation root through first successful end-to-end model turn (user input → assistant output persisted in transcript_entries). Steps must not require editing files under internal/, cmd/, or equivalent runtime source—only env, auth, documented CLI, and one-time schema apply if applicable.
Forbidden tagging the build DELIVERED while the issue tracker (or closed equivalent process named in the matrix) contains any open item of type spec-must (or the closed label named in the matrix header) that maps to a must in this spec. Merge / release gates must block on failed §F.1 script or open spec-must.
Human review may reject on policy grounds in addition to automation; human review must not waive a failed sdlc-acceptance.sh run or a missing §F.2 row. DELIVERED is a logical AND of automation and artefact completeness—not a vote.
End of specification.