## [2026-05-19] SKILL-48 subtask-3 per-repo-schema-customization
Areas: orchestration/contracts, runtime-kotlin/runtime-core/scaffold, runtime-kotlin/runtime-domain/scaffold/model, AGENTS.md
- `orchestration/contracts/platform-pack-schema.yaml` now treats the TOP-LEVEL mapping as the per-repo extension surface: `additionalProperties: true` at root. Every TOP-LEVEL field the Kotlin runtime consumes by name carries `x-runtime-anchored: true` (10 total: `platform`, `contract_version`, `display_name`, `notes`, `routing_signals`, `declared_code_review_areas`, `declared_files`, `area_metadata`, `declared_quality_check_file`, `pointers`). Nested objects (`routing_signals`, `declared_files`, `area_metadata.<area>`, `$defs.codeReviewArea`, `pointers` entries) stay strict (`additionalProperties: false`) — per-repo extensions only relax the top-level mapping. reusable: the `x-runtime-anchored` marker is exclusive to platform-pack-schema; other runtime contracts (telemetry/workflow/install/native-agent) stay runtime-anchored end-to-end.
- New file-level helper in `PlatformPackSchemaValidator.kt`: `internal fun anchoredTopLevelFieldNames(): Set<String>` delegating to a file-private `ANCHORED_TOP_LEVEL_FIELD_NAMES: Set<String>` `by lazy { ... }`. Single parse of the bundled YAML; lazy init throws `InvalidManifestSchemaError` if the schema is missing the top-level `properties` node. Schema is the single source of truth — Kotlin never hardcodes the anchored set. reusable: any future schema that needs a "list of anchored property names" can mirror this lazy + JsonNode.asBoolean(false) pattern.
- `PlatformManifest` (`runtime-kotlin/runtime-domain/src/main/kotlin/skillbill/scaffold/model/ScaffoldModels.kt`) gains `customFields: Map<String, Any?> = emptyMap()`. Default keeps the only construction site (`ShellContentLoader.buildPack`) and every named-arg consumer compiling unchanged. Intentionally untyped per the parent spec — pack authors get raw SnakeYAML-parsed values, no code-gen.
- `validateAgainstCanonicalSchema` in `ShellContentLoader.kt` now RETURNS the validated `Map<String, Any?>` (the typed manifest map). `buildPack` reuses it to derive `customFields = typedManifest.filterKeys { it !in anchoredKeys }`. This avoids re-walking the raw `Map<*, *>` and re-doing the non-string-key shape check.
- A5(b) is enforced in Kotlin, not the schema. Required anchored fields (`platform`, `contract_version`, `routing_signals`, `declared_code_review_areas`) catch typos via JSON Schema `required`; OPTIONAL anchored fields (`display_name`, `notes`, `declared_files`, `declared_quality_check_file`, `area_metadata`, `pointers`) would otherwise slip silently into `customFields` because top-level `additionalProperties` is now `true`. File-private `guardAgainstAnchoredFieldTypos` in `ShellContentLoader.kt` walks every customFields key against the anchored set and loud-fails with `InvalidManifestSchemaError("...top-level field '<key>' that looks like a typo of the anchored field '<anchored>' (did you mean '<anchored>'?)...")`. Edit-distance computed by file-private `levenshtein1` split into `substitutionMatches` + `insertionOrDeletionMatches` helpers to satisfy detekt `NestedBlockDepth`/`ReturnCount` at root (no suppressions). reusable: typo-guard pattern fits any future "additive open object" schema where named keys remain reserved.
- Bijection test `PlatformPackSchemaAnchoredBijectionTest` (mirrors `TelemetryEventInputSchemaParityTest` two-direction shape) curates the Kotlin side via an `expectedAnchoredFields` constant rather than reflecting `PlatformManifest` properties — YAML snake_case vs Kotlin camelCase + derived properties (e.g. `routedSkillName`) make 1:1 reflection brittle. Failure message names the asymmetric difference (which side is missing what). reusable: curated-constant + two-direction parity is the right fit when a mechanical mapping is non-trivial.
- A5(b) coverage: TWO tests in `PlatformPackSchemaViolationsTest.kt` — `SKILL-48 nested anchored block typo fails loudly` (typo `baselin` under `declared_files`; fires via nested `additionalProperties: false`) and `SKILL-48 Subtask 3 typo on anchored top-level field fails loudly with field path` (top-level `declared_filez:`; fires via the Kotlin Levenshtein guard, message asserts BOTH the offending key AND the suggested anchored field). Defense in depth: keep both.
- `PlatformPackCustomFieldsRoundTripTest` covers A5(a). Fork-specific keys `custom_thing` (nested map) and `another_custom` (string) round-trip via `customFields` verbatim. The boring path (stock manifest → empty `customFields`) is also pinned to guard against accidental anchored leak.
- AGENTS.md "Governed platform packs" section now documents the per-repo customization contract inline (single bullet — covers top-level extension, x-runtime-anchored, customFields, loud-fail on optional-anchored typos, nested strictness, intentional untyped Map). Pairs with subtask-2's "every schema under orchestration/contracts/" rule in the same section.
- Reviewer-deferred Nits (none blocking): F-004 (`PlatformPackCustomFieldsRoundTripTest.newTempPackRoot` doesn't register cleanup — switch to `@TempDir`), F-005 (`PlatformPackSchemaAnchoredBijectionTest.expectedAnchoredFields` could be derived by grepping `buildPack` source). Reviewer triage record: rvw-20260519-174330-s48c.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-05-19] SKILL-48 runtime-contracts-2d telemetry-event-schema
Areas: orchestration/contracts, runtime-kotlin/runtime-mcp/mcp, runtime-kotlin/runtime-contracts/error, runtime-kotlin/runtime-mcp build, runtime-kotlin/runtime-mcp tests
- FIFTH runtime contract under the same architecture as 2a/2b/2c: `orchestration/contracts/telemetry-event-schema.yaml` (Draft 2020-12) is the SSOT for every MCP `tools/call` envelope. `TELEMETRY_EVENT_CONTRACT_VERSION` ("1.0.0") + `TelemetryEventSchemaPaths` (`runtime-kotlin/runtime-mcp/src/main/kotlin/skillbill/mcp/TelemetryEventSchemaPaths.kt`) pin the const; `TelemetryEventSchemaContractVersionTest` pins the constant ↔ top-level YAML `contract_version` ↔ EXPECTED_SCHEMA_ID ↔ every per-branch `contract_version.const`. reusable
- Validator placement DEVIATES from 2a/2b/2c: `TelemetryEventSchemaValidator` (`runtime-kotlin/runtime-mcp/src/main/kotlin/skillbill/mcp/TelemetryEventSchemaValidator.kt`) lives in `runtime-mcp` because `McpToolRegistry.toolNames` + `inputSchemas` is the source-of-truth for event names and the parity test must co-locate. Gradle `copyTelemetryEventSchema` therefore lives in `runtime-mcp/build.gradle.kts`. Side effect: outbound emit seams in `runtime-application`/`runtime-infra-*` cannot reuse this validator without inverting the module graph — schema validates *MCP envelopes*, not *outbound telemetry*. Worth a follow-up to lift validator + Paths into `runtime-contracts` (typed error already lives there) if a non-MCP emitter ever needs to validate.
- 37-event discriminated-union pattern: top-level `oneOf` + `$defs/<event>Event` keyed on `event_name.const` (mirrors 2a's workflow_name-keyed precedent). Strict events mirror `McpToolRegistry.inputSchemas` exactly; events that fall through to `openObjectSchema()` get a branch with `additionalProperties: true`; passthrough events declare properties+required even though additionalProperties is open. Top-level `additionalProperties: false` is INTENTIONALLY OMITTED: per-branch consts + oneOf+discriminator enforce shape, and a top-level closed-object would double-reject payload fields already accepted by a branch. Documented in `x-coherence-checks`. reusable
- Parity test (`TelemetryEventInputSchemaParityTest`) walks `McpToolRegistry.tools` DYNAMICALLY (no hard-coded count) in BOTH directions — Kotlin→YAML (every tool has a branch) and YAML→Kotlin (every branch maps to a known tool). Enforces event_name const + additionalProperties policy + property keys + required keys. Per-property TYPE equivalence is NOT enforced (documented tradeoff); the `TelemetryEventSchemaValidatesAllEventsTest` representative-payload check catches type drift incidentally via networknt validation.
- Classpath-shadow guard CANNOT extend `PlatformPackSchemaCleanupTest.kt` (the runtime-core canonical home) because runtime-core does not depend on runtime-mcp and importing the validator would invert the module graph. Resolution: sibling `TelemetryEventSchemaCleanupTest.kt` in `runtime-mcp/src/test`. The pattern now lives in two places — runtime-core's cleanup test for schemas in `runtime-core`/`runtime-domain`; runtime-mcp's cleanup test for schemas owned by `runtime-mcp`. reusable: cleanup-test placement follows the validator's home module.
- McpStdioServer catch ladder WIDENED uniformly: removed the narrow `InvalidTelemetryEventSchemaError` arm; added `catch (ShellContentContractException)` parent arm (subsumes the four typed schema errors — install-plan, workflow-state, native-agent-composition, telemetry-event) + final defensive `catch (Exception)`. `Throwable` was rejected via review F-101: java.lang.Error subtypes (OOM, NoClassDefFoundError, AssertionError) and future coroutine `CancellationException` must propagate to crash the process. reusable: this is now the canonical catch ladder for the MCP stdio loop.
- Unified argument-shape error contract: `validateStrictArguments` failures (formerly JSON-RPC `-32602 INVALID_PARAMS`) now route through MCP `isError=true` (200-OK with error payload), matching the schema validator's surface. Both paths produce the same transport shape for any "request structurally invalid for the named tool" fault. Documented in `x-coherence-checks.argument-shape-failures-surface`; `-32602` is now reserved for transport-level protocol violations only. reusable
- F-002 audit (committed as comment near `TelemetryEventSchemaValidator.validate(...)` in `McpToolDispatcher.kt`): every in-tree native emitter supplies all required schema fields. The dispatcher has exactly ONE production caller (`McpStdioServer.callToolResult`) which threads JSON-RPC arguments straight through; handler-level defaults (`arguments.int(name, 0)`, `arguments.string(name) -> ""`, `arguments.boolean(name) -> false`) construct typed models AFTER schema validation already accepted the payload — they don't mask missing required keys from real emitters. Loud-fail at the dispatcher seam is safe without a WARN-and-pass phase.
- `loadSchema()` wraps any non-typed cause (`JsonParseException`, `IOException`, networknt compile failure) in `InvalidTelemetryEventSchemaError(fieldPath="<root>", eventName=null, reason=..., cause=original)` so a corrupted classpath YAML surfaces as a typed error through the widened catch ladder rather than dying on the stdio loop. Detekt rule `InstanceOfCheckForException` forced splitting the `try { ... } catch (Throwable) { if (is InvalidTelemetryEventSchemaError) ...}` into two typed catch blocks (typed rethrow first, generic wrap second). reusable: any future schema validator should mirror this two-arm wrapping pattern.
- RuntimeArchitectureTest enforces NO `java.nio.file.Files` usage in `runtime-mcp` adapters: the validator relies solely on the classpath resource bundled by `copyTelemetryEventSchema`; the on-disk schema-walk fallback was removed. KDoc that incidentally mentioned the banned FQN was rephrased because the architecture test scans source text including comments. reusable: rule applies to any future MCP-adapter-owned validator.
- Reviewer-deferred Minor/Nit follow-ups (none blocking): F-005 (telemetryEnvelope strips reserved keys from envelope but not arguments — asymmetric stripping), F-006 (parity test short-circuits for passthrough additionalProperties:true), F-007 (per-field enum sets not compared; validates-all picks enum.first()), F-008 (no end-to-end dispatcher-seam negative test), F-009 (formatValidationReason iterates violations uncapped — exception message can balloon), F-010 (unknown-event_name assertion uses near-tautological "schema" signal), F-011 (open-event additionalProperties:true never exercised in a test), F-012 (assertIdentity doesn't check per-branch contract_version.const), F-013 (validator not warmed at process start), F-014 (validator placement vs outbound emit seams), F-015 (cleanup-test pattern split across two files), F-016 (OpenAPI-3 `discriminator` keyword is ignored by networknt — move to `x-discriminator` or document), F-017 (open events lack documentation hints), F-018 (InvalidTelemetryEventSchemaError message embeds free-form networknt reason; not version-stable), F-019 (loadSchema YAML→JSON-string round-trip), F-020 (Regex compiled per-segment on failure path), F-021 (cleanup test does not exercise real loadSchema classpath path), F-022 (x-coherence-checks named rules not contract-tested), F-102 (lazy SEVERE log claim of boot-time signal is inaccurate — fires on first validate). Reviewer triage record: rvw-20260519-162500-a2d4.
Feature flag: N/A
Acceptance criteria: 10/10 implemented

## [2026-05-19] SKILL-48 runtime-contracts-2c native-agent-composition-schema
Areas: orchestration/contracts, runtime-kotlin/runtime-core/nativeagent, runtime-kotlin/runtime-contracts/error, runtime-kotlin/runtime-core build, runtime-kotlin/runtime-core scaffold tests
- FOURTH runtime contract under the same architecture as 2a/2b: `orchestration/contracts/native-agent-composition-schema.yaml` (Draft 2020-12) is the SSOT for the native-agent envelope. `NATIVE_AGENT_COMPOSITION_CONTRACT_VERSION` ("0.1") + `NativeAgentCompositionSchemaPaths` (`runtime-kotlin/runtime-core/src/main/kotlin/skillbill/nativeagent/NativeAgentCompositionSchemaPaths.kt`) pin the const; parity test pins schema const ↔ Kotlin constant ↔ EXPECTED_SCHEMA_ID. reusable
- Validator placement DEVIATES from 2a/2b: `NativeAgentCompositionSchemaValidator` (`runtime-kotlin/runtime-core/src/main/kotlin/skillbill/nativeagent/NativeAgentCompositionSchemaValidator.kt`) lives in `runtime-core` (NOT `runtime-domain`) because its only callers are `parseNativeAgentBundle` / `parseNativeAgentSourceFile` already in runtime-core; matches the in-module `PlatformPackSchemaValidator` precedent. Gradle `copyNativeAgentCompositionSchema` therefore lives in `runtime-core/build.gradle.kts` next to `copyPlatformPackSchema` — Copy-task placement rule: live in the same module as the validator that loads the classpath resource. reusable
- DUAL-SEAM rationale DIVERGES from 2b: 2b validated at builder + CLI emission (two emission seams); 2c validates at two SOURCE-format seams — bundle YAML (`parseNativeAgentBundle`) and single-md frontmatter (`parseNativeAgentSourceFile` → `parseNativeAgentSourceText`). Two on-disk wire formats, one schema, two parse paths.
- Schema dual-shape via top-level `oneOf` + `$defs/agentEntry`: Branch A = bundle `{agents: [agentEntry, ...] (minItems 1), contract_version?}`, Branch B = single-md frontmatter (inlined agentEntry + optional contract_version). Dispatch is reliable: both branches use `additionalProperties: false`; bundle envelope can only match A, single-md can only match B, ambiguous documents fail "matches 0 of 2".
- CRITICAL DESIGN DECISION (Option a): top-level `contract_version` is OPTIONAL on every envelope (not in `required`); the `const` only enforces value when the key is present. Existing fixtures across `skills/**/native-agents/` and `platform-packs/**/native-agents/` carry NO `contract_version` today and validate clean. `parseSimpleFrontmatter` whitelist in `NativeAgentSource.kt` was expanded to accept `contract_version` so future writes including it don't get rejected by the parser. Schema describes what is emitted today. reusable: "optional-pin, const-when-present" pattern is the right fit for migrating an existing on-disk format toward a runtime contract without forcing a fleet rewrite.
- VALIDATOR-RUNS-AFTER-MANUAL-CHECKS deviation: schema validation fires AFTER existing `require(...)` checks in both seams (defense-in-depth backstop), NOT before. Rationale: preserves 12 existing parser tests that pin `IllegalArgumentException` messages naming the offending key. Today the typed `InvalidNativeAgentCompositionSchemaError` is observable in practice only on `contract_version` const drift (every other envelope violation trips the manual `require` first). Dedicated `NativeAgentCompositionSchemaViolationsTest` bypasses the manual checks and calls the validator directly so AC4/AC5 typed-error contracts are still proven. Open question: should AC4 expect the typed error on every envelope violation, or is "schema-layer = contract_version-only backstop" the intended semantics? Worth a one-line AGENTS.md policy.
- SINGLE-MD seam asymmetry: bundle seam passes raw YAML text to the validator; single-md seam validates a hand-built `JsonNode` constructed from the parsed frontmatter map via `validateParsedNode`. Necessary because the simple line-by-line frontmatter parser tolerates descriptions containing colons that Jackson's general YAML parser would reject as ambiguous. Side effect: schema's `additionalProperties: false` on Branch B can never catch an unknown frontmatter key the schema doesn't already know about — the parser's manual whitelist is the only defense. Two parallel sources of truth for "allowed single-md keys" — keep them in lockstep. reusable: when a custom parser deliberately accepts looser input than a general YAML parser would, the schema layer cannot police what the parser never surfaces.
- Error class `InvalidNativeAgentCompositionSchemaError` (`runtime-kotlin/runtime-contracts/src/main/kotlin/skillbill/error/ShellContentContractErrors.kt`) mirrors `InvalidInstallPlanSchemaError`'s shape (`sourceLabel`/`fieldPath` + `reason` + cause) — typed properties, not just message.
- Gradle `copyNativeAgentCompositionSchema` reuses the F-101 String-capture + `inputs.file` + `doFirst { require(File(path).exists()) }` pattern from 2a/2b; configuration cache reused on re-run. Schema auto-listed in desktop "Contracts" tree via 2a's `Files.list(orchestration/contracts/*.yaml)` — no desktop code edit needed.
- Classpath-shadow guard block added to `PlatformPackSchemaCleanupTest.kt` (NOT a new file) — that file is now the canonical home for per-schema shadow assertions across all four runtime contracts. reusable: extend, don't duplicate.
- Schema enum strings quoted (`- "governed-content"`) per 2b's SnakeYAML YAML-1.1 boolean-coercion pitfall. Continues to apply to any new enum in `orchestration/contracts/*.yaml`.
- Reviewer-deferred Minor/Nit follow-ups (none blocking): F-001 (single-md JsonNode bypass risk vs `additionalProperties: false`), F-002 (AC4 typed-error coverage = contract_version-only in practice — needs AGENTS.md policy), F-003 (Branch A/B inline property parity test), F-004 (per-violation tests for `name` regex / `description` minLength / `agents` minItems), F-005 (direct-validator pass in `NativeAgentCompositionValidatesExistingBundlesTest` to disambiguate parser vs schema failures), F-006 (single-md missing-name violation case), F-007 (extract shared `SchemaLoader<E>` helper to deduplicate across the three validators), F-008/F-009 (document Copy-task placement and validator-ordering policies once centrally), F-010 (strengthen `unknown_top_level_property` assertion), F-011 (require fixtures to quote `contract_version: "0.1"` to avoid cross-envelope Double-vs-string divergence).
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-19] SKILL-48 runtime-contracts-2b install-plan-schema
Areas: orchestration/contracts, runtime-kotlin/runtime-domain/install, runtime-kotlin/runtime-contracts/error, runtime-kotlin/runtime-cli/install, runtime-kotlin/runtime-core/install, runtime-kotlin/runtime-domain build
- THIRD runtime contract landed under the same architecture as 2a: `orchestration/contracts/install-plan-schema.yaml` (Draft 2020-12) is the SSOT for the install-plan wire payload. `INSTALL_PLAN_CONTRACT_VERSION` + `InstallPlanSchemaPaths` (`runtime-kotlin/runtime-domain/src/main/kotlin/skillbill/install/model/InstallPlanSchemaPaths.kt`) pin the const; parity test pins schema const ↔ Kotlin constant. reusable
- Validator placement mirrors 2a: `InstallPlanSchemaValidator` (`runtime-kotlin/runtime-domain/src/main/kotlin/skillbill/install/model/InstallPlanSchemaValidator.kt`) lives in `runtime-domain` next to `InstallModels.kt`. Module-public `assertIdentity(yamlText)` enables cross-module shadow-guard tests; classpath-shadow guard rejects mismatched `$id` or `contract_version.const`. Bounded WARN log + SEVERE on schema-load failure preserved.
- Shared wire-map helper `buildInstallPlanWireMap` (`runtime-kotlin/runtime-domain/src/main/kotlin/skillbill/install/model/InstallPlanWireMap.kt`) is the single source of the install-plan wire shape. DELIBERATELY DIVERGES from 2a single-seam pattern: validates at BOTH `buildInstallPlan` (`runtime-kotlin/runtime-core/src/main/kotlin/skillbill/install/InstallPlanBuilder.kt`) AND CLI emission (`installPlanPayload` in `runtime-kotlin/runtime-cli/src/main/kotlin/skillbill/cli/InstallCliPayloads.kt`) per AC4 — decision recorded in `runtime-kotlin/agent/decisions.md`. Adds `contract_version` to the wire shape — observably the only key change. reusable
- Schema YAML pitfall: SnakeYAML follows YAML 1.1 and coerces bare `off`/`on`/`yes`/`no` to booleans, so an enum authored as `- off` advertises `[..., false]` while the runtime emits the string `"off"` — silent contract drift that loud-fails only at validation. ALWAYS quote enum string values in `orchestration/contracts/*.yaml` (`- "off"`). reusable
- Error class `InvalidInstallPlanSchemaError` (`runtime-kotlin/runtime-contracts/src/main/kotlin/skillbill/error/ShellContentContractErrors.kt`) carries `fieldPath` + `reason` as typed properties (not just message), so tests can assert the offending location without parsing strings.
- Gradle `copyInstallPlanSchema` Copy task in `runtime-kotlin/runtime-domain/build.gradle.kts` reuses the configuration-cache-friendly `inputs.file + doFirst` pattern from 2a; both `processResources` and `processTestResources` now depend on it. Schema auto-listed in desktop "Contracts" tree via 2a's `Files.list(orchestration/contracts/*.yaml)` — no desktop code edit needed.
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-19] SKILL-48 runtime-contracts-2a workflow-state-and-auto-listing
Areas: orchestration/contracts, runtime-kotlin/runtime-domain/workflow, runtime-kotlin/runtime-contracts/error, runtime-kotlin/runtime-application/workflow, runtime-kotlin/runtime-desktop/core/data, runtime-kotlin/runtime-domain build
- Extended the platform-pack schema architecture to a SECOND runtime contract: `orchestration/contracts/workflow-state-schema.yaml` (Draft 2020-12) is now the SSOT for `WorkflowStateSnapshot`; `WORKFLOW_STATE_CONTRACT_VERSION` pins it; parity test pins schema const ↔ Kotlin constant ↔ `FeatureImplement/VerifyWorkflowDefinition.contractVersion` (three sources). reusable
- Per-skill enum divergence handled via JSON Schema `oneOf` keyed on `workflow_name` const, not flat enum. FeatureImplement (12 steps / 6 statuses incl. `blocked`) and FeatureVerify (8 steps / 5 statuses) each get a `$defs` branch. Adding a third workflow family later requires editing three coupled sites (top enum + new $defs branch + new oneOf entry). reusable
- Validator placement DEVIATES from the platform-pack mirror: `WorkflowStateSchemaValidator` lives in `runtime-domain/workflow` (not `runtime-core/scaffold`) because `WorkflowEngine` is its primary consumer and `runtime-core` already api-depends on `runtime-domain`. Tests still live in `runtime-core/src/test/kotlin/skillbill/scaffold/` via existing `testFixtures` capability. `assertWorkflowStateSchemaIdentity` is module-public (not internal) for cross-module shadow-guard test access.
- F-101 follow-up adopted day one: `runtime-domain/build.gradle.kts` Copy task uses `inputs.file(schemaPath)` + `doFirst { require(file.exists()) {...} }` — NO configure-time `File.exists()` reads. Configuration cache reused on every run. reusable: this is now the canonical pattern for any future `orchestration/contracts/*.yaml` Copy task.
- Desktop auto-listing: `RuntimeRepoBrowserService.loadContracts` switched from a hard-coded single leaf to `Files.list(orchestration/contracts/*.yaml)` sorted alphabetically, with label derivation by suffix-strip + dash-replace + title-case of first character. Wrapped in `runCatching` so an IO failure on the contracts dir degrades ONLY the Contracts group, not the whole repo tree. New YAMLs surface automatically with no code change. reusable
- Observability: validator emits a bounded WARN log on validation failure (slug + first 1-2 dotted field paths + offending values + violation count) before throwing `InvalidWorkflowStateSchemaError`, plus SEVERE on schema-load failure naming the classpath resource. Loud-fail preserved; payload bodies never logged.
- Backward-compat: read-seam loud-fail intentionally rejects pre-existing durable rows whose `workflow_status`/`current_step_id` no longer match the new per-skill enums; operators recover by deleting/migrating affected rows. AGENTS.md D2 B-half rule paragraph + `WorkflowRecordMapping.toSnapshot` KDoc document the recipe.
- Pre-existing bug surfaced and fixed in `FeatureVerifyWorkflowRuntimeTest`: it was constructing `workflow_status="blocked"` for the verify family which has no such status. Rewritten to `workflow_status="running"` with `step.status="blocked"` (the actual scenario the test exercises — continueDecision reopening a blocked step). No production code path produces the invalid combination; the validator now prevents future regressions.
- `WorkflowRecordMapping.toSnapshot` is intentionally non-validating; every consumer in `WorkflowService` (open/update/get/list/latest/resume/continue) funnels through `WorkflowEngine.{full,summary,resume}Payload` which validates BEFORE returning. KDoc documents the seam-coverage invariant.
- Reviewer-deferred Minor/Nit follow-ups: F-001 (drop two internal const-val aliases mirroring PlatformPackSchemaValidator anti-pattern), F-002 (architecture test pinning seam-coverage invariant), F-004 (parity test pinning workflow_name enum ⊇ oneOf branch consts), F-204 (assertContains dotted path on more violation tests), F-302 narrow runCatching catch from Throwable to Exception, F-501 E2E unreadable-dir test for Contracts group degradation, F-502 narrow loadSchema catch from Throwable to Exception.
Feature flag: N/A
Acceptance criteria: 9/9 implemented

## [2026-05-19] SKILL-48 skill47-cleanup
Areas: runtime-kotlin/runtime-core/scaffold, runtime-kotlin/runtime-domain/scaffold, runtime-kotlin/runtime-desktop (core/data, feature/skillbill), orchestration/contracts, runtime-kotlin/runtime-core build
- Cross-boundary validator signature tightened: `PlatformPackSchemaValidator.validate(parsedYaml: Map<String, Any?>, slug: String)` replaces the legacy `Any?` form; `ShellContentLoader.validateAgainstCanonicalSchema` now loud-fails (reusing `InvalidManifestSchemaError`) on non-String top-level YAML keys before reaching the validator. Pattern: push shape/type invariants into the SHAPE-check helper at the boundary, not into the next layer.
- New classpath-shadow guard: `assertSchemaIdentity` (called from the lazy schema load) verifies the loaded YAML's `$id` matches `PlatformPackSchemaPaths.EXPECTED_SCHEMA_ID` AND its `contract_version.const` matches the Kotlin `SHELL_CONTRACT_VERSION`. Either mismatch throws `InvalidManifestSchemaError` naming both values. Downstream JARs shipping a stale classpath copy now loud-fail at first validator use. reusable: same `EXPECTED_*` + `$id`+`const` pattern transfers to any future schema bundled via classpath.
- Build-time guard: `runtime-core/build.gradle.kts` configure-time `require(canonicalPlatformPackSchema.exists())` makes a misconfigured build fail before any task runs (caveat: this is a configuration-cache-unfriendly `File.exists()` read; a follow-up should move it to `copyPlatformPackSchema.doFirst {}` or declare it as a tracked `inputs.file` — see F-101 / SKILL-48 follow-ups).
- Schema-vs-Kotlin SSOT split clarified (C1): deleted the duplicated Kotlin `content.md`-suffix and pointer-name `.md`/`..`/`/` checks; kept `requireSafePointerSubpath` and `requireSafePointerTarget` because the schema does NOT express absolute-vs-relative or full path/`..`-segment semantics. Each kept guard now carries a comment explaining the duplication rationale. reusable: shape stays in YAML, semantic invariants stay in Kotlin with documented "why-kept" comments.
- `declared_code_review_areas` gets `uniqueItems: true`; duplicate areas are now rejected at the schema layer rather than silently deduped downstream via `toSet()`.
- Shared test helper consolidated via `java-test-fixtures` on runtime-core: `skillbill.testing.repoRootFromTest()` (in `src/testFixtures/kotlin/skillbill/testing/RepoRoot.kt`) replaces five local copies. runtime-desktop KMP leaf modules (core/data, feature/skillbill) consume it through the `dev.skillbill:runtime-core-test-fixtures` capability dependency — the `testFixtures(project(...))` shorthand is unavailable under the KMP DSL. reusable: capability-dep pattern for any future test-support shared across runtime-core ↔ KMP-leaf modules.
- KDoc on `PlatformManifest.routedSkillName` and `DeclaredFiles.baseline` documents the intentional nullable contract: nullable means absent/not-applicable at the manifest layer; consumers MUST NOT re-narrow (no `!!`, no platform-side defaulting hiding absence).
- Reviewer-deferred follow-ups (all Minor; not blocking): F-101 (move configure-time `require` to `doFirst`); F-103 (distinguish missing / non-text / empty in `assertSchemaIdentity` error message); F-205 (replace C8 hardcoded scan list with a glob walk over `runtime-kotlin/**/src/{test,jvmTest}/**/*.kt`).
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-19] SKILL-47 platform-pack-schema-source-of-truth
Areas: runtime-kotlin/runtime-core/scaffold, runtime-kotlin/runtime-domain/scaffold, orchestration/contracts, runtime-kotlin/runtime-core build
- `orchestration/contracts/platform-pack-schema.yaml` is the single canonical JSON Schema (Draft 2020-12, authored in YAML) describing `platform-packs/<slug>/platform.yaml`. New fields land in the schema first; the parser consumes it. reusable
- `ShellContentLoader.buildPack` delegates shape validation to `CanonicalPlatformPackSchemaValidator` (lazy singleton, JsonSchema cached). SnakeYAML → Jackson `JsonNode` bridge; networknt 1.5.x runs the validation. Errors are reformatted to typed `InvalidManifestSchemaError` messages that NAME the offending field path (AC8). Contract-version `const` violation surfaces directly as `ContractVersionMismatchError` from the validator — loud-fail uniform across `loadPlatformManifest` and `loadPlatformPack`.
- Five cross-field rules stay in Kotlin as named coherence checks, listed verbatim in the schema's `x-coherence-checks` prose block: `slug-parity`, `areas-require-baseline`, `areas-equal-declared` (bijection both directions), `area-metadata-keys-subset-declared`, `pointers-unique-name-per-dir`. Schema-only rules (type, enum, `content.md` suffix pattern, pointer name/target pattern) live exclusively in the YAML.
- Code-review area enum is declared once under `$defs/codeReviewArea` and `$ref`'d from `declared_code_review_areas.items`, `declared_files.areas.propertyNames`, `area_metadata.propertyNames`. `PlatformPackSchemaContractVersionTest` asserts BOTH `contract_version` parity vs `SHELL_CONTRACT_VERSION` AND `$defs/codeReviewArea/enum` parity vs `APPROVED_CODE_REVIEW_AREAS` (both directions) — neither can drift.
- `PlatformPackSchemaPaths { REPO_RELATIVE_PATH; CLASSPATH_RESOURCE }` is the single source of truth for the schema location. Build-time Gradle Copy task lands the canonical YAML into runtime-core resources under `skillbill/contracts/`; runtime loader tries classpath first, falls back to walking up from a path hint. Desktop, validator, and tests all reference the constant — no hardcoded path strings.
- `DeclaredFiles.baseline` and `PlatformManifest.routedSkillName` widened to nullable; the in-progress branch relaxations (`baseline` / `declared_files` / `area_metadata` all optional) are preserved by schema + parser. `parseDeclaredFiles` returns `null` for fully-omitted blocks; `areas-require-baseline` coherence still gates non-empty `areas`. Every consumer was updated to null-check.
- Tests: `PlatformPackSchemaValidatesExistingPacksTest` proves the schema validates both shipped packs as-is; `PlatformPackSchemaViolationsTest` has one discrete test per documented violation including the 5 named coherence rules and split pointer cases (name-without-.md, name-with-.., target-with-..); `PlatformPackSchemaContractVersionTest` pins both parity invariants.
- Reusable: schema-first validation pattern (`x-coherence-checks` prose + Kotlin enforcement); single `PlatformPackSchemaPaths` object pattern when a shared path constant needs to cross module boundaries; build-time `Copy` task to expose a repo-root contract on the runtime classpath; networknt + Jackson + SnakeYAML stack for validating YAML via JSON Schema.
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-17] final-integration-docs-validation
Areas: runtime-kotlin runtime-core install, runtime-cli install, runtime-desktop packaging/runtime lookup, docs
- Final SKILL-45 integration kept the runtime install contract unchanged: CLI, install.sh, and desktop all route through typed install plan/apply instead of parsing shell output. reusable
- Documentation now names package tasks, app-resource runtime bundle lookup, `~/.skill-bill/installed-skills` staging, telemetry anonymous/full/off, MCP intent, and Windows Developer Mode/elevated-shell guidance.
- Coverage audit found existing focused tests for plan/apply, shell delegation argv, dynamic platform selection, telemetry/MCP, Windows symlink outcomes, desktop gateway state, runtime asset lookup, and package task wiring; no new test files were needed.
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-17] desktop-packaging-runtime-bundling
Areas: runtime-kotlin/runtime-desktop packaging, runtime-desktop/core/data first-run gateway, runtime-cli installDist, runtime-mcp installDist
- Desktop packages now stage a loose `skill-bill-runtime` app-resource bundle from authored `skills`, dynamic `platform-packs`, `orchestration`, and packaged runtime-cli/runtime-mcp installDist outputs. reusable
- `JvmRuntimeAssetLocator` resolves runtime assets from dev checkouts, explicit `skillbill.runtime.assets.dir` / `SKILL_BILL_RUNTIME_ASSETS`, or Compose installed resources before the gateway builds shared install plans. reusable
- First-run install planning now feeds resolved `skillsRoot`, `platformPacksRoot`, runtime distribution dirs, and packaged runtime-mcp bin into the existing typed install model while preserving `~/.skill-bill/installed-skills` staging and Windows symlink outcomes.
- Packaging task wiring pins DMG/MSI/Deb/RPM targets and makes `prepareAppResources` plus package tasks depend on runtime bundle staging so native packages cannot race an empty app resource directory.
Feature flag: N/A
Acceptance criteria: 7/7 implemented

## [2026-05-17] install-migration-validation
Areas: runtime-kotlin/runtime-cli install tests, runtime-kotlin/runtime-core install/architecture tests, runtime-domain install model
- Added final validation coverage for install migration contracts: CLI plan/apply payload mapping, manual/detected agents, telemetry anonymous/full/off, MCP intent, Windows symlink messages, and staging-cache boundaries.
- Future CLI install tests should keep driving `CliRuntime` JSON payloads instead of adding filesystem behavior to CLI adapters. reusable
- Dynamic platform coverage now includes a newly discovered `python` pack in plan-builder and install.sh delegation tests so selected-platform behavior is not tied to the built-in pack set.
- Staging assertions now pin rendered `SKILL.md` and support pointers under `~/.skill-bill/installed-skills`, preserving the generated-output boundary.
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-17] install-sh-runtime-delegation
Areas: install.sh, runtime-kotlin/runtime-cli install commands, runtime-kotlin/runtime-core install apply/native-agent cleanup
- `install.sh` now owns prompting/runtime distribution only and delegates typed install apply to the durable runtime CLI, including manual/detected agents, platform modes, telemetry, MCP, runtime dirs, and replacement cleanup. reusable
- Runtime apply gained `replaceExistingSkillBillLinks` cleanup for current and legacy Skill Bill links so base-only or selected reinstall removes stale platform and renamed skill entries before relinking staged skills. reusable
- Native-agent replacement cleanup now unlinks manifest-declared deselected platform agents from both current staged output and the legacy generated cache before linking selected provider artifacts.
- Regression coverage moved shell argv execution and replacement cleanup into focused tests; future install.sh changes should assert the single `install apply` argv rather than reintroducing shell-owned install loops.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-05-17] runtime-cli-install-plan-apply
Areas: runtime-kotlin/runtime-cli install commands, runtime-kotlin/runtime-core install plan/apply contract
- Added stable `skill-bill install plan` and `skill-bill install apply` entrypoints that build `InstallPlanRequest`, call `InstallOperations.planInstall` / `applyInstall`, and keep CLI handlers as thin parse/render adapters. reusable
- CLI inputs now cover detected/manual `copilot`, `claude`, `codex`, `opencode`, `junie`, platform pack none/selected/all, telemetry anonymous/full/off, MCP registration choices, and Windows symlink preflight state.
- Plan/apply JSON/text payloads are rendered from structured runtime outcomes, including base-skill inclusion, dynamic platform discovery, MCP/telemetry intent, warnings/failures, and Windows symlink guidance.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-05-16] validation-contract-coverage
Areas: runtime-kotlin/runtime-core install plan/apply tests, runtime-kotlin/runtime-cli install tests, runtime-domain install model
- Added final regression coverage for shared install plan/apply contracts: plan inputs/outputs, dynamic platform discovery, base-skill inclusion, staging-cache/source immutability, telemetry levels, MCP intent/outcomes, and Windows symlink guidance.
- New focused `InstallPlanContractCoverageTest` keeps plan assertions at the typed contract boundary without applying side effects; future install changes should extend this instead of parsing CLI text. reusable
- Apply coverage now asserts telemetry/MCP no-side-effect behavior on preflight failure and selected all-agent behavior distinguishes Copilot skill links from Claude/Codex/OpenCode/Junie native-agent providers.
- Validation loop fixed Spotless formatting after review/audit and confirmed the Kotlin Gradle gate passes; remaining known limitation is Minor follow-up to strengthen all-agent MCP config-file assertions.
Feature flag: N/A
Acceptance criteria: 9/9 implemented

