diff --git a/.github/workflows/deploy-config-schema.yml b/.github/workflows/deploy-config-schema.yml deleted file mode 100644 index d5d13356..00000000 --- a/.github/workflows/deploy-config-schema.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Deploy config schema - -on: - push: - branches: - - main - paths: - - config/pkl/** - - .github/workflows/deploy-config-schema.yml - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: deploy-config-schema - cancel-in-progress: false - -jobs: - deploy: - name: Deploy config schema to config.sce.crocoder.dev - timeout-minutes: 60 - runs-on: ubuntu-latest - env: - VERCEL_CLI_VERSION: 58.7.1 - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Set up Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: 24 - - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22 - - - name: Enable Magic Nix Cache - uses: DeterminateSystems/magic-nix-cache-action@908b263ff629f4cc17666315b7fd3ec127c6244d # v14 - with: - use-flakehub: false - use-gha-cache: true - - - name: Ensure Vercel deployment secrets are configured - shell: bash - run: | - set -euo pipefail - - missing="" - for name in VERCEL_TOKEN VERCEL_ORG_ID VERCEL_PROJECT_ID; do - if [ -z "${!name:-}" ]; then - missing="${missing} ${name}" - fi - done - - if [ -n "$missing" ]; then - printf 'Missing required repository secret(s):%s\n' "$missing" >&2 - exit 1 - fi - - - name: Generate the canonical config schema - shell: bash - run: | - set -euo pipefail - - staging_dir="${RUNNER_TEMP}/config-schema-site" - schema_module="${RUNNER_TEMP}/config-schema-deploy.pkl" - mkdir -p "$staging_dir" - - cat > "$schema_module" </dev/null - - file_count="$(find "$staging_dir" -type f | wc -l)" - if [ "$file_count" -ne 1 ] || [ ! -f "${staging_dir}/config.json" ]; then - printf 'Deployment staging must contain only config.json\n' >&2 - find "$staging_dir" -type f -print >&2 - exit 1 - fi - - printf 'STAGING_DIR=%s\n' "$staging_dir" >> "$GITHUB_ENV" - - - name: Deploy the schema to Vercel - shell: bash - run: | - set -euo pipefail - - npx --yes "vercel@${VERCEL_CLI_VERSION}" deploy "$STAGING_DIR" \ - --prod \ - --yes \ - --token "$VERCEL_TOKEN" diff --git a/.github/workflows/release-sce.yml b/.github/workflows/release-sce.yml index 085aee74..06d926b2 100644 --- a/.github/workflows/release-sce.yml +++ b/.github/workflows/release-sce.yml @@ -258,7 +258,7 @@ jobs: merge-multiple: true - name: Create GitHub release - uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: tag_name: ${{ needs.resolve-release.outputs.tag }} name: sce ${{ needs.resolve-release.outputs.tag }} diff --git a/.sce/config.json b/.sce/config.json index 0cea5dab..5f4db1c4 100644 --- a/.sce/config.json +++ b/.sce/config.json @@ -1,5 +1,5 @@ { - "$schema": "https://sce.crocoder.dev/config.json", + "$schema": "https://sce.crocoder.dev/v0.4.0-pre-alpha-v5/config.json", "agent_trace": { "auto_sync": true }, diff --git a/cli/build.rs b/cli/build.rs index 08228085..e416953a 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -20,6 +20,7 @@ const PACKAGE_FALLBACK_INVENTORY: &str = "SHA256SUMS"; const OPTIONAL_WORKFLOW_MANIFEST: &str = "config/optional-workflows.json"; const OPTIONAL_WORKFLOW_MANIFEST_SCHEMA_VERSION: u64 = 1; const CANONICAL_GENERATOR_INPUTS: &[&str] = &[ + ".version", "config/pkl", "config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts", "config/lib/bash-policy-plugin/opencode-bash-policy-plugin.ts", diff --git a/cli/src/services/agent_trace.rs b/cli/src/services/agent_trace.rs index 53237968..5ec19dfe 100644 --- a/cli/src/services/agent_trace.rs +++ b/cli/src/services/agent_trace.rs @@ -31,6 +31,10 @@ use super::version::PACKAGE_VERSION; pub const AGENT_TRACE_VERSION: &str = "0.1.0"; pub(crate) const SCE_WEB_BASE_URL: &str = "https://sce.crocoder.dev"; +pub(crate) fn sce_config_schema_url() -> String { + format!("{SCE_WEB_BASE_URL}/v{PACKAGE_VERSION}/config.json") +} + const RANGE_CONTENT_HASH_PREFIX: &str = "murmur3:"; const RANGE_CONTENT_HASH_INPUT_VERSION: &[u8] = b"sce-agent-trace-range-content-hash-v1\0"; const TOUCHED_LINE_ADDED_TAG: &[u8] = b"added\0"; diff --git a/cli/src/services/config/schema.rs b/cli/src/services/config/schema.rs index 016b0ff4..846eab52 100644 --- a/cli/src/services/config/schema.rs +++ b/cli/src/services/config/schema.rs @@ -738,6 +738,15 @@ mod agent_trace_config_tests { ) } + #[test] + fn accepts_versioned_schema_declaration() { + let schema_url = format!( + "https://sce.crocoder.dev/v{}/config.json", + env!("CARGO_PKG_VERSION") + ); + parse(&format!(r#"{{"$schema":"{schema_url}"}}"#)).unwrap(); + } + #[test] fn parses_agent_trace_repository_identity_keys() { let config = parse( diff --git a/cli/src/services/setup/mod.rs b/cli/src/services/setup/mod.rs index 7c076ada..66d58f71 100644 --- a/cli/src/services/setup/mod.rs +++ b/cli/src/services/setup/mod.rs @@ -59,8 +59,8 @@ pub(crate) fn is_missing_git_remote_error(error: &anyhow::Error) -> bool { /// Contains only the `$schema` declaration pointing to the SCE config JSON Schema. fn repo_local_config_bootstrap_payload() -> String { format!( - "{{\n \"$schema\": \"{}/config.json\"\n}}\n", - crate::services::agent_trace::SCE_WEB_BASE_URL + "{{\n \"$schema\": \"{}\"\n}}\n", + crate::services::agent_trace::sce_config_schema_url() ) } @@ -1949,6 +1949,19 @@ mod tests { } } + #[test] + fn repo_local_config_bootstrap_payload_uses_versioned_schema_url() { + let payload = repo_local_config_bootstrap_payload(); + + assert_eq!( + payload, + format!( + "{{\n \"$schema\": \"https://sce.crocoder.dev/v{}/config.json\"\n}}\n", + env!("CARGO_PKG_VERSION") + ) + ); + } + #[test] fn resolve_setup_request_accepts_pi_target() { let request = resolve_setup_request(options_with(|options| { diff --git a/config/pkl/base/sce-config-schema.pkl b/config/pkl/base/sce-config-schema.pkl index 625ec996..49d99fec 100644 --- a/config/pkl/base/sce-config-schema.pkl +++ b/config/pkl/base/sce-config-schema.pkl @@ -3,6 +3,7 @@ import "workflow-catalog.pkl" as catalog import "../deps/org.json_schema/JsonSchema.pkl" local presetIds = bash_policy_presets.presets.toList().map((preset) -> preset.id).toListing() +local configSchemaUrl = "https://sce.crocoder.dev/v\(read("../../../.version").text.trim())/config.json" local optionalWorkflowIds = catalog.workflows.toMap() .filter((_, workflow) -> workflow.optional) @@ -59,7 +60,7 @@ local perDbRetrySchema = new JsonSchema { local sceConfigSchema = new JsonSchema { $schema = "https://json-schema.org/draft/2020-12/schema" - $id = "https://sce.crocoder.dev/config.json" + $id = configSchemaUrl title = "SCE Config" description = "Canonical JSON Schema for global and repo-local sce/config.json files." type = "object" @@ -67,7 +68,7 @@ local sceConfigSchema = new JsonSchema { properties { ["$schema"] = new JsonSchema { type = "string" - `const` = "https://sce.crocoder.dev/config.json" + `const` = configSchemaUrl } ["log_level"] = new JsonSchema { type = "string" diff --git a/config/pkl/generator-inputs.txt b/config/pkl/generator-inputs.txt index 3b23492e..deec2637 100644 --- a/config/pkl/generator-inputs.txt +++ b/config/pkl/generator-inputs.txt @@ -1,4 +1,5 @@ # Repository-relative files and directories that determine generate.pkl output. +.version config/pkl config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts config/lib/bash-policy-plugin/opencode-bash-policy-plugin.ts diff --git a/context/cli/config-precedence-contract.md b/context/cli/config-precedence-contract.md index 1ac3e725..f498e205 100644 --- a/context/cli/config-precedence-contract.md +++ b/context/cli/config-precedence-contract.md @@ -78,7 +78,7 @@ When a default-discovered global or repo-local config file exists but fails JSON - `sce config validate` and `sce doctor` both validate config-file structure against that shared generated schema before applying Rust-owned semantic checks such as duplicate custom `argv_prefix` detection and redundancy warnings. - Each reported schema-validation error is prefixed with the failing value's JSON-pointer location when it has one (for example `/integrations/optional_workflows/0: "nonesuch" is not one of "brownfield"`), so a rejected value names the key it came from; root-level errors keep their unprefixed text. Errors remain sorted and joined with ` | `. - After schema validation, `cli/src/services/config/schema.rs` deserializes top-level and nested config structure (`policies`, `policies.bash`, `policies.attribution_hooks`) into typed serde DTOs and applies focused Rust-owned mapping helpers for enum conversion and source attribution; policy-specific semantic checks are owned by `cli/src/services/config/policy.rs`. -- The canonical top-level schema declaration `"$schema": "https://sce.crocoder.dev/config.json"` is a supported config key for both explicit and discovered `sce/config.json` files, including command-startup paths like `sce version` and other config-loading commands that parse config before normal command dispatch. +- The canonical top-level schema declaration `"$schema": "https://sce.crocoder.dev/v/config.json"` (where `` is the CLI release version) is a supported config key for both explicit and discovered `sce/config.json` files, including command-startup paths like `sce version` and other config-loading commands that parse config before normal command dispatch. - Startup/runtime config resolution now degrades gracefully only for default-discovered files: invalid discovered files are skipped and reported via collected `validation_errors`, while explicit `--config` / `SCE_CONFIG_FILE` targets still fail immediately on the same parse or validation errors. - Config file content must be valid JSON with a top-level object. diff --git a/context/glossary.md b/context/glossary.md index 0424a317..e1b44628 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -111,7 +111,7 @@ - `setup required-hook install orchestration`: Setup-service flow in `cli/src/services/setup/mod.rs` (`install_required_git_hooks`, backed by the rename-injectable `install_required_git_hooks_with_rename`) that resolves repository root + effective hooks directory via git truth, then for each hook computes the bytes to stage with the `setup hook-merge seam` (`hook_merge::merge_or_create_hook`) instead of writing the canonical asset verbatim, reports deterministic per-hook outcomes (`Installed`, `Updated`, `Skipped`) against that merged content plus the executable bit, enforces executable permissions, sets `RequiredHookInstallResult.unreachable_block_advisory` (rendered as a named advisory line) when an appended block would be unreachable, and uses the `setup atomic-swap` policy (see `setup atomic-swap`) — staged content is renamed directly over an existing hook without unlinking it first — with deterministic recovery guidance on swap failure. - `setup hooks CLI mode`: `sce setup` behavior activated by `--hooks` (with optional `--repo `), supporting both hooks-only runs and composable target+hooks runs in one invocation; implemented through `cli/src/services/setup/command.rs` + `cli/src/services/setup/mod.rs`, enforces deterministic compatibility validation (`--repo` requires `--hooks`; target flags remain mutually exclusive), and emits stable setup/hook status output. - `setup repo gate`: Preflight check in `cli/src/services/setup/command.rs` that calls `cli/src/services/setup/mod.rs` (`ensure_git_repository`) before any setup writes begin; enforces that all `sce setup` modes (config-only, hooks-only, combined, and interactive) require the current directory to be inside a git repository, failing with actionable guidance to run `git init` and rerun `sce setup` when the precondition is not met. -- `setup local bootstrap`: Pre-install setup bootstrap behavior now owned by lifecycle providers: `ConfigLifecycle::setup` creates missing `.sce/config.json` with the canonical schema-only payload (`{"$schema": "https://sce.crocoder.dev/config.json"}`), `LocalDbLifecycle::setup` initializes the canonical local DB via `LocalDb::new()`, and `AgentTraceDbLifecycle::setup` creates/reuses checkout identity, resolves repository identity, initializes the repository-scoped Agent Trace DB via `agent_trace_storage`, and records repository ID, checkout ID, and `database_path`; the setup command aggregates these calls before config/hooks dispatch across all normal setup modes after context baseline bootstrap. +- `setup local bootstrap`: Pre-install setup bootstrap behavior now owned by lifecycle providers: `ConfigLifecycle::setup` creates missing `.sce/config.json` with the canonical versioned schema-only payload (`{"$schema": "https://sce.crocoder.dev/v/config.json"}`, using the CLI release version), `LocalDbLifecycle::setup` initializes the canonical local DB via `LocalDb::new()`, and `AgentTraceDbLifecycle::setup` creates/reuses checkout identity, resolves repository identity, initializes the repository-scoped Agent Trace DB via `agent_trace_storage`, and records repository ID, checkout ID, and `database_path`; the setup command aggregates these calls before config/hooks dispatch across all normal setup modes after context baseline bootstrap. - `setup context baseline bootstrap`: Additive durable-context tree bootstrap in `cli/src/services/setup/mod.rs` (`bootstrap_context_baseline`) that create-if-missing writes neutral baseline Markdown files, working directories, and `context/tmp/.gitignore` via `RepoPaths` accessors. `sce setup --bootstrap-context` is the dedicated context-only mode and must be used alone; every normal successful setup path also ensures the same baseline after the Git gate and before lifecycle/config install work without overwriting existing content. - `CLI redaction-safe diagnostics contract`: baseline security behavior implemented via `cli/src/services/security.rs` (`redact_sensitive_text`) and applied to app-level errors, setup git-diagnostic surfacing, and observability output sinks so common secret-bearing token forms are masked before emission. - `setup directory write-permission probe`: deterministic pre-write guard implemented in `cli/src/services/security.rs` (`ensure_directory_is_writable`) and used by setup install/hook flows to fail fast with actionable remediation when target directories are not writable. @@ -141,7 +141,7 @@ - `sce shared output-format contract`: Canonical parser contract in `cli/src/services/output_format.rs` (`OutputFormat`) that centralizes supported `--format` values (`text`, `json`) and emits command-specific actionable invalid-value guidance (`Run ' --help' ...`) for commands wired to dual-output rendering. - `sce shell completion contract`: Deterministic CLI completion contract where `sce completion --shell ` emits parser-aligned Bash/Zsh/Fish completion scripts for current top-level commands and supported options/subcommands. - `CLI default path catalog`: Canonical production path-ownership contract in `cli/src/services/default_paths.rs`; it is the shared owner for non-test production CLI path definitions, covering per-user persisted paths plus repo-relative, install, hook, and context-path definitions used by production CLI code; build-time payload paths are `OUT_DIR`-owned and excluded. -- `SCE web URL owner`: Rust service module at `cli/src/services/agent_trace.rs`; owns `SCE_WEB_BASE_URL = "https://sce.crocoder.dev"` and helper functions for SCE-owned URL construction, including Agent Trace conversation URLs, persisted Agent Trace trace URLs, Agent Trace session URLs, and setup-created config schema URLs. It does not own the `control_plane_base_url` used by `sce sync`, whose baked default is `https://sce.crocoderlab.dev`. +- `SCE web URL owner`: Rust service module at `cli/src/services/agent_trace.rs`; owns `SCE_WEB_BASE_URL = "https://sce.crocoder.dev"` and helper functions for SCE-owned URL construction, including Agent Trace conversation URLs, persisted Agent Trace trace URLs, Agent Trace session URLs, and setup-created versioned config schema URLs. It does not own the `control_plane_base_url` used by `sce sync`, whose baked default is `https://sce.crocoderlab.dev`. - `CLI capability traits`: Broad capability seam in `cli/src/services/capabilities.rs` consumed by the borrowed, compile-time-typed `AppContext`. `FsOps`/`StdFsOps` wrap filesystem operations and `GitOps`/`ProcessGitOps` wrap git process execution plus repository-root and hooks-directory resolution; current service internals do not consume them directly yet. - `FsOps`: Filesystem capability trait in `cli/src/services/capabilities.rs` with `read_file`, `write_file`, `metadata`, and `exists`, implemented in production by `StdFsOps`. - `GitOps`: Git capability trait in `cli/src/services/capabilities.rs` with `run_command`, `resolve_repository_root`, `resolve_hooks_directory`, and `is_available`, implemented in production by `ProcessGitOps`. diff --git a/context/overview.md b/context/overview.md index 1c8fcb6c..dd0cc802 100644 --- a/context/overview.md +++ b/context/overview.md @@ -30,7 +30,7 @@ The `setup` command includes an `inquire`-backed target-selection flow: default For repository generation consumers, `config/pkl/generator-inputs.txt` declares the canonical Pkl/plugin input set and `scripts/produce-cli-generated-input.sh` owns its discovery, two-pass `config/pkl/generate.pkl` evaluation, determinism comparison, payload/input inventories, in-flight input-mutation rejection, atomic handoff publication, and staging cleanup. `scripts/run-cli-cargo.sh` creates a fresh temporary destination, delegates generation to that producer, invokes the requested Cargo workflow with `SCE_CLI_GENERATED_INPUT_DIR`, and removes the handoff after Cargo success, failure, or handled signals. `config/pkl/check-generated.sh` delegates the same production mechanics while retaining contract and path assertions. The root flake also runs `codex-hook-command`, which generates the Codex assets and verifies root, nested-cwd, spaced-path, stdin-forwarding, and fail-open invocation behavior against a stub `sce`. `scripts/prepare-cli-generated-assets.sh` moves the producer-validated Pkl payload and checksums into the unchanged package fallback, adds hooks, migrations, and the Agent Trace schema, and appends only those static checksums to the combined inventory. The root flake's pre-Cargo `cliGeneratedInput` derivation invokes the same producer from a declarative source containing the producer plus its declared inputs. `cli/build.rs` rejects missing, incomplete, modified, or stale repository handoffs, copies the validated payload into Cargo `OUT_DIR/pkl-generated`, stages static inputs under `OUT_DIR/static`, and writes setup-asset, optional-workflow-catalog, and migration Rust manifests into `OUT_DIR`; it never invokes Pkl. Published crates carry the ignored packaging-only fallback, and unpacked downstream builds validate and copy it into their own `OUT_DIR` without requiring Pkl or parent repository paths. The setup service also provides repository-root install orchestration: it resolves the repository root, ensures the additive durable-context baseline, then for normal modes derives a repo-root-scoped `AppContext` from the runtime command context, aggregates `ServiceLifecycle::setup` calls across lifecycle providers (config → local_db → auth_db → agent_trace_db → hooks when requested), handles interactive or flag-based target selection for config asset installation, and reports deterministic completion details (selected target(s) and installed file counts). Setup installs config assets (`.opencode`/`.claude`/`.pi`) per file: each embedded asset is staged and swapped into its own destination path, creating parent directories as needed, without removing or recreating the target directory as a whole, so files a repository owns inside an SCE-managed target directory survive a setup run untouched. Two assets are merge targets rather than verbatim writes: Claude's `.claude/settings.json` and OpenCode's `.opencode/opencode.json`. For each, setup JSON-merges the generated document into the user's existing file rather than overwriting it, and fails deterministically without writing if the existing file is not valid JSON; a missing file is still created from the generated document verbatim. Claude's merge replaces only SCE-owned hook entries (identified by a command containing `run-sce-or-show-install-guidance.sh`) and the `$schema` key while preserving every other key and hook entry untouched. OpenCode's merge replaces the `$schema` key and merges the `plugin` array as a set: any entry shaped like an SCE plugin path (`./plugins/sce-*`) is dropped, structurally, so a path an older or renamed catalog once installed is still recognized and pruned, and the generated document's canonical plugin entries are appended after the surviving user entries. Required-hook install uses the same per-file stage/atomic-swap choreography as config-asset install — the staging file is renamed directly over an existing hook without unlinking it first, so a rename failure leaves the prior hook untouched. Both flows return deterministic recovery guidance (recover from version control) on swap failure, without creating backup artifacts. After installing, config install prunes stale SCE-owned assets: it deletes every path the full embedded catalog for the target claims but the current selection did not install (a deselected optional workflow, or an asset a newer catalog renamed or dropped), then removes any parent directory left empty by that deletion, leaving a directory intact if a user file still lives inside it. The setup command gates all modes on an existing git repository before any writes. Internally, `cli/src/services/setup/mod.rs`now separates install-flow logic from interactive prompt logic through focused support seams. The CLI now also applies baseline security hardening for reliability-driven automation: diagnostics/logging paths use deterministic secret redaction,`sce setup --hooks --repo ` canonicalizes and validates repository paths before execution, and setup write flows run explicit directory write-permission probes before staging/swap operations. -The config service now provides deterministic runtime config resolution with explicit precedence (`flags > env > config file > defaults`), strict config-file validation (`$schema`, `log_level`, `log_format`, `log_to_file`, `log_dir`, `timeout_ms`, `workos_client_id`, and nested `policies.bash`, `policies.attribution_hooks.enabled`, plus `policies.database_retry` with per-DB `connection_open`/`query` retry policy specs), deterministic default discovery/merge of global+local config files (`${config*root}/sce/config.json`then`.sce/config.json`with local override, where`config_root` comes from the shared default-path seam with XDG/`dirs::config_dir()` config-root resolution), defaults for the resolved observability value set (`log_level=error`, `log_format=text`, `log_dir=/sce/logs`), shared auth-key resolution with optional baked defaults starting at `workos_client_id`, first-class bash-policy preset/custom parsing with deterministic conflict and duplicate-prefix validation, custom-policy `satisfied_by`wrapper exemption (a policy does not fire when the matched command was unwrapped from a declared wrapper such as`nix shell nixpkgs#ripgrep`), and a canonical Pkl-authored `sce/config.json`JSON Schema generated beneath Cargo`OUT_DIR`and embedded by`cli/src/services/config/mod.rs`for both`sce config validate`and doctor-time config checks. Runtime startup config loading keeps parity with that schema by accepting its`$schema`declaration in repo-local and global config files, so startup commands such as`sce version`no longer fail before dispatch on that field; the canonical declaration is`"https://sce.crocoder.dev/config.json"`; this schema URL is separate from the `https://sce.crocoderlab.dev` baked default used by `sce sync` for control-plane ingestion. App-runtime observability now consumes flat logging keys through the shared resolver, so env values still override config-file values while config files provide deterministic fallback for `log_dir`; positive-integer `log_file_retention_limit` uses config-file/default precedence, defaults to `10`, and controls creation-triggered cleanup for primary and v2 log files; `sce config show` reports resolved observability/auth/policy values with provenance, while `sce config validate` is now a trimmed validation surface that reports only pass/fail plus validation errors or warnings in text and JSON modes. The canonical preset catalog and matching contract live in `config/pkl/base/bash-policy-presets.pkl` and `context/sce/bash-tool-policy-enforcement-contract.md`. +The config service now provides deterministic runtime config resolution with explicit precedence (`flags > env > config file > defaults`), strict config-file validation (`$schema`, `log_level`, `log_format`, `log_to_file`, `log_dir`, `timeout_ms`, `workos_client_id`, and nested `policies.bash`, `policies.attribution_hooks.enabled`, plus `policies.database_retry` with per-DB `connection_open`/`query` retry policy specs), deterministic default discovery/merge of global+local config files (`${config*root}/sce/config.json`then`.sce/config.json`with local override, where`config_root` comes from the shared default-path seam with XDG/`dirs::config_dir()` config-root resolution), defaults for the resolved observability value set (`log_level=error`, `log_format=text`, `log_dir=/sce/logs`), shared auth-key resolution with optional baked defaults starting at `workos_client_id`, first-class bash-policy preset/custom parsing with deterministic conflict and duplicate-prefix validation, custom-policy `satisfied_by`wrapper exemption (a policy does not fire when the matched command was unwrapped from a declared wrapper such as`nix shell nixpkgs#ripgrep`), and a canonical Pkl-authored `sce/config.json`JSON Schema generated beneath Cargo`OUT_DIR`and embedded by`cli/src/services/config/mod.rs`for both`sce config validate`and doctor-time config checks. Runtime startup config loading keeps parity with that schema by accepting its`$schema`declaration in repo-local and global config files, so startup commands such as`sce version`no longer fail before dispatch on that field; the canonical declaration is versioned as`"https://sce.crocoder.dev/v/config.json"` using the CLI release version; this schema URL is separate from the `https://sce.crocoderlab.dev` baked default used by `sce sync` for control-plane ingestion. App-runtime observability now consumes flat logging keys through the shared resolver, so env values still override config-file values while config files provide deterministic fallback for `log_dir`; positive-integer `log_file_retention_limit` uses config-file/default precedence, defaults to `10`, and controls creation-triggered cleanup for primary and v2 log files; `sce config show` reports resolved observability/auth/policy values with provenance, while `sce config validate` is now a trimmed validation surface that reports only pass/fail plus validation errors or warnings in text and JSON modes. The canonical preset catalog and matching contract live in `config/pkl/base/bash-policy-presets.pkl` and `context/sce/bash-tool-policy-enforcement-contract.md`. Invalid default-discovered config files now also degrade gracefully at startup: `sce` keeps running with degraded observability defaults, logs `sce.config.invalid_config` warnings, and reserves hard failures for explicit `--config` / `SCE_CONFIG_FILE` targets or other truly invalid runtime observability inputs. `cli/src/services/config/mod.rs` is now a module facade that declares focused config submodules (`types`, `schema`, `policy`, `resolver`, private `render`, `command`, and `lifecycle`), re-exporting `pub use types::*`and`pub(crate) use schema::validate_config_file`. Shared config primitive ownership is delegated to `cli/src/services/config/types.rs`; schema loading and file parsing to `cli/src/services/config/schema.rs`; bash-policy semantic validation and policy-specific formatting to `cli/src/services/config/policy.rs`; runtime discovery/precedence to `cli/src/services/config/resolver.rs`; and `sce config show`/`sce config validate`text+JSON output construction to`cli/src/services/config/render.rs`. Downstream modules continue importing through `services::config`unchanged. The CLI now has a generic borrowed`AppContext`dependency view in`cli/src/app.rs`; `AppRuntime`owns concrete production logger/telemetry/fs/git dependencies, and command execution receives context views that borrow those dependencies plus an optional`repo_root: Option`. `AppContext::with_repo_root(...)`/`ContextWithRepoRoot`derives repo-root-scoped views while preserving the borrowed runtime dependencies, and command execution is generic over associated-type narrow accessor traits where practical. The broad capability seam lives in`cli/src/services/capabilities.rs`, where `FsOps`/`StdFsOps`wrap filesystem operations and`GitOps`/`ProcessGitOps`wrap git process execution plus repository-root/hooks-directory resolution. The shared default path service in`cli/src/services/default_paths.rs`is now the canonical owner for production CLI path definitions. It resolves per-user config/state/cache roots through a dedicated internal`roots`seam, exposes the current persisted-artifact inventory (global config and auth tokens), and also defines named DB paths (auth DB, local DB, Agent Trace DB) plus the repo-relative, install, hook, and context-path accessors consumed across current CLI production code. Non-test production modules should consume this shared catalog instead of hardcoding owned path literals. No default cache-backed persisted artifact currently exists, so cache-root resolution remains available without speculative cache-path features and no legacy default-path fallback is supported. diff --git a/context/sce/config-schema-publication.md b/context/sce/config-schema-publication.md index 6f3c35fb..d05daddd 100644 --- a/context/sce/config-schema-publication.md +++ b/context/sce/config-schema-publication.md @@ -6,15 +6,17 @@ The canonical `sce/config.json` JSON Schema is authored in `config/pkl/base/sce-config-schema.pkl`, generated ephemerally, and embedded into the CLI for config validation. This file records that there is currently no separate public schema-hosting workflow; the canonical config declaration -uses the SCE web application URL. +uses the versioned SCE web application URL. ## Current behavior - The schema's `$id` and the config `$schema` property use - `https://sce.crocoder.dev/config.json`. -- `sce setup` writes that same declaration into a newly created repo-local - `.sce/config.json`. -- The generated schema is not committed. Repository builds and packaged builds + `https://sce.crocoder.dev/v/config.json`, where `` is the + CLI release version. +- `sce setup` writes that same versioned declaration into a newly created + repo-local `.sce/config.json`. +- The generated schema is not committed. Its version is read from the root + `.version` file during Pkl generation. Repository builds and packaged builds consume the validated generated payload through Cargo `OUT_DIR` or the packaging fallback. - `sce.crocoder.dev` remains the SCE web application host. Agent Trace diff --git a/context/sce/setup-repo-local-config-bootstrap.md b/context/sce/setup-repo-local-config-bootstrap.md index e190fa4e..10f38ffd 100644 --- a/context/sce/setup-repo-local-config-bootstrap.md +++ b/context/sce/setup-repo-local-config-bootstrap.md @@ -7,7 +7,7 @@ Task `setup-repo-gate-and-local-config-bootstrap` T02, `turso-local-db-sync` T04 ## Behavior - Any successful `sce setup` run in a git-backed repository creates `.sce/config.json` when the file is absent. -- The bootstrap writes the canonical schema-only JSON payload: `{"$schema": "https://sce.crocoder.dev/config.json"}` (with trailing newline). +- The bootstrap writes the canonical schema-only JSON payload: `{"$schema": "https://sce.crocoder.dev/v/config.json"}` (where `` is the CLI release version, with a trailing newline). - If `.sce/config.json` already exists, the bootstrap step returns `Ok(())` immediately and leaves the file untouched — no merge, no reformat, no overwrite. - The parent `.sce/` directory is created via `fs::create_dir_all` if missing. - The setup flow also bootstraps the canonical local DB through `LocalDbLifecycle::setup` and the Agent Trace DB through `AgentTraceDbLifecycle::setup`; both use the shared `TursoDb` adapter. diff --git a/flake.nix b/flake.nix index da4d86d7..0e064803 100644 --- a/flake.nix +++ b/flake.nix @@ -174,6 +174,7 @@ root = workspaceRoot; fileset = pkgs.lib.fileset.unions [ ./config/pkl + ./.version ./config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts ./config/lib/bash-policy-plugin/opencode-bash-policy-plugin.ts ./config/lib/pi-plugin/sce-pi-extension.ts @@ -238,6 +239,7 @@ root = workspaceRoot; fileset = pkgs.lib.fileset.unions [ ./config/pkl + ./.version ./config/pkl/renderers/fixtures/atomic-commit-content-check.pkl ./config/pkl/renderers/fixtures/commit-forbidden-path-check.pkl ./config/pkl/renderers/fixtures/layout-reference-check.pkl @@ -1646,7 +1648,7 @@ type = "app"; program = "${bumpVersionApp}/bin/bump-version"; meta = { - description = "Bump the checked-in version in .version, Cargo.toml, Cargo.lock, npm package.json, and Flatpak metainfo"; + description = "Bump the checked-in version and create the versioned Agent Trace schema"; }; }; diff --git a/nix/bump-version.sh b/nix/bump-version.sh index 0c8eee18..4d73e37a 100755 --- a/nix/bump-version.sh +++ b/nix/bump-version.sh @@ -7,6 +7,7 @@ usage() { printf 'usage: bump-version.sh --repo-root --version \n\n' >&2 printf 'Updates the project version in .version, cli/Cargo.toml, cli/Cargo.lock,\n' >&2 printf 'npm/package.json, and the first release in packaging/flatpak/%s.metainfo.xml.\n' "$APP_ID" >&2 + printf 'Also creates schema/v/config.json from the Agent Trace schema.\n' >&2 printf '\n' >&2 printf 'Options:\n' >&2 printf ' --repo-root Repository root directory\n' >&2 @@ -74,6 +75,8 @@ cargo_toml_path="$repo_root/cli/Cargo.toml" cargo_lock_path="$repo_root/cli/Cargo.lock" npm_package_path="$repo_root/npm/package.json" metainfo_path="$repo_root/packaging/flatpak/$APP_ID.metainfo.xml" +agent_trace_schema_path="$repo_root/config/schema/agent-trace.schema.json" +schema_output_path="$repo_root/schema/v${version}/config.json" if [[ -z "$from_version" ]]; then from_version="$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' < "$version_path")" @@ -85,6 +88,7 @@ files=( "$cargo_lock_path" "$npm_package_path" "$metainfo_path" + "$agent_trace_schema_path" ) for f in "${files[@]}"; do @@ -152,6 +156,19 @@ replace_once \ "primary Flatpak release version" \ "0,/ "${test_repo}/.version" printf 'generator-v1\n' > "${test_repo}/config/pkl/generate.pkl" printf 'agent-trace\n' > "${test_repo}/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts"