Metadata-Version: 2.4
Name: keble-positioning
Version: 0.37.0
Summary: Positioning (axes/tags/records) for Keble v2.1 market exploration.
Author-email: zhenhao-ma <bob0103779@gmail.com>
Requires-Python: <3.14,>=3.13
Requires-Dist: griffe<2,>=1.14
Requires-Dist: keble-agentic-chat<4.0.0,>=3.41.0
Requires-Dist: keble-db<2.0.0,>=1.6.2
Requires-Dist: keble-helpers<2.0.0,>=1.41.0
Requires-Dist: keble-segmenting<1,>=0.38.0
Requires-Dist: keble-task<3.0.0,>=2.21.0
Requires-Dist: pydantic-ai-slim<2.0.0,>=1.102.0
Requires-Dist: pydantic<3.0.0,>=2
Provides-Extra: test
Requires-Dist: logfire<5.0.0,>=4.19.0; extra == 'test'
Requires-Dist: openai<3.0.0,>=2.15.0; extra == 'test'
Requires-Dist: pydantic-settings<3.0.0,>=2.12.0; extra == 'test'
Requires-Dist: pytest-asyncio<2.0.0,>=1.3.0; extra == 'test'
Requires-Dist: pytest<9.0.0,>=8.2.0; extra == 'test'
Requires-Dist: socksio<2.0.0,>=1.0.0; extra == 'test'
Description-Content-Type: text/markdown

# keble-positioning

`keble-positioning` is a thin bridge between datasource-backed positioning records and the canonical `keble-segmenting` runtime.

## Package Line

1. Package version: `0.37.0`
2. Python baseline: `>=3.13,<3.14`
3. Environment manager: `uv` only
4. Segmenting dependency line: `keble-segmenting >=0.38.0,<1`
5. Task dependency line: `keble-task >=2.21.0,<3.0.0`
6. Helpers dependency line: `keble-helpers >=1.41.0,<2.0.0`

## Testing

`keble-positioning` follows the root `TESTING_GUIDELINE.md` marker vocabulary.
Default tests must stay offline: unit-style schema/client/tool tests can run
without MongoDB, Redis, Keepa, or real LLM credentials. Tests that need MongoDB
or Redis are marked `integration`, `db`, `mongo`, and `local_stack`; live tests
add `live` plus `keepa_live` and/or `llm` as needed.

Default fast test command:

```bash
uv run pytest -m "not live and not slow and not eval and not local_stack"
```

Run real Mongo/Redis integration tests:

```bash
RUN_INTEGRATION=1 uv run pytest -m "integration and local_stack"
```

Run live Keepa tests:

```bash
RUN_INTEGRATION=1 RUN_KEEPA_LIVE=1 RUN_LLM_LIVE=1 uv run pytest -m keepa_live
```

Run live LLM wrapper tests:

```bash
RUN_INTEGRATION=1 RUN_LLM_LIVE=1 uv run pytest -m "live and llm"
```

Before completing a testing-infrastructure change, run:

```bash
uv run pytest -m "not live and not slow and not eval and not local_stack"
npx --yes pyright .
```

Do not add unguarded real-service tests to the default suite. Keep Mongo/Redis
tests isolated through `tests/conftest.py`, and put new live Keepa/LLM behavior
under explicit markers and environment guards.

## Version 0.37.0 Cross-Repo Test Layers

Positioning now consumes the `keble-segmenting 0.38.0` test-layer release and
registers the shared Keble marker vocabulary. Integration tests are marked
`local_stack` and require `RUN_INTEGRATION=1`; live Keepa tests require
`RUN_KEEPA_LIVE=1`; live LLM tests require `RUN_LLM_LIVE=1`.

Side effect if changes:
- Backend positioning workflows depend on package tests staying split between
  offline client/schema/tool coverage and explicit real-service integration
  coverage.
- Removing the opt-in guards can make cross-repo default CI depend on local
  MongoDB, Redis, Keepa credentials, or private LLM credentials.
- If new testing layers or commands are added, update `pyproject.toml`,
  `tests/pytest.ini`, `tests/conftest.py`, `README.md`, `AGENTS.md`, and
  `CLAUDE.md` together.

## Version 0.35.0 No-Legacy Agentic Contract

Positioning now registers tools and agent descriptors through the canonical
`AgentToolRegistrationConfig`, `AgentDescriptor`, `AgentId`, and
`KebleProviderId` contracts. It does not import or expose the retired
tool-config aliases, chat-tool spec aliases, subagent alias models, or
deferred-tool form models.
The package depends on `keble-segmenting >=0.36.0` and `keble-task >=2.21.0`
so the backend receives one aligned provider/action vocabulary.

Side effect if changes:
- `agent/chat_provider.py` and `agent/archetypes.py` feed backend task-agent
  registration and must stay aligned with `keble-helpers.ai.agent_descriptor`.
- `agent/tools/*.py` map registration config to pydantic-ai tool boundaries; the
  external pydantic-ai `requires_approval` flag is derived from the canonical
  `AgentToolApprovalMode` enum.
- `tests/test_package_contract.py` pins the bundled segmenting wheel source so
  local package tests and backend wheel repins cannot drift.

## Version 0.34.0 First-Class AgentDescriptor Projection

Positioning now exposes `build_positioning_study_agent_descriptor()` and
`PositioningSubAgentProvider.agent_manifest` beside the existing
`AgentRegistration` path. The new descriptor uses `AgentId.POSITIONING_STUDY`,
`AgentRuntime.EXTERNAL_TASK`, and `AgentRunPlacement.BACKGROUND`, while reusing
the same `PositioningStudyInput` / `PositioningStudyOutput` JSON schemas.

`build_positioning_study_descriptor()` returns the canonical descriptor consumed
by the `keble-agentic-chat` `AgentRegistration` runtime contract. This release
raises the helper floor to `keble-helpers >=1.40.0` and uses
`KebleProviderId.for_provider(...)` so provider values and first-class provider
ids cannot drift.

Build packaging explicitly excludes local agent scratch directories
(`.claude/`, `.worktrees/`, `.wt-discovery/`) so private symlinks never enter
release artifacts.

## Version 0.30.0 Single Coverage Mutate-Permission

`client.aget_view_detailed` no longer takes two `can_queue_coverage` /
`can_retry_coverage` (`bool | None`) inputs. It takes one required
`can_mutate_coverage: bool` — "may this viewer trigger/retry coverage work"
(owner-or-not), decided by the caller. The redundant two-bool input (every caller
set them equal, then the bridge re-derived owner-permission) is gone. The returned
`coverage_state` still exposes `can_queue`/`can_retry`, now derived inside
`keble-segmenting` by gating `can_mutate` on `final_status`. Requires
`keble-segmenting >= 0.30.0`. Frontend wire shape UNCHANGED.

## 0.31.0 Agentic `ForAgent` convention re-rebased onto 0.30.0

The `ForAgent` agentic-schema naming + placement refactor (originally `0.23.0`)
re-applied on top of the `0.30.0` main line. The intervening main sweeps
(factory-helpers-onto-owning-types + identity-enums + dead client-API cleanup)
were preserved: the mutation registrar now delegates grid authorization to the
owning type via `ctx.deps.positioning.ensure_grid_authorized(...)` (no local
`_ensure_grid_authorized` helper), and the query registrar keeps main's
`parse_object_id_or_model_retry` / `require_object_or_model_retry` id-gating plus
the AGENT-viewport (`handled_by=PositioningViewHandledBy.AGENT`) read path — all
projected through the `*ForAgent` types in `schemas/for_agent.py`. No new agent
tool payload types appeared on `0.30.0`, so the `for_agent.py` `__all__` and the
`tests/schemas/test_for_agent.py` guard set are unchanged. See the convention
table below.

## 0.27.0 Dead Client-API Cleanup (clean-first)

Removed two confirmed-dead `PositioningClient` symbols (definition-only on
`origin/main`, no production consumer anywhere — backend/keble-core/frontend):

- `_apply_view_update_locally` — private "read-only fallback" view-patch helper,
  zero references.
- `aresolve_positioning_context_by_task` — public bridge resolver with no
  production caller; a thin not-found wrapper over the heavily-used canonical
  `aget_positioning_by_task_id`, which is retained. Its orphaned test and the
  README enrichment note were dropped/repointed.

No behavior change (both were unreachable). Persisted view updates continue to
flow only through `aupdate_view` (CRUD-backed).

## 0.23.0 Agentic schema convention (`ForAgent` + `agent/tools/`)

Every schema lands in exactly one bucket; the bucket dictates its NAME and its
LOCATION. Enforced by `tests/schemas/test_for_agent.py`.

| Bucket | What it is | Name | Lives in |
| --- | --- | --- | --- |
| Agent tool I/O | typed input to / return projection of a pydantic-ai `@agent.tool` | `*ForAgent` suffix | `schemas/for_agent.py` only — never in `agent/` |
| Agent tool config/enum | tool-registration config or approval/mode enum for agent tools | `*Agent…Config` / `*Agent…Policy` infix | `schemas/for_agent.py` |
| Domain "view" | a persisted/configured *saved view* of a grid (the noun) | `*View*` is legitimate | `schemas/view.py` etc. (unchanged) |
| Persisted / CRUD / event | `*Base`/`*Update`/`*MongoObject`/`*Event`/`*Upserted` | unchanged | `schemas/` |

The registrars under `agent/tools/` (`mutation.py`, `query.py`) are BEHAVIOR
ONLY — they import contracts from `schemas/` and define zero `BaseModel`
classes. Do NOT reintroduce `*View`/`*Display` names for agent tool payloads:
in this package `View` is the domain saved-view noun, so an agent projection
named `…View` is ambiguous by construction. The query projections (formerly
`PositioningSummaryView`, `PositioningViewDetailView`, `CellMaskResultView`, …)
are now `*ForAgent`; the configs are `PositioningAgent{Query,Mutation}ToolsConfig`
and `PositioningAgentMutationApprovalPolicy`. Registrars are
`register_mutation_tools` / `register_query_tools` (the bare `register` alias is gone).

## 0.21.0 Single-Source Prompt Context (breaking)

`PositioningTaskMetadata` no longer carries loose top-level `language` /
`marketplace` fields. The typed `prompt_context: SegmentingPromptContext` is now
REQUIRED and is the single source of truth for locale and marketplace across the
whole positioning task tree:

1. `to_segmenting_prompt_context()` returns the canonical context (only the
   nullable `user_prompt` falls back to `positioning_context`).
2. `require_marketplace()` returns the concrete room/memory marketplace, raising a
   typed `ServerSideInvalidParams` if the context carries none.
3. `from_task_metadata_payload(raw)` is the ONE storage-boundary adapter: it heals
   legacy stored docs (loose `language`/`marketplace`, no `prompt_context`) by
   rebuilding the typed context before validation, so already-created/completed
   tasks keep running. Use it at every parse site instead of `model_validate`.

## Version 0.33.0 One Action Per Tool Call (no list-wrapper payload)

The positioning task workspace mutation surface is now **one action per tool
call**. Each concrete action (`CreateMappedViewportAction`,
`CreateReducedChildAction`) inherits the single-action `AgenticActionPayload`
(its own defaulted `message`/`warning_level`); the batch wrapper
`PositioningTaskWorkspaceActions` and its batch result
`PositioningTaskWorkspaceActionedResults` are **removed**. The
`mutate_positioning_task_workspace` tool takes one
`PositioningTaskWorkspaceAction` and returns one
`PositioningTaskWorkspaceActionedResult`; multiplicity comes from the agent
emitting several tool calls (the deferred batch holds them and the frontend
resolves one card per click, in order). Consumes `keble-segmenting 0.34.0`,
where the ordered-atomic grid `Actions` batch is now a plain `BaseModel`
(no `message`/`warning_level`), so the grid-tool `Actions.build(...)` calls drop
those kwargs.

## Version 0.20.0 DEFAULT_ Prefix For Overridable Tool Descriptions

The four agent mutation-tool description constants are renamed to carry a
`DEFAULT_` prefix, marking them as **overridable fallback defaults** (they are
consumed in `register(...)` as `config.description or DEFAULT_*`). This is a
clean breaking rename — no aliases:

- `MUTATE_SEGMENTING_TOOL_DESCRIPTION` → `DEFAULT_MUTATE_SEGMENTING_TOOL_DESCRIPTION`
- `POSITIONING_EDIT_GRID_SCHEMA_TOOL_DESCRIPTION` → `DEFAULT_POSITIONING_EDIT_GRID_SCHEMA_TOOL_DESCRIPTION`
- `POSITIONING_REBUCKET_DIMENSION_TOOL_DESCRIPTION` → `DEFAULT_POSITIONING_REBUCKET_DIMENSION_TOOL_DESCRIPTION`
- `POSITIONING_APPLY_CELL_ASSIGNMENTS_TOOL_DESCRIPTION` → `DEFAULT_POSITIONING_APPLY_CELL_ASSIGNMENTS_TOOL_DESCRIPTION`

Convention (see `CODE_GUIDELINES.md`): a module-level constant gets the
`DEFAULT_` prefix only when it is an overridable fallback default. Fixed prompts
that are passed directly and never overridden keep their plain name.
`tests/agent/test_default_constant_naming.py` guards this.

## Version 0.19.0 Handler-Owned Viewport (`handled_by`)

A `PositioningView` is now keyed by `(view_owner, positioning_id, handled_by)`.
`PositioningViewHandledBy` separates the human `USER` viewport from the AI
`AGENT` viewport, so an agent re-projection (e.g. `CHANGE_VIEW`) writes its OWN
row and never clobbers the human's pinned/auto viewport. This is orthogonal to
`PositioningViewDisplaySource` (`AUTO` vs `USER`), which still describes whether
the dimensions inside one row are system-derived or pinned.

- New field `PositioningViewBase.handled_by` defaults to `USER`; it is part of
  the stored identity and is intentionally absent from `PositioningViewUpdate`
  (never patchable).
- `CRUDPositioningView` exposes `afirst_by_view_identity(view_owner,
  positioning_id, handled_by)`; `aensure_indexes` backfills legacy rows to
  `USER`, creates the unique compound index
  `view_owner_positioning_handled_by_unique_idx`, and drops the redundant
  `(view_owner, positioning_id)` index.
- `afirst_or_create_view` / `aget_view_detailed` take `handled_by` (default
  `USER`). A brand-new `AGENT` row seeds its default display axes from the
  positioning owner's `USER` viewport.

## Version 0.16.0 Positioning Study TASK Archetype

Program Phase 6 (subagent provider canonicalization):

1. `PositioningSubAgentProvider` declares the `positioning_study` task
   archetype through the shared `AgentProviderProtocol` shape. Hosts should
   register this archetype and let agentic-chat generate
   `delegate_positioning_study`; do not add another backend wrapper tool for
   positioning creation.
2. `PositioningStudyInput` is the typed parent-to-child contract:
   `positioning_context`, `marketplace`, optional `name`, and optional
   `search_terms`. Backend validates this model before creating the existing
   `POSITIONING` worker task.
3. The archetype output is a host task result projection, not a second
   positioning workflow. `keble-positioning` continues to own positioning
   schemas and conversion; backend owns queueing, task metadata snapshots, and
   worker dispatch.

## Version 0.15.0 Native Mutation Provider + Manifests

Program Phase 6 (provider canonicalization):

1. `PositioningMutationChatToolProvider` — the native provider for the
   mutation tools (`provider_id="positioning_mutation"`, the id the backend
   wrapper used, so room diagnostics stay stable). Hosts must drop their
   generic wrapper adapters; provider classes are the only cross-repo seam.
2. `register_query_tools` is no longer exported from the package namespace —
   it is module-level in `agent/query_registry.py` for the owning package's
   internal use only.
3. Both providers expose a declarative `manifest`
   (`keble_helpers.ChatToolProviderManifest`): the declared inventory powers
   registration assertions, generated Available-tools prompt blocks, and the
   frontend label-parity gate. Mutation `requires_approval` mirrors the
   runtime approval policy from the SAME source.

## Version 0.13.0 Native Chat Tool Provider

`keble_positioning.agent.PositioningQueryChatToolProvider` ships the
`keble_helpers.ChatToolProviderProtocol` implementation natively, so hosts
(keble.backend today) can drop their generic wrapper adapter around
`register_query_tools`:

1. construction captures `positioning_client` + optional `tools_config`;
2. `provider_id` is the stable diagnostics id `"positioning_query"` (recorded in
   room diagnostics and mapped to a user-readable label in the frontend — do
   not change it);
3. `register(*, agent, context)` ignores `context` and delegates to the
   canonical `register_query_tools(agent, ...)` registrar, so the mounted tools
   are identical to a direct registrar call.

The keble-helpers floor moves to `>=1.15.0` (first release exporting
`ChatToolProviderProtocol`).

## Version 0.11.1 Prompt-Context user_prompt Single-Source

Bootstrap dimension discovery no longer passes `user_prompt=metadata.positioning_context`
separately. `to_segmenting_prompt_context()` already resolves the canonical
`prompt_context.user_prompt` (the user's typed intent, falling back to positioning_context only when
absent); passing it again let segmenting's `with_user_prompt(...)` overwrite that canonical value with
the loose top-level context. Discovery now passes only `prompt_context`, so the typed user intent is
the single source and cannot be clobbered. (Same single-source principle as the 0.11.0 language fix.)

## Version 0.11.0 Language Single-Source And Segmenting 0.11.21 Migration

Two changes, no parallel architecture added:

1. **Language single-source.** Bootstrap dimension discovery previously read the loose
   top-level `PositioningTaskMetadata.language` directly while every other segmenting path
   reads `prompt_context.language`. When the two disagreed (a stale legacy field beside a
   refreshed typed context) discovery ran in the wrong language. `to_segmenting_prompt_context()`
   is now the single derivation every runtime path uses; `prompt_context.language` is canonical
   and the top-level `language` is documented as a legacy fallback only.
2. **Segmenting 0.11.21 migration.** Adopts `keble-segmenting` 0.11.21 (covariant
   `Actions.build(Sequence[Action])`, fixing the `grid_tools.py` variance lint). The 0.11.18+
   "agent cohesion" refactor moved three free functions onto owning classes; positioning now
   calls `SegmentingCoverageBase.build_source_signature`,
   `SegmentingCoverageBase.normalize_dimension_keys`, and `PresetMaskStageGroup.from_stage`
   instead of the removed module-level helpers.

## Version 0.10.20 Live Env Discovery And Pydantic-AI Floor

- Live wrapper tests now load package-local env files first and then fall back
  to the project backend `.env`, so a clean project checkout can rerun the
  real LLM approval-loop proof without copying secrets into `tests/.env`.
- Backend-style Azure provider env fields are converted by `tests.config.Settings`
  into the package text-model input shape, preserving one source of truth for
  live-test configuration.
- The package requires `pydantic-ai-slim >=1.102.0` to match the current chat
  runtime streaming API used by the backend service line.

## Version 0.10.19 Live Grid Wrapper Runtime Parity Follow-Up

- Wrapper-tool live coverage now verifies all three approved grid wrapper
  tools through a real pydantic-ai tool approval loop:
  `positioning_edit_grid_schema`, `positioning_rebucket_dimension`, and
  `positioning_apply_cell_assignments`.
- The package consumes `keble-helpers >=1.12.16` so Python 3.13 clean installs
  keep the existing top-level helper imports working while preserving Aliyun
  OSS compatibility.
- Local uv development keeps exactly one bundled segmenting wheel beside the
  active `keble-segmenting` source path.

## Version 0.10.17 Live Grid Wrapper Runtime Parity

- Positioning grid wrapper tools remain approval-only conversion helpers:
  schema-owned payloads build canonical `keble-segmenting Actions`, then
  delegate to `PositioningClient.aapply_segmenting_actions(...)`.
- Live coverage now verifies a real env-backed pydantic-ai agent requests
  `positioning_edit_grid_schema`, receives approval, and persists the mutation
  through the same canonical segmenting client path.
- Downstream services should consume this line when they need package tags,
  backend bundled wheels, and live grid-wrapper verification to represent the
  same repo state.

## Version 0.10.16 Usage Accounting Bridge

- `PositioningDependencies` can carry a host-owned usage recorder.
- Positioning bootstrap forwards that recorder into segmenting dimension
  discovery, and `aapply_segmenting_actions(...)` forwards it into explicit
  segmenting action batches.
- Positioning remains a bridge package: it does not price or persist usage
  events.

## Version 0.10.14 Domain-Neutral Segmenting Prompt Follow-Up

- This patch consumes `keble-segmenting 0.11.14`, where generic segmenting
  prompt prose also follows the item/source contract instead of product/brand
  wording.
- No positioning bridge schema was added. Commerce facts still convert through
  generic segmenting metadata rows before crossing the package boundary.

## Version 0.10.13 Domain-Neutral Segmenting Contract

- This is a breaking dependency-line update for the segmenting item/source
  contract. Positioning remains the bridge between commerce/domain data and
  generic segmenting rows.
- Do not add commerce-specific fields to segmenting schemas from positioning.
  Provider facts must be converted into generic `CellDisplayMetadataRow` rows
  before they cross into `keble-segmenting`.
- Backend or provider-owned item classes may still know ASIN, brand, marketplace,
  price, rating, reviews, and monthly-sold semantics. Segmenting receives only
  generic row keys, labels, values, item references, and source labels.

## Version 0.10.12 Segmenting Coverage Signature Bridge

- Local uv development now consumes `keble-segmenting 0.11.12`, where coverage
  source signatures include mask semantics and completion events are
  envelope-idempotent.
- Positioning now passes the current grid masks into segmenting coverage
  signature/view-state calls so visible coverage invalidates when mask
  definitions or preset semantics change.
- Publish metadata no longer carries the invalid private classifier string that
  PyPI rejects for public package uploads.

## Version 0.10.11 Review Cleanup

- Local uv development keeps exactly one bundled segmenting wheel: the
  `keble-segmenting` source declared in `pyproject.toml`.
- Package contract tests now reject stale historical `keble_segmenting-*.whl`
  artifacts so future dependency bumps cannot leave multiple segmenting payloads
  in the same repo line.
- Auto-mask metadata remains a bridge contract: positioning validates and
  carries `PositioningTaskMetadata.mask_generation_enabled` / `auto_mask_types`,
  while backend owns the post-bootstrap `aauto_segment_and_mask(...)` call.

## Version 0.10.10 Progressive Bootstrap View Events

- Positioning bootstrap emits the existing `UPSERT_POSITIONING_VIEW` event once
  the positioning shell exists, then emits it again after initial dimensions are
  discovered.
- `PositioningViewBase.display_dimension_ids=[]` is now the valid shell-view
  state before any display dimensions exist. User view updates still require a
  meaningful non-empty display-dimension patch.
- Shell view reads skip segmenting coverage aggregation until display
  dimensions exist, so task-room subscribers can render the room immediately
  without queueing coverage or leaking zero-axis coverage errors.

## Version 0.10.9 Segmenting Mutation Tool Contract

- Positioning mutation tool registration now gives `mutate_segmenting` an
  explicit rebucketing contract by default.
- Price-band split, merge, and rebucketing requests must not relabel old option
  keys with `UPDATE_DIMENSION_OPTIONS`.
- The required flow is to delete retired option keys, create fresh option
  buckets, and finish with `AUTO_SEGMENT_AND_MASK` for the affected dimension so
  products move into the correct cells.

## Version 0.10.8 Bootstrap Prompt Context Propagation

- `PositioningTaskMetadata.to_segmenting_prompt_context()` is now the single
  task-owned conversion path from positioning metadata to segmenting prompt
  context.
- Bootstrap dimension discovery forwards that typed context into
  `SegmentingClient.adiscover_dimensions(...)`, so the user's original prompt,
  language, and marketplace policy reach the initial root-axis discovery step.
- Older queued tasks without `prompt_context` rebuild the same typed context
  from `positioning_context`, `language`, and `marketplace`; no keyword-based
  off-scope guard is introduced in this release.

## Version 0.10.7 Merged Context Metadata

- This release supersedes `0.10.6` after the context branch merged with the
  already-published `0.10.5` item title fragment line.
- `PositionableItem.title_fragments` and `PositioningTaskMetadata.prompt_context`
  are both present in the same package line so backend consumers do not lose the
  item display contract while adopting prompt-context inheritance.

## Version 0.10.6 Prompt Context Metadata

- `PositioningTaskMetadata` carries optional `prompt_context` typed as
  `SegmentingPromptContext` so backend root tasks and reduced child tasks can
  preserve the user prompt, output language, and marketplace policy across
  queued segmenting work.
- The field is optional for older queued tasks; callers should prefer it when
  present and fall back to loose `positioning_context`, `language`, and
  `marketplace` fields only for legacy rows.
- This release builds on `0.10.5` item title fragments rather than replacing
  that display metadata contract.

## Version 0.10.5 Item Title Fragments

- `PositionableItem` now carries provider-owned `title_fragments`, defaulting
  to an empty list for existing payload compatibility.
- The field is serialized into `PositionableItemRecord.item_payload` so hosts
  can build compact relationship-group titles without parsing commerce-specific
  nested payloads.
- Positioning still treats the fragments as display data only; provider item
  classes own the meaning and ordering of those fragments.
- `PositioningTaskMetadata.prompt_context` remains typed through
  `SegmentingPromptContext` so backend child queues can inherit prompt,
  language, and marketplace context without string parsing.

## Version 0.10.4 Lock Refresh

- Local uv development still consumes `keble-segmenting 0.11.4`.
- The package lock now records the current bundled wheel hash so `uv run` and
  `uv sync` can install the bridge without a stale same-version artifact
  mismatch.
- No segmenting contract changed in this patch; the refresh only repairs the
  positioning consumer artifact state before backend consumes `0.10.4`.

## Version 0.10.3 Auto-Mask Default Bridge

- `PositioningTaskMetadata.auto_mask_types` can carry a per-task preset mask
  allowlist for the backend-managed post-bootstrap segmenting run.
- `PositioningClient(default_auto_mask_types=...)` is bridge plumbing only:
  positioning applies the host default to omitted `AUTO_SEGMENT_AND_MASK`
  actions, while explicit `auto_mask_types=[]` still disables preset stages.
- This package does not own the default allowlist. Backend hosts decide whether
  feasibility is part of their default positioning flow.

## Version 0.10.2 Pair Default View Source

- `PositioningView` now stores `display_source` as `AUTO` or `USER`.
- AUTO views can follow the product default as preset dimensions arrive:
  pricing first, features/functionality second, scene/use-case as fallback, then
  grid order.
- USER views preserve explicit manual display choices, including
  pricing + scene, and `aupdate_view(...)` marks display-dimension updates as
  USER unless the caller already supplied a source.
- Positioning coverage reads continue to delegate durable readiness to
  `keble-segmenting 0.11.3` pair-based coverage scopes.

## Version 0.10.1 Update

- `PositioningViewDetailed` now includes package-owned
  `coverage_state`, derived from exact segmenting coverage for the displayed
  dimension ids and current grouped item source.
- Missing exact coverage derives `NOT_LOADED` instead of relying on aggregate
  cells to infer whether a selected dimension combination has ever been
  queued.
- Added typed coverage claim helpers for backend owner view updates. Owners can
  queue/retry unloaded or failed coverage, while non-owner reads stay
  read-only.
- The package now consumes `keble-segmenting 0.11.2` so coverage signatures,
  Mongo rows, and phase state enums come from the segmenting source of truth.

## Version 0.10.0 Update

- The package now consumes `keble-segmenting 0.10.4`, including typed OTHER
  dimension options, segmenting-owned Infos, `cell` mask-result payloads, and
  demand/sales metric summaries.
- Reduced-child bootstrap now shares the source grid's Infos row when selected
  item scopes point to one unambiguous source context. Multi-source children
  still create a fresh Infos row because their reusable market context is not
  singular.
- Positioning view/detail payloads keep delegating cells, option roles, preset
  mask types, and metric summaries to segmenting-owned schemas instead of
  duplicating those contracts.
- Test dependencies include `socksio` so HTTPX can run under developer SOCKS
  proxy environments during live/model-backed package tests.

## Version 0.9.5 Update

- `PositioningTaskMetadata` now uses the general `mask_generation_enabled`
  field that matches `keble-segmenting 0.8.5`.
- Lightweight host-owned preview tasks can set the flag to `False` to generate
  dimensions and cell membership without any generated mask work.
- The old preset-only mask field was removed rather than kept as an alias.

## Version 0.9.4 Update

- `PositioningTaskMetadata` now accepts optional `preset_dimension_types` and
  forwards them to segmenting bootstrap discovery.
- `FreshItemsRequest` forwards persisted `positioning_metadata` to host
  callbacks so backend-owned preview filters can stay typed instead of being
  embedded in prompt text.
- Lightweight AMZ setup previews can request pricing and feature axes while
  keeping full positioning tasks on normal open-ended discovery.

## Version 0.9.3 Update

- `PositioningTaskMetadata` added a preview-facing mask override through the
  existing backend bridge.
- Lightweight host-owned preview tasks can set the flag to `False` to generate
  dimensions and cell membership without generated masks.

## Version 0.9.2 Update

- `PositioningTaskMetadata` now accepts optional `owner_type` so backend task
  envelopes can carry seller-profile scope into segmenting progress and preset
  market reasoning without breaking already queued legacy tasks.

## Version 0.9.1 Update

- The package now consumes `keble-segmenting 0.6.1`, where stale direct-result
  test/API residue from the multi-select rewrite is removed.
- Local uv development resolves the same `keble_segmenting-0.6.1` wheel as the
  current backend line, so package-contract tests exercise the active cleanup
  release.
- Runtime positioning behavior is unchanged from `0.9.0`; this is a dependency
  consumption and repo-hygiene release.

## Version 0.9.0 Update

- The package now consumes `keble-segmenting 0.6.0`, where segmented results
  store `option_keys` and multi-select dimensions expand one item into multiple
  cell placements.
- View-detail reads continue to delegate placement counts to segmenting
  aggregation; positioning does not reimplement cartesian cell expansion.

## Version 0.8.1 Update

- View-detail display-axis resolution now preserves every valid saved view
  exactly, including user-selected pricing + scene/use-case pairs.
- Preset-priority defaults are still used when no saved view exists, preferring
  pricing, then features/functionality, then scene/use-case, then grid order.
- The stale first-N replacement heuristic was removed so valid user choices are
  never mistaken for old auto defaults.

## Version 0.8.0 Update

- Local uv development now resolves `keble-segmenting 0.5.0`, which adds
  durable terminal omitted segmentation rows and aggregate empty-vs-processing
  status semantics for lazy positioning axes.
- Runtime view-detail aggregation still relies on the package-owned
  segmenting aggregate contract; no positioning workaround or copied omitted
  result payload is introduced.

## Version 0.7.6 Update

- Local uv development now resolves `keble-segmenting 0.4.9`, matching the
  backend current-line lazy auto-segment bootstrap fix.
- The package dependency floor is raised to `keble-segmenting >=0.4.9,<1` so
  positioning package-contract tests cannot drift behind the segmenting line
  consumed by backend workers.
- Runtime positioning behavior is unchanged from `0.7.5`; this patch keeps
  dependency metadata, local wheel sources, lockfile, and docs aligned.

## Version 0.7.5 Update

- Local uv development now resolves `keble-segmenting 0.4.8`, matching the
  display-critical auto-segment action contract consumed by backend and
  frontend lazy axis selection.
