diff --git a/agent/tools_insert.go b/agent/tools_insert.go index ffbd353..8f8ae55 100644 --- a/agent/tools_insert.go +++ b/agent/tools_insert.go @@ -365,9 +365,9 @@ func (t *ToolSet) insertAssets() actool.CoreTool { // type; side-effect-derived assets don't go through here, so the scope // doesn't widen blindly. No-op when taskID=0. // The item may come from target content, so this auto row counts toward - // coverage and list_untested_assets only under a declared root (a - // non-auto row: goal target, add_task_scope or manual scope, see - // db.declaredScopeFilter). task_scope is + // coverage and list_untested_assets only under a declared domain or IP + // root (a non-auto root_domain/subdomain/ip/cidr row: goal target, + // add_task_scope or manual scope, see db.declaredScopeFilter). task_scope is // coverage scope, not enforced: nothing blocks a call outside it. // Independent of the coverage switch: task_scope is the task's range // boundary (the filter basis for list/query); the coverage switch only @@ -454,7 +454,7 @@ func (t *ToolSet) addTaskScope() actool.CoreTool { "add_task_scope", "Add test scope to [this task] — "+taskScopeLabel+": it is the denominator of asset test coverage; nothing blocks a tool call outside it.\n"+ "Supported kinds: company (all assets under the company) / root_domain (a whole root domain, including all subdomains) / subdomain (a single precise subdomain) / ip / cidr / icp / keyword.\n"+ - "Note: hosts a worker touches one by one are [automatically] added to the scope by the system (precise subdomains), but they count toward coverage and list_untested_assets only under a declared root (a goal target or scope added here or manually); this tool is for [deliberate widening] — pulling in a whole root domain / whole company, or adding a specific subdomain/IP.\n"+ + "Note: hosts a worker touches one by one are [automatically] added to the scope by the system (precise subdomains), but they count toward coverage and list_untested_assets only under a declared domain or IP root (a root_domain/subdomain/ip/cidr goal target or scope added here or manually; a company/icp/keyword entry is not such a root); this tool is for [deliberate widening] — pulling in a whole root domain / whole company, or adding a specific subdomain/IP.\n"+ "value: for company pass the company name or id (the company must already exist); for root_domain/subdomain pass a domain; for ip/cidr pass an IP or range; for icp/keyword pass an ICP number or company keyword.\n"+ "Always give reason explaining the basis (auditable). Use the entries array for multiple entries.", obj(map[string]any{ diff --git a/agent/worker.go b/agent/worker.go index 88d280b..093c3cc 100644 --- a/agent/worker.go +++ b/agent/worker.go @@ -455,7 +455,7 @@ func (w *Worker) execute(ctx context.Context, name string, taskID int64, as *db. // ON CONFLICT DO NOTHING + the uq_task_scope unique index guarantee no // duplicate additions; reruns/retries are idempotent no-ops as well. // asset_ids can trace back to target content, so these auto rows count - // toward coverage only under a declared root (db.declaredScopeFilter); + // toward coverage only under a declared domain or IP root (db.declaredScopeFilter); // coverage scope, not enforced. // When the asset-coverage feature is off, the test scope (denominator) is no // longer accumulated. diff --git a/db/schema.sql b/db/schema.sql index 92bc174..aa24e93 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -760,7 +760,7 @@ WHERE t.active_llm_profile_id IS NULL -- side-effect-derived assets do not enter the scope (the hook sits at the handler top level; derivation is internal to the db layer). -- Agent-filled (source='agent'): add_task_scope adds company/root_domain/subdomain/ip. -- Coverage = among assets matching active rows (denominator), the share anchored by fact nodes (numerator). --- An auto row is active only under a declared (non-auto) root of the same task (declaredScopeFilter in task_scope.go). +-- An auto row is active only under a declared (non-auto) domain or IP root of the same task (declaredScopeFilter in task_scope.go). CREATE TABLE IF NOT EXISTS task_scope ( id BIGSERIAL PRIMARY KEY, task_id BIGINT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, diff --git a/db/task_assets_context.go b/db/task_assets_context.go index 8ac524b..2e6b85f 100644 --- a/db/task_assets_context.go +++ b/db/task_assets_context.go @@ -121,7 +121,7 @@ ORDER BY CASE WHEN ts.task_id=$1 THEN 0 ELSE 1 END, ts.id`, taskID) // TaskCoverageWithSources computes one coverage view over the union of the // current task and its direct sources: source scopes and fact-anchored assets // extend the denominator, while fact anchors count as tested. Auto scope rows -// count only under a declared root (declaredScopeFilter), and an intent anchor +// count only under a declared domain or IP root (declaredScopeFilter), and an intent anchor // alone (insert_assets, asset_ids) no longer adds an asset. No row is copied. func (s *AssetStore) TaskCoverageWithSources(taskID int64) (*Coverage, error) { cov := &Coverage{ByType: []CoverageByType{}} diff --git a/db/task_scope.go b/db/task_scope.go index bafdf38..ae98628 100644 --- a/db/task_scope.go +++ b/db/task_scope.go @@ -14,7 +14,8 @@ import ( // conservative, one per explicitly-inserted asset), from the add_task_scope tool // (source='agent': the goal targets the decomposer registers, or planner widening), // or from the operator (source='manual'). An auto row counts toward coverage only -// under a declared (non-auto) root, see declaredScopeFilter. +// under a declared domain or IP root (a non-auto root_domain/subdomain/ip/cidr row), +// see declaredScopeFilter. type TaskScope struct { ID int64 `json:"id"` TaskID int64 `json:"task_id"` @@ -109,7 +110,7 @@ func (s *AssetStore) upsertTaskScope(ts TaskScope) error { // never from a db-layer side effect (linkHostAssets), so derived assets do not blindly expand the scope. // Rule: scope granularity follows the asset's own type. taskID<=0 → no-op. // The asset comes from target-derived content, so the row counts toward coverage and -// list_untested_assets only under a declared root (declaredScopeFilter). +// list_untested_assets only under a declared domain or IP root (declaredScopeFilter). func (s *AssetStore) AddAutoScope(taskID int64, assetType, domain, rawURL, ip string) error { if taskID <= 0 { return nil @@ -280,7 +281,7 @@ type CoverageByType struct { // Coverage is a task's rough asset test coverage — a reference figure for the agent, // NOT a precise metric. Denominator = assets matching a declared task_scope row or an -// auto row under a declared root (declaredScopeFilter); Tested = those anchored to at +// auto row under a declared domain or IP root (declaredScopeFilter); Tested = those anchored to at // least one fact node in the exploration. type Coverage struct { Enabled bool `json:"enabled"` // whether the asset coverage feature is on; when false the other fields are zero @@ -307,11 +308,11 @@ func (s *AssetStore) CoverageEnabled(taskID int64) bool { // declaredScopeFilter keeps a task_scope row ts in the coverage match only when it is // declared (source<>'auto': goal targets registered through add_task_scope, or manual -// scope) or when it is an auto row under a root the same task declared: the same or -// a deeper domain of a declared root_domain/subdomain, or a net inside a declared -// ip/cidr. Auto rows come from target-derived content (insert_assets, intent -// asset_ids), so anything else stays out of the coverage denominator and -// list_untested_assets. +// scope) or when it is an auto row under a domain or IP root the same task declared: +// the same or a deeper domain of a declared root_domain/subdomain, or a net inside a +// declared ip/cidr. A declared company, icp or keyword row is not a root here. Auto +// rows come from target-derived content (insert_assets, intent asset_ids), so +// anything else stays out of the coverage denominator and list_untested_assets. const declaredScopeFilter = `(ts.source <> 'auto' OR EXISTS ( SELECT 1 FROM task_scope d WHERE d.task_id = ts.task_id AND d.source <> 'auto' AND (