## [2026-05-16] telemetry-mcp-adapter-wiring
Areas: runtime-kotlin/runtime-domain install model, runtime-kotlin/runtime-core install apply, runtime-kotlin/runtime-core launcher MCP registration, runtime-kotlin/runtime-application telemetry
- Shared `InstallOperations.applyInstall` now executes typed telemetry intent through existing anonymous/full/off semantics and returns `InstallTelemetryApplyOutcome` instead of requiring shell-output parsing. reusable
- Apply executes or skips typed MCP registration intent per supported `InstallAgent`, returns per-agent `McpRegistrationApplyOutcome`, and keeps MCP/telemetry setup failures as structured non-fatal warnings like `install.sh`. reusable
- Apply side effects use the plan-owned home with an empty environment so ambient telemetry env vars cannot redirect the shared install contract; legacy launcher `glm` support remains outside `InstallAgent.supportedIds`.
- Focused tests cover telemetry full/off/success/skip/failure, MCP success/skip/failure, and warning aggregation; CLI command migration and desktop setup UI remain deferred.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-05-16] apply-staging-agent-links
Areas: runtime-kotlin/runtime-domain install model, runtime-kotlin/runtime-core install, runtime-kotlin/runtime-core nativeagent, runtime-kotlin/runtime-cli install
- Added typed `InstallOperations.applyInstall` and structured apply outcomes for staging, skill links, native-agent links, Windows symlink states, telemetry/MCP intent carry-through.
- Apply validates planned staging hash/dir before linking and fails stale plans instead of installing changed source; native-agent apply renders only selected planned skill roots. reusable
- Native-agent apply reuses render/link operations with request/override objects, materializes apply-time provider artifacts under `~/.skill-bill/installed-skills`, and preserves legacy generated-cache links only for replacement. reusable
- Symlink replacement uses temp-link then non-overwriting move, preserves user-owned files/symlinks, and reports structured failures with Developer Mode/elevated shell guidance.
Feature flag: N/A
Acceptance criteria: 7/7 implemented

## [2026-05-16] shared-install-plan-contract-builder
Areas: runtime-core install plan/build, runtime-domain install model, runtime-core contract/tests
- SKILL-45 subtask 1 added a pure typed install-plan API (`InstallPlanRequest` -> `InstallPlan`) plus `InstallOperations.planInstall`; it models agent/platform/telemetry/MCP/runtime/target/staging/Windows-preflight intent without applying symlinks, rendering staging output, or registering MCP. reusable
- Supported install agents are locked to `copilot`, `claude`, `codex`, `opencode`, `junie`; the plan builder asserts runtime primitive drift so legacy GLM cannot re-enter through install planning. reusable
- Platform packs are discovered through governed `discoverPlatformPacks`, not raw manifest loading; bad contract versions, missing declared content, duplicate skill names/manifest slots, escaped declared content paths, and pointer targets escaping through symlinked parents must loud-fail during planning. reusable
- Base skills must be represented and must fail if the skills root is missing, empty, or contains `bill-*` directories without `content.md`; future apply/staging work must preserve content-hash parity with the plan, especially for custom `skillsRoot` support pointers.
- Test coverage lives in `InstallPlanBuilderTest` for selected/all/unknown packs, multi-area packs, manual and supplied/detected targets, no home/source mutation, staging root under `~/.skill-bill/installed-skills`, Windows decision/message, pointer target escapes, and base-skill loud-fails.
Feature flag: N/A
Acceptance criteria: 9/9 implemented

## [2026-05-11] state-repo-controls-tree-polish
Areas: runtime-desktop core data/domain/testing, feature skillbill UI/state
- Split repo browser state into typed path text, current session/tree, selected item, expanded groups, busy operation, and status-bar model so UI reads one coherent source of truth. reusable
- Open/refresh now rebuild through runtime services, preserve selection only for same-repo existing ids, clear stale invalid state, and keep generated artifacts from shared discovery read-only with `RO`.
- Desktop chooser is a common/JVM boundary that feeds the same open flow as typed paths; open/refresh load off the UI thread and apply token-checked results to prevent stale completions overwriting newer state. reusable
- Tree interactions now support expand/collapse and keyboard movement over visible rows while busy state disables conflicting repo/tree actions and renders progress.
Feature flag: N/A
Acceptance criteria: 11/11 implemented

## [2026-05-11] runtime-desktop-repo-browser-readonly-tree
Areas: runtime-kotlin runtime-core scaffold/nativeagent, runtime-desktop core data/domain/testing, feature skillbill UI/state, desktop app docs
- Added the Iteration 02 repo browser path: desktop repo selection validates a local Skill Bill checkout, builds a read-only tree from `AuthoringOperations`, `RepoValidationRuntime`, governed add-on discovery, generated-artifact guard discovery, and provider-neutral native-agent source parsing. reusable
- `RuntimeRepoBrowserService` now keeps desktop as an adapter over shared runtime contracts: do not reintroduce UI-local guesses for add-ons, generated wrappers, pointer files, or provider-native agent output. reusable
- Selection IDs are repo-scoped, invalid/blank paths return error state instead of throwing, malformed native-agent sources remain visible as invalid read-only items, source reads tolerate file races, and git branch lookup has a timeout.
- `SkillBillFrame` preserves the existing shell but renders tree, editor, inspector, status, and refresh from state; editing, scaffolding, git diff/commit, and PR flows remain deferred.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-05-10] runtime-desktop-navigation-room3
Areas: runtime-kotlin runtime-desktop core navigation, core database, app shell, architecture guardrails
- Added `runtime-desktop/core/navigation` with typed desktop routes, explicit back-stack state, and a navigator that supports push, root replacement, reset, and guarded back navigation. reusable
- Added `runtime-desktop/core/database` using the KMPComposeStarter Room3 pattern: `androidx.room3` plugin/runtime/compiler, exported schema, generated database constructor, JVM SQLite driver, app-scoped database provider, and DAO tests for recent repository rows. reusable
- Wired the app shell through navigation state instead of a hard-coded destination enum, and exposed the Room3 provider from the application DI component so the app graph proves database construction. reusable
Feature flag: N/A
Acceptance criteria: desktop navigation and Room3 foundation

## [2026-05-10] runtime-desktop-kmp-starter-base
Areas: runtime-kotlin Gradle build-logic, runtime-desktop app/core/feature modules, runtime architecture guardrails, desktop Skill Bill app docs
- Reworked `runtime-desktop` from a thin Compose app into a KMPComposeStarter-style desktop-only graph: app module plus nested `runtime-desktop/core/common`, `runtime-desktop/core/domain`, `runtime-desktop/core/data`, `runtime-desktop/core/designsystem`, `runtime-desktop/core/ui`, `runtime-desktop/core/testing`, and `runtime-desktop/feature/skillbill` modules. reusable
- Added repo-local KMP convention plugins for library, Compose, application, and kotlin-inject/KSP/Anvil wiring; keep external plugin classes out of build-logic compile classpath to avoid Kotlin metadata mismatch. reusable
- Desktop DI is now starter-shaped and compile-time generated: `@MergeComponent(AppScope)`, contributed `UserScope` and `ScreenScope` subcomponents, `UserComponentManager`, and screen-scoped Skill Bill app state; placeholder services remain no-write adapters for iteration 01.
- Architecture guards now understand nested Gradle paths and pin desktop module dependency direction separately from shared runtime modules.
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-10] runtime-desktop-starter-shell-completion
Areas: runtime-kotlin runtime-desktop app shell, desktop core modules, architecture guardrails, packaging
- Completed the desktop-only KMPComposeStarter architecture pass by adding `runtime-desktop/core/datastore`, persisted recent-repo preferences, native distribution metadata, app-shell state, and composition-local user/screen component wiring. reusable
- `SkillBillRoute` now resolves its screen component from the app-provided screen factory instead of taking a feature factory directly, matching the starter host boundary and keeping screen creation behind the user component. reusable
- Desktop module catalogs and architecture tests now include `core:datastore` so the nested app/core/feature graph remains explicit.
Feature flag: N/A
Acceptance criteria: desktop shell architecture completion

## [2026-05-10] runtime-desktop-skillbill-theme
Areas: runtime-kotlin runtime-desktop design system, desktop app shell
- Replaced the thin Skill Bill palette with a KMPComposeStarter-style Material 3 theme stack: color scheme, extended colors, content alpha, typography, shapes, background, gradients, and text-field colors. reusable
- Desktop design tokens now derive from `docs/assets/readme-hero-preview.html` (`ink`, `panel`, `line`, `yellow`, `muted`, `steel`, `green`) and are pinned by a design-system test. reusable
- User-facing desktop app naming is `Skill Bill`; no authored runtime desktop source uses the old interim naming.
Feature flag: N/A
Acceptance criteria: Skill Bill desktop theme uses README hero palette