- Runtime positioning behavior is unchanged from `0.7.4`; this patch keeps the
  package contract tests and bundled segmenting wheel aligned with the current
  line.

## Version 0.7.4 Update

- Positioning view-detail aggregation now explicitly opts into segmenting
  synthetic status cells after reducing the grid detail to display dimensions.
- Full-grid prompt/admin reads stay guarded by the segmenting default and do
  not build cartesian status cells for every grid dimension.
- Local uv development now resolves `keble-segmenting 0.4.7`.

## Version 0.7.3 Update

- Local uv development now resolves `keble-segmenting 0.4.5`, matching the
  backend current-line preset-mask runtime.
- The package dependency floor is raised to `keble-segmenting >=0.4.5,<1` so
  positioning tests exercise the same preset-role and grid-scoped mask contract
  consumed by backend `2.5.4`.
- Historical local `keble_segmenting` wheel artifacts older than `0.4.5` were
  removed after the source, lockfile, and docs moved to the current wheel.

## Version 0.7.2 Update

- Positioning view defaults now treat historical first-N grid-order views as
  replaceable auto defaults.
- When preset roles exist, default display axes resolve pricing first, then
  features/functionality, then scene/use-case, with grid order as the final
  fill.
- Non-default saved user views remain authoritative and keep their saved axis
  order.

## Version 0.7.1 Update

- Package contract tests now derive the current version from `pyproject.toml`
  instead of pinning a stale historical release value.
- `mutate_positioning_task_workspace` keeps name/description configurable but
  always requires approval because it creates durable tasks and relations.

## Version 0.7.0 Update

- Positioning workspace action/event contracts now live in
  `keble_positioning.schemas.workspace_actions` instead of backend execution
  modules.
- `PositioningTaskWorkspaceActionedResult` reuses generic `keble-task`
  `TaskActionCreatedTask` and `TaskActionCreatedRelation` payloads, so created
  child tasks and relation metadata share one canonical DTO.
- `keble_positioning.agent.registry.register(...)` registers
  `mutate_positioning_task_workspace` only when a backend execution handler is
  supplied; the package owns tool schema/approval metadata, while backend owns
  task spawning and SSE publishing.

## Version 0.6.0 Update

- Reduced child positionings now persist typed `item_source_scopes` and read
  source-owned item records on demand instead of copying selected records into
  the child row.
- `PositioningClient.avalidate_item_source_scopes(...)`,
  `alist_scoped_item_records(...)`, `alist_scoped_item_relationships(...)`, and
  `aget_positioning_items_data(...)` are the canonical map-only read surface.
- `SEED_ONLY` writes no child item records or relationships; `SEED_AND_DISCOVER`
  writes only newly discovered fresh child records.
- Mapped viewport rows still use `source_positioning_id`; reduced children use
  `item_source_scopes`, so the two source mechanisms stay separate.

## Version 0.4.14 Update

- Scoped carryover now fails fast when a selected source item key is missing from
  the source positioning, rather than silently falling back to fresh items.
- Missing scope diagnostics report the source positioning
  and stale selected key so backend/chat callers can surface a refresh/retry
  boundary instead of creating a partial reduced child.

## Version 0.4.12 Update

- Removed old positioning runtime-field cleanup and index-upgrade helpers after
  the task-root hard break; current CRUD only creates canonical indexes.
- Keeps the current task-room bridge boundary: `PositioningAgentContext` carries
  `root_task_id` and `allowed_grid_ids`, while segmenting mutations target
  `Actions.grid_id`.

## Version 0.4.11 Update

- Invalid-parameter diagnostics now pass typed primitive strings into
  `keble-exceptions` instead of dict payloads.
- Mapped-row mutation rejection and relationship endpoint validation keep the
  same user-facing context while satisfying Pylance/Pyright argument typing.

## Version 0.4.10 Update

- `PositioningAgentContext` is now task-room scoped: `owner`, `language`,
  `root_task_id`, and `allowed_grid_ids`.
- `mutate_segmenting` validates `Actions.grid_id` against `allowed_grid_ids`
  and delegates the whole canonical `Actions` payload.
- `PositioningClient.aapply_segmenting_actions(...)` resolves the source
  positioning from `payload.grid_id` and owner; callers no longer pass
  `positioning_id`, `grid_id`, and `actions` separately.
- Backend enrichment can use `aget_positioning_by_task_id(...)` and
  `aget_source_positioning_by_grid_id(...)`.
- `aget_grid_for_agent(...)` now requires `compare_dimension_key` and builds
  `GridAgentContextRequest`.

## Version 0.4.9 Update

- `aapply_segmenting_actions(...)` now requires explicit `grid_id` and forwards
  one canonical `Actions(grid_id=...)` payload into `keble-segmenting`.
- Positioning no longer builds or passes `SegmentingUxContext`; workspace
  selection stays outside the bridge and segmenting execution targets only the
  explicit grid/action payload.
- `aapply_segmenting_actions(...)` now inherits nested
  `SegmentingAgentDeps.segmenting.event_callbacks` when callers do not provide
  explicit callbacks.
- Backend composite deps can pass through positioning into segmenting without
  dropping room SSE callbacks.
- Grouped followers remain metadata-only; positioning still does not copy
  source/group segmenting rows to follower item keys.
- `PositionableItemLoaded.to_cell_display(...)` and
  `PositionableItemsByRel.to_cell_display(...)` now delegate display projection
  to the provider item class. Positioning groups records and preserves item
  identity, but it does not parse commerce payloads into UI summaries.
- `CellDisplay` is display-only (`description` / `images`); complete selected
  item identity comes from segmenting aggregate `item_keys`.

## Design Direction

The current rewrite is bridge-first:

1. `keble-segmenting` owns action execution, progress, and grid mutation.
2. `keble-positioning` owns only bridge-specific persistence and conversion:
   - positioning objects and views,
   - positionable item records,
   - positionable relationship records,
   - fresh-item callback flows,
   - grouped representative selection with metadata-only followers.
3. This package no longer exposes positioning-owned agentic payloads, local runtime status models, or task/background orchestration helpers.

