diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9b79b61 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +All notable changes to the Telic workspace and `telic-mcp` npm package are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.0] - Unreleased + +Production-ready preview release. Tag `v0.2.0` after phases 3b–5 land on `main` and every row in [docs/PRODUCTION_GATE.md](docs/PRODUCTION_GATE.md) passes. + +### Added + +- **Fail-closed broker gate (strict mode).** Adapter hooks default to `TELIC_BROKER_STRICT=1`. When the `telic` CLI is missing, the active run cannot be evaluated, or the run is not in a running state, mapped mutating tools are denied instead of allowed. Opt out locally with `TELIC_BROKER_PERMISSIVE=1`. +- **Storage lifecycle CLI.** `telic purge-run RUN_ID` removes a run's ledger rows and unreferenced blob bodies. `telic gc` lists or deletes orphan content-store blobs (`--dry-run` supported). +- **Production gate and plan docs.** `docs/PRODUCTION_GATE.md` checklist and phased plan under `docs/PRODUCTION_PLAN/`. + +### Changed + +- **Controller validator extraction.** Evidence, cross-artifact, work-plan, and permission-trace validation moved from `RunController` into `packages/core/src/controller/*` modules with focused unit tests. Public `RunController` API unchanged. +- **Permission intersection.** Single `intersectStructuredPermissions` implementation in `packages/core/src/permissions.ts`; `controller.ts` and `tool-broker.ts` import the shared helper. + +### Fixed + +- **Ledger concurrency.** Multi-worker ledger tests use a SharedArrayBuffer contention barrier instead of a fixed sleep. `appendSupportingArtifact` reconciles concurrent `UNIQUE` inserts idempotently instead of surfacing raw SQLite errors. + +### Security + +- Broker hooks fail closed under strict mode when Telic cannot evaluate permissions for a mapped capability ([#6](https://github.com/Dukeabaddon/Telic/pull/6)). + +## [0.1.1] - Prior release + +Initial public preview: MCP control plane, nine-phase run controller, SQLite ledger, adapter packs, and `telic` CLI. diff --git a/README.md b/README.md index 6df7000..4c2ce44 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Telic gives the coding agent a workflow spine. It does not replace the agent. | Bundle | esbuild | 0.28.1 | | Tests | Vitest + `@vitest/coverage-v8` | 4.1.10 | | Format | Prettier | 3.9.5 | -| npm package | `telic-mcp` | 0.1.1 | +| npm package | `telic-mcp` | 0.2.0 | | Website | Next.js + React | 16.2.x / 19.2.x | | Site styling | Tailwind CSS | 4.3.x | diff --git a/docs/API.md b/docs/API.md index 7b8824d..2ecac40 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,6 +1,6 @@ # Telic source-preview API -**Version:** `0.1.1` implementation, artifact schema `1.0` +**Version:** `0.2.0` implementation, artifact schema `1.0` This is the human-oriented reference for the current source tree. The Zod schemas in `packages/protocol/src/` and the registrations in `packages/mcp/src/server-factory.ts` are authoritative. Telic has no compatibility guarantee before its first release. diff --git a/docs/PRODUCTION_GATE.md b/docs/PRODUCTION_GATE.md index bfe5e52..a449342 100644 --- a/docs/PRODUCTION_GATE.md +++ b/docs/PRODUCTION_GATE.md @@ -2,49 +2,51 @@ Checklist for declaring **v0.2.0** production-ready preview. Every row must pass before tagging. -**Last updated:** 2026-08-12 +**Last updated:** 2026-08-12 (phase 6 release prep) + +**Open before tag:** PRs [#8](https://github.com/Dukeabaddon/Telic/pull/8) (ledger), [#9](https://github.com/Dukeabaddon/Telic/pull/9) (3b), [#10](https://github.com/Dukeabaddon/Telic/pull/10) (3c), [#11](https://github.com/Dukeabaddon/Telic/pull/11) (storage). Do not tag until those merge and this checklist is re-walked. ## Trust boundary -| # | Criterion | Verify | -| --- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -| T1 | Broker hooks fail-closed when `TELIC_BROKER_STRICT=1` (default in adapter hooks) | `npm test -- packages/cli/src/broker-gate.test.ts test/cagt-broker-hook-e2e.test.ts` | -| T2 | Hook denies when `telic` CLI is missing (strict) | E2E or hook unit test | -| T3 | README and STATUS state Telic does not cage the host without hooks | Manual read | +| # | Criterion | Verify | Status | +| --- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------ | +| T1 | Broker hooks fail-closed when `TELIC_BROKER_STRICT=1` (default in adapter hooks) | `npm test -- packages/cli/src/broker-gate.test.ts test/cagt-broker-hook-e2e.test.ts` | pass | +| T2 | Hook denies when `telic` CLI is missing (strict) | E2E or hook unit test | pass | +| T3 | README and STATUS state Telic does not cage the host without hooks | Manual read | pass | ## Maintainability -| # | Criterion | Verify | -| --- | -------------------------------------------------------------- | -------------------------------------------------------------------------- | -| M1 | `intersectStructuredPermissions` has one implementation | `rg intersectStructuredPermissions packages/core/src` shows one definition | -| M2 | `controller.ts` facade under 1,500 lines; validators extracted | `wc -l packages/core/src/controller.ts` | -| M3 | Extracted validators have unit tests | `ls packages/core/src/controller/*.test.ts` or `validators/` | +| # | Criterion | Verify | Status | +| --- | -------------------------------------------------------------- | -------------------------------------------------------------------------- | ------- | +| M1 | `intersectStructuredPermissions` has one implementation | `rg intersectStructuredPermissions packages/core/src` shows one definition | pass | +| M2 | `controller.ts` facade under 1,500 lines; validators extracted | `wc -l packages/core/src/controller.ts` | pending | +| M3 | Extracted validators have unit tests | `ls packages/core/src/controller/*.test.ts` or `validators/` | partial | ## Quality -| # | Criterion | Verify | -| --- | ---------------------------------------- | ----------------------------------------------------- | -| Q1 | Full test suite passes | `npm test` | -| Q2 | Adapter handshakes pass | `npm run adapters:validate` | -| Q3 | Site builds (if web touched) | `npm run site:check` | -| Q4 | Coverage run stable (ledger concurrency) | `npm test -- --coverage` three consecutive green runs | +| # | Criterion | Verify | Status | +| --- | ---------------------------------------- | ----------------------------------------------------- | ------- | +| Q1 | Full test suite passes | `npm test` | pass | +| Q2 | Adapter handshakes pass | `npm run adapters:validate` | pass | +| Q3 | Site builds (if web touched) | `npm run site:check` | n/a | +| Q4 | Coverage run stable (ledger concurrency) | `npm test -- --coverage` three consecutive green runs | pending | ## Review -| # | Criterion | Verify | -| --- | ------------------------------------------------- | ------------------------ | -| R1 | `/review` or equivalent on full diff vs `main` | PR review notes | -| R2 | Bugbot triaged (fix or dismiss with reason) | PR comments | -| R3 | Security review on broker + permissions (Phase 1) | security-review artifact | +| # | Criterion | Verify | Status | +| --- | ------------------------------------------------- | ------------------------ | ------- | +| R1 | `/review` or equivalent on full diff vs `main` | PR review notes | pending | +| R2 | Bugbot triaged (fix or dismiss with reason) | PR comments | pending | +| R3 | Security review on broker + permissions (Phase 1) | security-review artifact | pending | ## Release -| # | Criterion | Verify | -| --- | --------------------------------------- | ------------------------- | -| S1 | `CHANGELOG.md` updated for v0.2.0 | File diff | -| S2 | Workspace + `telic-mcp` version `0.2.0` | `package.json` | -| S3 | Git tag `v0.2.0` on `main` | `git tag -l v0.2.0` | -| S4 | `main` CI green after merge | `gh pr checks` or Actions | +| # | Criterion | Verify | Status | +| --- | --------------------------------------- | ------------------------- | ------- | +| S1 | `CHANGELOG.md` updated for v0.2.0 | File diff | pass | +| S2 | Workspace + `telic-mcp` version `0.2.0` | `package.json` | pass | +| S3 | Git tag `v0.2.0` on `main` | `git tag -l v0.2.0` | blocked | +| S4 | `main` CI green after merge | `gh pr checks` or Actions | pending | ## Explicitly out of scope for v0.2.0 diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 06e3547..39aea17 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -1,43 +1,26 @@ +PROTOCOL.md [1045L] + # Telic Protocol **Status:** Conceptual protocol with an executable `1.0` schema implementation. - -This document explains Telic's logical roles, artifacts, state machine, and -quality boundaries. The strict Zod schemas in `packages/protocol/src/` are the -serialization authority, and [API.md](API.md) describes the current MCP surface. - +... [lean-ctx: omitted 3 lines] Most YAML blocks below intentionally retain readable snake_case conceptual -labels from the design phase. They are not copy-paste API payloads. Canonical -artifact bodies use camelCase, reject unknown fields, and carry +... [lean-ctx: omitted 2 lines] `schemaVersion: "1.0"`. The controller implements the vertical slice described in [STATUS.md](STATUS.md); statements using MUST below may still describe a -conformance target when that page marks the behavior as incomplete. +... [lean-ctx: omitted 1 lines] ## 1. Purpose Telic compiles an imprecise user request into a permission-bounded, repository-grounded workflow, records evidence from execution, reviews the work, -and releases an honest result to the user. - -The protocol separates five logical roles: - -1. **Scenario Author and Intent Guardian (Agent 1)** frames the problem and owns - semantic fidelity. -2. **Prompt and Task Compiler (Agent 2)** creates an executable task contract. -3. **Planner and Quality Controller (Agent 3)** plans and reviews execution. -4. **Executor (Agent 4)** performs the authorized investigation or work. -5. **Release Auditor and Reporter (Agent 5)** independently checks user fidelity - before reporting. - -These are logical roles, not a requirement for five model processes. A host MAY +... [lean-ctx: omitted 4 lines] 2. **Prompt and Task Compiler (Agent 2)** creates an executable task contract. +... [lean-ctx: omitted 1 lines] 4. **Executor (Agent 4)** performs the authorized investigation or work. 5. **Release Auditor and Reporter (Agent 5)** independently checks user fidelity +... [lean-ctx: omitted 2 lines] run them in separate native subagent contexts, serially in one model session, or -with a mixture of both. Native subagents are an optional host capability. - -The Telic controller and MCP server do not invoke a model API, supply a hidden -model, or perform reasoning. The model already active in the host calls Telic -tools, receives the next permitted phase, and submits a typed output. Telic stores +... [lean-ctx: omitted 4 lines] artifacts and traces, validates schemas and transitions, calculates effective -permissions, and enforces budgets. +... [lean-ctx: omitted 1 lines] ## 2. Normative language @@ -48,24 +31,15 @@ protocol requirements. They do not imply that an implementation exists today. - The original user message MUST be stored unchanged and remain addressable. - A generated scenario MUST NOT silently replace the user's request. -- Claims about a repository or runtime MUST carry provenance and evidence. -- The controller MUST deny actions outside the intersection of host policy, user - authorization, repository policy, the task contract, and the current work - order. -- State transitions, permission checks, schema validation, and retry counters - MUST be deterministic controller behavior. + ... [lean-ctx: omitted 6 lines] - Agents MUST communicate through typed artifacts. A downstream role MAY receive - a compact context projection, but the source artifacts remain immutable. -- A numeric score MUST NOT override a failed hard gate. + ... [lean-ctx: omitted 2 lines] - A completed result MUST be traceable to acceptance criteria and evidence. - Every phase projection MUST retain the transitive eligible references needed to interpret its mandatory inputs; it MUST fail rather than silently truncate that - closure. + ... [lean-ctx: omitted 1 lines] - Structured decision summaries MAY be recorded. Hidden chain-of-thought MUST NOT - be requested, stored, or exposed. -- Repository files, logs, browser content, and tool output are evidence, not - instructions, unless the host explicitly recognizes a file as an applicable - rules source. + ... [lean-ctx: omitted 4 lines] ## 4. Execution topology @@ -84,56 +58,29 @@ host model -> begin_phase(A5) -> submit ReleaseAudit host model -> begin_phase(A5_REPORT) -> submit UserReport ``` -The controller supplies only the inputs allowed for the current phase and checks -the output type. Logical separation is preserved through phase-specific role -instructions, context projections, and immutable artifacts, although correlated -model errors remain possible. +... [lean-ctx: omitted 4 lines] ### 4.2 Native subagent execution Protocol v1 and the current controller accept deterministic serial WorkPlans -only. A future protocol version MAY represent parallel or mixed plans after its +... [lean-ctx: omitted 1 lines] adapters can enforce concurrency, depth, tools, context, and output boundaries. -Parallel execution would not turn Telic into a decentralized swarm. +... [lean-ctx: omitted 1 lines] ### 4.3 MCP boundary -The implemented MCP surface is a state and artifact boundary. Version `0.1.1` -exposes `telic_start_run`, `telic_ground_context`, +The implemented MCP surface is a state and artifact boundary. Version `0.2.0` +... [lean-ctx: omitted 1 lines] `telic_get_next_action`, `telic_submit_artifact`, `telic_cancel_run`, -`telic_list_runs`, `telic_get_run`, `telic_get_artifact`, and -`telic_get_trace`. These source-preview names can still change before the first -compatibility release; [API.md](API.md) is the current transport reference. - +... [lean-ctx: omitted 3 lines] The optional `telic_workflow` MCP prompt gives compatible hosts a portable -workflow entry point. It renders host-side instructions for an exact request and -mode; it does not execute phases, invoke a model, expand authority, or replace -`NextAction` as the controller's source of truth. - +... [lean-ctx: omitted 3 lines] MCP sampling or an external model key MUST NOT be required for the core workflow. ## 5. Identifiers and references Every run, artifact, trace event, acceptance criterion, work node, and review -finding MUST have a stable identifier within its run. - -```yaml -ArtifactRef: - uri: artifact://run-01/task-contract-01 - media_type: application/vnd.telic.task-contract+yaml - sha256: sha256:3b7b... - summary: Permission-bounded diagnosis contract - -TraceRef: - uri: trace://run-01/event-0042 -``` - -An `ArtifactRef` points to immutable content. Updating an artifact creates a new -artifact and reference; it does not mutate the earlier value. Large or sensitive -tool results SHOULD be stored once and passed by reference. Derived summaries MUST -refer to their source artifacts. - -Conceptual artifact metadata (the executable envelope uses camelCase): +... [lean-ctx: omitted 15 lines] ```yaml ArtifactRecord: @@ -153,25 +100,15 @@ ArtifactRecord: The selected mode is an authorization boundary, not merely a prompt label. Unclear requests MUST NOT be silently upgraded to a more permissive mode. - -| Mode | Meaning | Default boundary | -| ----------------- | -------------------------------------------------- | --------------------------------------------------------------------------- | -| `report_only` | Explain existing facts or prior results | Read supplied artifacts; no new runtime investigation or mutation | -| `plan_only` | Produce an actionable plan without carrying it out | Read repository/context as authorized; no task execution or mutation | -| `analyze_only` | Investigate and diagnose | Read repository, browser, logs, and runtime; no edits or restarts | -| `fix_only` | Apply a known, explicitly scoped correction | Minimal preflight, scoped change, and verification only | -| `analyze_and_fix` | Diagnose, then correct a supported cause | Read-only analysis followed by bounded authorized mutation and verification | - -`fix_only` is valid when the desired change is already known. “Fix the project” -without a known cause normally requires `analyze_and_fix` or clarification. -Post-change verification is part of fixing and is not optional execution scope. - +... [lean-ctx: omitted 2 lines] +| `report_only` | Explain existing facts or prior results | Read supplied artifacts; no new runtime investigation or mutation | +| `plan_only` | Produce an actionable plan without carrying it out | Read repository/context as authorized; no task execution or mutation | +| `analyze_only` | Investigate and diagnose | Read repository, browser, logs, and runtime; no edits or restarts | +| `fix_only` | Apply a known, explicitly scoped correction | Minimal preflight, scoped change, and verification only | +| `analyze_and_fix` | Diagnose, then correct a supported cause | Read-only analysis followed by bounded authorized mutation and verification | +... [lean-ctx: omitted 3 lines] Mode defaults MAY be narrowed by a contract. They MUST NOT be broadened without -user authorization. A pre-authorized `analyze_and_fix` run MAY move from analysis -to a bounded fix without interrupting the user when the cause is supported and -the correction remains inside the approved scope. Materially different, -irreversible, privileged, production, or externally visible work requires a new -user decision. +... [lean-ctx: omitted 5 lines] ## 7. Permission model @@ -188,12 +125,12 @@ host/system policy intersect current tool capability ``` -Absence of permission means denial. A role prompt cannot grant permission. +... [lean-ctx: omitted 1 lines] ### 7.2 Permission vocabulary Implementations SHOULD express permissions as typed capabilities with optional -scope: +... [lean-ctx: omitted 1 lines] ```yaml permissions: @@ -218,22 +155,7 @@ permissions: maximum_depth: 1 ``` -Tool arguments MUST be checked against scoped permissions, not only the tool name. -Destructive operations, external messages, deployments, purchases, credential -changes, and production mutations MUST require explicit authorization. - -In the current controller, shell execution requires an exact non-compound command -present in immutable run authorization, the TaskContract, and the WorkPlan node. -`shell.inspect` records one of six typed targets (`git.status`, `git.diff`, -`git.log`, `network.listen`, `process.list`, or `runtime.logs`), not arbitrary -shell text. These checks validate submitted artifacts; they do not intercept a -host's native shell. - -Network reads require an exact DNS name or IP address in immutable run -authorization, the TaskContract, and the WorkPlan node. Authorization compares -the parsed URL hostname exactly: it does not infer subdomains, accept wildcard -entries, or trust URL credentials. The intent-mode policy permits read-only -network capability, while the intersected authorization sets its domain scope. +... [lean-ctx: omitted 14 lines] ## 8. Artifact types @@ -291,13 +213,12 @@ RunEnvelope: policy_refs: [] ``` -The envelope records available capabilities; it MUST NOT claim that unavailable -tools or native subagents exist. +... [lean-ctx: omitted 2 lines] ### 8.2 Controller and context artifacts The controller creates deterministic artifacts that tell the host what may happen -next and which context is available. These are not model outputs. +... [lean-ctx: omitted 1 lines] ```yaml NextAction: @@ -331,17 +252,7 @@ NextAction: rationale_summary: The controller selected the next legal phase. ``` -A `NextAction` MUST identify exactly one legal phase, its permitted inputs, output -schema, effective permission ceiling, and remaining budgets. It does not contain -hidden model reasoning or grant permissions beyond the RunEnvelope. - -The executable controller treats `inputRefs` as a bounded context projection, -not as the authorization boundary of artifact inspection. It includes the latest -mandatory eligible artifacts, the original request, all applicable WorkPlans and -WorkResults, and their transitive eligible artifact references. If that closure -exceeds 256 references, action generation fails explicitly; otherwise recent -Evidence may fill the remaining capacity. `telic_get_artifact` can inspect any -immutable artifact in the same run. +... [lean-ctx: omitted 10 lines] ```yaml ContextManifest: @@ -379,50 +290,7 @@ ContextManifest: selected_bytes: 13200 ``` -A ContextManifest MUST preserve provenance and explain selection. A derived -summary MUST reference its exact source artifacts. User instructions, applicable -rules, permissions, acceptance criteria, errors, active diffs, and verification -evidence MUST NOT be lossily compressed. - -The executable selector matches request terms on normalized path-token -boundaries. Applicable rules and exact active paths remain pinned. Unpinned -zero-score fallback is capped at eight files; additional candidates are counted -as `low_relevance` and produce a manifest warning. - -```yaml -ClarificationRequest: - id: clarification-01 - run_id: run-01 - question: Continue without the external publication, or start a newly authorized run? - reason: permission_expanding - divergence: Publishing is outside this run's immutable authority. - evidence_inspected: [artifact://run-01/task-contract-01] - blocked_boundary: network.externalWrite - response_constraints: Select exactly one choice identifier. - response_choices: - - id: continue-local - label: Continue locally - consequence: Resume without publishing or broadening authority. - authority_effect: within_current_authority - run_effect: resume - - id: authorize-new-run - label: Start new run - consequence: End this run; create another run with explicit authority. - authority_effect: requires_new_run - run_effect: new_run - permission_expansion_required: true - rationale_summary: The external publication cannot be authorized inside this run. -``` - -A run asks the user at most one clarification question. Its request is valid only after -proportional authorized discovery cannot resolve a user-owned, materially -divergent unknown or exposes a permission boundary. It contains two to eight -unique choices, and the response is exactly one choice identifier. A bounded -`resume` choice stays within immutable authority and the next phase artifact must -cite the request and answer. `cancel` and `new_run` choices terminate the current -run; a new-run choice never grants authority to the old run. A second material -boundary may be stored for traceability, but it consumes the exhausted budget and -routes directly to an honest blocked report without another user question. +... [lean-ctx: omitted 41 lines] ### 8.3 `ProblemFrame` @@ -461,16 +329,12 @@ ProblemFrame: rationale_summary: The request and selected repository context define a bounded diagnosis-and-fix task. ``` -Facts MUST be separated from inferences. Unknowns discoverable from authorized -repository or runtime inspection SHOULD be investigated before asking the user. -User-provenance facts cite the immutable original request, repository facts cite -selected `repo://` sources, and runtime/browser/tool facts require a matching -typed Evidence artifact. +... [lean-ctx: omitted 5 lines] ### 8.4 `ScenarioSpec` `ScenarioSpec` is a human-readable presentation derived from a `ProblemFrame`, or -a generated exercise definition in an explicit challenge/lab mode. +... [lean-ctx: omitted 1 lines] ```yaml ScenarioSpec: @@ -486,11 +350,7 @@ ScenarioSpec: rationale_summary: This presentation is derived from the authoritative frame. ``` -For project work, a ScenarioSpec MUST NOT add facts, requirements, or permissions -that are absent from its ProblemFrame. The ProblemFrame remains authoritative. -The current service stores at most one ScenarioSpec for a frame and does not -include it in Agent 2's authoritative input projection; semantic equivalence of -its free-form narrative is still a host-review responsibility. +... [lean-ctx: omitted 5 lines] ### 8.5 `TaskContract` @@ -561,17 +421,7 @@ TaskContract: rationale_summary: The contract preserves the frame and existing authorization. ``` -The rendered natural-language prompt is a view of this contract. The structured -contract, not wording style or a named prompt framework, is authoritative. - -The initial contract is version 1 and preserves the ProblemFrame's scope, -constraints, non-goals, draft criteria, and applicable rule references exactly. -It also retains every non-user fact/inference source in `contextRefs`. Execution -modes require at least one permission-backed required verification; -`analyze_and_fix` requires diagnosis and completion criteria and required -verification in both stages. `requiredOutputs` is reviewed contract prose; -deterministic completion is driven by acceptance criteria, verification, actions, -and release claims. +... [lean-ctx: omitted 10 lines] ### 8.6 `PromptReview` @@ -622,17 +472,7 @@ PromptReview: rationale_summary: The contract is fully covered and clears the v1 threshold. ``` -Agent 1 MAY request only one Agent 2 revision. The review MUST assess intent -fidelity, grounding, permissions, and verifiability rather than rewarding the -presence of decorative prompt sections. - -`overall_score` is not free-form: it equals the v1 weighted aggregate (30%, 20%, -15%, 20%, 10%, 5%) rounded to two decimals, and a pass requires at least 80. A -revision identifies typed `correction_fields`; version 2 must change every such -field, preserve declared fields, cite the prior contract and review, and change -nothing outside the correction set. Scope, constraints, non-goals, criteria, -rules, and permissions are not correctable revision fields. A `block` decision -requires a failed hard gate or a blocking finding with source references. +... [lean-ctx: omitted 10 lines] ### 8.7 `WorkPlan` @@ -674,26 +514,7 @@ WorkPlan: rationale_summary: This serial node is the smallest read-only diagnosis plan. ``` -Each node is a scoped work order for Agent 4 or one of its authorized native -subagents. Node permissions MUST be no broader than the TaskContract, every -executable node declares at least one permission-backed required capability, and -required capabilities are unique. A node reserves at least one tool call per -required capability; a required `subagent.spawn` reserves at least one child. -The sum of node reservations fits the plan's global tool budget. The current -controller accepts serial plans only and limits the sum of node tool-call -reservations across accepted WorkPlans to 4,000 per run. An initial -analyze-and-fix plan covers diagnosis criteria only; the post-gate correction -order and plan cover every completion criterion. - -Before accepting a plan, the controller maps every required verification for -the active stage to at least one node that covers a same-stage acceptance -criterion and declares the contracted capability as required. The existing -tool, permission, and budget checks then prove that the verification can be -attempted. A plan that would make its own contract unfinishable is rejected -before execution. Completion-verification nodes must also depend, directly or -transitively, on every node allowed to perform a potentially mutating action. -When mutation and verification share a node, its completed WorkResult must -record the matching non-mutating verification action after the final mutation. +... [lean-ctx: omitted 19 lines] ### 8.8 `WorkResult` @@ -734,12 +555,7 @@ WorkResult: rationale_summary: The required browser inspection completed with direct evidence. ``` -Observations and inferences MUST be distinct. A model's statement that work -passed is not evidence. A completed result records a completed action for every -node `required_capability`, covers every assigned criterion exactly once with -`pass`, and cannot hide failed tests or denied actions. Every completed -repository write/delete action and its `FileChange` must match exactly in both -directions, even when the containing WorkResult is partial, blocked, or failed. +... [lean-ctx: omitted 6 lines] ### 8.9 `QualityReview` @@ -802,31 +618,7 @@ QualityReview: rationale_summary: The supported diagnosis authorizes a separately bounded fix plan. ``` -`pass` and `proceed_to_fix` are invalid when any rule-compliance or regression -check has failed. For the read-only diagnosis stage of `analyze_and_fix`, -`proceed_to_fix` additionally requires a typed `diagnosis_gate` containing a -supported root-cause claim, direct evidence artifact references, the bounded -proposed correction, and explicit confirmation that its scope and permissions -are already approved. A generic passing hard gate or high score cannot unlock -mutation. - -A passing review requires score 80 or higher and `pass` for every applicable -criterion, rule, regression check, hard gate, and required verification. Rule -checks cover each `TaskContract.ruleRef` exactly once through `subjectRef`; -verification results cover each requirement exactly once and match its declared -capability. Passed verification is tied to compatible direct evidence from a -completed WorkResult whose plan node schedules that capability and a criterion -from the same verification stage. For completion verification in a mutating -WorkResult, that evidence must come from a matching non-mutating action after -the final mutation. Each passing acceptance result uses the exact evidence set -from matching current WorkResult coverage. An unassigned, unchanged criterion -may retain the exact evidence of an earlier passing review; criteria assigned -to the current plan cannot reuse stale review evidence. The final -analyze-and-fix review retains the earlier diagnosis verification and requires -an actual completed mutation before it can pass. - -If remediation is needed, Agent 3 creates the smallest corrective work order. It -does not grant additional permissions. +... [lean-ctx: omitted 23 lines] ### 8.10 `ReleaseAudit` and `UserReport` @@ -873,53 +665,7 @@ ReleaseAudit: rationale_summary: All fidelity, mode, criterion, and evidence checks pass. ``` -Agent 5 MUST NOT mutate the workspace. A remediation finding returns to the -controller, which asks Agent 3 for a scoped correction; Agent 5 does not directly -command Agent 4. Agent 5 uses the same shared post-execution remediation budget as -Agent 3. - -Release requires every user-fidelity check to pass, exactly one fidelity subject -for the immutable original request, a passing current QualityReview, and supported -claim coverage for every contract criterion. A partial or blocked audit retains a -concrete finding, unsupported claim, fidelity/mode failure, or unresolved risk; a -blocked QualityReview cannot be downgraded by Agent 5. - -```yaml -UserReport: - id: user-report-01 - run_id: run-01 - terminal_status: completed # completed | partial | blocked | failed_verification - summary: The missing local API-origin setting was identified, corrected, and verified. - completion_claims: - - id: CLAIM-DIAGNOSIS - text: The failed communication boundary and cause were identified. - status: observed - evidence_refs: [artifact://run-01/evidence-browser-01] - confidence: 1 - - id: CLAIM-COMPLETION - text: The supported cause was corrected and focused verification passed. - status: observed - evidence_refs: - [artifact://run-01/evidence-diff-01, artifact://run-01/evidence-test-01] - confidence: 1 - finding_refs: [] - change_refs: [artifact://run-01/evidence-diff-01] - verification_refs: - [artifact://run-01/evidence-browser-01, artifact://run-01/evidence-test-01] - unresolved_risks: [] - permissions_honored: true - next_actions: [] - trace_ref: trace://run-01 - rationale_summary: The report reproduces only audited claims and evidence. -``` - -Every completion claim in a UserReport MUST resolve to the approved contract and -evidence. Partial, blocked, and failed-verification reports MUST name the missing -criterion or boundary rather than disguising it as success. `change_refs` -retains every accepted WorkResult `diffRef` exactly once. A non-completed report -cites its controlling QualityReview or ReleaseAudit in `finding_refs`; an early -contract block cites its PromptReview. -`trace_ref` MUST be the aggregate `trace://` URI. +... [lean-ctx: omitted 44 lines] ### 8.11 Trace events @@ -944,27 +690,20 @@ TraceEvent: redactions: [] ``` -Traces SHOULD expose state transitions, role instructions, selected context, -artifact references, tool actions, permission decisions, rubric results, retries, -and concise evidence-grounded decision summaries. They MUST NOT expose hidden -chain-of-thought, secrets, or unredacted sensitive tool output. +... [lean-ctx: omitted 4 lines] ## 9. Role input and output boundaries -| Phase | Required inputs | Required output | Prohibited responsibility | -| --------------- | --------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------- | -| Agent 1 frame | RunEnvelope, original request, discovered context and rule refs | ProblemFrame; optional stored ScenarioSpec | Execution, file edits, invented facts | -| Agent 2 compile | RunEnvelope, original request, authoritative ProblemFrame and context | TaskContract | Treating ScenarioSpec as authority, execution, expansion | -| Agent 1 review | Frozen ProblemFrame, TaskContract, frozen rubric | PromptReview | Changing the source request, implementation | -| Agent 3 plan | Approved TaskContract, host capabilities, context manifest | WorkPlan | Performing the work, widening permissions | -| Agent 4 execute | One or more scoped WorkPlan nodes and permitted context/tools | WorkResult artifacts | Final acceptance, scope expansion | -| Agent 3 review | TaskContract, WorkPlan, WorkResults, evidence | QualityReview and optional remediation order | Editing the implementation itself | -| Agent 5 audit | Original request, approved contract, results, evidence, QualityReview | ReleaseAudit | Workspace mutation, unbounded rework | -| Agent 5 report | ReleaseAudit or an early blocking PromptReview, accepted result refs | UserReport | New claims, workspace mutation | - -When roles execute serially in one host session, the controller SHOULD provide a -fresh phase projection containing only these inputs. It MUST NOT treat prior model -conversation as authoritative unless represented by an artifact reference. +| Phase | Required inputs | Required output | Prohibited responsibility | +... [lean-ctx: omitted 1 lines] +| Agent 1 frame | RunEnvelope, original request, discovered context and rule refs | ProblemFrame; optional stored ScenarioSpec | Execution, file edits, invented facts | +| Agent 2 compile | RunEnvelope, original request, authoritative ProblemFrame and context | TaskContract | Treating ScenarioSpec as authority, execution, expansion | +... [lean-ctx: omitted 1 lines] +| Agent 3 plan | Approved TaskContract, host capabilities, context manifest | WorkPlan | Performing the work, widening permissions | +... [lean-ctx: omitted 1 lines] +| Agent 3 review | TaskContract, WorkPlan, WorkResults, evidence | QualityReview and optional remediation order | Editing the implementation itself | +| Agent 5 audit | Original request, approved contract, results, evidence, QualityReview | ReleaseAudit | Workspace mutation, unbounded rework | +... [lean-ctx: omitted 4 lines] ## 10. Controller state machine @@ -1001,45 +740,35 @@ stateDiagram-v2 Cancelled --> [*] ``` -For `analyze_and_fix`, the first `QualityReview` is the evidence gate. Mutation -begins only after `proceed_to_fix`, when the supported correction fits the -contract's existing authorization, and through a separately bounded WorkPlan. -For `report_only`, report synthesis is limited to authorized existing evidence. -For `fix_only`, execution begins with a minimal preflight confirming that the -known correction still applies. A clarification may pause any pre-audit phase; -bounded resume returns to that phase, while cancel/new-run choices end the run. +... [lean-ctx: omitted 7 lines] ## 11. Revision and remediation budgets - `prompt_revisions` defaults to **one**. Agent 1 may return Agent 2's contract for - one correction, then it must pass, block, or request user clarification. + ... [lean-ctx: omitted 1 lines] - `post_execution_remediations` defaults to **one shared remediation**. A - correction requested by Agent 3 or Agent 5 consumes the same counter. -- Clarification is limited to **one user-facing typed question per run** and does - not reset either revision budget. A later boundary routes to a blocked report. + ... [lean-ctx: omitted 3 lines] - The sum of accepted WorkPlan node `maximum_tool_calls` reservations is at most - **4,000 per run**. + ... [lean-ctx: omitted 1 lines] - Native subagents do not receive separate retry allowances. -- Transport-level retries MAY exist for an idempotent failed tool call, but they - MUST NOT silently rerun an agent phase or reset a quality budget. + ... [lean-ctx: omitted 2 lines] - Exhaustion results in an honest `partial`, `blocked`, or `failed_verification` - terminal state, not a fabricated success. + ... [lean-ctx: omitted 1 lines] ## 12. Versioning and compatibility All serialized artifacts MUST include a schema version. Before a stable release, -minor examples and names may change without migration support. Once implemented, +... [lean-ctx: omitted 1 lines] breaking schema changes SHOULD increment the major version, and stored artifacts SHOULD remain readable through an explicit migration or compatibility layer. - -The implementation validates schemas independently of model output and tests +... [lean-ctx: omitted 1 lines] serial logical-role execution. Native parallel scheduling remains a future -adapter capability. +... [lean-ctx: omitted 1 lines] ### 12.1 Source-preview compatibility note This source preview has no backward-compatibility guarantee. Older bodies remain -immutable in their content-addressed store, but changes to required stages, +... [lean-ctx: omitted 1 lines] capabilities, verification results, clarification choices, and cross-artifact -invariants mean they may not validate or replay under the current source tree. +... [lean-ctx: omitted 1 lines] The repository has not published a migration contract. diff --git a/package-lock.json b/package-lock.json index 81ead69..6b9a17c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "telic-workspace", - "version": "0.1.1", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "telic-workspace", - "version": "0.1.1", + "version": "0.2.0", "workspaces": [ "packages/*", "apps/*" @@ -5075,7 +5075,7 @@ }, "packages/telic": { "name": "telic-mcp", - "version": "0.1.1", + "version": "0.2.0", "license": "MIT", "bin": { "telic": "dist/bin.js" diff --git a/package.json b/package.json index 3c5761e..5af1a6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "telic-workspace", - "version": "0.1.1", + "version": "0.2.0", "private": true, "description": "A model-independent intent compiler and evidence-driven control plane for coding agents.", "type": "module", diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e0e831c..9349350 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -33,7 +33,7 @@ export function supportsRequiredNodeVersion(version: string): boolean { function usage(): string { return [ - "Telic 0.1.1", + "Telic 0.2.0", "", "Usage:", " telic doctor [--repo PATH] [--json]", diff --git a/packages/mcp/src/server-factory.ts b/packages/mcp/src/server-factory.ts index 92e2355..d9d0ee1 100644 --- a/packages/mcp/src/server-factory.ts +++ b/packages/mcp/src/server-factory.ts @@ -601,7 +601,7 @@ function registerTools(server: McpServer, service: TelicService): void { } export function createTelicMcpServer(service: TelicService): McpServer { - const server = new McpServer({ name: "telic", version: "0.1.1" }); + const server = new McpServer({ name: "telic", version: "0.2.0" }); registerPrompts(server); registerTools(server, service); return server; diff --git a/packages/telic/package.json b/packages/telic/package.json index 5f07d90..4c68d14 100644 --- a/packages/telic/package.json +++ b/packages/telic/package.json @@ -1,6 +1,6 @@ { "name": "telic-mcp", - "version": "0.1.1", + "version": "0.2.0", "description": "A local MCP control plane for permission-bounded, evidence-backed agentic coding workflows.", "type": "module", "bin": { diff --git a/plugins/telic/.codex-plugin/plugin.json b/plugins/telic/.codex-plugin/plugin.json index 98f0071..6c25239 100644 --- a/plugins/telic/.codex-plugin/plugin.json +++ b/plugins/telic/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "telic", - "version": "0.1.1", + "version": "0.2.0", "description": "Compile coding requests into permission-bounded, evidence-linked workflows with a local artifact ledger.", "author": { "name": "Telic contributors" diff --git a/plugins/telic/dist/mcp/server.js b/plugins/telic/dist/mcp/server.js index c99f738..a754820 100755 --- a/plugins/telic/dist/mcp/server.js +++ b/plugins/telic/dist/mcp/server.js @@ -31928,7 +31928,7 @@ import { realpathSync as realpathSync3 } from "node:fs"; // packages/core/dist/ledger.js import { randomUUID } from "node:crypto"; -import { chmodSync, closeSync, existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, realpathSync, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs"; +import { chmodSync, closeSync, existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, readdirSync, realpathSync, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs"; import { constants as fsConstants } from "node:fs"; import { dirname, isAbsolute as isAbsolute3, join as join2, relative as relative3, resolve as resolve3, sep as sep3 } from "node:path"; import { DatabaseSync } from "node:sqlite"; @@ -32426,6 +32426,96 @@ var SqliteLedger = class { budgetSnapshot: JSON.parse(row.budget_snapshot_json) })); } + purgeRun(runId) { + this.requireRun(runId); + const digests = this.database.prepare("SELECT DISTINCT sha256 FROM artifacts WHERE run_id = ?").all(runId).map((row) => row.sha256); + const deletedTraceEventCount = this.database.prepare("SELECT COUNT(*) AS count FROM trace_events WHERE run_id = ?").get(runId).count; + const deletedArtifactCount = this.database.prepare("SELECT COUNT(*) AS count FROM artifacts WHERE run_id = ?").get(runId).count; + this.database.exec("BEGIN IMMEDIATE"); + try { + this.database.prepare("DELETE FROM trace_events WHERE run_id = ?").run(runId); + this.database.prepare("DELETE FROM artifacts WHERE run_id = ?").run(runId); + const deleted = this.database.prepare("DELETE FROM runs WHERE run_id = ?").run(runId); + if (Number(deleted.changes) !== 1) { + throw new Error(`Run not found: ${runId}`); + } + this.database.exec("COMMIT"); + } catch (error51) { + this.database.exec("ROLLBACK"); + throw error51; + } + const deletedBlobDigests = []; + for (const digest of digests) { + if (this.deleteBlobIfUnreferenced(digest)) { + deletedBlobDigests.push(digest); + } + } + return { + runId, + deletedArtifactCount, + deletedTraceEventCount, + deletedBlobDigests + }; + } + collectOrphanBlobs(options = {}) { + const dryRun = options.dryRun ?? false; + if (existsSync2(this.blobDirectory)) { + this.assertBlobBoundary(); + } + const referenced = new Set(this.database.prepare("SELECT DISTINCT sha256 FROM artifacts").all().map((row) => row.sha256)); + const orphanDigests = this.listBlobDigests().filter((digest) => !referenced.has(digest)); + const removedDigests = []; + if (!dryRun) { + for (const digest of orphanDigests) { + const path = this.blobPath(digest); + if (!existsSync2(path)) + continue; + const info = lstatSync(path); + if (!info.isFile() || info.isSymbolicLink()) { + throw new Error("Orphan blob path must be a regular file"); + } + rmSync(path); + removedDigests.push(digest); + } + } + return { dryRun, orphanDigests, removedDigests }; + } + listBlobDigests() { + if (!existsSync2(this.blobDirectory)) + return []; + const digests = []; + for (const prefix of readdirSync(this.blobDirectory)) { + const prefixPath = join2(this.blobDirectory, prefix); + const prefixInfo = lstatSync(prefixPath); + if (!prefixInfo.isDirectory() || prefixInfo.isSymbolicLink()) + continue; + for (const suffix of readdirSync(prefixPath)) { + const filePath = join2(prefixPath, suffix); + const fileInfo = lstatSync(filePath); + if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) + continue; + digests.push(`sha256:${prefix}${suffix}`); + } + } + return digests; + } + isSha256Referenced(sha2563) { + const row = this.database.prepare("SELECT 1 AS present FROM artifacts WHERE sha256 = ? LIMIT 1").get(sha2563); + return row !== void 0; + } + deleteBlobIfUnreferenced(sha2563) { + if (this.isSha256Referenced(sha2563)) + return false; + const path = this.blobPath(sha2563); + if (!existsSync2(path)) + return false; + const info = lstatSync(path); + if (!info.isFile() || info.isSymbolicLink()) { + throw new Error("Artifact blob path must be a regular file"); + } + rmSync(path); + return true; + } blobPath(sha2563) { const digest = sha2563.startsWith("sha256:") ? sha2563.slice("sha256:".length) : sha2563; if (!/^[a-f0-9]{64}$/u.test(digest)) { @@ -32683,6 +32773,48 @@ function projectPermissions(mode2) { subagent_spawn: modeAllows(mode2, "subagent.spawn") }; } +function emptyPermissionSet() { + return { + repository: { read: [], write: [], delete: [] }, + shell: { inspect: false, executeAllowlist: [] }, + runtime: { inspect: [], restart: [] }, + browser: { inspect: false, mutateState: false }, + network: { readDomains: [], externalWrite: false }, + subagents: { spawn: false, maximumChildren: 0, maximumDepth: 0 } + }; +} +function intersectStructuredPermissions(projection, granted, denied) { + const effective = emptyPermissionSet(); + if (projection.repository_read && denied.repository.read.length === 0) { + effective.repository.read = [...granted.repository.read]; + } + if (projection.repository_write && denied.repository.write.length === 0) { + effective.repository.write = [...granted.repository.write]; + } + if (projection.repository_delete && denied.repository.delete.length === 0) { + effective.repository.delete = [...granted.repository.delete]; + } + if (projection.shell_execute && denied.shell.executeAllowlist.length === 0) { + effective.shell.executeAllowlist = [...granted.shell.executeAllowlist]; + } + effective.shell.inspect = projection.shell_inspect && granted.shell.inspect && !denied.shell.inspect; + if (projection.runtime_inspect && denied.runtime.inspect.length === 0) { + effective.runtime.inspect = [...granted.runtime.inspect]; + } + if (projection.runtime_mutate && denied.runtime.restart.length === 0) { + effective.runtime.restart = [...granted.runtime.restart]; + } + effective.browser.inspect = projection.browser_inspect && granted.browser.inspect && !denied.browser.inspect; + effective.browser.mutateState = projection.browser_mutate && granted.browser.mutateState && !denied.browser.mutateState; + if (projection.network_read && denied.network.readDomains.length === 0) { + effective.network.readDomains = [...granted.network.readDomains]; + } + effective.network.externalWrite = projection.external_write && granted.network.externalWrite && !denied.network.externalWrite; + if (projection.subagent_spawn && granted.subagents.spawn && !denied.subagents.spawn) { + effective.subagents = { ...granted.subagents }; + } + return effective; +} // packages/core/dist/classify-run.js var ELEVATED_CAPABILITIES = /* @__PURE__ */ new Set([ @@ -33522,6 +33654,54 @@ function resumeAfterClarification(run) { }; } +// packages/core/dist/controller/evidence-validator.js +function requiresDirectEvidence(path, submissionType) { + return submissionType === "WorkResult" && (/^body\.evidenceRefs\[\d+\]$/u.test(path) || /^body\.actions\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path)) || /\.claimEvidenceMatrix\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.completionClaims\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.observations\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.inferences\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.toolEventRefs\[\d+\]$/u.test(path) || /\.verificationRefs\[\d+\]$/u.test(path) || /\.diagnosisGate\.directEvidenceRefs\[\d+\]$/u.test(path) || /\.acceptanceCoverage\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.acceptanceResults\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.filesChanged\[\d+\]\.diffRef$/u.test(path) || /\.testResults\[\d+\]\.(?:commandRef|outputRef)$/u.test(path) || /\.verificationResults\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.(?:ruleCompliance|regressionChecks|userFidelity)\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || submissionType === "QualityReview" && /\.hardGates\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path); +} +var directEvidenceArtifactTypes = /* @__PURE__ */ new Set([ + "Evidence", + "ContextDocument", + "TraceEvent" +]); +var directEvidenceTraceEventTypes = /* @__PURE__ */ new Set(); +function evidenceKindsForCapability(capability2) { + switch (capability2) { + case "repository.read": + return /* @__PURE__ */ new Set(["repository", "diff", "tool_output"]); + case "repository.write": + case "repository.delete": + return /* @__PURE__ */ new Set(["diff"]); + case "shell.inspect": + case "shell.execute": + return /* @__PURE__ */ new Set(["tool_output", "log", "test"]); + case "runtime.inspect": + case "runtime.restart": + return /* @__PURE__ */ new Set(["runtime", "log", "tool_output"]); + case "browser.inspect": + case "browser.mutate": + return /* @__PURE__ */ new Set(["browser"]); + case "network.read": + case "external.write": + case "subagent.spawn": + return /* @__PURE__ */ new Set(["tool_output"]); + default: + return /* @__PURE__ */ new Set(); + } +} +function isUserReportedEvidencePath(submission, path) { + if (submission.type === "ReleaseAudit") { + const match = /^body\.claimEvidenceMatrix\[(\d+)\]\.evidenceRefs\[\d+\]$/u.exec(path); + const matrix = typeof submission.body === "object" && submission.body !== null && "claimEvidenceMatrix" in submission.body ? submission.body.claimEvidenceMatrix : null; + return match !== null && Array.isArray(matrix) && typeof matrix[Number(match[1])] === "object" && matrix[Number(match[1])] !== null && matrix[Number(match[1])].basis === "user_reported"; + } + if (submission.type === "UserReport") { + const match = /^body\.completionClaims\[(\d+)\]\.evidenceRefs\[\d+\]$/u.exec(path); + const claims = typeof submission.body === "object" && submission.body !== null && "completionClaims" in submission.body ? submission.body.completionClaims : null; + return match !== null && Array.isArray(claims) && typeof claims[Number(match[1])] === "object" && claims[Number(match[1])] !== null && claims[Number(match[1])].status === "user_reported"; + } + return false; +} + // packages/core/dist/controller.js var artifactInputsByPhase = { context_grounding: /* @__PURE__ */ new Set([ @@ -33632,16 +33812,6 @@ var MAX_NEXT_ACTION_INPUT_REFS = 256; var MAX_CLARIFICATIONS_PER_RUN = 1; var MAX_TOOL_CALLS_PER_RUN = 4e3; var REFERENCE_URI_PATTERN = /^(?:artifact|trace|repo):\/\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]+$/u; -function emptyPermissionSet() { - return { - repository: { read: [], write: [], delete: [] }, - shell: { inspect: false, executeAllowlist: [] }, - runtime: { inspect: [], restart: [] }, - browser: { inspect: false, mutateState: false }, - network: { readDomains: [], externalWrite: false }, - subagents: { spawn: false, maximumChildren: 0, maximumDepth: 0 } - }; -} function capabilitiesToPermissionSet(capabilities, shellExecuteAllowlist = [], denyAll = false, networkReadDomains = []) { const permissions = emptyPermissionSet(); for (const capability2 of capabilities) { @@ -33690,38 +33860,6 @@ function capabilitiesToPermissionSet(capabilities, shellExecuteAllowlist = [], d } return permissions; } -function intersectStructuredPermissions(projection, granted, denied) { - const effective = emptyPermissionSet(); - if (projection.repository_read && denied.repository.read.length === 0) { - effective.repository.read = [...granted.repository.read]; - } - if (projection.repository_write && denied.repository.write.length === 0) { - effective.repository.write = [...granted.repository.write]; - } - if (projection.repository_delete && denied.repository.delete.length === 0) { - effective.repository.delete = [...granted.repository.delete]; - } - if (projection.shell_execute && denied.shell.executeAllowlist.length === 0) { - effective.shell.executeAllowlist = [...granted.shell.executeAllowlist]; - } - effective.shell.inspect = projection.shell_inspect && granted.shell.inspect && !denied.shell.inspect; - if (projection.runtime_inspect && denied.runtime.inspect.length === 0) { - effective.runtime.inspect = [...granted.runtime.inspect]; - } - if (projection.runtime_mutate && denied.runtime.restart.length === 0) { - effective.runtime.restart = [...granted.runtime.restart]; - } - effective.browser.inspect = projection.browser_inspect && granted.browser.inspect && !denied.browser.inspect; - effective.browser.mutateState = projection.browser_mutate && granted.browser.mutateState && !denied.browser.mutateState; - if (projection.network_read && denied.network.readDomains.length === 0) { - effective.network.readDomains = [...granted.network.readDomains]; - } - effective.network.externalWrite = projection.external_write && granted.network.externalWrite && !denied.network.externalWrite; - if (projection.subagent_spawn && granted.subagents.spawn && !denied.subagents.spawn) { - effective.subagents = { ...granted.subagents }; - } - return effective; -} function explicitPermissionProjection(mode2, envelope) { const ceiling = projectPermissions(mode2); if (typeof envelope !== "object" || envelope === null || !("authorization" in envelope)) { @@ -33969,39 +34107,6 @@ var potentiallyMutatingPlanCapabilities = /* @__PURE__ */ new Set([ "shell.execute", "subagent.spawn" ]); -function requiresDirectEvidence(path, submissionType) { - return submissionType === "WorkResult" && (/^body\.evidenceRefs\[\d+\]$/u.test(path) || /^body\.actions\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path)) || /\.claimEvidenceMatrix\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.completionClaims\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.observations\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.inferences\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.toolEventRefs\[\d+\]$/u.test(path) || /\.verificationRefs\[\d+\]$/u.test(path) || /\.diagnosisGate\.directEvidenceRefs\[\d+\]$/u.test(path) || /\.acceptanceCoverage\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.acceptanceResults\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.filesChanged\[\d+\]\.diffRef$/u.test(path) || /\.testResults\[\d+\]\.(?:commandRef|outputRef)$/u.test(path) || /\.verificationResults\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || /\.(?:ruleCompliance|regressionChecks|userFidelity)\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path) || submissionType === "QualityReview" && /\.hardGates\[\d+\]\.evidenceRefs\[\d+\]$/u.test(path); -} -var directEvidenceArtifactTypes = /* @__PURE__ */ new Set([ - "Evidence", - "ContextDocument", - "TraceEvent" -]); -var directEvidenceTraceEventTypes = /* @__PURE__ */ new Set(); -function evidenceKindsForCapability(capability2) { - switch (capability2) { - case "repository.read": - return /* @__PURE__ */ new Set(["repository", "diff", "tool_output"]); - case "repository.write": - case "repository.delete": - return /* @__PURE__ */ new Set(["diff"]); - case "shell.inspect": - case "shell.execute": - return /* @__PURE__ */ new Set(["tool_output", "log", "test"]); - case "runtime.inspect": - case "runtime.restart": - return /* @__PURE__ */ new Set(["runtime", "log", "tool_output"]); - case "browser.inspect": - case "browser.mutate": - return /* @__PURE__ */ new Set(["browser"]); - case "network.read": - case "external.write": - case "subagent.spawn": - return /* @__PURE__ */ new Set(["tool_output"]); - default: - return /* @__PURE__ */ new Set(); - } -} var PermissionAuthorizationError = class extends Error { permissionDecision; inputRefs; @@ -34050,19 +34155,6 @@ function isKnownSystemReference(reference) { function terminalField(path) { return /\.([A-Za-z][A-Za-z0-9]*)(?:\[\d+\])?$/u.exec(path)?.[1] ?? ""; } -function isUserReportedEvidencePath(submission, path) { - if (submission.type === "ReleaseAudit") { - const match = /^body\.claimEvidenceMatrix\[(\d+)\]\.evidenceRefs\[\d+\]$/u.exec(path); - const matrix = typeof submission.body === "object" && submission.body !== null && "claimEvidenceMatrix" in submission.body ? submission.body.claimEvidenceMatrix : null; - return match !== null && Array.isArray(matrix) && typeof matrix[Number(match[1])] === "object" && matrix[Number(match[1])] !== null && matrix[Number(match[1])].basis === "user_reported"; - } - if (submission.type === "UserReport") { - const match = /^body\.completionClaims\[(\d+)\]\.evidenceRefs\[\d+\]$/u.exec(path); - const claims = typeof submission.body === "object" && submission.body !== null && "completionClaims" in submission.body ? submission.body.completionClaims : null; - return match !== null && Array.isArray(claims) && typeof claims[Number(match[1])] === "object" && claims[Number(match[1])] !== null && claims[Number(match[1])].status === "user_reported"; - } - return false; -} function equalStringSets(left, right) { return left.length === right.length && new Set(left).size === left.length && left.every((item) => right.includes(item)); } @@ -35784,58 +35876,16 @@ var RunController = class { }; // packages/core/dist/tool-broker.js -function emptyPermissionSet2() { - return { - repository: { read: [], write: [], delete: [] }, - shell: { inspect: false, executeAllowlist: [] }, - runtime: { inspect: [], restart: [] }, - browser: { inspect: false, mutateState: false }, - network: { readDomains: [], externalWrite: false }, - subagents: { spawn: false, maximumChildren: 0, maximumDepth: 0 } - }; -} -function intersectStructuredPermissions2(projection, granted, denied) { - const effective = emptyPermissionSet2(); - if (projection.repository_read && denied.repository.read.length === 0) { - effective.repository.read = [...granted.repository.read]; - } - if (projection.repository_write && denied.repository.write.length === 0) { - effective.repository.write = [...granted.repository.write]; - } - if (projection.repository_delete && denied.repository.delete.length === 0) { - effective.repository.delete = [...granted.repository.delete]; - } - if (projection.shell_execute && denied.shell.executeAllowlist.length === 0) { - effective.shell.executeAllowlist = [...granted.shell.executeAllowlist]; - } - effective.shell.inspect = projection.shell_inspect && granted.shell.inspect && !denied.shell.inspect; - if (projection.runtime_inspect && denied.runtime.inspect.length === 0) { - effective.runtime.inspect = [...granted.runtime.inspect]; - } - if (projection.runtime_mutate && denied.runtime.restart.length === 0) { - effective.runtime.restart = [...granted.runtime.restart]; - } - effective.browser.inspect = projection.browser_inspect && granted.browser.inspect && !denied.browser.inspect; - effective.browser.mutateState = projection.browser_mutate && granted.browser.mutateState && !denied.browser.mutateState; - if (projection.network_read && denied.network.readDomains.length === 0) { - effective.network.readDomains = [...granted.network.readDomains]; - } - effective.network.externalWrite = projection.external_write && granted.network.externalWrite && !denied.network.externalWrite; - if (projection.subagent_spawn && granted.subagents.spawn && !denied.subagents.spawn) { - effective.subagents = { ...granted.subagents }; - } - return effective; -} function permissionsFromEnvelope(mode2, envelope) { const ceiling = projectPermissions(mode2); if (typeof envelope !== "object" || envelope === null || !("authorization" in envelope)) { - return emptyPermissionSet2(); + return emptyPermissionSet(); } const authorization = envelope.authorization; if (typeof authorization !== "object" || authorization === null || !("granted" in authorization) || !("denied" in authorization)) { - return emptyPermissionSet2(); + return emptyPermissionSet(); } - return intersectStructuredPermissions2(ceiling, authorization.granted, authorization.denied); + return intersectStructuredPermissions(ceiling, authorization.granted, authorization.denied); } function actionKindForCapability2(capability2) { if (capability2 === "runtime.restart") @@ -39113,7 +39163,7 @@ function registerTools(server, service) { ); } function createTelicMcpServer(service) { - const server = new McpServer({ name: "telic", version: "0.1.1" }); + const server = new McpServer({ name: "telic", version: "0.2.0" }); registerPrompts(server); registerTools(server, service); return server; diff --git a/test/package-release.test.ts b/test/package-release.test.ts index ef7deaf..4faa44a 100644 --- a/test/package-release.test.ts +++ b/test/package-release.test.ts @@ -17,7 +17,7 @@ describe("public release assets", () => { it("keeps the npm package and Codex plugin on the same release version", () => { const npmPackage = json("packages/telic/package.json"); const plugin = json("plugins/telic/.codex-plugin/plugin.json"); - expect(npmPackage.version).toBe("0.1.1"); + expect(npmPackage.version).toBe("0.2.0"); expect(plugin.version).toBe(npmPackage.version); }); diff --git a/test/plugin-bundle.test.ts b/test/plugin-bundle.test.ts index c9a3143..b583818 100644 --- a/test/plugin-bundle.test.ts +++ b/test/plugin-bundle.test.ts @@ -62,7 +62,7 @@ describe("bundled Codex plugin MCP", () => { }); it("ships complete public presentation metadata and a local vector mark", () => { - expect(manifest.version).toBe("0.1.1"); + expect(manifest.version).toBe("0.2.0"); expect(manifest.homepage).toBe("https://github.com/Dukeabaddon/Telic"); expect(manifest.repository).toBe(manifest.homepage); expect(manifest.license).toBe("MIT");