## [2026-05-10] runtime-desktop-shell
Areas: runtime-kotlin Gradle build, runtime-desktop Compose shell, runtime architecture guardrails, desktop Skill Bill app docs
- Added optional `runtime-desktop` Compose Multiplatform JVM module with repo-local `skillbill.kmp-compose-application` convention plugin, `commonMain`/`jvmMain` source sets, and `:runtime-desktop:run`, mirroring KMPComposeStarter app/core/feature layering while staying isolated from shared runtime modules. reusable
- Skill Bill shell owns only presentation and in-memory state: app entrypoint, design system, repo toolbar, tree/editor/status placeholders, placeholder repo/tree/authoring/git gateways, and no repo-content writes on launch/close.
- Desktop boundary guardrails now live in `RuntimeDesktopBoundaryTest`: non-desktop runtime modules must not import desktop/Compose APIs or apply Compose dependencies. reusable
- Review pitfall: do not mark `List`-backed Compose state as `@Immutable`; snapshot service-returned tree items at the state boundary instead.
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-09] native-agent-bundles
Areas: runtime-core nativeagent parser/discovery/validation/install, runtime-core scaffold, Kotlin/KMP platform-pack native agents, shell-content-contract docs
- Native-agent discovery now treats `native-agents/agents.yaml` and `native-agents/*.md` as source files, expanding bundles into logical `NativeAgentSource` entries only after selection filters so targeted regeneration stays cheap. reusable
- Bundle validation keeps platform composition manifest-bound and horizontal composition frontmatter-bound, with duplicate-name diagnostics including `agents.yaml` entry names and custom-body parity against markdown sources.
- Multi-specialist scaffolds emit thin `compose: governed-content` bundle entries in `agents.yaml`; standalone markdown stub rendering remains the custom-body path for authored worker prompts. reusable
- Kotlin and KMP header-only specialist sources moved from one markdown file per agent into pack-owned bundle files without changing provider-native install output.
Feature flag: N/A
Acceptance criteria: 16/16 implemented

## [2026-05-09] native-agent-composition-validation-final-coverage
Areas: runtime-core nativeagent validation, runtime-core scaffold repo validation, runtime-cli validation, scripts/validate_agent_configs
- Final validation coverage pins native-agent source preservation through `RepoValidationRuntime.validateRepo`; validation must report issues without rewriting or deleting `native-agents/*.md`. reusable
- Composition rejection coverage now includes malformed directives, missing platform manifests, contract-version drift, missing governed content, undeclared local markdown sidecars, and install-render refusal before provider output is staged.
- `validate-agent-configs` CLI coverage now surfaces native-agent composition failures, matching the `scripts/validate_agent_configs` Kotlin-backed path used by repository gates. reusable
- `skillbill.nativeagent` docs now list optional `compose: governed-content`, manifest/sibling target resolution, and self-contained provider-native install output.
Feature flag: N/A
Acceptance criteria: 4/4 implemented

## [2026-05-09] native-agent-composition-rendering
Areas: runtime-core nativeagent rendering/validation, scaffold authored-content rendering, Kotlin/KMP specialist native-agent sources
- Composed native agents now expand `compose: governed-content` before provider rendering/install; `NativeAgentProvider` still owns Claude/Codex/Opencode/Junie shapes while render inputs are self-contained. reusable
- `AuthoredContentRendering` centralizes frontmatter stripping, title stripping, heading demotion, and LF normalization so wrapper rendering and native-agent composition share the authored `content.md` body contract. reusable
- `NativeAgentSidecarInlining` rewrites local markdown links to plain labels and appends referenced sibling or manifest-pointer sidecars recursively; unresolved local links fail validation instead of leaking repo-local runtime dependencies.
- Direct Kotlin/KMP specialist native-agent source files remain under `native-agents/*.md` but now declare `compose: governed-content` and drop duplicated long specialist prose.
- Regression coverage pins manifest-driven composition, arbitrary platform slugs, install-cache self-containment, source preservation, generated-artifact rejection, and KMP UI source snapshots.
Feature flag: N/A
Acceptance criteria: 4/4 implemented

## [2026-05-09] native-agent-composition-foundation
Areas: runtime-core nativeagent parser/validation, shell-content-contract docs, native-agent tests
- Native-agent source frontmatter now supports explicit `compose: governed-content`; parser round-trips the directive while provider renderers still use the source body only until rendering migration lands.
- Composition target resolution is manifest-bound for platform packs: `platform.yaml` declared content paths are authoritative, and undeclared sibling `content.md` files must not be used as fallback. reusable
- Repo validation aggregates malformed compose directives, missing declared content targets, unresolved targets, and manifest loader failures as native-agent validation issues alongside existing provider-agnostic/render checks.
- Regression coverage pins parser acceptance/rejection, manifest-driven target resolution, missing target failure, no undeclared platform fallback, discovery preservation, and composition round-trip.
Feature flag: N/A
Acceptance criteria: 4/4 implemented

## [2026-05-09] authored-content-scaffold-commands
Areas: runtime-core scaffold/authoring/render validation, runtime-cli authoring tests, shell-content-contract docs
- Scaffold now threads `content_body` into governed `content.md` for code-review areas and quality-check overrides, validates horizontal scaffolds against the planned `content.md` target after staging, and rolls back generated-wrapper headings byte-identically. reusable
- `edit --section` rejects generated wrapper sections (`Descriptor`, `Execution`, `Ceremony`) before mutation with a diagnostic pointing authors to authored `content.md` sections or manifest/frontmatter metadata. reusable
- Regression coverage pins clean authored scaffold bodies, wrapper-heading rejection, quality-check override rollback, horizontal name-collision validation, deterministic render wrapper sections, and fill/edit authored-content mutation behavior.
- Validation note: full Gradle check also required formatting an unrelated pre-existing Spotless issue in `InstallOperations.kt`; keep formatter-only unblock changes isolated from scaffold behavior.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-05-09] generated-skill-artifact-source-contract
Areas: runtime-core scaffold loader/render/validation/install, runtime-cli validation, runtime-core tests
- `ShellContentLoader` now treats platform.yaml-declared governed files as exact `content.md` paths with governed frontmatter plus Descriptor/Execution/Ceremony H2s; generated wrapper body-shape validation was removed from the declared-source path. reusable
- `GeneratedArtifactGuard` no longer grandfather-lists committed wrappers/pointers, and `GovernedSkillDriftValidation` compares deterministic in-memory render output instead of requiring source `SKILL.md` files. reusable
- Install staging excludes stale source generated artifacts on both rebuild and cache-hit paths, then renders `SKILL.md`/pointers into the install cache without mutating source. reusable
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-05-09] render-snapshots-final-validation
Areas: runtime-core scaffold render snapshot tests, runtime-core test-support, runtime Gradle test wiring
- Added resource-backed render snapshots for a standalone governed skill (`bill-pr-description`), `bill-kotlin-code-review`, and `bill-kmp-code-review-ui`; snapshots exercise `renderAuthoringTarget` output instead of duplicating wrapper/pointer formatting logic. reusable
- `AuthoringRenderSnapshotTest` asserts repeated in-memory renders are byte-identical and derives expected platform pointer headers from `platform.yaml`, so pointer block order remains manifest-declaration order even though pointer write paths sort independently. reusable
- Added `SnapshotAssertions` with deterministic `-Pupdate-snapshots` support, LF normalization, fixture-path failure messages, and containment checks that reject escaped `../` paths before read/write. reusable
- KMP UI native-agent coverage snapshots the provider-neutral source at `platform-packs/kmp/code-review/bill-kmp-code-review/native-agents/bill-kmp-code-review-ui.md`, not the specialist directory, matching the governed native-agent source layout.
- Validation note: stale Gradle configuration cache can false-fail this area; clearing cache and rerunning the gate produced a clean pass with no source edits.
Feature flag: N/A
Acceptance criteria: 4/4 implemented

## [2026-05-09] drift-and-agent-config-guards
Areas: runtime-core scaffold validation/rendering, runtime-cli validation task, runtime Gradle lifecycle, agent-config validation
- `RepoValidationRuntime.validateRepo` now runs `validateGovernedSkillDrift`: discovers content-managed targets once, renders each `AuthoringTarget` twice in memory, compares emitted SKILL.md bytes to disk, and fails on unresolved platform.yaml pointer targets. reusable
- `renderAuthoringTarget(repoRoot, target)` overload avoids per-skill rediscovery during repeated-render checks; keep render stdout deterministic (`SKILL.md` block first, pointer blocks in manifest order) when adding future render surfaces. reusable
- `GeneratedArtifactGuard` grandfather-lists the current committed generated SKILL.md wrappers and pointer files, uses `git ls-files` when available so untracked local fixtures stay ignored, and fails newly tracked governed SKILL.md/platform pointer outputs. reusable
- `runtime-cli:check` depends on `validateAgentConfigs`, so `(cd runtime-kotlin && ./gradlew check)` exercises the same Kotlin-backed path as `scripts/validate_agent_configs`; tests pin repo-validation, CLI, Gradle wiring, git-index behavior, platform-pack wrapper coverage, and stale-wrapper drift.
- Known limit: this is a guard for future deletion policy, not cleanup; current committed wrappers/pointers remain allowed until the retirement subtask removes them.
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-05-09] render-cli-output
Areas: runtime-cli scaffold commands, runtime-core scaffold rendering, runtime CLI/core tests
- `skill-bill render <skill-id>` is now a read-only stdout surface, separate from `upgrade`; `--dry-run` is accepted as the same no-write preview path.
- Runtime-core owns `renderAuthoringTarget`, which emits `SKILL.md` from `renderWrapper` plus pointer blocks from `renderPointer`; platform-pack pointer blocks preserve `platform.yaml` declaration order instead of `PointerOperations.regenerate` write-path sorting. reusable
- Separator headers are deterministic LF text (`SKILL.md` first, then pointer files), while source `SKILL.md` and pointer files remain untouched.
- Known test limit: CLI coverage proves no-write/dry-run equivalence on a horizontal fixture; platform pointer order is pinned at the core renderer boundary.
Feature flag: N/A
Acceptance criteria: 7/7 implemented

## [2026-05-09] install-staging-pipeline
Areas: runtime-core install (new InstallStaging.kt, InstallStagingIO.kt, InstallStagingIdentity.kt; rewired InstallPrimitives.installSkill), runtime-core scaffold (ScaffoldService.performInstall threads repoRoot + hoists discoverPlatformPackManifests), runtime-domain install/model (added RenderedSkill DTO)
- Content-managed install now stages into `~/.skill-bill/installed-skills/<slug>-<16hex>/` (cache root outside repo, mirroring SKILL-38 native-agent shape) and symlinks the staging dir; source tree is never written to. Authored files copied verbatim, SKILL.md from `renderWrapper(target)`, pointer files from `renderPointer(repoRoot, packRoot, spec)`. Atomic-move promotion via `Files.createTempDirectory` + `Files.move(ATOMIC_MOVE/REPLACE_EXISTING)` with `AtomicMoveNotSupportedException` swap-and-cleanup fallback. reusable
- Cleanup ownership: `promoted: Boolean` flag (NOT `finalExistedBefore` TOCTOU) so failure cleanup only deletes finalStagingDir we promoted in this attempt — concurrent installs cannot destroy each other's good output. Catch envelope is `Throwable` so any failure (NPE/ISE/Error) cleans up tempDir + finalStagingDir-if-promoted; cleanup paths log-and-suppress secondary IOExceptions. reusable
- Content hash: SHA-256 first 8 bytes lowercase hex over sorted `<rel-fwd-slash>\n<bytes>\n` for authored files plus sorted `<skillRelativeDir>|<name>|<target>\n` for applicable pointer specs; SKILL-39 determinism rules (`Files.walk(...).sorted()`, explicit byte-newline, `trimEnd('\n','\r')`, `Path.relativize` + forward-slash) followed throughout.
- Stale staging-dir pruning uses STRICT regex `^<slug>-[0-9a-f]{16}$` (not prefix match) — pitfall: prefix match collides on shared-slug-prefixes (e.g. installing `bill-kotlin-code-review` would have wiped `bill-kotlin-code-review-security-<hash>`). Regression test in `InstallStagingTest` plants a sibling cache dir with non-hex suffix and asserts it survives. Slug normalization re-trims `-` after `take(32)` so a 32nd-char `-` doesn't collide with the `<slug>-<hash>` separator.
- Install-pipeline boundary rule: `installSkill(repoRoot=null, ...)` OR `!isContentManagedSkill(sourceSkillDir)` falls through to legacy direct-symlink (preserves manual `link-skill` CLI flow with no `content.md`). Content-managed installs always go through `ScaffoldService.performInstall` which threads repoRoot + manifests via the new `InstallContext` data class so `discoverPlatformPackManifests` runs ONCE per scaffold (not per skill). reusable
- Defense-in-depth: `requireWithinSource` post-filters every walked authored path with `toRealPath().startsWith(resolvedSource)`; every staging write asserts `dest.startsWith(tempDir)`/`startsWith(cacheRoot)`. `isReusableInstallStaging` requires both `.content-hash` marker match AND `SKILL.md` regular-file existence (partial-write residue → cache miss → rebuild).
- Pitfall: `applicablePointers` lookup uses `installPath.toAbsolutePath().normalize().startsWith(packRoot)`; for skills under `skills/` (non-pack), no manifest matches and pointer list is empty — that path skips renderPointer entirely. Future packs that nest skill dirs deeper than one level under packRoot need `skillRelativeDir` rules to match.
- Logging via `java.util.logging.Logger` (no SLF4J/log4j dep — runtime-core has none): INFO on stage entry (skill, hash, finalDir, reuse=true|false), WARN on cleanup (promoted flag + error class), SEVERE on each failure path with full context.
- Subtask 3 owns the snapshot tests + CI drift check that pin "staged bytes == committed bytes" — this subtask trusts SKILL-39's renderers to produce the same output structurally.
Feature flag: N/A
Acceptance criteria: 9/9 implemented (subtask 2 of SKILL-40; AC #8 byte-identity invariant structurally preserved by reusing SKILL-39 renderers, but no direct snapshot test here — explicitly scoped to subtask 3)

## [2026-05-08] skill-discovery-marker-content-md
Areas: runtime-core scaffold (AuthoringDiscovery, AuthoringTarget, AuthoringMutation, AuthoringRender, AuthoringStatus, AuthoringContentMutation, ScaffoldTemplateRendering, RepoValidationRuntime, SkillMdShapeValidator, ShellContentLoader), runtime-domain workflow definitions/engine, runtime-cli authoring-parity tests, runtime-mcp golden fixtures, all 26 governed content.md files
- Skill-discovery marker switched from SKILL.md to content.md across the three tree-walk filter callsites (AuthoringDiscovery + RepoValidationRuntime). content.md is now the canonical authoring surface; SKILL.md remains on disk as the rendered wrapper through subtasks 2-3, retired in subtask 4.
- Frontmatter migrated SKILL.md -> content.md for all 26 governed skills as a one-shot in-scope expansion (originally not in the subtask spec but required for AC #4 to be honest about content.md owning the frontmatter). renderWrapper now sources frontmatter from target.contentFile so the wrapper tracks content.md byte-for-byte. reusable
- SkillMdShapeValidator gained `validateBodyShape: Boolean = false` (frontmatter-only by default; wrapper-only body-shape rules behind the flag); error messages parameterized on Path.fileName so the same validator serves both content.md and SKILL.md callers cleanly until the wrapper is retired. reusable
- hasGenerationDrift removed end-to-end: definition + AuthoringStatus property + all consumers (AuthoringMutation, AuthoringOperations recommended-commands). Drift detection is replaced by renderWrapper sourcing-from-content.md (wrapper can no longer drift); subtask 3 adds the CI drift check that replaces the deleted scaffold-managed render drift signal.
- Pitfall: pack-manifest `baseline:` strings in `platform-packs/{kmp,kotlin}/platform.yaml` plus the `ShellContentLoader.validateGovernedSkill` body-shape branch CANNOT flip to content.md until subtask 4 retires the wrapper — `validateGovernedSkill` enforces wrapper-shape (Descriptor/Execution/Ceremony H2 + exact rendered match) which authored content.md does not satisfy. Originally listed in subtask 1 AC #8 but explicitly deferred to subtask 4 (subtask 1 spec amended; subtask 4 spec gained an AC line for the residual).
- ScaffoldTemplateRendering.renderContentBody and AuthoringContentMutation.coerceFullContentText now fail loudly (SkillBillRuntimeException with file path + recovery command) when frontmatter is missing or stacked, rather than silently writing a malformed content.md that the validator only catches after rollback. reusable
- Workflow-label sweep at WorkflowEngine.kt + FeatureImplementWorkflowDefinition.kt + FeatureVerifyWorkflowDefinition.kt is label-only — step IDs preserved verbatim. continuationReferenceSections is in-memory only, so NO DatabaseMigrations entry was needed; future SKILL.md path-string changes that touch state keys must add migrations.
- Breaking change for CLI consumers of `--format json`: `generation_drift` boolean removed from skill-bill list/show/validate output. Pinned by strict key-set equality test in AuthoringOperationsTest.kt so future contract drift is caught.
Feature flag: N/A
Acceptance criteria: 5/5 implemented (subtask 1 of SKILL-40; AC #8 pack-manifest portion deferred to subtask 4 by spec amendment)

## [2026-05-03] repo-script-validation-migration
Areas: runtime-core repo validation, runtime-cli validation commands, scripts/, workflows, docs, feature specs
- Moved repo validation and release-ref checks off standalone Python scripts into Kotlin CLI/runtime commands, with shell wrappers kept at `scripts/validate_agent_configs` and `scripts/validate_release_ref`. reusable
- Retired one-off `migrate_to_content_md.py` and `skill_repo_contracts.py` as markdown notes; current docs/workflows now call Kotlin-backed validation and no maintainer workflow requires Python scripts.
- Strengthened Kotlin repo validation for manifest-loaded platform packs, pack-owned add-ons, sibling supporting sidecar symlinks, README/catalog references, workflow markers, telemetry contract drift, plugin metadata, and SemVer release refs.
- Regression coverage now lives in Kotlin CLI/core tests plus the remaining self-contained Python contract test; no current test imports the retired script modules.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-05-03] installer-runtime-cutover
Areas: install.sh, uninstall.sh, runtime-cli install commands, runtime-core install primitives, runtime-core launcher MCP config mutation, pyproject.toml, installer tests
- Moved installer/uninstaller runtime ownership off Python: shell scripts now build/use packaged Kotlin `installDist` bin shims and call `skill-bill install ...` commands for agent paths, link/unlink, cleanup, MCP registration/removal, and telemetry level mutation. reusable
- Added Kotlin install parity for Codex/OpenCode native agent path/link/unlink, selected-platform discovery (base skills plus selected pack roots), user-file-preserving target handling, symlinked-source rejection, and cleanup operations that propagate filesystem delete failures. reusable
- Added Kotlin MCP config mutation for Claude/Copilot JSON, Codex TOML, and OpenCode JSONC using packaged `runtime-mcp` commands and atomic writes; invalid OpenCode JSONC now loud-fails without overwriting user config. reusable
- Pitfall: Bash `${array[@]:-}` under `set -u` can inject an empty platform slug; use explicit length guards before iterating selected/required platform arrays.
- `pyproject.toml` remains for remaining Python maintainer tooling, but Python console scripts are gone; Python package deletion remains a later SKILL-36 subtask.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-05-03] kotlin-contract-parity
Areas: runtime-core scaffold/shell-content loader, runtime-cli scaffold commands, runtime-contracts errors, governed skill wrappers
- Ported remaining scaffold, shell-content-contract loading, render/fill/upgrade, and authoring validation behavior onto Kotlin-owned APIs/CLI while keeping `skill_bill/` as reference-only for later subtasks.
- Added canonical SKILL.md shape validation, render-drift validation, regular-file sidecar validation, feature-verify `audit-rubrics.md` ceremony support, and shared display-name derivation from manifest/fallback sources. reusable
- Implemented Kotlin scaffold parity for `subagent_specialists` / `no_subagents`, Codex/OpenCode stub emission, create-and-fill multi-artifact rejection, quality-check manifest registration, and byte-identical rollback tests. reusable
- Guardrail: feature-verify sidecars need both `requiredSupportingFilesForSkill` and `supportingFileTargets` entries; missing the target breaks newly scaffolded platform verify overrides even if existing wrappers render correctly.
- Validation gate passed: focused scaffold/CLI tests, `runtime-kotlin ./gradlew check`, Python unittest suite, `agnix --strict`, and agent-config validation.
Feature flag: N/A
Acceptance criteria: 4/4 implemented

## [2026-05-01] adoption-docs-and-external-author-dry-run
Areas: runtime-cli external-author tests, runtime-core scaffold manifest paths, docs adoption guides
- Rewrote adoption docs around packaged Kotlin-only CLI/MCP behavior, fail-closed vs degraded boundaries, strict contract guarantees, and model-mediated review/planning limits.
- Added a Kotlin CLI external-author dry run that scaffolds a temporary platform pack through `new --payload`, validates it, links a generated skill into a temp agent path, removes it, and validates cleanup. reusable
- Fixed scaffold manifest writes to use pack-root-relative declared file paths for new platform packs, code-review-area edits, and quality-check overrides. reusable
- Reusable guardrail: external-author validation should exercise CLI payload parsing plus manifest-loader resolution, not call the scaffold core directly.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-05-01] python-runtime-retirement
Areas: skill_bill.launcher, runtime-core launcher contract, runtime-mcp launch path, runtime docs
- Removed the TODO(3c) launcher surface entries for `python-fallback` and `mcp-python-fallback`; the contract now locks only packaged Kotlin CLI/MCP selection. reusable
- The 3b bridge teardown is now complete at the outer launcher boundary: retired runtime env vars no longer select Python and the Python MCP bootstrap script is gone.
- Future runtime rollback guidance should install the previous release instead of preserving Python runtime ownership.
Feature flag: N/A
Acceptance criteria: 11/11 implemented

## [2026-05-01] port-or-retire-python-backed-cli-closeout
Areas: runtime-cli authoring tests, SKILL-32 3b specs, runtime validation gate
- Closed the broad 3b parent spec after 3b_1 through 3b_4 landed by marking stale 3b/3b_2/3b_3 specs complete and adding parent close-out evidence.
- Added missing Kotlin CLI coverage for `upgrade`, `render`, `edit --body-file`, and `fill` using isolated temp authoring repos so mutating commands never touch the real workspace. reusable
- Reusable guardrail: broad close-out should audit every ported command for a concrete Kotlin CLI test, not only source-level Python-bridge removal.
- Validation gate passed after extracting authoring test helpers to satisfy Detekt `LongMethod` without suppressions.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-05-01] bridge-teardown-and-arch-ban
Areas: runtime-cli bridge teardown, runtime architecture tests, runtime scaffold authoring helpers
- Deleted the remaining Kotlin CLI Python bridge helpers after 3b_1/3b_2/3b_3 ported their callers; native payload reads now avoid `java.nio.file.Files` in runtime-cli main sources.
- Promoted the deferred runtime-cli FS/HTTP/SQL architecture bans and removed the temporary Python bridge allowlist. reusable
- Split `AuthoringOperations` helper groups into focused sibling files to satisfy the validation gate without Detekt suppressions. reusable
- Python CLI/scaffold tests remain intentionally for 3c/fallback while `skill_bill/cli.py` and `skill_bill/scaffold` still exist.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-04-30] scaffold-payload-ports
Areas: runtime-cli scaffold commands, runtime scaffold adapter, CLI golden fixtures
- Ported payload-mode `new-skill`, `new`, `new-addon`, and `create-and-fill` CLI paths to the native scaffold runtime while leaving interactive prompt modes on the Python bridge.
- Reusable pattern: keep bridge helpers present during staged retirement, but source-test ported command blocks and their native helper so payload paths cannot silently shell back to Python.
- Locked `new-skill --dry-run --format json` with a golden fixture using dynamic session/path normalization, and split scaffold CLI tests out of the broad runtime test class.
- Known limitation: editor-backed `create-and-fill` and broader inspection/authoring command ports remain deferred to later 3b subtasks.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-04-30] install-and-doctor-ports
Areas: runtime-cli install commands, runtime-core install facade, runtime-cli doctor subject routing, CLI source-reference tests
- Ported `install agent-path`, `install detect-agents`, and `install link-skill` from Python bridge calls to a public `InstallOperations` facade over internal install primitives. reusable
- Retired `doctor skill` on the Kotlin CLI with a stable replacement message while leaving subjectless `doctor` on the native `SystemService` path.
- Reusable guardrail: when bridge helpers must remain for deferred commands, add targeted source-reference tests around the newly ported command blocks instead of promoting broad architecture bans early.
- Known limitation: scaffold/authoring commands and launcher Python fallback remain owned by later 3b/3c subtasks.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-04-30] kotlin-native-contract-tests
Areas: runtime-cli golden fixtures, runtime-mcp golden fixtures, runtime architecture tests, runtime surface contracts
- Added golden JSON contract coverage for Kotlin-native CLI/MCP surfaces while explicitly deferring Python-backed scaffold/authoring/install and `doctor <subject>` work to 3b.
- Reusable pattern: normalize dynamic workflow ids/timestamps and scaffold paths only after asserting shape, prefixes, timestamp format, and path suffixes.
- Strengthened architecture guardrails with source-reference bans for Python bridge markers and runtime-mcp FS/HTTP/SQL dependencies, with only the current `McpScaffoldRuntime` repo-root lookup exception.
- Added active runtime surface locks for launcher, install, scaffold, feature-implement workflow, and feature-verify workflow; launcher Python fallback entries remain a TODO for 3c.
Feature flag: N/A
Acceptance criteria: 7/7 implemented

## [2026-04-30] packaging-schema-integration-validation
Areas: skill_bill.launcher, install.sh, runtime-mcp schemas, repository validation gate
- Confirmed the packaged Kotlin CLI/MCP installDist launch path and strict MCP schema boundary coexist through the full repository gate plus installer/stdio smoke checks. reusable
- Reusable validation pattern: run the four-command repo gate, force `McpStdioServerTest` when schema behavior matters, then smoke `skill-bill doctor` and MCP `initialize`/`tools/list` with Kotlin runtime overrides unset.
- Known limitation: `install.sh` is interactive and resets `~/.skill-bill` during reinstall, so workflow telemetry/state opened before an installer rehearsal is intentionally discarded.
Feature flag: N/A
Acceptance criteria: 5/5 implemented (Python rollback criterion superseded by Kotlin-only runtime direction)

## [2026-04-30] runtime-mcp-strict-priority-schemas
Areas: runtime-mcp tool registry, MCP stdio argument boundary, MCP schema tests
- Published strict root input schemas for priority telemetry, review, learning, scaffold, and workflow MCP tools; zero-argument workflow tools now advertise empty strict object schemas instead of open objects. reusable
- Added stdio boundary validation that rejects undeclared top-level arguments for tools whose published schema sets `additionalProperties=false`, before handler dispatch.
- Expanded `McpStdioServerTest` to lock strictness, required argument publication, enum publication, unknown argument rejection, and zero-argument strict schemas.
Feature flag: N/A
Acceptance criteria: 8/8 implemented

## [2026-04-30] packaged-runtime-installer
Areas: skill_bill.launcher, install.sh, runtime-cli installDist, runtime-mcp installDist, launcher/installer tests
- Switched default Kotlin CLI/MCP launch commands from Gradle `run` tasks to packaged application `installDist` bin scripts; missing distributions now loud-fail with installDist guidance. reusable
- `install.sh` now builds runtime-cli/runtime-mcp distributions and verifies both packaged bin scripts before registering MCP shims.
- Kept Kotlin development override env vars for local commands; the Python launcher shim is transitional and should not shape new runtime acceptance gates.
- Regression coverage locks packaged CLI/MCP path resolution, missing-distribution messaging, installer build/location behavior, and no-Gradle MCP registration.
Feature flag: N/A
Acceptance criteria: 5/5 implemented (Python rollback criteria superseded by Kotlin-only runtime direction)

## [2026-04-30] runtime-green-gate-and-python-ownership
Areas: runtime-application lifecycle validation, telemetry sync, SKILL-27 cutover checklist
- Split lifecycle telemetry validation into feature-implement, feature-verify, quality-check, and shared validator files so each lifecycle family owns its validation rules and the runtime-application Detekt `TooManyFunctions` gate stays green. reusable
- Collapsed `TelemetryService.autoSync` into a single guard predicate to satisfy the return-count rule without changing sync behavior.
- Added the Python retirement ownership inventory to the SKILL-27 cutover checklist, classifying CLI commands, MCP tools, scaffold/authoring commands, install primitives, validation scripts, and release/support scripts before any Python deletion starts.
Feature flag: N/A
Acceptance criteria: runtime green gate restored, Python ownership inventory published

## [2026-04-30] runtime-mcp-test-telemetry-loopback
Areas: runtime-mcp tests, lifecycle telemetry fixtures, MCP stdio tests
- PostHog inspection found `test-install-id` lifecycle/review events from Kotlin MCP tests because enabled test configs left `proxy_url` blank, which correctly resolves to the hosted relay in production settings.
- Added a loopback telemetry proxy override to enabled MCP test environments, including the user-home config coverage path and stdio tool-call fixture, so auto-sync cannot emit test events to production telemetry. reusable
- Reusable pattern: tests that enable telemetry should either inject a fake requester or set an explicit test-only proxy; do not rely on blank proxy config when the runtime treats blank as hosted relay.
Feature flag: N/A
Acceptance criteria: test telemetry cannot target hosted relay, runtime-mcp check passes

## [2026-04-30] runtime-mcp-feature-implement-lifecycle-schemas
Areas: runtime-mcp tool registry, MCP stdio tools/list contract, lifecycle telemetry tools
- Added explicit MCP input schemas for `feature_implement_started` and `feature_implement_finished`; the previous open schema made Codex expose these mandatory lifecycle tools as no-argument tools even though handlers required fields. reusable
- Locked the regression with an MCP stdio tools/list test that asserts required lifecycle fields such as `feature_size`, `issue_key`, `session_id`, and `completion_status` are advertised.
- Tightened feature-implement lifecycle validation so missing/defaulted started fields (`spec_input_types=[]`, zero criteria, zero text spec word count, `issue_key_type=none` with an issue key) and impossible completed metrics (`review_iterations=0`, skipped audit/validation) fail instead of emitting low-value events. reusable
- Operational symptom: a completed SKILL-32 feature-implement run committed successfully but initially missed `skillbill_feature_implement_started` and `skillbill_feature_implement_finished`; the missing pair was backfilled through the Kotlin MCP stdio server as session `fis-20260430-074408-b27m`.
- Known limit: the backfilled session has `duration_seconds=0` because started/finished were emitted together after the fact; future valid runs must call started at assessment time and finished at finalization time.
Feature flag: N/A
Acceptance criteria: MCP lifecycle schemas exposed, incomplete telemetry rejected, focused runtime-mcp check passes, telemetry pair backfilled

## [2026-04-25] runtime-mcp-lifecycle-native
Areas: runtime-application lifecycle telemetry service, runtime-infra-sqlite lifecycle telemetry store, runtime-mcp dispatcher, docs/migrations/SKILL-27-cutover-checklist.md
- Ported the remaining MCP telemetry lifecycle tools to Kotlin-native services and SQLite persistence while preserving standalone session/outbox behavior and orchestrated child telemetry payloads.
- Removed the temporary Kotlin-to-Python MCP bridge and `skill_bill.mcp_tool_bridge`; Python remains only as the explicit CLI/MCP fallback path.
- Reusable pattern: once the process boundary is cut over, retire compatibility bridges by adding a narrow application service plus persistence port, then keep MCP argument coercion at the adapter edge.
Feature flag: N/A
Acceptance criteria: native lifecycle MCP handlers, bridge removal, standalone/orchestrated telemetry parity, runtime validation

## [2026-04-25] runtime-mcp-stdio-cutover
Areas: runtime-mcp application entrypoint, skill_bill.launcher, installer MCP registration, docs/migrations/SKILL-27-cutover-checklist.md
- Added a Kotlin stdio MCP server that speaks line-delimited JSON-RPC, exposes the Python-compatible tool inventory, and dispatches ported MCP tools through Kotlin runtime services.
- Switched `skill-bill-mcp` to default to the Kotlin MCP server through the launcher, with `SKILL_BILL_MCP_RUNTIME=python` as the explicit rollback path.
- Kept a narrow Python bridge for telemetry lifecycle tools that are still Python-owned, so MCP callers keep stable tool names and payload shapes during the Kotlin server cutover.
- Reusable pattern: executable cutover can move the process boundary first while preserving unported leaf behavior behind a named compatibility bridge; document the bridge as transitional, not retired architecture.
Feature flag: N/A
Acceptance criteria: Kotlin stdio MCP packaging, launcher default switch, Python fallback, tool inventory compatibility

## [2026-04-25] runtime-cli-final-cutover
Areas: skill_bill.launcher, pyproject.toml, runtime-cli application entrypoint, docs/migrations/SKILL-27-cutover-checklist.md, docs/getting-started.md
- Switched the installed `skill-bill` script to a launcher that defaults to the Kotlin CLI and keeps `SKILL_BILL_RUNTIME=python` as the explicit rollback path.
- Added a Kotlin CLI `main` plus Gradle application run path, including stdin forwarding so payload-backed commands such as `new-skill --payload - --dry-run` work through the Kotlin-default launcher.
- Kept `skill-bill-mcp` Python-backed and made Kotlin MCP selection loud-fail until a real Kotlin stdio MCP server is packaged.
- Reusable pattern: cut over one executable surface only when it has an executable runtime path; leave unsupported sibling surfaces on documented fallback instead of pretending parity exists.
Feature flag: N/A
Acceptance criteria: Kotlin-default CLI launcher, Python fallback, MCP fallback boundary

## [2026-04-25] runtime-cutover-preparation
Areas: docs/migrations/SKILL-27-cutover-checklist.md, runtime-kotlin/ARCHITECTURE.md, runtime surface contracts, runtime smoke tests
- Added a maintained Kotlin runtime cutover checklist that names the current Python default, Kotlin parity gates, Phase 9 switch plan, and rollback path.
- Updated runtime surface contracts so scaffold and install are active Kotlin-owned surfaces after the completed scaffold-loader-install bridge, while launcher remains the reserved default-runtime switch surface.
- Reusable pattern: do not flip entrypoints in the same step that documents cutover; keep Phase 8 as handoff preparation and Phase 9 as the intentional default-runtime switch.
Feature flag: N/A
Acceptance criteria: cutover checklist, active surface metadata, launcher boundary preserved

## [2026-04-25] runtime-scaffold-loader-install-adapter
Areas: skillbill.scaffold, skillbill.install, skillbill.cli, skillbill.mcp, runtime-core, runtime-cli, runtime-mcp, runtime-contracts
- Added Kotlin scaffold-loader/install primitives and wired the CLI/MCP scaffold paths onto the Kotlin core while keeping the Python runtime as the broader oracle for the remaining surface.
- Preserved the governed loader and scaffold payload contract in the adapter layer, including explicit repo-root injection, typed started/finished payloads, and install symlink handling.
- Reusable pattern: boundary adapters should normalize payload quirks at the edge, then let the shared Kotlin core own the actual filesystem and manifest behavior.
- Runtime-kotlin `./gradlew check` passes with the new scaffold-loader-install bridge in place.
Feature flag: N/A
Acceptance criteria: scaffold loader/install bridge, adapter payload parity, full runtime-kotlin check