Queued auto-segment fanout stays backend-owned. Positioning forwards optional
backend scheduler/context into `keble-segmenting`; grouped followers remain
metadata-only and are not copied into segmenting result rows after workers finish.
Room/SSE transports should stream direct package `AgenticActionEvent` JSON.
Positioning should not create backend-specific room event wrappers. View events
use uppercase package-owned `action_type` values on the shared helper event
shape, and event `source` is the shared `AgenticActionEventSource.KEBLE_POSITIONING`
enum (not a free string). Positioning emits through the shared
`AgenticEventEmitter` held on the unified `AgentDbDeps.event_emitter`; it no
longer hand-rolls its own callback loop. The field accepts a callback sequence,
`None`, or a ready emitter (coerced via `AgenticEventEmitter.build`). Since
`0.18.0`, every emit site coerces through `PositioningClient._ensure_event_emitter`
before calling `.aemit`, so emission is robust even when a host builds the deps
with `model_construct(...)` (which bypasses the field validator and would otherwise
leave a raw callback `list` that crashed `.aemit`). Public client methods drain
the emitter at the runtime boundary before returning.
Positioning now emits direct `UPSERT_ITEM_RECORDS` and
`UPSERT_ITEM_RELATIONSHIPS` events for fresh-item CRUD batches, and forwards
request-scoped callbacks into segmenting action execution so segmenting
mutation events can reach the same task-root SSE room.
In `0.4.8`, positioning also returns and emits `PositioningViewDetailed` with
the full grid `dimensions` / `masks` plus current-view `aggregated_cells`, so
production rooms can switch axes locally while reading only the active view
matrix. Room stores can patch the visible node without a backend wrapper.
Downstream TypeScript consumers should use `keble-core 0.1.33+`
`TaskWorkspaceEvent.build(...)` for these direct package payloads instead of
frontend-local unknown payload normalization.

## Public Package Surface

The package root exports the slim bridge contract:

```python
from keble_positioning import (
    PositionableItemLoaded,
    PositionableItemsByRel,
    PositioningAgentContext,
    PositioningAgentDeps,
    PositioningBase,
    PositioningBootstrapResult,
    PositioningClient,
    PositioningMutationToolsConfig,
    PositioningMongoObject,
    PositioningTaskMetadata,
    PositioningUpdate,
    PositioningViewBase,
    PositioningViewDetailed,
    PositioningViewMongoObject,
    PositioningViewUpdate,
    register_mutation_tools,
)
```

The main client surface is now:

1. persisted positioning CRUD
2. persisted view CRUD
3. slim upstream bootstrap via `PositioningTaskMetadata`, `PositioningBootstrapResult`, and `abootstrap_positioning(...)`
4. thin upstream task wrapper via `atask_handler(...)`
5. fresh-item loading / next-page loading
6. viewport-map creation via `acreate_mapped_positioning(...)`
7. canonical `aapply_segmenting_actions(...)`
8. explicit `aget_segmenting_progress(...)`
9. explicit `ainterrupt_segmenting(...)`
10. canonical `aget_view_detailed(...)`

The package still does not own background orchestration or runtime status recovery.
The new bootstrap surface only covers the initial grid + positioning shell, the first
fresh-item load, and the first dimension-discovery pass.
Bootstrap is now retry-safe for one task id: if a previous attempt already created the
positioning shell, `abootstrap_positioning(...)` reloads that persisted positioning/grid
pair instead of creating a second shell.
Current bootstrap and segmenting bridge calls emit typed events through
`AgentDbDeps.event_emitter` and forward request-scoped callbacks into
`keble-segmenting`, so upstream runtime messages flow through the shared event
stream. The legacy task-tree `progress_task` path is retired for current
handlers.

## Bootstrap Storage Contract

The persisted bridge contract is intentionally short:

1. one upstream `task_id` owns one positioning shell
2. one positioning shell points at one segmenting `grid_id`
3. bootstrap retries reuse that existing shell instead of creating duplicates
4. resolved `search_terms` are persisted on task metadata and callback pagination state, while positioning rows do not persist `search_terms`
5. mapped positioning rows use their own `task_id` and view, share the source `grid_id`, and point at source-owned items through `source_positioning_id`
6. mapped positioning rows are viewport-only for mutations; `aapply_segmenting_actions(...)` rejects them so source dimensions/results cannot diverge by accident

Mapped positioning rows are viewport-only in this line. They can read source
items/relationships and shared segmenting results, but fresh item loading and
next-page pagination must run against the source positioning row.

```python
mapped_positioning = await positioning_client.acreate_mapped_positioning(
    amongo,
    owner=owner,
    task_id=mapped_task.id,
    source_positioning_id=source_positioning.id,
    view_owner=owner,
    display_dimension_ids=[dimension_id],
)

data = await positioning_client.aget_positioning_data(
    amongo,
    positioning_id=mapped_positioning.id,
    positioning_owner=owner,
)
```

## Upstream Task Wrapper

Use the upstream task metadata plus the thin task wrapper when the parent backend
already owns task scheduling and completion:

```python
from keble_positioning import PositioningTaskMetadata

task_handler_metadata = PositioningTaskMetadata(
    positioning_context=payload.positioning_context,
    search_terms=payload.search_terms,
    language=language,
    marketplace=payload.marketplace,
    entity_type=payload.entity_type,
    name=payload.name,
    positioning_metadata={},
)

await positioning_client.atask_handler(request=request)
```

`request.metadata` should serialize the `PositioningTaskMetadata` payload. The
positioning package still does not own background orchestration, runtime status
recovery, or backend-specific Redis namespace setup.
When the parent backend resolves prompt-to-model search keywords, it should pass
those typed `search_terms` here so bootstrap can start with the intended
retrieval list. Later rounds may supply a different list, and next-page
continuity is carried by callback metadata rather than by the positioning row.

## Canonical Write Bridge

Use the direct segmenting bridge instead of any positioning-owned action wrapper:

```python
from keble_helpers import Language
from keble_helpers import AgenticActionWarningLevel
from keble_segmenting.schemas import Action, ActionedResults, Actions


async def apply_actions(
    *,
    positioning_client: PositioningClient,
    owner: str,
    db_deps,
    grid_id,
    actions: list[Action],
) -> ActionedResults:
    return await positioning_client.aapply_segmenting_actions(
        owner=owner,
        db_deps=db_deps,
        language=Language.ENGLISH,
        payload=Actions.build(
            message="Apply positioning segmenting actions.",
            warning_level=AgenticActionWarningLevel.MUTATION,
            grid_id=grid_id,
            actions=actions,
        ),
    )
```

