docs: migrate legacy specs and add product context (#2964)
This commit is contained in:
@@ -178,7 +178,7 @@ Standalone agentctl is launched in its own process group so terminal Ctrl+C is h
|
|||||||
- **Two session paths bypass the task service and must guard themselves.** (1) Handlers that resolve an execution by a bare in-memory lookup (`GetExecutionBySessionID`, `*BySessionID`) skip the `GetOrEnsure*` chokepoint where the lifecycle check runs — call `service.AuthorizeSessionAccess` (see `ProcessHandlers.denySessionAccess`) or `lifecycleMgr.CheckSessionAccess` first. (2) The orchestrator reads sessions through its own repo handle, so it inherits nothing from the task service; session-keyed entry points there call `authorizeSession` / `authorizeTask`, wired by `SetSessionAccessChecker` / `SetTaskAccessChecker`. An entry point that accepts **both** a task and a session ID must use `authorizeTaskSessionPair`, never `authorizeSession` alone: the caller can satisfy a session-only check with one of their own sessions while pointing `taskID` at someone else's task, which the method then uses for its task-scoped work. Put the guard **first** in the method, before any repo/executor/agent-manager use — `TestSessionKeyedEntryPointsGuardBeforeDependencies` runs each entry point with nil dependencies and fails on a panic if a guard is placed too late.
|
- **Two session paths bypass the task service and must guard themselves.** (1) Handlers that resolve an execution by a bare in-memory lookup (`GetExecutionBySessionID`, `*BySessionID`) skip the `GetOrEnsure*` chokepoint where the lifecycle check runs — call `service.AuthorizeSessionAccess` (see `ProcessHandlers.denySessionAccess`) or `lifecycleMgr.CheckSessionAccess` first. (2) The orchestrator reads sessions through its own repo handle, so it inherits nothing from the task service; session-keyed entry points there call `authorizeSession` / `authorizeTask`, wired by `SetSessionAccessChecker` / `SetTaskAccessChecker`. An entry point that accepts **both** a task and a session ID must use `authorizeTaskSessionPair`, never `authorizeSession` alone: the caller can satisfy a session-only check with one of their own sessions while pointing `taskID` at someone else's task, which the method then uses for its task-scoped work. Put the guard **first** in the method, before any repo/executor/agent-manager use — `TestSessionKeyedEntryPointsGuardBeforeDependencies` runs each entry point with nil dependencies and fails on a panic if a guard is placed too late.
|
||||||
- **Dispatched WS actions have a gateway backstop.** `Client.authorizeAction` (`internal/gateway/websocket/dispatch_scope.go`) checks any payload carrying `task_id` or `session_id` against the client's identity *before* dispatch, so a newly added action is scoped by default rather than only when its author remembers. Handler-level scoping is still required (defense in depth, and it covers non-WS callers) — do not delete a service-layer check because the backstop exists. The backstop reads `task_id`, `session_id` and `task_environment_id`, plus `id` on **top-level `task.<verb>` actions only** (`task.state`, `task.move`, `task.get`, … — `task.` and exactly one more segment). Keep using those names for task-scoped actions, because an action that invents a new name for the same kind of resource silently opts out. (`user_shell.stop` was exactly that: it keys off `task_environment_id` with `task_id` optional, so a `task_id`-only backstop missed it. `task.state` and `task.move` were the second instance: they name the task `id`, so the backstop parsed no refs and let one user mutate any other user's task — hence the `id` rule.) Deeper `task.*` namespaces are deliberately excluded: in `task.plan.revision.get` and `task.review.finding.update` the `id` names a revision or a finding, so those actions must carry `task_id` when they are task-scoped. Also avoid permissive types (`any`, `json.RawMessage`) for those fields: a payload that fails to unmarshal into the ref struct is treated as naming nothing.
|
- **Dispatched WS actions have a gateway backstop.** `Client.authorizeAction` (`internal/gateway/websocket/dispatch_scope.go`) checks any payload carrying `task_id` or `session_id` against the client's identity *before* dispatch, so a newly added action is scoped by default rather than only when its author remembers. Handler-level scoping is still required (defense in depth, and it covers non-WS callers) — do not delete a service-layer check because the backstop exists. The backstop reads `task_id`, `session_id` and `task_environment_id`, plus `id` on **top-level `task.<verb>` actions only** (`task.state`, `task.move`, `task.get`, … — `task.` and exactly one more segment). Keep using those names for task-scoped actions, because an action that invents a new name for the same kind of resource silently opts out. (`user_shell.stop` was exactly that: it keys off `task_environment_id` with `task_id` optional, so a `task_id`-only backstop missed it. `task.state` and `task.move` were the second instance: they name the task `id`, so the backstop parsed no refs and let one user mutate any other user's task — hence the `id` rule.) Deeper `task.*` namespaces are deliberately excluded: in `task.plan.revision.get` and `task.review.finding.update` the `id` names a revision or a finding, so those actions must carry `task_id` when they are task-scoped. Also avoid permissive types (`any`, `json.RawMessage`) for those fields: a payload that fails to unmarshal into the ref struct is treated as naming nothing.
|
||||||
- **WS**: clients carry their identity; dispatched actions and subscriptions are scoped; workspace-carrying events route via `Hub.BroadcastToWorkspace`. A new `hub.Broadcast` (global) call site needs a `//ws:global` justification comment. Subscription actions (`*.subscribe`) `return` before reaching the dispatch backstop (`Client.authorizeAction`), so scoping a new subscribe topic means adding a hook to `SubscriptionAccessPolicy` (`internal/gateway/websocket/access.go`) *and* calling it explicitly inside that action's own handler — the backstop's `scopedActionRefs` cannot reach it. `run.subscribe`'s `Subscriptions.Run` hook (added for WO-02, resolving the run's workspace via `runs.GetRunWorkspaceID`) is the worked example.
|
- **WS**: clients carry their identity; dispatched actions and subscriptions are scoped; workspace-carrying events route via `Hub.BroadcastToWorkspace`. A new `hub.Broadcast` (global) call site needs a `//ws:global` justification comment. Subscription actions (`*.subscribe`) `return` before reaching the dispatch backstop (`Client.authorizeAction`), so scoping a new subscribe topic means adding a hook to `SubscriptionAccessPolicy` (`internal/gateway/websocket/access.go`) *and* calling it explicitly inside that action's own handler — the backstop's `scopedActionRefs` cannot reach it. `run.subscribe`'s `Subscriptions.Run` hook (added for WO-02, resolving the run's workspace via `runs.GetRunWorkspaceID`) is the worked example.
|
||||||
- **Task overview vs. session detail:** Keep cross-task state in the bounded, persisted `TaskStatusSummary` projection and `task.status_summary.updated`; keep unbounded transcript and execution data session-scoped. The [bounded task-status spec](../../docs/specs/platform/bounded-task-status-delivery.md) and [accepted ADR](../../docs/decisions/2026-08-01-separate-task-summary-session-stream-traffic.md) define the shared contract.
|
- **Task overview vs. session detail:** Keep cross-task state in the bounded, persisted `TaskStatusSummary` projection and `task.status_summary.updated`; keep unbounded transcript and execution data session-scoped. The [bounded task-status spec](../../docs/specs/platform/requirements/bounded-task-status-delivery.md) and [accepted ADR](../../docs/decisions/2026-08-01-separate-task-summary-session-stream-traffic.md) define the shared contract.
|
||||||
- **Port-proxy subtree capability:** with auth enforced, `/port-proxy/:sessionId/:port/*` needs a credential on *every* request, but browser subresource fetches cannot always carry one (the `<link rel="manifest">` fetch never sends cookies; sandboxed iframes and `?token=`-opened previews drop them elsewhere). `PortProxyHandler` mints a short-lived (15 min, sliding), path-scoped, HMAC-signed capability after the document authenticates and propagates it as a cookie scoped to the proxy subtree plus a `kandev_cap` query parameter appended to rewritten asset URLs. `requireConnectionAuth` (`internal/gateway/websocket/access.go`) accepts either form only for the exact session:port it was minted for and restores the issuing identity, so `CheckSessionAccess` still gates on the real owner. Synthetic identities (auth disabled) skip minting — proxied bodies stay byte-identical to the pre-auth behavior.
|
- **Port-proxy subtree capability:** with auth enforced, `/port-proxy/:sessionId/:port/*` needs a credential on *every* request, but browser subresource fetches cannot always carry one (the `<link rel="manifest">` fetch never sends cookies; sandboxed iframes and `?token=`-opened previews drop them elsewhere). `PortProxyHandler` mints a short-lived (15 min, sliding), path-scoped, HMAC-signed capability after the document authenticates and propagates it as a cookie scoped to the proxy subtree plus a `kandev_cap` query parameter appended to rewritten asset URLs. `requireConnectionAuth` (`internal/gateway/websocket/access.go`) accepts either form only for the exact session:port it was minted for and restores the issuing identity, so `CheckSessionAccess` still gates on the real owner. Synthetic identities (auth disabled) skip minting — proxied bodies stay byte-identical to the pre-auth behavior.
|
||||||
- **Self-authenticating webhooks** (automation and office channels) and `/health`, `/api/v1/features`, `/api/v1/app-state` stay public — the allowlist lives in `auth/httpmw/middleware.go` with a pinning test. Plugin webhooks are not allowlisted: `auth/httpmw` structurally defers only GET/POST relay paths because it cannot read manifests, and `internal/plugins.Controller.webhook` enforces each manifest's `access` policy.
|
- **Self-authenticating webhooks** (automation and office channels) and `/health`, `/api/v1/features`, `/api/v1/app-state` stay public — the allowlist lives in `auth/httpmw/middleware.go` with a pinning test. Plugin webhooks are not allowlisted: `auth/httpmw` structurally defers only GET/POST relay paths because it cannot read manifests, and `internal/plugins.Controller.webhook` enforces each manifest's `access` policy.
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const steerSetupHoldMillis = 30_000
|
|||||||
// text of its own, so a mid-turn steer delivered while it runs can only be
|
// text of its own, so a mid-turn steer delivered while it runs can only be
|
||||||
// answered by the steer's own successor turn. This reproduces the "folded"
|
// answered by the steer's own successor turn. This reproduces the "folded"
|
||||||
// outcome from the mid-turn steering spec's outcome taxonomy
|
// outcome from the mid-turn steering spec's outcome taxonomy
|
||||||
// (docs/specs/platform/mid-turn-steering.md): the predecessor settles without
|
// (docs/specs/platform/requirements/mid-turn-steering.md): the predecessor settles without
|
||||||
// having produced an answer, and the operator sees a single, combined reply.
|
// having produced an answer, and the operator sees a single, combined reply.
|
||||||
func scenarioSteerFoldSetup(e *emitter) {
|
func scenarioSteerFoldSetup(e *emitter) {
|
||||||
waitForDelay(e.ctx, steerSetupHoldMillis)
|
waitForDelay(e.ctx, steerSetupHoldMillis)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ type sshSessionState struct {
|
|||||||
// Each session owns its own *ssh.Client (no shared pool). One SSH connection
|
// Each session owns its own *ssh.Client (no shared pool). One SSH connection
|
||||||
// per session keeps teardown simple — closing the executor instance closes
|
// per session keeps teardown simple — closing the executor instance closes
|
||||||
// the client — at the cost of an extra TCP+handshake per session on the same
|
// the client — at the cost of an extra TCP+handshake per session on the same
|
||||||
// host. See docs/specs/ssh-executor/spec.md for the full design.
|
// host. See docs/specs/executors/requirements/ssh-executor.md for the full design.
|
||||||
type SSHExecutor struct {
|
type SSHExecutor struct {
|
||||||
agentctlResolver *AgentctlResolver
|
agentctlResolver *AgentctlResolver
|
||||||
secretStore secrets.SecretStore
|
secretStore secrets.SecretStore
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ func (c *Config) ToSharedConfig() *shared.Config {
|
|||||||
//
|
//
|
||||||
// Delivery is opportunistic: whether the agent folds the prompt into the running
|
// Delivery is opportunistic: whether the agent folds the prompt into the running
|
||||||
// turn or runs it as the next turn is the agent's decision and is not advertised
|
// turn or runs it as the next turn is the agent's decision and is not advertised
|
||||||
// over the protocol. See docs/specs/platform/mid-turn-steering.md.
|
// over the protocol. See docs/specs/platform/requirements/mid-turn-steering.md.
|
||||||
type SteerablePrompter interface {
|
type SteerablePrompter interface {
|
||||||
SupportsSteering() bool
|
SupportsSteering() bool
|
||||||
PromptSteer(
|
PromptSteer(
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (a *Adapter) SupportsSteering() bool {
|
|||||||
// Delivery is opportunistic. Whether the agent folds this prompt into the
|
// Delivery is opportunistic. Whether the agent folds this prompt into the
|
||||||
// running turn or runs it as the next turn is the agent's decision and is not
|
// running turn or runs it as the next turn is the agent's decision and is not
|
||||||
// advertised over the protocol, so both outcomes must be correct. See
|
// advertised over the protocol, so both outcomes must be correct. See
|
||||||
// docs/specs/platform/mid-turn-steering.md.
|
// docs/specs/platform/requirements/mid-turn-steering.md.
|
||||||
func (a *Adapter) PromptSteer(
|
func (a *Adapter) PromptSteer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
message string,
|
message string,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const promptQueueingMetaKey = "promptQueueing"
|
|||||||
// the bridge, is not advertised over ACP, and its version is not observable
|
// the bridge, is not advertised over ACP, and its version is not observable
|
||||||
// here. Mid-turn delivery is therefore opportunistic, and a caller must be
|
// here. Mid-turn delivery is therefore opportunistic, and a caller must be
|
||||||
// correct whether the agent folds the prompt or runs it as the next turn. See
|
// correct whether the agent folds the prompt or runs it as the next turn. See
|
||||||
// docs/specs/platform/mid-turn-steering.md.
|
// docs/specs/platform/requirements/mid-turn-steering.md.
|
||||||
//
|
//
|
||||||
// Every malformed shape fails closed to false: absent `_meta`, absent
|
// Every malformed shape fails closed to false: absent `_meta`, absent
|
||||||
// namespace, a namespace that is not an object, a missing key, or a value that
|
// namespace, a namespace that is not an object, a missing key, or a value that
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ type AgentEvent struct {
|
|||||||
// prompt while another prompt for the same session is still in flight. It is
|
// prompt while another prompt for the same session is still in flight. It is
|
||||||
// the negotiated precondition for prompt handoff and mid-turn steering, and
|
// the negotiated precondition for prompt handoff and mid-turn steering, and
|
||||||
// does not by itself promise the agent will fold that prompt into the running
|
// does not by itself promise the agent will fold that prompt into the running
|
||||||
// turn. See docs/specs/platform/mid-turn-steering.md.
|
// turn. See docs/specs/platform/requirements/mid-turn-steering.md.
|
||||||
SupportsPromptQueueing bool `json:"supports_prompt_queueing"`
|
SupportsPromptQueueing bool `json:"supports_prompt_queueing"`
|
||||||
|
|
||||||
// AuthMethods lists authentication methods from ACP initialize.
|
// AuthMethods lists authentication methods from ACP initialize.
|
||||||
@@ -541,7 +541,7 @@ type ConfigOptionValue struct {
|
|||||||
// per-turn total — see normalizeCodexPromptUsage in
|
// per-turn total — see normalizeCodexPromptUsage in
|
||||||
// server/adapter/transport/acp/dialect_codex.go). Rows flagged estimated
|
// server/adapter/transport/acp/dialect_codex.go). Rows flagged estimated
|
||||||
// still count toward budget totals at face value per
|
// still count toward budget totals at face value per
|
||||||
// docs/specs/office/costs.md.
|
// docs/specs/office/requirements/costs.md.
|
||||||
type PromptUsage struct {
|
type PromptUsage struct {
|
||||||
InputTokens int64 `json:"input_tokens"`
|
InputTokens int64 `json:"input_tokens"`
|
||||||
OutputTokens int64 `json:"output_tokens"`
|
OutputTokens int64 `json:"output_tokens"`
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ func (h *Handlers) logout(c *gin.Context) {
|
|||||||
// deliberately left alone: on a host serving a default-port instance (or a
|
// deliberately left alone: on a host serving a default-port instance (or a
|
||||||
// not-yet-upgraded one), it is that instance's LIVE session cookie, not an
|
// not-yet-upgraded one), it is that instance's LIVE session cookie, not an
|
||||||
// inert legacy jar — expiring it would log the other instance out (see
|
// inert legacy jar — expiring it would log the other instance out (see
|
||||||
// docs/specs/fix-multi-instance-cookie-isolation/spec.md: the upgraded
|
// docs/specs/auth/requirements/fix-multi-instance-cookie-isolation.md: the upgraded
|
||||||
// instance does not proactively delete the legacy cookie).
|
// instance does not proactively delete the legacy cookie).
|
||||||
clearSessionCookie(c, h.svc.CookieNameForRequest(c.Request))
|
clearSessionCookie(c, h.svc.CookieNameForRequest(c.Request))
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ func TestEnabledModeAllowlistMatrix(t *testing.T) {
|
|||||||
|
|
||||||
{name: "office without bearer", method: http.MethodGet, path: "/api/v1/office/tasks/t1", blocked: true},
|
{name: "office without bearer", method: http.MethodGet, path: "/api/v1/office/tasks/t1", blocked: true},
|
||||||
{
|
{
|
||||||
// AC-14 (docs/specs/health-endpoint-version/spec.md): /health
|
// AC-14 (docs/specs/platform/requirements/health-endpoint-version.md): /health
|
||||||
// gained a version field, but /api/v1/system/info is not on the
|
// gained a version field, but /api/v1/system/info is not on the
|
||||||
// allowlist and must stay rejected — the change must not widen it.
|
// allowlist and must stay rejected — the change must not widen it.
|
||||||
name: "system info", method: http.MethodGet, path: "/api/v1/system/info", blocked: true,
|
name: "system info", method: http.MethodGet, path: "/api/v1/system/info", blocked: true,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package automation
|
|||||||
import "gopkg.in/yaml.v3"
|
import "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
// exportDocumentVersion and exportDocumentType are the fixed values of the export
|
// exportDocumentVersion and exportDocumentType are the fixed values of the export
|
||||||
// document's `version` and `type` keys. See docs/specs/automations-yaml-export/spec.md
|
// document's `version` and `type` keys. See docs/specs/office/requirements/automations-yaml-export.md
|
||||||
// § Data model.
|
// § Data model.
|
||||||
const (
|
const (
|
||||||
exportDocumentVersion = 1
|
exportDocumentVersion = 1
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
// fieldDisposition records what AC-22 calls a field's "disposition": either exported
|
// fieldDisposition records what AC-22 calls a field's "disposition": either exported
|
||||||
// under a named YAML key, or excluded for a stated reason. See
|
// under a named YAML key, or excluded for a stated reason. See
|
||||||
// docs/specs/automations-yaml-export/spec.md § Round-trip completeness, the field
|
// docs/specs/office/requirements/automations-yaml-export.md § Round-trip completeness, the field
|
||||||
// disposition table under "Data model".
|
// disposition table under "Data model".
|
||||||
//
|
//
|
||||||
// This is the anti-`reports_to` guard: Office's config export declared and emitted an
|
// This is the anti-`reports_to` guard: Office's config export declared and emitted an
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ExportHandler serves the two read-only YAML/zip automation export
|
// ExportHandler serves the two read-only YAML/zip automation export
|
||||||
// endpoints (see docs/specs/automations-yaml-export/spec.md § API surface).
|
// endpoints (see docs/specs/office/requirements/automations-yaml-export.md § API surface).
|
||||||
type ExportHandler struct {
|
type ExportHandler struct {
|
||||||
svc *Service
|
svc *Service
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// yamlNewlineChars is YAML 1.2's line-break set, per
|
// yamlNewlineChars is YAML 1.2's line-break set, per
|
||||||
// docs/specs/automations-yaml-export/spec.md's Definitions section:
|
// docs/specs/office/requirements/automations-yaml-export.md's Definitions section:
|
||||||
// {U+000A, U+000D, U+0085, U+2028, U+2029}. AC-17 and AC-46 are stated against this
|
// {U+000A, U+000D, U+0085, U+2028, U+2029}. AC-17 and AC-46 are stated against this
|
||||||
// set, not against "\n" alone — a CR-only, NEL-only, LS-only, or PS-only prompt still
|
// set, not against "\n" alone — a CR-only, NEL-only, LS-only, or PS-only prompt still
|
||||||
// forces a non-literal emission and must still warn under AC-17.
|
// forces a non-literal emission and must still warn under AC-17.
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ type Automation struct {
|
|||||||
// Derived in SQL (`execution_mode = 'task'`) rather than by reading the
|
// Derived in SQL (`execution_mode = 'task'`) rather than by reading the
|
||||||
// column into a field, so the raw mode never enters the Go model and
|
// column into a field, so the raw mode never enters the Go model and
|
||||||
// cannot grow a firing-path branch: this is a migration-window notice,
|
// cannot grow a firing-path branch: this is a migration-window notice,
|
||||||
// not a second destination. See docs/specs/office/automations-settings.md
|
// not a second destination. See docs/specs/office/requirements/automations-settings.md
|
||||||
// § Migration.
|
// § Migration.
|
||||||
LegacyBoardCard bool `json:"legacy_board_card" db:"legacy_board_card"`
|
LegacyBoardCard bool `json:"legacy_board_card" db:"legacy_board_card"`
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ const (
|
|||||||
// automation used to put a card on the board", for a migration notice that
|
// automation used to put a card on the board", for a migration notice that
|
||||||
// closes once. Projecting the mode itself would hand every future caller a
|
// closes once. Projecting the mode itself would hand every future caller a
|
||||||
// mode to branch on, and the whole point of withdrawing it is that no firing
|
// mode to branch on, and the whole point of withdrawing it is that no firing
|
||||||
// path has one. See docs/specs/office/automations-settings.md § Migration.
|
// path has one. See docs/specs/office/requirements/automations-settings.md § Migration.
|
||||||
const automationColumns = `id, workspace_id, name, description, workflow_id, workflow_step_id,
|
const automationColumns = `id, workspace_id, name, description, workflow_id, workflow_step_id,
|
||||||
agent_profile_id, executor_profile_id, prompt, task_title_template,
|
agent_profile_id, executor_profile_id, prompt, task_title_template,
|
||||||
enabled, max_concurrent_runs, webhook_secret, last_triggered_at, created_at, updated_at,
|
enabled, max_concurrent_runs, webhook_secret, last_triggered_at, created_at, updated_at,
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func TestBuildAgentProfileMatcher_SkipsDisabledProfile(t *testing.T) {
|
|||||||
// TestBuildAgentProfileMatcher_PreservesDisabledExistingBinding is the
|
// TestBuildAgentProfileMatcher_PreservesDisabledExistingBinding is the
|
||||||
// reconciliation regression: a workflow step already bound to a profile that
|
// reconciliation regression: a workflow step already bound to a profile that
|
||||||
// gets disabled after the fact must keep that binding on the next sync, per
|
// gets disabled after the fact must keep that binding on the next sync, per
|
||||||
// docs/specs/agents/profile-disable.md ("existing sessions/workflows keep
|
// docs/specs/agents/requirements/profile-disable.md ("existing sessions/workflows keep
|
||||||
// their profile"). Excluding disabled profiles from *candidate selection*
|
// their profile"). Excluding disabled profiles from *candidate selection*
|
||||||
// must not also strip a binding that was already in place.
|
// must not also strip a binding that was already in place.
|
||||||
func TestBuildAgentProfileMatcher_PreservesDisabledExistingBinding(t *testing.T) {
|
func TestBuildAgentProfileMatcher_PreservesDisabledExistingBinding(t *testing.T) {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ type clarificationResponseClaim struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolver implements ResolveBundle
|
// Resolver implements ResolveBundle
|
||||||
// (docs/specs/external-question-answering/spec.md, "Resolution semantics"),
|
// (docs/specs/integrations/requirements/external-question-answering.md, "Resolution semantics"),
|
||||||
// the single service-layer operation the REST endpoints and the
|
// the single service-layer operation the REST endpoints and the
|
||||||
// answer_question_kandev MCP tool both call. It resolves a bundle's identity
|
// answer_question_kandev MCP tool both call. It resolves a bundle's identity
|
||||||
// and authorization (M5, A1-A9), validates the outcome (N6-N8b), claims it
|
// and authorization (M5, A1-A9), validates the outcome (N6-N8b), claims it
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// cookie scope — so multiple kandev instances on one host (same IP,
|
// cookie scope — so multiple kandev instances on one host (same IP,
|
||||||
// different ports) share one cookie jar. Suffixing cookie names with the
|
// different ports) share one cookie jar. Suffixing cookie names with the
|
||||||
// request's port lets each instance read and write only its own cookies
|
// request's port lets each instance read and write only its own cookies
|
||||||
// (see docs/specs/fix-multi-instance-cookie-isolation/spec.md).
|
// (see docs/specs/auth/requirements/fix-multi-instance-cookie-isolation.md).
|
||||||
package httpcookie
|
package httpcookie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ type IssueWatch struct {
|
|||||||
PollIntervalSeconds int `json:"pollIntervalSeconds" db:"poll_interval_seconds"`
|
PollIntervalSeconds int `json:"pollIntervalSeconds" db:"poll_interval_seconds"`
|
||||||
// MaxInflightTasks caps how many open watcher-created tasks this watch can
|
// MaxInflightTasks caps how many open watcher-created tasks this watch can
|
||||||
// hold at once. nil = uncapped. Values <= 0 are rejected at the API layer.
|
// hold at once. nil = uncapped. Values <= 0 are rejected at the API layer.
|
||||||
// See docs/specs/throttle-watcher-fanout/spec.md for the open-task definition.
|
// See docs/specs/tasks/system-design/wip-limit-pull-system.md for the open-task definition.
|
||||||
MaxInflightTasks *int `json:"maxInflightTasks,omitempty" db:"max_inflight_tasks"`
|
MaxInflightTasks *int `json:"maxInflightTasks,omitempty" db:"max_inflight_tasks"`
|
||||||
LastPolledAt *time.Time `json:"lastPolledAt,omitempty" db:"last_polled_at"`
|
LastPolledAt *time.Time `json:"lastPolledAt,omitempty" db:"last_polled_at"`
|
||||||
// LastError / LastErrorAt are stamped when the dispatch pipeline self-
|
// LastError / LastErrorAt are stamped when the dispatch pipeline self-
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const createTablesSQL = `
|
|||||||
poll_interval_seconds INTEGER NOT NULL DEFAULT 300,
|
poll_interval_seconds INTEGER NOT NULL DEFAULT 300,
|
||||||
-- Cap on concurrent open watcher-created tasks for this watch.
|
-- Cap on concurrent open watcher-created tasks for this watch.
|
||||||
-- NULL = uncapped. Positive integer = cap. Values <= 0 are rejected at
|
-- NULL = uncapped. Positive integer = cap. Values <= 0 are rejected at
|
||||||
-- the API layer. See docs/specs/throttle-watcher-fanout/.
|
-- the API layer. See docs/specs/tasks/system-design/wip-limit-pull-system.md.
|
||||||
max_inflight_tasks INTEGER DEFAULT 5,
|
max_inflight_tasks INTEGER DEFAULT 5,
|
||||||
last_polled_at DATETIME,
|
last_polled_at DATETIME,
|
||||||
last_error TEXT NOT NULL DEFAULT '',
|
last_error TEXT NOT NULL DEFAULT '',
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ type IssueWatch struct {
|
|||||||
PollIntervalSeconds int `json:"pollIntervalSeconds" db:"poll_interval_seconds"`
|
PollIntervalSeconds int `json:"pollIntervalSeconds" db:"poll_interval_seconds"`
|
||||||
// MaxInflightTasks caps how many open watcher-created tasks this watch can
|
// MaxInflightTasks caps how many open watcher-created tasks this watch can
|
||||||
// hold at once. nil = uncapped. Values <= 0 are rejected at the API layer.
|
// hold at once. nil = uncapped. Values <= 0 are rejected at the API layer.
|
||||||
// See docs/specs/throttle-watcher-fanout/spec.md for the open-task definition.
|
// See docs/specs/tasks/system-design/wip-limit-pull-system.md for the open-task definition.
|
||||||
MaxInflightTasks *int `json:"maxInflightTasks,omitempty" db:"max_inflight_tasks"`
|
MaxInflightTasks *int `json:"maxInflightTasks,omitempty" db:"max_inflight_tasks"`
|
||||||
// SortBy sets the dispatch order for matched issues; empty = Linear default order.
|
// SortBy sets the dispatch order for matched issues; empty = Linear default order.
|
||||||
SortBy IssueSortBy `json:"sortBy,omitempty" db:"sort_by"`
|
SortBy IssueSortBy `json:"sortBy,omitempty" db:"sort_by"`
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const createTablesSQL = `
|
|||||||
poll_interval_seconds INTEGER NOT NULL DEFAULT 300,
|
poll_interval_seconds INTEGER NOT NULL DEFAULT 300,
|
||||||
-- Cap on concurrent open watcher-created tasks for this watch.
|
-- Cap on concurrent open watcher-created tasks for this watch.
|
||||||
-- NULL = uncapped. Positive integer = cap. Values <= 0 are rejected at
|
-- NULL = uncapped. Positive integer = cap. Values <= 0 are rejected at
|
||||||
-- the API layer. See docs/specs/throttle-watcher-fanout/.
|
-- the API layer. See docs/specs/tasks/system-design/wip-limit-pull-system.md.
|
||||||
max_inflight_tasks INTEGER DEFAULT 5,
|
max_inflight_tasks INTEGER DEFAULT 5,
|
||||||
-- Dispatch order for matched issues under the in-flight cap.
|
-- Dispatch order for matched issues under the in-flight cap.
|
||||||
-- '' = Linear default order (updatedAt asc). See linear/issue_sort.go.
|
-- '' = Linear default order (updatedAt asc). See linear/issue_sort.go.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Budget period and action-on-exceed constants. See spec
|
// Budget period and action-on-exceed constants. See spec
|
||||||
// docs/specs/office-costs/spec.md for semantics.
|
// docs/specs/office/requirements/costs.md for semantics.
|
||||||
const (
|
const (
|
||||||
budgetPeriodMonthly = "monthly"
|
budgetPeriodMonthly = "monthly"
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ func (s *CostService) EvaluateBudget(
|
|||||||
// CheckPreExecutionBudget checks all applicable budget policies before
|
// CheckPreExecutionBudget checks all applicable budget policies before
|
||||||
// launching an agent session. Returns (allowed, reason, error). Only
|
// launching an agent session. Returns (allowed, reason, error). Only
|
||||||
// pause_agent and block_new_tasks return allowed=false (notify_only
|
// pause_agent and block_new_tasks return allowed=false (notify_only
|
||||||
// alerts but does not block). See docs/specs/office-costs/spec.md.
|
// alerts but does not block). See docs/specs/office/requirements/costs.md.
|
||||||
// Implements shared.BudgetChecker.
|
// Implements shared.BudgetChecker.
|
||||||
func (s *CostService) CheckPreExecutionBudget(
|
func (s *CostService) CheckPreExecutionBudget(
|
||||||
ctx context.Context, agentInstanceID, projectID, workspaceID string,
|
ctx context.Context, agentInstanceID, projectID, workspaceID string,
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func (c *Client) catalogVersionLocked() string {
|
|||||||
// cold-cache-buffer parse path, where the buffer and its version are
|
// cold-cache-buffer parse path, where the buffer and its version are
|
||||||
// captured together before parsing — so a background refresh landing
|
// captured together before parsing — so a background refresh landing
|
||||||
// mid-call can never pair one catalogue's rates with a different
|
// mid-call can never pair one catalogue's rates with a different
|
||||||
// catalogue's version identifier (docs/specs/office/costs.md).
|
// catalogue's version identifier (docs/specs/office/requirements/costs.md).
|
||||||
func (c *Client) LookupForModelWithVersion(ctx context.Context, modelID string) (shared.ModelPricing, string, bool) {
|
func (c *Client) LookupForModelWithVersion(ctx context.Context, modelID string) (shared.ModelPricing, string, bool) {
|
||||||
key, strategy := Normalize(modelID)
|
key, strategy := Normalize(modelID)
|
||||||
if strategy != StrategyLookup {
|
if strategy != StrategyLookup {
|
||||||
@@ -222,7 +222,7 @@ func (c *Client) LookupForModelWithVersion(ctx context.Context, modelID string)
|
|||||||
// wholesale under c.mu — so a later, unrelated lookup for the same key would
|
// wholesale under c.mu — so a later, unrelated lookup for the same key would
|
||||||
// then read old rates paired with the new catalogue version, the exact
|
// then read old rates paired with the new catalogue version, the exact
|
||||||
// provenance lie LookupForModelWithVersion exists to prevent
|
// provenance lie LookupForModelWithVersion exists to prevent
|
||||||
// (docs/specs/office/costs.md). Compares catalogGen rather than the RFC3339
|
// (docs/specs/office/requirements/costs.md). Compares catalogGen rather than the RFC3339
|
||||||
// version string: the string only has one-second resolution, so two installs
|
// version string: the string only has one-second resolution, so two installs
|
||||||
// landing in the same wall-clock second would otherwise compare equal and
|
// landing in the same wall-clock second would otherwise compare equal and
|
||||||
// defeat this guard. The returned (pricing, bufVersion) pair for THIS call is
|
// defeat this guard. The returned (pricing, bufVersion) pair for THIS call is
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package dashboard
|
|||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
// TestDeriveTaskTimestamps locks down the Started/Completed sidebar
|
// TestDeriveTaskTimestamps locks down the Started/Completed sidebar
|
||||||
// derivation described in docs/specs/office/tasks.md: startedAt is the
|
// derivation described in docs/specs/office/requirements/tasks.md: startedAt is the
|
||||||
// earliest transition into in_progress, completedAt is the latest
|
// earliest transition into in_progress, completedAt is the latest
|
||||||
// transition into done. A regression here silently reverts the Office
|
// transition into done. A regression here silently reverts the Office
|
||||||
// task detail sidebar to permanently blank Started/Completed rows.
|
// task detail sidebar to permanently blank Started/Completed rows.
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ func (s BudgetScopeType) Valid() bool {
|
|||||||
// before this field existed — never inferred.
|
// before this field existed — never inferred.
|
||||||
type CostSource string
|
type CostSource string
|
||||||
|
|
||||||
// Cost-source values. See docs/specs/office/costs.md.
|
// Cost-source values. See docs/specs/office/requirements/costs.md.
|
||||||
const (
|
const (
|
||||||
// CostSourceProviderReported means the CLI forwarded an authoritative
|
// CostSourceProviderReported means the CLI forwarded an authoritative
|
||||||
// USD amount (claude-acp's usage_update.cost.amount); no rates apply.
|
// USD amount (claude-acp's usage_update.cost.amount); no rates apply.
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ var ValidProjectStatuses = map[ProjectStatus]bool{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CostContractVersion is the in-band activation point for the cache-split /
|
// CostContractVersion is the in-band activation point for the cache-split /
|
||||||
// cost-provenance / turn-attribution columns (docs/specs/office/costs.md).
|
// cost-provenance / turn-attribution columns (docs/specs/office/requirements/costs.md).
|
||||||
// The Rill cost extract has no schema versioning of its own, so a row
|
// The Rill cost extract has no schema versioning of its own, so a row
|
||||||
// written under a prior contract is distinguished by comparing
|
// written under a prior contract is distinguished by comparing
|
||||||
// cost_contract_version, not by a date an analyst has to be told out of
|
// cost_contract_version, not by a date an analyst has to be told out of
|
||||||
@@ -332,7 +332,7 @@ const CostContractVersion int64 = 3
|
|||||||
// turn. The JSON cost-list representation includes this field as null so API
|
// turn. The JSON cost-list representation includes this field as null so API
|
||||||
// consumers can make the same distinction. NULL is never backfilled to 0;
|
// consumers can make the same distinction. NULL is never backfilled to 0;
|
||||||
// TokensCachedIn keeps its original read+write sum semantics on every row so
|
// TokensCachedIn keeps its original read+write sum semantics on every row so
|
||||||
// existing consumers of that column are unaffected. See docs/specs/office/costs.md.
|
// existing consumers of that column are unaffected. See docs/specs/office/requirements/costs.md.
|
||||||
type CostEvent struct {
|
type CostEvent struct {
|
||||||
ID string `json:"id" db:"id"`
|
ID string `json:"id" db:"id"`
|
||||||
SessionID string `json:"session_id" db:"session_id"`
|
SessionID string `json:"session_id" db:"session_id"`
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ func (r *Repository) createCostTables() error {
|
|||||||
// columns must stay byte-identical to — see base_migrations.go). NULL
|
// columns must stay byte-identical to — see base_migrations.go). NULL
|
||||||
// means "not recorded" (legacy row, or an adapter that did not report
|
// means "not recorded" (legacy row, or an adapter that did not report
|
||||||
// cache data); 0 would silently claim zero cache activity. See
|
// cache data); 0 would silently claim zero cache activity. See
|
||||||
// docs/specs/office/costs.md.
|
// docs/specs/office/requirements/costs.md.
|
||||||
_, err := r.db.Exec(`
|
_, err := r.db.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS office_cost_events (
|
CREATE TABLE IF NOT EXISTS office_cost_events (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (r *Repository) runMigrations() {
|
|||||||
|
|
||||||
// migrateCostEventContract adds the cache read/write split, turn
|
// migrateCostEventContract adds the cache read/write split, turn
|
||||||
// attribution, and cost-provenance columns to office_cost_events for
|
// attribution, and cost-provenance columns to office_cost_events for
|
||||||
// databases created before this contract (docs/specs/office/costs.md). Every
|
// databases created before this contract (docs/specs/office/requirements/costs.md). Every
|
||||||
// ALTER is nullable with no DEFAULT: a legacy row must read NULL, never 0,
|
// ALTER is nullable with no DEFAULT: a legacy row must read NULL, never 0,
|
||||||
// because a merged tokens_cached_in cannot be decomposed and an unversioned
|
// because a merged tokens_cached_in cannot be decomposed and an unversioned
|
||||||
// "0" would be indistinguishable from "no cache activity". Keep this column
|
// "0" would be indistinguishable from "no cache activity". Keep this column
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// TestCostEventContractMigration_LegacyRowsGetNullColumns covers the
|
// TestCostEventContractMigration_LegacyRowsGetNullColumns covers the
|
||||||
// legacy-column contract from docs/specs/office/costs.md: an office_cost_events
|
// legacy-column contract from docs/specs/office/requirements/costs.md: an office_cost_events
|
||||||
// table that predates the cache-split / cost-provenance / turn-attribution
|
// table that predates the cache-split / cost-provenance / turn-attribution
|
||||||
// columns gets them added as nullable, a pre-existing row is left with NULL
|
// columns gets them added as nullable, a pre-existing row is left with NULL
|
||||||
// (never 0) in every new column, the partial unique index on
|
// (never 0) in every new column, the partial unique index on
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// TestPostgresCostEventContractMigration is the PostgreSQL twin of
|
// TestPostgresCostEventContractMigration is the PostgreSQL twin of
|
||||||
// TestCostEventContractMigration_LegacyRowsGetNullColumns
|
// TestCostEventContractMigration_LegacyRowsGetNullColumns
|
||||||
// (docs/specs/office/costs.md, ADR 0027): a pre-migration office_cost_events
|
// (docs/specs/office/requirements/costs.md, ADR 0027): a pre-migration office_cost_events
|
||||||
// table gets the cache-split / provenance / turn-attribution columns added
|
// table gets the cache-split / provenance / turn-attribution columns added
|
||||||
// nullable, a legacy row is left NULL (never 0), the partial unique index
|
// nullable, a legacy row is left NULL (never 0), the partial unique index
|
||||||
// exists, and the migration replays cleanly on a second boot. Skips unless
|
// exists, and the migration replays cleanly on a second boot. Skips unless
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
var ErrDuplicateUsageEvent = errors.New("duplicate usage_event_id")
|
var ErrDuplicateUsageEvent = errors.New("duplicate usage_event_id")
|
||||||
|
|
||||||
// usageEventIndexName is the partial unique index enforcing at most one row
|
// usageEventIndexName is the partial unique index enforcing at most one row
|
||||||
// per non-NULL usage_event_id (docs/specs/office/costs.md).
|
// per non-NULL usage_event_id (docs/specs/office/requirements/costs.md).
|
||||||
const usageEventIndexName = "uniq_office_cost_usage_event"
|
const usageEventIndexName = "uniq_office_cost_usage_event"
|
||||||
|
|
||||||
// sqliteUsageEventViolationMessage is the substring go-sqlite3 puts in a
|
// sqliteUsageEventViolationMessage is the substring go-sqlite3 puts in a
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func TestTaskMigrations_NewColumnsExist(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify session columns (cost_subcents stores hundredths of a cent
|
// Verify session columns (cost_subcents stores hundredths of a cent
|
||||||
// per docs/specs/office-costs/spec.md — the column was renamed from
|
// per docs/specs/office/requirements/costs.md — the column was renamed from
|
||||||
// cost_cents pre-release; no migration kept).
|
// cost_cents pre-release; no migration kept).
|
||||||
sessionColumns := []string{"cost_subcents", "tokens_in", "tokens_out"}
|
sessionColumns := []string{"cost_subcents", "tokens_in", "tokens_out"}
|
||||||
for _, col := range sessionColumns {
|
for _, col := range sessionColumns {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
var ErrTaskNotFound = errors.New("task not found")
|
var ErrTaskNotFound = errors.New("task not found")
|
||||||
|
|
||||||
// Automation runs never appear in a task list: they are hidden by their
|
// Automation runs never appear in a task list: they are hidden by their
|
||||||
// provenance, not by ephemerality (docs/specs/office/automations-settings.md).
|
// provenance, not by ephemerality (docs/specs/office/requirements/automations-settings.md).
|
||||||
// is_ephemeral keeps its original quick-chat meaning, so every list read here
|
// is_ephemeral keeps its original quick-chat meaning, so every list read here
|
||||||
// pairs the two.
|
// pairs the two.
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -758,7 +758,7 @@ func TestCountActionableTasksForAgent_AgentIsolation(t *testing.T) {
|
|||||||
|
|
||||||
// Automation runs are hidden from the task list by their origin, not by
|
// Automation runs are hidden from the task list by their origin, not by
|
||||||
// is_ephemeral: they are ordinary persistent tasks with their own destination
|
// is_ephemeral: they are ordinary persistent tasks with their own destination
|
||||||
// (docs/specs/office/automations-settings.md). The quick chat alongside them
|
// (docs/specs/office/requirements/automations-settings.md). The quick chat alongside them
|
||||||
// still behaves exactly as it did.
|
// still behaves exactly as it did.
|
||||||
func TestOfficeTaskListsExcludeAutomationOriginTasks(t *testing.T) {
|
func TestOfficeTaskListsExcludeAutomationOriginTasks(t *testing.T) {
|
||||||
repo := newSearchTestRepo(t)
|
repo := newSearchTestRepo(t)
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ type ResolveOptions struct {
|
|||||||
type healthIndex map[ProviderID]map[string]map[string]models.ProviderHealth
|
type healthIndex map[ProviderID]map[string]map[string]models.ProviderHealth
|
||||||
|
|
||||||
// Resolve runs the resolution algorithm described in
|
// Resolve runs the resolution algorithm described in
|
||||||
// docs/specs/office-provider-routing/plan.md §Phase 2.
|
// docs/plans/office-execution-profile-routing/plan.md §Phase 2.
|
||||||
func (r *Resolver) Resolve(
|
func (r *Resolver) Resolve(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
workspaceID string,
|
workspaceID string,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
// This pair exists because handlePromptUsage previously had two silent
|
// This pair exists because handlePromptUsage previously had two silent
|
||||||
// early returns (decode failure, task lookup failure) and a silent insert
|
// early returns (decode failure, task lookup failure) and a silent insert
|
||||||
// failure — "if this writer silently stops, nothing today would notice"
|
// failure — "if this writer silently stops, nothing today would notice"
|
||||||
// (docs/specs/office/costs.md).
|
// (docs/specs/office/requirements/costs.md).
|
||||||
var (
|
var (
|
||||||
costEventsWrittenTotal = expvar.NewMap("cost_events_written_total")
|
costEventsWrittenTotal = expvar.NewMap("cost_events_written_total")
|
||||||
costEventsDroppedTotal = expvar.NewMap("cost_events_dropped_total")
|
costEventsDroppedTotal = expvar.NewMap("cost_events_dropped_total")
|
||||||
|
|||||||
@@ -596,7 +596,7 @@ func (s *Service) handleAgentFailed(ctx context.Context, event *bus.Event) error
|
|||||||
// Office failure path (v1): every agent error is terminal. The
|
// Office failure path (v1): every agent error is terminal. The
|
||||||
// retry-by-classifier path lives behind HandleRunFailure for
|
// retry-by-classifier path lives behind HandleRunFailure for
|
||||||
// rate-limit-retry callers; we deliberately do NOT call into it
|
// rate-limit-retry callers; we deliberately do NOT call into it
|
||||||
// here. See docs/specs/office-agent-error-handling.
|
// here. See docs/specs/office/requirements/runtime.md.
|
||||||
errMsg := enrichModelFailureMessage(run, data.ErrorMessage)
|
errMsg := enrichModelFailureMessage(run, data.ErrorMessage)
|
||||||
if err := s.HandleAgentFailure(ctx, run, errMsg); err != nil {
|
if err := s.HandleAgentFailure(ctx, run, errMsg); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -684,7 +684,7 @@ func (s *Service) tryPostStartFallback(
|
|||||||
|
|
||||||
// handlePromptUsage records a cost event from a session/prompt usage
|
// handlePromptUsage records a cost event from a session/prompt usage
|
||||||
// update. Cost resolution follows the two-layer order from
|
// update. Cost resolution follows the two-layer order from
|
||||||
// docs/specs/office/costs.md, and CostSource on the row records which
|
// docs/specs/office/requirements/costs.md, and CostSource on the row records which
|
||||||
// layer actually produced the dollar amount (see resolveCostForUsage in
|
// layer actually produced the dollar amount (see resolveCostForUsage in
|
||||||
// prompt_usage_cost.go — distinct from Estimated, a usage-authority flag):
|
// prompt_usage_cost.go — distinct from Estimated, a usage-authority flag):
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func (s *Service) resolveCostForUsage(ctx context.Context, data PromptUsageData)
|
|||||||
// one atomic snapshot when s.pricingLookup satisfies
|
// one atomic snapshot when s.pricingLookup satisfies
|
||||||
// shared.PricingLookupWithVersion, so a concurrent background refresh can
|
// shared.PricingLookupWithVersion, so a concurrent background refresh can
|
||||||
// never pair one catalogue's rates with a different catalogue's version
|
// never pair one catalogue's rates with a different catalogue's version
|
||||||
// identifier on the stored row (docs/specs/office/costs.md). Falls back to two
|
// identifier on the stored row (docs/specs/office/requirements/costs.md). Falls back to two
|
||||||
// separate calls — accepting that narrower race — only for a PricingLookup
|
// separate calls — accepting that narrower race — only for a PricingLookup
|
||||||
// implementation (e.g. a test double) that doesn't support the atomic form;
|
// implementation (e.g. a test double) that doesn't support the atomic form;
|
||||||
// CatalogVersion is optional there too, so a non-implementer simply reports
|
// CatalogVersion is optional there too, so a non-implementer simply reports
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
// across CLIs, and don't depend on prompt instructions the agent
|
// across CLIs, and don't depend on prompt instructions the agent
|
||||||
// may rotate.
|
// may rotate.
|
||||||
//
|
//
|
||||||
// See docs/specs/office-heartbeat-rework/spec.md §"Continuation
|
// See docs/specs/office/requirements/scheduler.md §"Continuation
|
||||||
// summary contract" for the section schema.
|
// summary contract" for the section schema.
|
||||||
package summary
|
package summary
|
||||||
|
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ func seedRunSkillSnapshotHandler(repo *officesqlite.Repository, log *logger.Logg
|
|||||||
// Used by the agent dashboard E2E spec to drive the costs section
|
// Used by the agent dashboard E2E spec to drive the costs section
|
||||||
// (aggregate + per-run rollup) without launching an agent. TokensOut is a
|
// (aggregate + per-run rollup) without launching an agent. TokensOut is a
|
||||||
// pointer so a test can omit it to seed the "never measured" NULL shape
|
// pointer so a test can omit it to seed the "never measured" NULL shape
|
||||||
// (docs/specs/office/costs.md) instead of always writing a measured 0.
|
// (docs/specs/office/requirements/costs.md) instead of always writing a measured 0.
|
||||||
type seedCostEventRequest struct {
|
type seedCostEventRequest struct {
|
||||||
AgentProfileID string `json:"agent_profile_id"`
|
AgentProfileID string `json:"agent_profile_id"`
|
||||||
TaskID string `json:"task_id"`
|
TaskID string `json:"task_id"`
|
||||||
|
|||||||
@@ -1689,7 +1689,7 @@ func (s *Service) handleRecoverableFailureLocked(ctx context.Context, data watch
|
|||||||
// working" and the topbar spinner clears. Kanban / quick-chat tasks
|
// working" and the topbar spinner clears. Kanban / quick-chat tasks
|
||||||
// keep the legacy WAITING_FOR_INPUT path so the user can resume via
|
// keep the legacy WAITING_FOR_INPUT path so the user can resume via
|
||||||
// the Resume / Start fresh recovery buttons in the existing chat
|
// the Resume / Start fresh recovery buttons in the existing chat
|
||||||
// surface. (See docs/specs/office-agent-error-handling.)
|
// surface. (See docs/specs/office/requirements/runtime.md.)
|
||||||
nextState := models.TaskSessionStateWaitingForInput
|
nextState := models.TaskSessionStateWaitingForInput
|
||||||
if s.isOfficeSession(ctx, data.SessionID) {
|
if s.isOfficeSession(ctx, data.SessionID) {
|
||||||
nextState = models.TaskSessionStateFailed
|
nextState = models.TaskSessionStateFailed
|
||||||
|
|||||||
@@ -513,7 +513,7 @@ const McpModeTaskTitlePending = "task-title-pending"
|
|||||||
// McpModeOffice restricts the MCP toolset for office (autonomous) agents to
|
// McpModeOffice restricts the MCP toolset for office (autonomous) agents to
|
||||||
// interaction + plan tools. Office agents manage tasks via the kandev CLI
|
// interaction + plan tools. Office agents manage tasks via the kandev CLI
|
||||||
// (exposed through agentctl + $KANDEV_CLI), not MCP — see
|
// (exposed through agentctl + $KANDEV_CLI), not MCP — see
|
||||||
// docs/specs/office-agent-cli/spec.md.
|
// docs/specs/office/system-design/agents-03.md.
|
||||||
const McpModeOffice = "office"
|
const McpModeOffice = "office"
|
||||||
|
|
||||||
// LaunchOptions contains optional parameters for LaunchPreparedSession.
|
// LaunchOptions contains optional parameters for LaunchPreparedSession.
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ type MessageCreator interface {
|
|||||||
// (Task tool) invocation observed on a tool-call frame. It returns nothing —
|
// (Task tool) invocation observed on a tool-call frame. It returns nothing —
|
||||||
// a repository failure never fails the enclosing message write, turn, or
|
// a repository failure never fails the enclosing message write, turn, or
|
||||||
// agent stream (AC-27 in
|
// agent stream (AC-27 in
|
||||||
// docs/specs/subagent-context-persistence/spec.md). Implemented by
|
// docs/specs/agents/requirements/subagent-context-persistence.md). Implemented by
|
||||||
// taskservice.Service via an adapter; optional, so an installation that
|
// taskservice.Service via an adapter; optional, so an installation that
|
||||||
// never wires it behaves exactly as before.
|
// never wires it behaves exactly as before.
|
||||||
type SubagentContextRecorder interface {
|
type SubagentContextRecorder interface {
|
||||||
|
|||||||
@@ -1144,7 +1144,7 @@ func (s *Service) startTask(ctx context.Context, taskID string, agentProfileID s
|
|||||||
|
|
||||||
// Office tasks restrict the MCP toolset: kanban tools (move/update/list
|
// Office tasks restrict the MCP toolset: kanban tools (move/update/list
|
||||||
// task, etc.) are excluded because office agents call those via the
|
// task, etc.) are excluded because office agents call those via the
|
||||||
// kandev CLI ($KANDEV_CLI). See docs/specs/office-agent-cli/spec.md.
|
// kandev CLI ($KANDEV_CLI). See docs/specs/office/system-design/agents-03.md.
|
||||||
mcpMode := ""
|
mcpMode := ""
|
||||||
if isOfficeTask {
|
if isOfficeTask {
|
||||||
mcpMode = executor.McpModeOffice
|
mcpMode = executor.McpModeOffice
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ func (s *Service) dispatchWatcherEvent(ctx context.Context, src WatcherSource, e
|
|||||||
// Per-watcher throttle. Synchronous: we MUST acquire the slot before
|
// Per-watcher throttle. Synchronous: we MUST acquire the slot before
|
||||||
// spawning the goroutine, otherwise a tight burst of bus events all
|
// spawning the goroutine, otherwise a tight burst of bus events all
|
||||||
// read the same pre-creation DB count and overshoot the cap. See
|
// read the same pre-creation DB count and overshoot the cap. See
|
||||||
// docs/specs/throttle-watcher-fanout/spec.md.
|
// docs/specs/tasks/system-design/wip-limit-pull-system.md.
|
||||||
//
|
//
|
||||||
// The slot covers the WHOLE dispatch pipeline, including the case where
|
// The slot covers the WHOLE dispatch pipeline, including the case where
|
||||||
// Dispatch returns early because Reserve loses the dedup race (no task
|
// Dispatch returns early because Reserve loses the dedup race (no task
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ func (w *pluginWorker) logOverflow(dropped string) {
|
|||||||
// Logs and gives up (event is lost from the live-delivery path; a
|
// Logs and gives up (event is lost from the live-delivery path; a
|
||||||
// subsequent health-poller error transition plus Flush is the recovery
|
// subsequent health-poller error transition plus Flush is the recovery
|
||||||
// path for further events, per the at-least-once/error-buffer semantics in
|
// path for further events, per the at-least-once/error-buffer semantics in
|
||||||
// docs/specs/plugins/spec.md) if every attempt fails.
|
// docs/specs/plugins/requirements/plugins.md) if every attempt fails.
|
||||||
func (w *pluginWorker) deliverWithRetry(ctx context.Context, d Delivery) {
|
func (w *pluginWorker) deliverWithRetry(ctx context.Context, d Delivery) {
|
||||||
var lastErr error
|
var lastErr error
|
||||||
attempts := 1 + len(w.deps.retryDelays)
|
attempts := 1 + len(w.deps.retryDelays)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type bufferedDelivery struct {
|
|||||||
// ringBuffer holds events for one plugin while it is in the error state, so
|
// ringBuffer holds events for one plugin while it is in the error state, so
|
||||||
// Deliverer.Flush can replay them in order after a health-poller recovery
|
// Deliverer.Flush can replay them in order after a health-poller recovery
|
||||||
// transition. Capacity and TTL are the numbers in
|
// transition. Capacity and TTL are the numbers in
|
||||||
// docs/specs/plugins/spec.md ("Event webhook delivery"): 100 events / 5
|
// docs/specs/plugins/requirements/plugins.md ("Event webhook delivery"): 100 events / 5
|
||||||
// minutes. The oldest entry is dropped on overflow; expired entries are
|
// minutes. The oldest entry is dropped on overflow; expired entries are
|
||||||
// purged lazily on the next Add, Drain, or Len call.
|
// purged lazily on the next Add, Drain, or Len call.
|
||||||
type ringBuffer struct {
|
type ringBuffer struct {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type SetAutoUpdateRequest struct {
|
|||||||
// SyncResult is the body of a successful POST /api/plugins/sync (and the
|
// SyncResult is the body of a successful POST /api/plugins/sync (and the
|
||||||
// return value of Service.Sync / Service.bootScan): what the filesystem
|
// return value of Service.Sync / Service.bootScan): what the filesystem
|
||||||
// scan under the plugins directory found and did this run, per
|
// scan under the plugins directory found and did this run, per
|
||||||
// docs/specs/plugins/spec.md ("Filesystem sideloading & sync").
|
// docs/specs/plugins/requirements/plugins.md ("Filesystem sideloading & sync").
|
||||||
type SyncResult struct {
|
type SyncResult struct {
|
||||||
// Added lists the plugin ids of directory sideloads
|
// Added lists the plugin ids of directory sideloads
|
||||||
// (<pluginsDir>/<id>/<version>/manifest.yaml found with no existing
|
// (<pluginsDir>/<id>/<version>/manifest.yaml found with no existing
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ var _ pluginsdk.Host = (*pluginHost)(nil)
|
|||||||
|
|
||||||
// permissionDenied builds the gRPC error RemotePlugin/Host RPCs return for
|
// permissionDenied builds the gRPC error RemotePlugin/Host RPCs return for
|
||||||
// an undeclared capability, matching the wire-level message from
|
// an undeclared capability, matching the wire-level message from
|
||||||
// docs/specs/plugins/spec.md ("Permissions"): "capability '<name>' not
|
// docs/specs/plugins/requirements/plugins.md ("Permissions"): "capability '<name>' not
|
||||||
// declared".
|
// declared".
|
||||||
func permissionDenied(capability string) error {
|
func permissionDenied(capability string) error {
|
||||||
return status.Errorf(codes.PermissionDenied, "capability '%s' not declared", capability)
|
return status.Errorf(codes.PermissionDenied, "capability '%s' not declared", capability)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package manifest defines the plugin manifest data model and validation
|
// Package manifest defines the plugin manifest data model and validation
|
||||||
// rules described in docs/specs/plugins/spec.md ("Data model / Plugin
|
// rules described in docs/specs/plugins/requirements/plugins.md ("Data model / Plugin
|
||||||
// registration"). A manifest is the YAML document an operator supplies to
|
// registration"). A manifest is the YAML document an operator supplies to
|
||||||
// POST /api/plugins/register.
|
// POST /api/plugins/register.
|
||||||
package manifest
|
package manifest
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func buildShiftAlteredKeySet() map[string]bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks the manifest against the plugin registration rules
|
// Validate checks the manifest against the plugin registration rules
|
||||||
// described in docs/specs/plugins/spec.md. It returns nil if the manifest
|
// described in docs/specs/plugins/requirements/plugins.md. It returns nil if the manifest
|
||||||
// is well-formed, or a joined error describing every violation found.
|
// is well-formed, or a joined error describing every violation found.
|
||||||
func (m *Manifest) Validate() error {
|
func (m *Manifest) Validate() error {
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// The marketplace is a discovery layer only — it never installs, spawns, or
|
// The marketplace is a discovery layer only — it never installs, spawns, or
|
||||||
// mutates a plugin. Installing a catalog entry reuses the existing
|
// mutates a plugin. Installing a catalog entry reuses the existing
|
||||||
// POST /api/plugins/install pipeline with the entry's package_url. See
|
// POST /api/plugins/install pipeline with the entry's package_url. See
|
||||||
// docs/specs/plugins/marketplace.md.
|
// docs/specs/plugins/requirements/marketplace.md.
|
||||||
package marketplace
|
package marketplace
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
@@ -20,7 +20,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// InstallState is the derived state of a catalog entry relative to what is
|
// InstallState is the derived state of a catalog entry relative to what is
|
||||||
// installed locally (docs/specs/plugins/marketplace.md → "State machine").
|
// installed locally (docs/specs/plugins/requirements/marketplace.md → "State machine").
|
||||||
type InstallState string
|
type InstallState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const tarballSuffix = ".tar.gz"
|
|||||||
// Sync scans the plugins directory for filesystem-level changes an operator
|
// Sync scans the plugins directory for filesystem-level changes an operator
|
||||||
// made outside the install/enable/disable/uninstall API — directory
|
// made outside the install/enable/disable/uninstall API — directory
|
||||||
// sideloads and dropped tarballs — and reconciles the registry with what
|
// sideloads and dropped tarballs — and reconciles the registry with what
|
||||||
// actually exists on disk, per docs/specs/plugins/spec.md ("Filesystem
|
// actually exists on disk, per docs/specs/plugins/requirements/plugins.md ("Filesystem
|
||||||
// sideloading & sync"):
|
// sideloading & sync"):
|
||||||
//
|
//
|
||||||
// 1. Dir sideloads: an extracted <id>/<version>/manifest.yaml with no
|
// 1. Dir sideloads: an extracted <id>/<version>/manifest.yaml with no
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Package state provides a plugin-scoped key/value store backed by SQLite.
|
// Package state provides a plugin-scoped key/value store backed by SQLite.
|
||||||
// Plugins read and write arbitrary JSON values under a (scope, scope_id, key)
|
// Plugins read and write arbitrary JSON values under a (scope, scope_id, key)
|
||||||
// tuple, always filtered by plugin_id so a plugin can never read or write
|
// tuple, always filtered by plugin_id so a plugin can never read or write
|
||||||
// another plugin's state (spec: docs/specs/plugins/spec.md, "plugin_state").
|
// another plugin's state (spec: docs/specs/plugins/requirements/plugins.md, "plugin_state").
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ func TestStoreDeleteRemovesEntry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestStoreDeleteAllRemovesEveryRowForPlugin pins the Uninstall cleanup
|
// TestStoreDeleteAllRemovesEveryRowForPlugin pins the Uninstall cleanup
|
||||||
// contract (docs/specs/plugins/spec.md "plugin_state"): a plugin's entire
|
// contract (docs/specs/plugins/requirements/plugins.md "plugin_state"): a plugin's entire
|
||||||
// state footprint — across every scope and scope_id — must be removable in
|
// state footprint — across every scope and scope_id — must be removable in
|
||||||
// one call, so a reinstalled or id-reused plugin never inherits stale state.
|
// one call, so a reinstalled or id-reused plugin never inherits stale state.
|
||||||
func TestStoreDeleteAllRemovesEveryRowForPlugin(t *testing.T) {
|
func TestStoreDeleteAllRemovesEveryRowForPlugin(t *testing.T) {
|
||||||
@@ -248,7 +248,7 @@ func TestStoreListReturnsOnlyMatchingScope(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestStorePluginsCannotReadEachOthersState pins the spec invariant that
|
// TestStorePluginsCannotReadEachOthersState pins the spec invariant that
|
||||||
// plugin state is always filtered by plugin_id (docs/specs/plugins/spec.md
|
// plugin state is always filtered by plugin_id (docs/specs/plugins/requirements/plugins.md
|
||||||
// "Plugins cannot read others' state").
|
// "Plugins cannot read others' state").
|
||||||
func TestStorePluginsCannotReadEachOthersState(t *testing.T) {
|
func TestStorePluginsCannotReadEachOthersState(t *testing.T) {
|
||||||
store := newTestStore(t)
|
store := newTestStore(t)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Status values for a plugin registration, per the state machine in
|
// Status values for a plugin registration, per the state machine in
|
||||||
// docs/specs/plugins/spec.md ("State machine").
|
// docs/specs/plugins/requirements/plugins.md ("State machine").
|
||||||
const (
|
const (
|
||||||
StatusRegistered = "registered"
|
StatusRegistered = "registered"
|
||||||
StatusActive = "active"
|
StatusActive = "active"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Status is a plugin's lifecycle state, per the state machine in
|
// Status is a plugin's lifecycle state, per the state machine in
|
||||||
// docs/specs/plugins/spec.md ("State machine"):
|
// docs/specs/plugins/requirements/plugins.md ("State machine"):
|
||||||
//
|
//
|
||||||
// registered -> active -> disabled -> uninstalled
|
// registered -> active -> disabled -> uninstalled
|
||||||
// registered|active|disabled --failure--> error
|
// registered|active|disabled --failure--> error
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package review turns a task's working diff into anchored, advisory code-review
|
// Package review turns a task's working diff into anchored, advisory code-review
|
||||||
// findings. See docs/specs/native-code-review/spec.md.
|
// findings. See docs/specs/agents/requirements/native-code-review.md.
|
||||||
//
|
//
|
||||||
// The package owns run orchestration only: persistence and event publication
|
// The package owns run orchestration only: persistence and event publication
|
||||||
// live in internal/task/service (ReviewService), and the inference call is made
|
// live in internal/task/service (ReviewService), and the inference call is made
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// and exposes a RunQueueAdapter the workflow engine can use to
|
// and exposes a RunQueueAdapter the workflow engine can use to
|
||||||
// enqueue runs without depending on the office package.
|
// enqueue runs without depending on the office package.
|
||||||
//
|
//
|
||||||
// Phase 3 of task-model-unification (see docs/specs/task-model-unification/plan.md
|
// Phase 3 of task-model-unification (see docs/specs/tasks/system-design/model-unification.md
|
||||||
// sections B3.2 and B3.5) lifted this logic out of internal/office/service
|
// sections B3.2 and B3.5) lifted this logic out of internal/office/service
|
||||||
// and added an event-driven claim signal so engine-emitted runs reach
|
// and added an event-driven claim signal so engine-emitted runs reach
|
||||||
// the scheduler in a few ms instead of waiting up to one tick (5s).
|
// the scheduler in a few ms instead of waiting up to one tick (5s).
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package cron owns the Phase 5 cron-driven trigger handlers (see
|
// Package cron owns the Phase 5 cron-driven trigger handlers (see
|
||||||
// docs/specs/task-model-unification/plan.md, B5.1–B5.4).
|
// docs/specs/tasks/system-design/model-unification.md, B5.1–B5.4).
|
||||||
//
|
//
|
||||||
// Three handlers run on a shared tick loop:
|
// Three handlers run on a shared tick loop:
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -2201,7 +2201,7 @@ type TaskReviewRun struct {
|
|||||||
// TaskReviewFinding is one anchored, advisory review comment produced by a
|
// TaskReviewFinding is one anchored, advisory review comment produced by a
|
||||||
// review run. It renders in the Changes/Review diff at File/StartLine..EndLine
|
// review run. It renders in the Changes/Review diff at File/StartLine..EndLine
|
||||||
// of Repository, and carries FileDiffHash so a client can tell whether the
|
// of Repository, and carries FileDiffHash so a client can tell whether the
|
||||||
// diff has moved under it (see ../../../../docs/specs/native-code-review/spec.md).
|
// diff has moved under it (see ../../../../docs/specs/agents/requirements/native-code-review.md).
|
||||||
type TaskReviewFinding struct {
|
type TaskReviewFinding struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
RunID string `json:"run_id"`
|
RunID string `json:"run_id"`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import "time"
|
|||||||
|
|
||||||
// SubagentContext is a durable, queryable row for one subagent (Task tool)
|
// SubagentContext is a durable, queryable row for one subagent (Task tool)
|
||||||
// invocation observed by the orchestrator. See
|
// invocation observed by the orchestrator. See
|
||||||
// docs/specs/subagent-context-persistence/spec.md.
|
// docs/specs/agents/requirements/subagent-context-persistence.md.
|
||||||
//
|
//
|
||||||
// Every nullable column is a pointer. This is not style: ToolUseCount in
|
// Every nullable column is a pointer. This is not style: ToolUseCount in
|
||||||
// particular must distinguish a reported 0 from "not reported", and a
|
// particular must distinguish a reported 0 from "not reported", and a
|
||||||
|
|||||||
@@ -579,7 +579,7 @@ type PlanRepository interface {
|
|||||||
|
|
||||||
// SubagentContextRepository persists the durable, queryable record of a
|
// SubagentContextRepository persists the durable, queryable record of a
|
||||||
// subagent (Task tool) invocation. See
|
// subagent (Task tool) invocation. See
|
||||||
// docs/specs/subagent-context-persistence/spec.md.
|
// docs/specs/agents/requirements/subagent-context-persistence.md.
|
||||||
type SubagentContextRepository interface {
|
type SubagentContextRepository interface {
|
||||||
// UpsertSubagentContext inserts or merges one subagent invocation row,
|
// UpsertSubagentContext inserts or merges one subagent invocation row,
|
||||||
// keyed on (task_session_id, tool_call_id). A single atomic statement —
|
// keyed on (task_session_id, tool_call_id). A single atomic statement —
|
||||||
|
|||||||
@@ -735,7 +735,7 @@ func (r *Repository) initSessionSchema() error {
|
|||||||
|
|
||||||
// initSubagentContextSchema creates task_session_subagents, the durable
|
// initSubagentContextSchema creates task_session_subagents, the durable
|
||||||
// relational record of a subagent (Task tool) invocation. See
|
// relational record of a subagent (Task tool) invocation. See
|
||||||
// docs/specs/subagent-context-persistence/spec.md. The three measurement
|
// docs/specs/agents/requirements/subagent-context-persistence.md. The three measurement
|
||||||
// columns (total_tokens, tool_use_count, duration_ms) deliberately carry no
|
// columns (total_tokens, tool_use_count, duration_ms) deliberately carry no
|
||||||
// DEFAULT: an unreported value must store NULL, never 0 (75% of observed
|
// DEFAULT: an unreported value must store NULL, never 0 (75% of observed
|
||||||
// invocations report none of them). turn_id carries no FOREIGN KEY so a turn
|
// invocations report none of them). turn_id carries no FOREIGN KEY so a turn
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ func scanClarificationBundleRows(rows *sql.Rows, isPostgres bool) ([]models.Clar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// visibilityPredicate builds the L1c/L1d three-way disjunction (spec
|
// visibilityPredicate builds the L1c/L1d three-way disjunction (spec
|
||||||
// docs/specs/external-question-answering/spec.md, "L1c"/"L1d"): the task's
|
// docs/specs/integrations/requirements/external-question-answering.md, "L1c"/"L1d"): the task's
|
||||||
// workspace_id is empty, names no existing workspace row, or is in the L1a
|
// workspace_id is empty, names no existing workspace row, or is in the L1a
|
||||||
// visible set. For an unscoped caller the predicate is satisfied
|
// visible set. For an unscoped caller the predicate is satisfied
|
||||||
// unconditionally, so no clause is added at all.
|
// unconditionally, so no clause is added at all.
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import "testing"
|
|||||||
// or a replayed one. The claim path itself has no statement of its own to
|
// or a replayed one. The claim path itself has no statement of its own to
|
||||||
// assert against: CompleteActiveClarificationBundle is the only clarification
|
// assert against: CompleteActiveClarificationBundle is the only clarification
|
||||||
// claim in the repository package, enforced by the compiler once the retired
|
// claim in the repository package, enforced by the compiler once the retired
|
||||||
// repo/model files are gone (docs/specs/external-question-answering/spec.md,
|
// repo/model files are gone (docs/specs/integrations/requirements/external-question-answering.md,
|
||||||
// P1).
|
// P1).
|
||||||
func TestClarificationResolutionsTableNeverCreated(t *testing.T) {
|
func TestClarificationResolutionsTableNeverCreated(t *testing.T) {
|
||||||
repo := newRepoForSessionTests(t)
|
repo := newRepoForSessionTests(t)
|
||||||
|
|||||||
@@ -2445,7 +2445,7 @@ func (r *Repository) GetActiveTaskInfoByAgentProfile(ctx context.Context, agentP
|
|||||||
// let one nightly report block its profile's deletion forever. Those are
|
// let one nightly report block its profile's deletion forever. Those are
|
||||||
// excluded, which makes them non-resumable if the profile goes: replying to
|
// excluded, which makes them non-resumable if the profile goes: replying to
|
||||||
// an old run afterwards fails. That is the accepted trade — see
|
// an old run afterwards fails. That is the accepted trade — see
|
||||||
// docs/specs/office/automations-settings.md.
|
// docs/specs/office/requirements/automations-settings.md.
|
||||||
rows, err := r.ro.QueryContext(ctx, r.ro.Rebind(`
|
rows, err := r.ro.QueryContext(ctx, r.ro.Rebind(`
|
||||||
SELECT DISTINCT t.id, t.title, t.is_ephemeral
|
SELECT DISTINCT t.id, t.title, t.is_ephemeral
|
||||||
FROM task_sessions ts
|
FROM task_sessions ts
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func (r *Repository) warnSubagentContextMigration(name string, err error) {
|
|||||||
// "subagent_task" (AC-21), and publishes the two write-once kandev_meta
|
// "subagent_task" (AC-21), and publishes the two write-once kandev_meta
|
||||||
// activation keys that let a consumer distinguish "zero fan-out" from
|
// activation keys that let a consumer distinguish "zero fan-out" from
|
||||||
// "unmeasured, before capture began" (AC-24, AC-25). See
|
// "unmeasured, before capture began" (AC-24, AC-25). See
|
||||||
// docs/specs/subagent-context-persistence/spec.md § Backfill.
|
// docs/specs/agents/requirements/subagent-context-persistence.md § Backfill.
|
||||||
//
|
//
|
||||||
// runMigrations() (and therefore this function) runs on every boot, not just
|
// runMigrations() (and therefore this function) runs on every boot, not just
|
||||||
// the first. AC-24d (revision 4) requires the guard to be "both backfill keys
|
// the first. AC-24d (revision 4) requires the guard to be "both backfill keys
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package sqlite
|
|||||||
// subagentPreciseTimestamp builds a SQL expression that evaluates to a value
|
// subagentPreciseTimestamp builds a SQL expression that evaluates to a value
|
||||||
// safe to compare with plain >= / < against another such expression, at no
|
// safe to compare with plain >= / < against another such expression, at no
|
||||||
// coarser than microsecond precision and monotonically across the whole
|
// coarser than microsecond precision and monotonically across the whole
|
||||||
// range (AC-24e in docs/specs/subagent-context-persistence/spec.md). expr may
|
// range (AC-24e in docs/specs/agents/requirements/subagent-context-persistence.md). expr may
|
||||||
// be a TIMESTAMP column reference or a bind-parameter placeholder carrying an
|
// be a TIMESTAMP column reference or a bind-parameter placeholder carrying an
|
||||||
// RFC3339(Nano) string (an activation key from kandev_meta).
|
// RFC3339(Nano) string (an activation key from kandev_meta).
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ type taskScanColumn struct {
|
|||||||
// Automation runs are hidden from the board and from task lists by their
|
// Automation runs are hidden from the board and from task lists by their
|
||||||
// provenance, not by ephemerality: their tasks are ordinary persistent tasks
|
// provenance, not by ephemerality: their tasks are ordinary persistent tasks
|
||||||
// that keep their worktree and stay repliable, they just have their own
|
// that keep their worktree and stay repliable, they just have their own
|
||||||
// destination (docs/specs/office/automations-settings.md). is_ephemeral keeps
|
// destination (docs/specs/office/requirements/automations-settings.md). is_ephemeral keeps
|
||||||
// its original quick-chat meaning, so every board read pairs the two.
|
// its original quick-chat meaning, so every board read pairs the two.
|
||||||
const (
|
const (
|
||||||
andNotAutomationOrigin = ` AND COALESCE(origin, '') != '` + models.TaskOriginAutomationRun + `'`
|
andNotAutomationOrigin = ` AND COALESCE(origin, '') != '` + models.TaskOriginAutomationRun + `'`
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const subagentContextSourceLive = "live"
|
|||||||
// invocation. It returns nothing: that is the mechanism by which AC-27
|
// invocation. It returns nothing: that is the mechanism by which AC-27
|
||||||
// holds — a writer with no error return cannot fail the enclosing message
|
// holds — a writer with no error return cannot fail the enclosing message
|
||||||
// write, turn, or agent stream, and no future caller can accidentally
|
// write, turn, or agent stream, and no future caller can accidentally
|
||||||
// propagate one. See docs/specs/subagent-context-persistence/spec.md.
|
// propagate one. See docs/specs/agents/requirements/subagent-context-persistence.md.
|
||||||
func (s *Service) RecordSubagentContext(ctx context.Context, req RecordSubagentContextRequest) {
|
func (s *Service) RecordSubagentContext(ctx context.Context, req RecordSubagentContextRequest) {
|
||||||
if s.subagentContexts == nil {
|
if s.subagentContexts == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import "expvar"
|
|||||||
// subagentContextTotal exposes writer-health counters for
|
// subagentContextTotal exposes writer-health counters for
|
||||||
// RecordSubagentContext, under the existing expvar convention used by
|
// RecordSubagentContext, under the existing expvar convention used by
|
||||||
// routing_* (internal/office/scheduler) and subproc_*. Counters only — see
|
// routing_* (internal/office/scheduler) and subproc_*. Counters only — see
|
||||||
// AC-26 in docs/specs/subagent-context-persistence/spec.md.
|
// AC-26 in docs/specs/agents/requirements/subagent-context-persistence.md.
|
||||||
//
|
//
|
||||||
// Keys: attempted, persisted, skipped_no_identity, anomalous_value, failed,
|
// Keys: attempted, persisted, skipped_no_identity, anomalous_value, failed,
|
||||||
// unknown_execution (Amendment 1, AC-31).
|
// unknown_execution (Amendment 1, AC-31).
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func (h *HTTPHandlers) httpRevoke(c *gin.Context) {
|
|||||||
|
|
||||||
// mapShareError translates a service error into the response body + status
|
// mapShareError translates a service error into the response body + status
|
||||||
// code. Returns (nil, 0) when err is nil. The status table is part of the
|
// code. Returns (nil, 0) when err is nil. The status table is part of the
|
||||||
// public contract; keep it in sync with docs/specs/public-share-links/spec.md.
|
// public contract; keep it in sync with docs/specs/auth/requirements/public-share-links.md.
|
||||||
func mapShareError(err error) (*errorBody, int) {
|
func mapShareError(err error) (*errorBody, int) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil, 0
|
return nil, 0
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ type SetSessionModeAction struct {
|
|||||||
|
|
||||||
// RunCodeReviewAction starts a native code-review pass on step entry.
|
// RunCodeReviewAction starts a native code-review pass on step entry.
|
||||||
// AgentProfileID is optional; empty means "use the configured code-review
|
// AgentProfileID is optional; empty means "use the configured code-review
|
||||||
// utility agent". See docs/specs/native-code-review/spec.md.
|
// utility agent". See docs/specs/agents/requirements/native-code-review.md.
|
||||||
type RunCodeReviewAction struct {
|
type RunCodeReviewAction struct {
|
||||||
AgentProfileID string
|
AgentProfileID string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type AgentProfileResolver func(profileID string) *AgentProfilePortable
|
|||||||
// during workflow-sync reconciliation); implementations should keep it when
|
// during workflow-sync reconciliation); implementations should keep it when
|
||||||
// it still matches the descriptor even if the profile was since disabled -
|
// it still matches the descriptor even if the profile was since disabled -
|
||||||
// disabling only hides a profile from new selection, it doesn't touch
|
// disabling only hides a profile from new selection, it doesn't touch
|
||||||
// existing bindings (docs/specs/agents/profile-disable.md).
|
// existing bindings (docs/specs/agents/requirements/profile-disable.md).
|
||||||
type AgentProfileMatcher func(agentName, model, mode, currentID string) string
|
type AgentProfileMatcher func(agentName, model, mode, currentID string) string
|
||||||
|
|
||||||
// WorkflowPortable is a workflow without instance-specific fields (IDs, timestamps).
|
// WorkflowPortable is a workflow without instance-specific fields (IDs, timestamps).
|
||||||
|
|||||||
@@ -674,7 +674,7 @@ func TestApplySyncedWorkflows_WorkflowRebindWarningDoesNotFireOnFailedUpdate(t *
|
|||||||
// updateSyncedWorkflow passes the step's existing AgentProfileID to the
|
// updateSyncedWorkflow passes the step's existing AgentProfileID to the
|
||||||
// matcher as currentID. A real matcher (buildAgentProfileMatcher) uses that
|
// matcher as currentID. A real matcher (buildAgentProfileMatcher) uses that
|
||||||
// to preserve a binding that was disabled after the fact instead of treating
|
// to preserve a binding that was disabled after the fact instead of treating
|
||||||
// disablement as a rebind (docs/specs/agents/profile-disable.md). This test
|
// disablement as a rebind (docs/specs/agents/requirements/profile-disable.md). This test
|
||||||
// stubs that same contract: the matcher echoes currentID back when given
|
// stubs that same contract: the matcher echoes currentID back when given
|
||||||
// one, standing in for "the bound profile still matches, keep it".
|
// one, standing in for "the bound profile still matches, keep it".
|
||||||
func TestApplySyncedWorkflows_StepMatcherReceivesCurrentBinding(t *testing.T) {
|
func TestApplySyncedWorkflows_StepMatcherReceivesCurrentBinding(t *testing.T) {
|
||||||
|
|||||||
+2
-2
@@ -90,7 +90,7 @@ For rebasing or finishing PRs written against the old Next.js runtime, follow [`
|
|||||||
|
|
||||||
Use subscription hooks only; the WS client auto-deduplicates.
|
Use subscription hooks only; the WS client auto-deduplicates.
|
||||||
|
|
||||||
**Task overview vs. session detail:** Shared task rows read `Task.statusSummary` and `task.status_summary.updated`; rich streams stay session-detail-only. Extend the bounded projection per the [spec](../../docs/specs/platform/bounded-task-status-delivery.md) and [ADR](../../docs/decisions/2026-08-01-separate-task-summary-session-stream-traffic.md).
|
**Task overview vs. session detail:** Shared task rows read `Task.statusSummary` and `task.status_summary.updated`; rich streams stay session-detail-only. Extend the bounded projection per the [spec](../../docs/specs/platform/requirements/bounded-task-status-delivery.md) and [ADR](../../docs/decisions/2026-08-01-separate-task-summary-session-stream-traffic.md).
|
||||||
**Branch-scoped task state:** For live worktree/session state plus `task_prs`, key by `(repository, checked-out branch)`, not task/repository alone. `branch_switched` invalidates prior status/commits; reject late results with a generation/identity guard and preserve siblings. Historical PRs affect Changes only when `repository_id` and normalized `head_branch` match; Review/PR history may still show them. Test single/multi-repo cases and desktop/mobile Changes behavior.
|
**Branch-scoped task state:** For live worktree/session state plus `task_prs`, key by `(repository, checked-out branch)`, not task/repository alone. `branch_switched` invalidates prior status/commits; reject late results with a generation/identity guard and preserve siblings. Historical PRs affect Changes only when `repository_id` and normalized `head_branch` match; Review/PR history may still show them. Test single/multi-repo cases and desktop/mobile Changes behavior.
|
||||||
**HTTP/WS cache races:** When HTTP hydrates a cache also updated by WebSockets, guard responses with per-scope revision and request/workspace generation; discard or refresh stale responses and cover deferred responses. `useEnsureTaskSession` re-fires `session.ensure` when an open task page sees zero sessions after a prior ensure, so deleting the last session from that page spawns a replacement; test zero-session states through the backend/API instead.
|
**HTTP/WS cache races:** When HTTP hydrates a cache also updated by WebSockets, guard responses with per-scope revision and request/workspace generation; discard or refresh stale responses and cover deferred responses. `useEnsureTaskSession` re-fires `session.ensure` when an open task page sees zero sessions after a prior ensure, so deleting the last session from that page spawns a replacement; test zero-session states through the backend/API instead.
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ mandatory reason — reasonless or stale entries are errors. For zh-tw/zh-hk run
|
|||||||
`i18n:check` also gates key/catalog drift, `<Trans>` indices, inline plurals,
|
`i18n:check` also gates key/catalog drift, `<Trans>` indices, inline plurals,
|
||||||
module-scope `t()`, em dashes, and the **pseudo-locale** check. Needs **Node 24**.
|
module-scope `t()`, em dashes, and the **pseudo-locale** check. Needs **Node 24**.
|
||||||
Guide: [`docs/i18n.md`](../../docs/i18n.md); spec
|
Guide: [`docs/i18n.md`](../../docs/i18n.md); spec
|
||||||
[`docs/specs/platform/i18n.md`](../../docs/specs/platform/i18n.md).
|
[`docs/specs/platform/requirements/i18n.md`](../../docs/specs/platform/requirements/i18n.md).
|
||||||
|
|
||||||
## Markdown safety
|
## Markdown safety
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ function InheritedTierHint({
|
|||||||
// AC-17 / AC-17a / AC-17b: names the wake reasons whose policy shadows the
|
// AC-17 / AC-17a / AC-17b: names the wake reasons whose policy shadows the
|
||||||
// tier displayed above, whichever level supplied it. The effective map is
|
// tier displayed above, whichever level supplied it. The effective map is
|
||||||
// the agent's own tier_per_reason when it overrides the workspace policy
|
// the agent's own tier_per_reason when it overrides the workspace policy
|
||||||
// (an override replaces the map entirely, per docs/specs/office/routing.md)
|
// (an override replaces the map entirely, per docs/specs/office/requirements/routing.md)
|
||||||
// and the workspace map otherwise; keys with an empty value do not shadow
|
// and the workspace map otherwise; keys with an empty value do not shadow
|
||||||
// anything and are excluded.
|
// anything and are excluded.
|
||||||
function effectiveWakeReasonMap(
|
function effectiveWakeReasonMap(
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function formatShortDate(iso: string): string {
|
|||||||
* Renders an int64 subcents (hundredths of a cent) value as USD. The
|
* Renders an int64 subcents (hundredths of a cent) value as USD. The
|
||||||
* backend ships every cost figure as a subcents integer so we don't
|
* backend ships every cost figure as a subcents integer so we don't
|
||||||
* carry float drift; the formatter just divides by 10000 at the edge.
|
* carry float drift; the formatter just divides by 10000 at the edge.
|
||||||
* See docs/specs/office-costs/spec.md for the unit contract.
|
* See docs/specs/office/requirements/costs.md for the unit contract.
|
||||||
*/
|
*/
|
||||||
export function formatSubcents(subcents: number): string {
|
export function formatSubcents(subcents: number): string {
|
||||||
if (!Number.isFinite(subcents)) return "$0.00";
|
if (!Number.isFinite(subcents)) return "$0.00";
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type Props = {
|
|||||||
// RoleTierCard renders the per-role tier policy table on the workspace
|
// RoleTierCard renders the per-role tier policy table on the workspace
|
||||||
// routing settings page: the third of four precedence levels, below a
|
// routing settings page: the third of four precedence levels, below a
|
||||||
// wake-reason policy and a per-agent override, above the workspace default.
|
// wake-reason policy and a per-agent override, above the workspace default.
|
||||||
// See docs/specs/office-agent-tier-routing/spec.md.
|
// See docs/specs/office/requirements/office-agent-tier-routing.md.
|
||||||
export function RoleTierCard({ roles, config, value, onChange, disabled }: Props) {
|
export function RoleTierCard({ roles, config, value, onChange, disabled }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleRowChange = (role: AgentRole, tier: Tier | typeof USE_WORKSPACE_DEFAULT) => {
|
const handleRowChange = (role: AgentRole, tier: Tier | typeof USE_WORKSPACE_DEFAULT) => {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function rememberWorkspaceSelectionById(id: string, kind: "office" | "kan
|
|||||||
* that instance's LIVE selection cookie, and for upgraded ported instances it
|
* that instance's LIVE selection cookie, and for upgraded ported instances it
|
||||||
* is the validated migration fallback until each writes its own scoped value.
|
* is the validated migration fallback until each writes its own scoped value.
|
||||||
* Scrubbing it from one instance would change the other instances' next boot
|
* Scrubbing it from one instance would change the other instances' next boot
|
||||||
* (see docs/specs/fix-multi-instance-cookie-isolation/spec.md: the upgraded
|
* (see docs/specs/auth/requirements/fix-multi-instance-cookie-isolation.md: the upgraded
|
||||||
* instance does not proactively delete the legacy cookie).
|
* instance does not proactively delete the legacy cookie).
|
||||||
*/
|
*/
|
||||||
function writeWorkspaceCookie(name: string, value: string): void {
|
function writeWorkspaceCookie(name: string, value: string): void {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { settingsActionClassName } from "@/components/settings/settings-control"
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Operator UI to browse, install, enable, disable, uninstall, and update kandev
|
* Operator UI to browse, install, enable, disable, uninstall, and update kandev
|
||||||
* plugins (docs/specs/plugins/marketplace.md). Gated on the `plugins` feature
|
* plugins (docs/specs/plugins/requirements/marketplace.md). Gated on the `plugins` feature
|
||||||
* flag by the page-level default export.
|
* flag by the page-level default export.
|
||||||
*/
|
*/
|
||||||
export function PluginsSettings() {
|
export function PluginsSettings() {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export type ConditionalTodoPanelAction = "add" | "remove" | "none";
|
|||||||
* Details conditional panel, there is no per-instance identity to reconcile
|
* Details conditional panel, there is no per-instance identity to reconcile
|
||||||
* and no closed-for-session suppression: the `showTodoListPanel` preference
|
* and no closed-for-session suppression: the `showTodoListPanel` preference
|
||||||
* is the single authoritative on/off switch (see
|
* is the single authoritative on/off switch (see
|
||||||
* docs/specs/ui/agent-todo-list-panel.md — "true, unconditional visibility
|
* docs/specs/ui/requirements/agent-todo-list-panel.md — "true, unconditional visibility
|
||||||
* gate"). The "only pin when not empty" sub-option (`onlyPinWhenNotEmpty`)
|
* gate"). The "only pin when not empty" sub-option (`onlyPinWhenNotEmpty`)
|
||||||
* gates only the automatic add: while it is on, an absent panel is not added
|
* gates only the automatic add: while it is on, an absent panel is not added
|
||||||
* until the active session's todo list is non-empty (`todoListNotEmpty`).
|
* until the active session's todo list is non-empty (`todoListNotEmpty`).
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function formatDate(dateStr: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// formatCurrency converts subcents (hundredths of a cent — the office
|
// formatCurrency converts subcents (hundredths of a cent — the office
|
||||||
// cost storage unit per docs/specs/office-costs/spec.md) to a USD string.
|
// cost storage unit per docs/specs/office/requirements/costs.md) to a USD string.
|
||||||
function formatCurrency(subcents: number): string {
|
function formatCurrency(subcents: number): string {
|
||||||
return new Intl.NumberFormat(undefined, {
|
return new Intl.NumberFormat(undefined, {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { AutomationsPage } from "../pages/automations-page";
|
|||||||
/**
|
/**
|
||||||
* E2E coverage for the github_pr_merged ("Pull request merged") trigger type.
|
* E2E coverage for the github_pr_merged ("Pull request merged") trigger type.
|
||||||
*
|
*
|
||||||
* Spec: docs/specs/automations-pr-merged-trigger/spec.md
|
* Spec: docs/specs/office/requirements/automations-pr-merged-trigger.md
|
||||||
* Decisions pinned here:
|
* Decisions pinned here:
|
||||||
* - Picker shows "Pull request merged" under GitHub group, immediately after "New pull requests"
|
* - Picker shows "Pull request merged" under GitHub group, immediately after "New pull requests"
|
||||||
* - Config round-trip: all_repos, repos, base_branches survive save/reopen
|
* - Config round-trip: all_repos, repos, base_branches survive save/reopen
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { registerSeparateQueueRows } from "../../helpers/message-queue-settings"
|
|||||||
registerSeparateQueueRows(test);
|
registerSeparateQueueRows(test);
|
||||||
|
|
||||||
// End-to-end coverage for the mid-turn steering composer contract
|
// End-to-end coverage for the mid-turn steering composer contract
|
||||||
// (docs/specs/platform/mid-turn-steering.md). CONTRIBUTING.md requires Playwright
|
// (docs/specs/platform/requirements/mid-turn-steering.md). CONTRIBUTING.md requires Playwright
|
||||||
// coverage for UI changes; this exercises the flag-gated decision and the real
|
// coverage for UI changes; this exercises the flag-gated decision and the real
|
||||||
// steer dispatch path through the mock agent, which advertises promptQueueing.
|
// steer dispatch path through the mock agent, which advertises promptQueueing.
|
||||||
//
|
//
|
||||||
@@ -153,7 +153,7 @@ test.describe.serial("Claude mid-turn steering experiment", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// The two outcomes below replay the mid-turn steering spec's "folded" vs
|
// The two outcomes below replay the mid-turn steering spec's "folded" vs
|
||||||
// "deferred" outcome taxonomy (docs/specs/platform/mid-turn-steering.md)
|
// "deferred" outcome taxonomy (docs/specs/platform/requirements/mid-turn-steering.md)
|
||||||
// via the dedicated mock-agent setup scenarios: steer-fold-setup never
|
// via the dedicated mock-agent setup scenarios: steer-fold-setup never
|
||||||
// answers on its own (task 08's mock-replay acceptance for "folded" —
|
// answers on its own (task 08's mock-replay acceptance for "folded" —
|
||||||
// pinned deterministically at the mock level by
|
// pinned deterministically at the mock level by
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { test, expect } from "../../fixtures/test-base";
|
|||||||
* externalization sweep: any plain-ASCII user-facing text under `pseudo` is a
|
* externalization sweep: any plain-ASCII user-facing text under `pseudo` is a
|
||||||
* literal that was never routed through `t()`.
|
* literal that was never routed through `t()`.
|
||||||
*
|
*
|
||||||
* See docs/specs/platform/i18n.md and docs/i18n.md.
|
* See docs/specs/platform/requirements/i18n.md and docs/i18n.md.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const APPEARANCE_URL = "/settings/preferences/appearance";
|
const APPEARANCE_URL = "/settings/preferences/appearance";
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ async function expandIntegrationsSection(testPage: Page) {
|
|||||||
await expect(integrationsToggle).toHaveAttribute("aria-expanded", "true");
|
await expect(integrationsToggle).toHaveAttribute("aria-expanded", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Covers docs/specs/integrations/enable-disable-toggle.md's nav-visibility
|
// Covers docs/specs/integrations/requirements/enable-disable-toggle.md's nav-visibility
|
||||||
// scenarios: with "Hide disabled integrations from left panel navigation"
|
// scenarios: with "Hide disabled integrations from left panel navigation"
|
||||||
// off (the default), a disabled-but-configured integration still shows in
|
// off (the default), a disabled-but-configured integration still shows in
|
||||||
// the sidebar; turning the setting on hides it; turning it back off (or
|
// the sidebar; turning the setting on hides it; turning it back off (or
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { test, expect } from "../../fixtures/test-base";
|
import { test, expect } from "../../fixtures/test-base";
|
||||||
|
|
||||||
// Covers docs/specs/integrations/enable-disable-toggle.md's per-workspace
|
// Covers docs/specs/integrations/requirements/enable-disable-toggle.md's per-workspace
|
||||||
// scoping: the enable toggle belongs to the workspace whose settings page it
|
// scoping: the enable toggle belongs to the workspace whose settings page it
|
||||||
// sits on, so disabling an integration in one workspace leaves every other
|
// sits on, so disabling an integration in one workspace leaves every other
|
||||||
// workspace's toggle alone. It shipped install-wide first, which silenced an
|
// workspace's toggle alone. It shipped install-wide first, which silenced an
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { test, expect } from "../../fixtures/test-base";
|
import { test, expect } from "../../fixtures/test-base";
|
||||||
|
|
||||||
// Covers docs/specs/integrations/enable-disable-toggle.md's index-page slider
|
// Covers docs/specs/integrations/requirements/enable-disable-toggle.md's index-page slider
|
||||||
// scenarios: every integration row has a working slider, toggling it never
|
// scenarios: every integration row has a working slider, toggling it never
|
||||||
// navigates, and the toggle stays in sync with that integration's own
|
// navigates, and the toggle stays in sync with that integration's own
|
||||||
// settings page (the shared per-workspace `useXEnabled` state).
|
// settings page (the shared per-workspace `useXEnabled` state).
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { test, expect } from "../../fixtures/office-fixture";
|
|||||||
/**
|
/**
|
||||||
* E2E coverage for the office task approval gate (in_review → done is
|
* E2E coverage for the office task approval gate (in_review → done is
|
||||||
* blocked until every approver has a current "approved" decision). Spec:
|
* blocked until every approver has a current "approved" decision). Spec:
|
||||||
* docs/specs/office-task-approval-flow/spec.md
|
* docs/specs/office/requirements/inbox.md
|
||||||
*
|
*
|
||||||
* Approach:
|
* Approach:
|
||||||
* - Use `officeApi.createAgent` to add an approver to the workspace.
|
* - Use `officeApi.createAgent` to add an approver to the workspace.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { test, expect } from "../../fixtures/office-fixture";
|
|||||||
* E2E coverage for the office dashboard "Agents" panel: per-agent cards
|
* E2E coverage for the office dashboard "Agents" panel: per-agent cards
|
||||||
* rendered always (even with zero activity), driven reactively by the
|
* rendered always (even with zero activity), driven reactively by the
|
||||||
* existing WS pipeline (`session.state_changed`, `office.task.updated`,
|
* existing WS pipeline (`session.state_changed`, `office.task.updated`,
|
||||||
* `office.agent.updated`). Spec: docs/specs/office-dashboard-agent-cards.
|
* `office.agent.updated`). Spec: docs/specs/office/requirements/dashboard.md.
|
||||||
*
|
*
|
||||||
* The lifecycle harness is rich enough to drive these scenarios without a
|
* The lifecycle harness is rich enough to drive these scenarios without a
|
||||||
* real executor: `apiClient.seedTaskSession` writes directly to the DB
|
* real executor: `apiClient.seedTaskSession` writes directly to the DB
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { balancedExecutionProfileRouting } from "../../helpers/office-routing";
|
|||||||
/**
|
/**
|
||||||
* Phase 7 spec #2 — provider fallback under quota_limited.
|
* Phase 7 spec #2 — provider fallback under quota_limited.
|
||||||
*
|
*
|
||||||
* Scenario (per docs/specs/office-provider-routing/spec.md):
|
* Scenario (per docs/specs/office/requirements/routing.md):
|
||||||
* 1. Workspace routing enabled with order `claude-acp → codex-acp`,
|
* 1. Workspace routing enabled with order `claude-acp → codex-acp`,
|
||||||
* tier `balanced`.
|
* tier `balanced`.
|
||||||
* 2. Backend is launched with `KANDEV_PROVIDER_FAILURES=claude-acp:
|
* 2. Backend is launched with `KANDEV_PROVIDER_FAILURES=claude-acp:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { test, expect } from "../../fixtures/office-fixture";
|
|||||||
* E2E coverage for the office task session lifecycle (per-(task, agent)
|
* E2E coverage for the office task session lifecycle (per-(task, agent)
|
||||||
* persistent sessions, RUNNING ↔ IDLE state cycling, COMPLETED only on
|
* persistent sessions, RUNNING ↔ IDLE state cycling, COMPLETED only on
|
||||||
* participation removal). Spec:
|
* participation removal). Spec:
|
||||||
* docs/specs/office-task-session-lifecycle/spec.md
|
* docs/specs/office/requirements/tasks.md
|
||||||
*
|
*
|
||||||
* Harness limitations:
|
* Harness limitations:
|
||||||
* - The /api/v1/_test/task-sessions route only INSERTS new rows; it does
|
* - The /api/v1/_test/task-sessions route only INSERTS new rows; it does
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from "node:path";
|
|||||||
import { test, expect } from "../../fixtures/test-base";
|
import { test, expect } from "../../fixtures/test-base";
|
||||||
import { OfficeApiClient } from "../../helpers/office-api-client";
|
import { OfficeApiClient } from "../../helpers/office-api-client";
|
||||||
|
|
||||||
// Verifies the spec-aligned skill injection (docs/specs/office/agents.md):
|
// Verifies the spec-aligned skill injection (docs/specs/office/requirements/agents.md):
|
||||||
// when a skill is assigned to an agent profile, launching a session writes
|
// when a skill is assigned to an agent profile, launching a session writes
|
||||||
// it to <worktree>/<projectSkillDir>/kandev-<slug>/SKILL.md and appends
|
// it to <worktree>/<projectSkillDir>/kandev-<slug>/SKILL.md and appends
|
||||||
// the kandev-* glob to .git/info/exclude.
|
// the kandev-* glob to .git/info/exclude.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* E2E: `PluginComposerCapability` against the real native composers
|
* E2E: `PluginComposerCapability` against the real native composers
|
||||||
* (docs/specs/plugins/voice-extraction-host.md).
|
* (docs/specs/plugins/requirements/voice-extraction-host.md).
|
||||||
*
|
*
|
||||||
* These are the prerequisites the extracted Voice Mode plugin stands on, so
|
* These are the prerequisites the extracted Voice Mode plugin stands on, so
|
||||||
* everything here goes through a genuinely registered plugin slot and then
|
* everything here goes through a genuinely registered plugin slot and then
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Pixel 5 coverage for `PluginComposerCapability`
|
* Pixel 5 coverage for `PluginComposerCapability`
|
||||||
* (docs/specs/plugins/voice-extraction-host.md, "Mobile Design Contract").
|
* (docs/specs/plugins/requirements/voice-extraction-host.md, "Mobile Design Contract").
|
||||||
*
|
*
|
||||||
* Capability parity, not layout parity: the same action, the same insertion
|
* Capability parity, not layout parity: the same action, the same insertion
|
||||||
* and the same native submit have to work by touch, from the shipped mobile
|
* and the same native submit have to work by touch, from the shipped mobile
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ function pluginExecutablePath(installPath: string): string {
|
|||||||
* Stages a filesystem sideload: extracts the same fixture package the
|
* Stages a filesystem sideload: extracts the same fixture package the
|
||||||
* upload tests use directly into `<pluginsDir>/<id>/<version>/`, with no
|
* upload tests use directly into `<pluginsDir>/<id>/<version>/`, with no
|
||||||
* `{id}.yml` record — the on-disk shape `Service.Sync`'s directory-sideload
|
* `{id}.yml` record — the on-disk shape `Service.Sync`'s directory-sideload
|
||||||
* step looks for (docs/specs/plugins/spec.md "Filesystem sideloading &
|
* step looks for (docs/specs/plugins/requirements/plugins.md "Filesystem sideloading &
|
||||||
* sync"). `checksums.txt` lands in the directory alongside the rest of the
|
* sync"). `checksums.txt` lands in the directory alongside the rest of the
|
||||||
* package; the sideload path ignores it (only the tarball-install path
|
* package; the sideload path ignores it (only the tarball-install path
|
||||||
* verifies checksums).
|
* verifies checksums).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { test, expect } from "../../fixtures/test-base";
|
import { test, expect } from "../../fixtures/test-base";
|
||||||
|
|
||||||
// Covers docs/specs/agents/hide-disabled-profiles-nav.md's nav-visibility
|
// Covers docs/specs/agents/requirements/hide-disabled-profiles-nav.md's nav-visibility
|
||||||
// scenarios: with "Hide disabled agent profiles from left panel navigation"
|
// scenarios: with "Hide disabled agent profiles from left panel navigation"
|
||||||
// off (the default), a disabled profile still shows in the Settings left
|
// off (the default), a disabled profile still shows in the Settings left
|
||||||
// panel's Agents tree; turning the setting on hides it; re-enabling the
|
// panel's Agents tree; turning the setting on hides it; re-enabling the
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user