Add entitlement approval gate for task classes - #923
Merged
Conversation
The entitlement taxonomy says what a task reaches. It has never answered the question a host actually asks it — given this class, must a person approve before it runs — so every host wanting a gate writes its own, downstream, against a taxonomy that grows here. An id added here would inherit a pass from a list nobody downstream remembers to widen. Written as an allowlist: INFERENCE_ENTITLEMENTS names the three ids a caller already running a model necessarily holds; everything else is reach it does not otherwise have. Matching is exact rather than hierarchical even though entitlementCovers sits next door and looks like the natural choice — an ambient `ai` must not silently cover an `ai:*` id minted after the fact. The case worth the file is the second one: an empty static declaration is not always about the task. A composed task computes its aggregate on the INSTANCE, so a graph wrapping a URL fetcher declares nothing and a class-only check certifies it as pure. That needed a fact this library did not state, so it now does. `entitlementsFromChildren` is false on Task and true on GraphAsTask, which every composed task inherits. It is deliberately NOT hasDynamicEntitlements, which reads like the same question and is not: a task is dynamic when it refines what it already declares, and every AI task sets it in order to attach the resolved model id to `ai:model`. Keying the gate on that flag puts an approval in front of running a model at all — the one thing a caller holding INFERENCE_ENTITLEMENTS was always going to do. Both readings are pinned by tests so the distinction cannot quietly collapse. describeTaskClassReach gives an approval prompt its one line, and reports a composed class as undeclared rather than as reaching nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEFYAGb9D3mWfyhYkmeAvN
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||
…ss also declares Review of the previous commit found two ways the approval prompt could still read as a complete answer when it is not. describeTaskClassReach chose between the declared list and the "reach lives in its children" caveat, so a composed class that ALSO declares something static printed only its wrapper's share — the same misleading reading as reporting the empty set, in a second spelling. The caveat is now appended rather than chosen between, and the emptiness test reads the reach array rather than the joined string so it cannot disagree with taskClassNeedsApproval about a class whose entitlement renders blank. VectorSimilarityTask extends GraphAsTask for its runner and config shape but computes in process and never runs a subgraph, so it inherited the composed answer and would have drawn a confirmation card describing tasks that do not exist. It declares entitlementsFromChildren = false. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEFYAGb9D3mWfyhYkmeAvN
The approval gate reads a task class's declaration, and almost nothing in this package declares. AiTask was the only class here overriding entitlements(), and no task anywhere in the repo claimed storage:* — the taxonomy had it and the profiles granted it, but nothing asked for it. So taskClassNeedsApproval answered false for KbDeleteTask: a skill that wipes a document and its chunks, behind a card reading "reaches nothing". Eleven classes now declare what they touch. ChunkRetrievalTask declares storage:write as well as read, because installTextIndex writes an index on the first hybrid search; the two AI-base subclasses merge with super rather than replacing, since entitlements() overrides rather than extends. The five run-time composers are deliberately NOT marked entitlementsFromChildren. They own fixed, hard-coded children — a TextEmbeddingTask, a CountTokensTask — so their reach is knowable from their own source, and the flag means the opposite: not knowable until run time. Setting it there would print "not declared up front" about a known child set, which is the same misleading prompt in the other direction. They declare the union instead, and Task's JSDoc now says which case the flag is for. Three gate fixes ride along. A class that cannot be asked for its declaration — catalogs populate taskClass by cast, so one without the static is reachable — was calling through unguarded, crashing inside the approval path; it now reads as unknown and gates, rather than as empty and passing. The "nothing" line is phrased against the ambient set rather than against inference, since that set is a parameter and a caller passing its own was told a class reaches nothing beyond running a model it never runs. An optional entitlement renders as "may use": it is kept rather than skipped, which is where this parts company with evaluatePolicy on purpose — that decides whether to allow a run, this decides what to tell a person first. VectorSimilarityTask now extends Task. It used nothing from GraphAsTask and overrode the composed answer to correct it; reparenting makes the wrong answer unrepresentable and drops two more inherited claims (dynamic schemas, dynamic entitlements) that were equally untrue of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEFYAGb9D3mWfyhYkmeAvN
Both came from sweeping for `kb.<method>(` and matching call-shaped text that was not a call. ChunkRetrievalTask does not write. It names installTextIndex once, inside an error message telling the caller how to fix a missing text index, and never calls it — and that method assigns a field rather than touching storage anyway. hybridSearch throws when no index is installed rather than lazily installing one; the index auto-writes happen on chunk upsert, and existing chunks are not back-indexed without an explicit reindexText. It searches, so it declares storage:read alone. TextRerankerTask reaches no knowledge base at all. Its input ports are query, documents, topK and model; the only `kb.` in the file is a comment noting that a knowledge base invokes THIS task, so the dependency runs the other way. Its declaration is removed entirely, leaving the ai:inference its base class declares — and leaving it ungated, which is right for a task that scores strings with a model. Over-declaring costs what under-declaring costs. A prompt that says a reranker touches storage is a prompt people learn to click through, and then the KbDeleteTask prompt does not stop anyone either. Every remaining declaration was re-checked against an awaited call rather than a grep hit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEFYAGb9D3mWfyhYkmeAvN
sroussey
added a commit
that referenced
this pull request
Sep 8, 2026
## @workglow/browser-control ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/task-graph ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) #### task-graph - add entitlement approval gate for ai task classes (#923) ### Bug Fixes #### task-graph - move `runPreview`'s return out of the `finally` block (#918) #### web-search - name a credential for a provider, fill Brave's open date bound, and thread abort - resolve the search credential once, in the task that sends it ### Chores - revert version change not made by bunset ## @workglow/javascript ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/web-search ### Features #### web-search - add OpenAI, OpenRouter and Gemini grounded providers - add runtime entries, registration and README - add SearXNG provider with a keyless live test - add Tavily provider with native filters and answers - add Brave provider over an owned FetchUrlTask - add WebSearchTask with pinned and routed provider selection - add provider registry with capability routing - translate domain filters to site: operators - add provider interface and capability check ### Bug Fixes #### web-search - refuse a domain entry that names no domain - address code review across the package and its adapters - honour or refuse a named credential, stop registering providers on import, and keep two ports honest - name a credential for a provider, fill Brave's open date bound, and thread abort - let a provider declare that it takes one domain list, not both - make maxResults mean the same thing for every provider - resolve the search credential once, in the task that sends it - make the instance entitlements reachable and fail closed - keep test declarations out of the published tarball - correct rate-limiter claim and gate the grounded answer - remove quadratic backtracking from trailing-slash trim ### Documentation - document @workglow/web-search in CLAUDE.md ### Chores #### web-search - bring the new package up to the workspace conventions ## @workglow/ai ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) #### task-graph - add entitlement approval gate for ai task classes (#923) ### Bug Fixes #### pricing - various fixes for pricing on models - merge a declared rate card over the provider's field by field - resolve rates from the live table and at the request's instant ### Chores - revert version change not made by bunset ## @workglow/knowledge-base ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## workglow ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/storage ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### postgres - give a vector storage a real pgvector column #### storage - stop connection transactions hanging and leaking uncommitted rows ### Refactors #### storage - route the tx handle through the public methods ### Chores - revert version change not made by bunset ## @workglow/mcp ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features - route human-in-the-loop tasks to the MCP client - serve registered tasks over MCP, behind a bearer token #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes - keep server-side failure detail off the wire - make the bearer and tool-name parsers linear - close the gaps a review of the MCP server turned up ### Chores - revert version change not made by bunset ## @workglow/triggers ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/util ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### util - repair the Node worker path and drop the Bun worker fork ### Chores - revert version change not made by bunset ## @workglow/test ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes - file the human-in-the-loop tasks under "Human", not "Flow Control" #### util - repair the Node worker path and drop the Bun worker fork #### postgres - give a vector storage a real pgvector column #### release - gate publish-all on tests again (#916) #### pricing - various fixes for pricing on models - resolve rates from the live table and at the request's instant #### storage - stop connection transactions hanging and leaking uncommitted rows - guard updateWhere's primary key on the transaction path ### Refactors #### storage - route the tx handle through the public methods ### Tests #### pricing - assert the billing unit in the contract suite, off the same fixtures ### Chores - revert version change not made by bunset - update bunset and its configuration and dependencies ### Updated Dependencies - `miniflare`: ^5.20260908.0-alpha ## @workglow/tasks ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes - file the human-in-the-loop tasks under "Human", not "Flow Control" ### Chores - revert version change not made by bunset ## @workglow/job-queue ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/indexeddb ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/openai ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) #### web-search - add OpenAI, OpenRouter and Gemini grounded providers ### Bug Fixes #### web-search - address code review across the package and its adapters - honour or refuse a named credential, stop registering providers on import, and keep two ports honest - name a credential for a provider, fill Brave's open date bound, and thread abort - make maxResults mean the same thing for every provider #### pricing - resolve rates from the live table and at the request's instant #### openrouter,openai - resolve the search API key explicitly and lazily ### Chores - revert version change not made by bunset ## @workglow/llamacpp-server ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/mlx ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### pricing - various fixes for pricing on models ### Chores - revert version change not made by bunset ## @workglow/electron ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/ollama ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/node-llama-cpp ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/aws ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/anthropic ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) #### anthropic - add grounded web-search provider subpath ### Bug Fixes #### web-search - address code review across the package and its adapters - honour or refuse a named credential, stop registering providers on import, and keep two ports honest - name a credential for a provider, fill Brave's open date bound, and thread abort - let a provider declare that it takes one domain list, not both - make maxResults mean the same thing for every provider - keep test declarations out of the published tarball - correct rate-limiter claim and gate the grounded answer #### pricing - resolve rates from the live table and at the request's instant #### anthropic - give the package a test script so its tests actually run ### Documentation - document @workglow/web-search in CLAUDE.md ### Chores - revert version change not made by bunset ## @workglow/duckdb ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### storage - stop connection transactions hanging and leaking uncommitted rows ### Refactors #### storage - route the tx handle through the public methods ### Chores - revert version change not made by bunset ## @workglow/google-gemini ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) #### web-search - add OpenAI, OpenRouter and Gemini grounded providers ### Bug Fixes #### web-search - address code review across the package and its adapters - honour or refuse a named credential, stop registering providers on import, and keep two ports honest - name a credential for a provider, fill Brave's open date bound, and thread abort - make maxResults mean the same thing for every provider #### pricing - various fixes for pricing on models - resolve rates from the live table and at the request's instant ### Chores - revert version change not made by bunset ## @workglow/postgres ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### postgres - give a vector storage a real pgvector column #### storage - stop connection transactions hanging and leaking uncommitted rows - guard updateWhere's primary key on the transaction path ### Refactors #### storage - route the tx handle through the public methods ### Chores - revert version change not made by bunset ## @workglow/stable-diffusion-server ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/supabase ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/xai ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### pricing - various fixes for pricing on models - resolve rates from the live table and at the request's instant ### Chores - revert version change not made by bunset ## @workglow/deepseek ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### pricing - resolve rates from the live table and at the request's instant ### Chores - revert version change not made by bunset ## @workglow/playwright ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/sqlite ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### storage - stop connection transactions hanging and leaking uncommitted rows - guard updateWhere's primary key on the transaction path ### Refactors #### storage - route the tx handle through the public methods ### Chores - revert version change not made by bunset ## @workglow/cloudflare ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/huggingface-transformers ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/tf-mediapipe ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/chrome-ai ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### pricing - various fixes for pricing on models ### Chores - revert version change not made by bunset ## @workglow/openrouter ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) #### web-search - add OpenAI, OpenRouter and Gemini grounded providers ### Bug Fixes #### web-search - address code review across the package and its adapters - honour or refuse a named credential, stop registering providers on import, and keep two ports honest - name a credential for a provider, fill Brave's open date bound, and thread abort - make maxResults mean the same thing for every provider #### pricing - various fixes for pricing on models #### openrouter,openai - resolve the search API key explicitly and lazily ### Chores - revert version change not made by bunset ## @workglow/huggingface-inference ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/cactus ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/bun-webview ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset ## @workglow/eval ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Bug Fixes #### util - repair the Node worker path and drop the Bun worker fork #### pricing - resolve rates from the live table and at the request's instant ### Chores - revert version change not made by bunset - update bunset and its configuration and dependencies ### Updated Dependencies - `hyparquet`: ^1.30.0 ## @workglow/cli ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features - route human-in-the-loop tasks to the MCP client - serve registered tasks over MCP, behind a bearer token #### storage - add join() to ITabularStorage with SQL pushdown (#893) #### cli - mark what an `all` command actually runs, and give a command group its own page in the web console ### Bug Fixes - close the gaps a review of the MCP server turned up - file the human-in-the-loop tasks under "Human", not "Flow Control" #### util - repair the Node worker path and drop the Bun worker fork #### pricing - various fixes for pricing on models - merge a declared rate card over the provider's field by field - resolve rates from the live table and at the request's instant #### cli/web - share one status-rail read instead of tearing down under it ### Chores - revert version change not made by bunset ## @workglow/web ### Breaking Changes - **features(storage)**: add join() to ITabularStorage with SQL pushdown (#893) ### Features #### storage - add join() to ITabularStorage with SQL pushdown (#893) ### Chores - revert version change not made by bunset - update bunset and its configuration and dependencies ### Updated Dependencies - `react-resizable-panels`: ^4.12.4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduces a static approval rule for task classes, and makes this package's RAG tasks declare the knowledge base they reach so the rule has something to read.
Why here rather than downstream
The entitlement taxonomy says what a task reaches. It has never answered the question a host actually asks it — given this class, must a person approve before it runs — so every host wanting a gate writes its own, downstream, against a taxonomy that grows here. An id added here inherits a pass from a list nobody downstream remembers to widen. That direction is the bug.
The rule
EntitlementApproval.tsexportsINFERENCE_ENTITLEMENTS,entitlementsBeyond,taskClassReach,taskClassNeedsApprovalanddescribeTaskClassReach.entitlementCoverssits next door and looks like the natural choice. Anai:autonomous-egressid minted later must gate the tasks declaring it rather than inherit a pass from an ambientai. Fail-closed on a new capability is the point.taskClassby cast, so one without the static is reachable; calling through unguarded crashed inside the approval path, and defaulting to an empty list is worse — indistinguishable from a task that genuinely reaches nothing.may use network:http. This parts company withevaluatePolicydeliberately: that decides whether to allow a run and may ignore reach a task degrades without; this decides what to tell a person before they approve.entitlementsFromChildren, and what it is notNew static on
Task(false) /GraphAsTask(true). It means reach not knowable until run time — the subgraph aGraphAsTaskis handed.It is deliberately not
hasDynamicEntitlements, which reads like the same question. A task is dynamic when it refines what it already declares —AiTasksets it to attach the resolved model id toai:model. 43 tasks in builder's catalog set that flag, every AI task among them; gating on it puts an approval in front of running a model at all.It is also not set on the five run-time composers in this package (
AiChatWithKbTask,ChunkRetrievalTask,ContextBuilderTask,DocumentEnricherTask,HierarchicalChunkerTask). They own fixed, hard-coded children — aTextEmbeddingTask, aCountTokensTask— so their reach is knowable from their own source. Flagging them would print "not declared up front" about a known child set, which is the same misleading prompt in the other direction. They declare the union instead.The case that motivated it
In the first consumer (workglow-dev/builder#461), six tasks in the system catalog are composed —
GraphAsTask,FallbackTask,MapTask,WhileTask,ReduceTask— and each declared nothing statically, so a downstream gate read the empty set as "reaches nothing". AMapTaskskill ran whatever subgraph an LLM handed it without asking.Then the same question turned on this package and found the mirror image:
AiTaskwas the only class inpackages/ai/src/task/overridingentitlements(), and no task anywhere in the repo declaredstorage:*— the taxonomy had it,EntitlementProfilesgranted it, nothing asked for it.taskClassNeedsApproval(KbDeleteTask)returnedfalse, anddescribeTaskClassReachsaid "reaches nothing", for a task whoseexecute()callskb.delete().Ten classes now declare what they touch, pinned by tests.
Correction, kept visible
Two of those declarations were wrong in an earlier commit on this branch and are fixed in
f2b9ebc. Both came from sweeping forkb.<method>(and matching call-shaped text that was not a call:ChunkRetrievalTaskwas givenstorage:writefor "installing a text index on first hybrid search". It does not. It namesinstallTextIndexinside an error message and never calls it; that method assigns a field rather than touching storage; andhybridSearchthrows without an index rather than installing one. It declaresstorage:read.TextRerankerTaskwas givenstorage:read. It reaches no knowledge base at all — its ports arequery,documents,topK,model, and the onlykb.in the file is a comment noting a KB invokes this task. Its override is removed and it is ungated.Over-declaring costs what under-declaring costs: a prompt saying a reranker touches storage is one people learn to click through, and then the
KbDeleteTaskprompt stops nobody either. Every remaining declaration was re-checked against an awaited call.VectorSimilarityTaskalso now extendsTask. It used nothing fromGraphAsTaskand an earlier commit overrode the inherited composed answer to correct it; reparenting makes the wrong answer unrepresentable and drops two more inherited claims (hasDynamicSchemas,hasDynamicEntitlements) equally untrue of it.Downstream
workglow-dev/builder#461 consumes these exports and is red until this merges and a release ships — its catalog pins
=0.4.8. Merge here → release → bump the pin there.Verification
bun scripts/test.ts ai rag task-graph entitlement unit vitest— 176 files, 1,743 passed, 0 failedbun run lint(type-aware,--deny-warnings) andbun run format-check— cleanbun scripts/test.ts --check-sections— new test files reachable by section+kind selectionRun on Node 22.22.2, one below the repo's declared floor of 24; storage-backed sections were left unrun for that reason.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JEFYAGb9D3mWfyhYkmeAvN