`PositioningClient` prepares the grouped representative loader and delegates the canonical `Actions` payload to `keble-segmenting`. Grouped follower keys are kept in group metadata only; missing follower result rows are expected and should not be repaired.

## Agent Tool Registration

Positioning owns the pydantic-ai registrar for positioning-scoped segmenting
mutations. Backend should register this package tool instead of defining a
manual duplicate in backend chat code.

```python
from keble_positioning.agent import PositioningAgentDeps, register_mutation_tools

agent = Agent[PositioningAgentDeps, Any](...)

register_mutation_tools(
    agent,
    positioning_client=positioning_client,
    enabled=True,
)
```

Deps shape:
- `PositioningAgentDeps` inherits `keble_db.AgentDbDeps`; Mongo/Redis are not separate tool args.
- Positioning runtime state is under `ctx.deps.positioning`.
- The tool delegates to `PositioningClient.aapply_segmenting_actions(...)`, so source-positioning mutation guards and grouped-loader behavior stay package-owned.
- Backend should omit mapped/view-only grids from `allowed_grid_ids`; they must
  not mutate shared source dimensions/results.

## Canonical Read Surface

`PositioningViewDetailed` is now a pure read model:

1. `view`
2. `positioning`
3. `grid`
4. `dimensions`
5. `aggregated_cells`

Live segmenting progress is not embedded in the room payload anymore. Call `aget_segmenting_progress(...)` explicitly when the caller needs exact-key runtime progress.

## Parent Repo Guidance

`[tool.uv.sources]` paths in this repo are parent-relative to this package directory. They are not inherited from a parent application repo:

```toml
[tool.uv.sources]
keble-segmenting = { path = "deps/keble_segmenting-0.6.1-py3-none-any.whl" }
keble-task = { path = "deps/keble_task-2.4.11-py3-none-any.whl" }
```

## Validation Commands

Use `uv` commands only:

1. `uv run python -c "import keble_segmenting"`
2. `uv run python -c "import keble_positioning"`
3. `uv run pytest tests -q`
4. `uv run pytest -m "not live" tests -q`
5. `uv run pytest -m live tests -q`
6. `uv run python tests/live/refresh_keepa_green_alien_plush_cache.py`
7. `uv lock`
8. `uv sync`

The default `pytest` run is intentionally strict and comprehensive:

1. it includes the live Keepa plus env-backed Azure/OpenAI tests under `tests/live`
2. it fails fast when `tests/.env`, Mongo, Redis, Keepa, or text-model config is missing
3. it keeps the refresh script as the only write path for tracked live cache files under `tests/asset/`
## 0.5.0 Preset Dimension Defaults And Child Discovery

`keble-positioning 0.5.0` consumes `keble-segmenting 0.2.0` and the new preset
dimension contract. View-detail defaults now prefer pricing as the row axis,
features/functionality as the column axis, scene/use-case as the next column
fallback, and then the existing grid order.

Reduced child positioning tasks default to `SEED_ONLY`; fresh product discovery
is an explicit `SEED_AND_DISCOVER` mode. This preserves copied selected item
records and in-scope relationships for child grids while preventing accidental
fresh expansion unless the caller requests it.

## 0.6.1 Chained Map-Only Source Scopes

`keble-positioning 0.6.1` resolves nested reduced-child item scopes through the
original source positionings before reads or bootstrap persistence. A child made
from a map-only child therefore reads the root/source item records selected by
the parent scope, not the intermediate child positioning that intentionally owns
no copied item records.

Use `PositioningClient.aresolve_item_source_scopes(...)` before persisting
reduced-child metadata, and use `aget_positioning_items_data(...)` for explicit
item payload reads. The seed-copy workflow remains removed.

## 0.6.2 Mapped Viewport Scope Alias

`keble-positioning 0.6.2` keeps map-only reduced children package-owned by
teaching `aresolve_item_source_scopes(...)` to treat mapped viewport rows as
aliases to their canonical `source_positioning_id`. Reducing from a mapped
viewport now reads the source-owned item records instead of rejecting the mapped
row as a non-source positioning.

## 0.8.0 Terminal Segmenting Consumption

`keble-positioning 0.8.0` raises its `keble-segmenting` floor to `>=0.5.0,<1`
and consumes the terminal omission aggregate contract. View-detail reads should
continue to call `aget_view_detailed(...)` with display axes narrowed before
synthetic status cells are requested; omitted segmenting rows are handled by the
segmenting read model and require no positioning-side workaround.

## 0.9.0 Multi-Select Segmenting Consumption

`keble-positioning 0.9.0` raises its `keble-segmenting` floor to
`>=0.6.0,<1`. The positioning view/detail API preserves the aggregate placement
counts returned by segmenting so multi-select axes can display one source item in
every selected option-cell combination without copying result rows.

## 0.9.1 Segmenting Cleanup Consumption

`keble-positioning 0.9.1` raises its local wheel source and dependency floor to
`keble-segmenting >=0.6.1,<1`. This keeps current package tests and downstream
backend installs on the release where stale singular-result helper residue was
removed.

## 0.9.2 Owner-Type Metadata Mirror

`keble-positioning 0.9.2` adds optional `owner_type` to
`PositioningTaskMetadata`. Backend queue envelopes can now carry the same owner
classification used by seller-profile context without adding a separate
positioning-side profile schema. The field is optional so already queued or
historical task metadata remains loadable.

## 0.10.15 Agent Grid Wrapper Tools

`keble-positioning 0.10.15` keeps `mutate_segmenting` and
`mutate_positioning_task_workspace` as the canonical mutation paths while
adding approved agent-facing wrappers for common grid editing intent:

1. `positioning_edit_grid_schema` accepts dimension, option, mask, and ordering
   schema edits, then converts directly to canonical keble-segmenting `Actions`.
2. `positioning_rebucket_dimension` deletes retired option keys, creates fresh
   buckets, and finishes with terminal `AUTO_SEGMENT_AND_MASK` for the affected
   dimension.
3. `positioning_apply_cell_assignments` converts approved explicit result or
   mask-result assignments into canonical upsert actions.

All wrappers require approval, validate the authorized grid id, and delegate to
`PositioningClient.aapply_segmenting_actions(...)`. Do not add wrapper-specific
execution semantics outside the conversion schemas.

## 0.10.18 Usage Recorder Forwarding

Positioning task resources and segmenting action bridges forward the optional
host usage recorder into downstream segmenting calls. Positioning does not price
or persist task costs directly; backend task handlers attach the recorder with
the task/root/owner context.