## [2026-04-25] runtime-model-package-ownership
Areas: runtime-domain model packages, runtime-ports model packages, runtime-application model packages, RuntimeContext, architecture tests
- Moved public data/enum model declarations out of service, runtime, and port interface files into explicit `model` packages.
- `LearningResolution`, `TelemetryOutboxRecord`, `WorkflowStateRecord`, and `HttpResponse` now live under port-owned model packages; learning/review/telemetry/domain DTOs live under area model packages.
- Moved `RuntimeContext` to `skillbill.model` and updated runtime composition/adapters to import it from the shared model package.
- Moved SQLite-owned tests from `runtime-core` to `runtime-infra-sqlite` so internal migration/schema details remain encapsulated in their owning module.
- Reusable guardrail: architecture tests now fail if a public data/enum/sealed model declaration appears in application/domain/port modules outside a `model` package.
Feature flag: N/A
Acceptance criteria: public runtime model types have explicit model package ownership

## [2026-04-25] runtime-deeper-gradle-module-split
Areas: settings.gradle.kts, runtime-contracts, runtime-domain, runtime-ports, runtime-application, runtime-infra-*, runtime-core, architecture tests
- Extracted the cleaned runtime boundaries into physical Gradle modules: contracts, domain, ports, application, SQLite infra, HTTP infra, filesystem infra, core composition, CLI, and MCP.
- Kept package names stable while moving ownership: `RuntimeContext` and ports now live in `runtime-ports`; Kotlin-Inject wiring and reserved surfaces remain in `runtime-core`.
- Reusable pattern: `runtime-core` re-exports shared modules for current CLI/MCP composition, while future cleanup can reduce those API re-exports once adapters declare direct dependencies.
- Architecture docs and guardrails now scan all module source roots and assert the implemented deeper split.
Feature flag: N/A
Acceptance criteria: deeper physical split implemented

## [2026-04-25] runtime-split-blocker-cleanup
Areas: skillbill.contracts, skillbill.application, skillbill.infrastructure.http, skillbill.infrastructure.sqlite.review, skillbill.telemetry, RuntimeContext, architecture tests
- Moved application/domain/port-to-contract mapping out of `skillbill.contracts`; contracts now stay DTO/serializer-only for future `runtime-contracts` extraction.
- Moved telemetry proxy batch mapping beside the HTTP adapter, leaving contract DTOs in `contracts.telemetry`.
- Removed the HTTP infrastructure default from `RuntimeContext`; CLI/MCP contexts own the JDK requester while core defaults to `UnconfiguredHttpRequester`.
- Moved SQL-backed review persistence, stats, feedback, and review telemetry helpers into `skillbill.infrastructure.sqlite.review`; `skillbill.review` is now persistence-free.
- Made telemetry compatibility facades port-backed so they no longer construct filesystem, SQLite, or HTTP adapters.
- Reusable guardrail: architecture tests now ban upward runtime imports from `skillbill.contracts`, infrastructure imports from `RuntimeContext` and telemetry facades, and persistence imports from `skillbill.review`.
Feature flag: N/A
Acceptance criteria: 4/4 deeper split blockers resolved

## [2026-04-25] runtime-gradle-module-split
Areas: settings.gradle.kts, build.gradle.kts, runtime-core, runtime-cli, runtime-mcp, architecture tests, SKILL-28 spec
- Split the runtime into `runtime-core`, `runtime-cli`, and `runtime-mcp`; CLI/MCP adapters now compile as independent Gradle modules over core.
- Kept deeper contract, domain, application, SQLite, and HTTP module extraction deferred in `docs/architecture/gradle-module-split-evaluation.md` until upward dependencies are removed.
- Reusable pattern: adapter modules use `api(project(":runtime-core"))` only where public context types expose core runtime ports; core exposes serialization as `api` because `JsonSupport` returns serialization types.
- Runtime architecture tests now scan all module source roots and assert the split decision plus declared Gradle modules.
Feature flag: N/A
Acceptance criteria: 3/3 implemented

## [2026-04-25] runtime-placeholder-surface-contracts
Areas: skillbill.install, skillbill.launcher, skillbill.scaffold, skillbill.workflow.*, skillbill.contracts.surface, runtime smoke tests
- Replaced empty marker interfaces with reserved runtime surface objects exposing `RuntimeSurfaceContract` metadata.
- Documented why install, launcher, scaffold, feature-implement workflow, and feature-verify workflow remain placeholder-only.
- Reusable pattern: reserved runtime surfaces must declare owner package, contract version, reserved status, and a concrete placeholder reason before implementation.
- Runtime smoke coverage now asserts reserved contracts and package boundaries instead of only checking class/package presence.
Feature flag: N/A
Acceptance criteria: 2/2 implemented

## [2026-04-25] runtime-contract-dtos-presenters
Areas: skillbill.contracts, skillbill.cli, skillbill.mcp, skillbill.application, architecture tests, golden fixtures
- Added explicit JSON contract DTOs for learning, review, MCP adapter-only payloads, and shared system version/doctor payloads.
- Moved CLI text rendering for learnings and review triage onto typed presenter models instead of rendering from raw map entries.
- Reusable pattern: external payload fields should be assembled in `skillbill.contracts.*`; CLI adapters choose text presenters or JSON payloads at the boundary.
- Golden coverage now pins representative CLI import-review JSON and MCP learning-resolution JSON while existing CLI/MCP behavior remains stable.
Feature flag: N/A
Acceptance criteria: 4/4 implemented

## [2026-04-25] runtime-versioned-db-migrations
Areas: skillbill.db, runtime architecture tests, database migration tests
- Added `schema_migrations` as the SQLite migration ledger and made `DatabaseMigrations` run ordered named migration objects only when their version is not recorded.
- Preserved legacy additive/backfill behavior by wrapping the existing review/workflow column and feedback-event normalization helpers as versioned migrations.
- Reusable pattern: future DB changes should append a new `DatabaseMigration` entry, keep names immutable, and cover legacy compatibility plus repeated-open behavior.
- Known limitation: base schema still creates the current full schema first; versioned migrations record compatibility state inside the current single-module bootstrap.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-04-25] workflow-runtime-phase-5
Areas: skillbill.workflow, skillbill.application, skillbill.ports.persistence, skillbill.infrastructure.sqlite, skillbill.cli, skillbill.mcp, runtime contracts
- Added Kotlin-owned durable workflow runtime behavior for `bill-feature-implement` and `bill-feature-verify`: open, update, get, list, latest, resume, and continue now route through `WorkflowService`.
- Reused the existing `feature_implement_workflows` and `feature_verify_workflows` SQLite tables through `WorkflowStateRepository`; no workflow-local store, schema redesign, Python entrypoint cutover, loader/scaffolder/install changes, launcher changes, or Python deletion were included.
- Preserved stable step ids, step state ordering, artifact patch semantics, resume summaries, session-summary hydration from telemetry session rows, blocked/done/already-running/reopened continuation decisions, and exact step-specific continuation directives in Kotlin domain/contract code.
- CLI and MCP adapters now delegate workflow calls to application services and add adapter-facing `status`/`db_path`/blocked-error payload fields at the boundary.
Feature flag: N/A
Acceptance criteria: 6/6 implemented

## [2026-04-24] runtime-telemetry-ported-subsystem
Areas: skillbill.application, skillbill.telemetry, skillbill.ports.telemetry, skillbill.infrastructure.http, skillbill.infrastructure.fs, skillbill.contracts.telemetry, architecture tests
- Added telemetry settings/config/client ports and wired them through Kotlin-Inject; application telemetry/status/sync/mutation paths now use ports plus `TelemetryOutboxRepository`.
- Moved telemetry config file reads/writes/deletes into `FileTelemetryConfigStore` and relay HTTP request/response mechanics into `HttpTelemetryClient`.
- Reusable contract surface: telemetry proxy batch and remote-stats payload mapping now lives in `contracts/telemetry`, outside sync orchestration.
- Reusable sync pattern: `TelemetrySyncRuntime` accepts outbox and client ports directly, with behavior coverage for disabled, noop, synced, failed, and unconfigured paths.
- Known limitation: legacy telemetry runtime objects remain as compatibility facades while review telemetry helpers are ported in later phases.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-04-24] runtime-domain-persistence-separation
Areas: skillbill.learnings, skillbill.review, skillbill.application, skillbill.ports.persistence, skillbill.infrastructure.sqlite, architecture tests
- Moved `LearningRecord` and learning request/source-validation rules into the learnings domain; `skillbill.learnings` now stays free of JDBC and review runtime imports.
- Added pure review parsing and triage decision parsing surfaces so application use cases no longer import mixed persistence runtimes for parsing/normalization.
- Reusable pattern: application use cases should query repository ports for source facts, then pass those facts to pure domain validation before calling write repositories.
- Reusable adapter: SQLite learning table access now lives in `SQLiteLearningStore`, with source-validation matching enforced before insert.
- Known limitation: review metrics/finished-payload helpers still contain transitional SQL until telemetry and review persistence are ported further.
Feature flag: N/A
Acceptance criteria: 4/4 implemented

## [2026-04-24] runtime-repository-unit-of-work
Areas: skillbill.application, skillbill.ports.persistence, skillbill.infrastructure.sqlite, skillbill.db, architecture tests
- Added persistence ports for database sessions, unit-of-work access, review repositories, learning repositories, telemetry outbox, and workflow state.
- Added SQLite adapters that wrap existing JDBC review/learning/store helpers; application services now call `read` or `transaction` on `DatabaseSessionFactory`.
- Reusable pattern: write use cases should own transaction choice in application services, while SQLite adapters call no-transaction repository helpers inside the active unit of work.
- Reusable tests: application use cases can be tested with fake repositories via `ApplicationPersistencePortTest`; SQLite transaction behavior is covered in `SQLiteDatabaseSessionFactoryTest`.
- Known limitation: review/learnings domain objects still contain JDBC-shaped helpers until the next domain-separation phase.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-04-24] runtime-typed-learning-results
Areas: skillbill.application, skillbill.learnings, skillbill.cli, skillbill.mcp, architecture tests
- Added typed learning result models for list, show, resolve, mutations, and delete; `LearningService` no longer returns map payloads for learning use cases.
- Added `LearningEntry` as the typed learning view over current `LearningRecord` rows while preserving the existing persisted/session JSON shape.
- CLI and MCP now convert typed learning results to their existing wire payloads at adapter boundaries; CLI text rendering consumes typed entries directly.
- Reusable pattern: typed-result phases should add architecture tests that block the targeted service from regressing to `Map<String, Any?>` returns.
- Known limitation: review and telemetry application result maps remain for later SKILL-28 phases.
Feature flag: N/A
Acceptance criteria: 4/4 implemented

## [2026-04-24] runtime-mcp-application-routing
Areas: skillbill.mcp, skillbill.application, skillbill.di, architecture tests, MCP tests
- Added `McpComponent` as the MCP Kotlin-Inject composition root over the shared `RuntimeComponent`.
- Routed MCP import, triage, learning resolution, stats, telemetry, version, and doctor calls through application services.
- Kept MCP telemetry-disabled skip payloads and orchestrated payload enrichment in the adapter; application services own shared persistence and payload assembly.
- Reusable pattern: architecture tests now ban MCP from importing DB/review/learnings runtime internals or telemetry runtime implementation APIs for shared workflows.
- Regression coverage compares CLI and MCP system-service payloads for version and doctor while preserving existing MCP payload tests.
Feature flag: N/A
Acceptance criteria: 5/5 implemented

## [2026-04-24] runtime-architecture-phase-0
Areas: runtime-kotlin architecture, RuntimeModule, architecture tests, SKILL-28 spec
- Added `ARCHITECTURE.md` as the runtime boundary map and target dependency-direction contract.
- Added architecture guardrail tests for the doc, declared package boundaries, application entrypoint independence, CLI command delegation, and future domain package infrastructure bans.
- Updated `RuntimeModule` so `application` and `di` are first-class declared subsystem packages.
- Reusable pattern: keep boundary tests scoped to rules true today, and document transitional exceptions such as MCP before enforcing them in later phases.
- Known limitation: MCP still bypasses application services until the planned Phase 1 refactor.
Feature flag: N/A
Acceptance criteria: 3/3 implemented
