Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/e2e-harness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,32 @@ jobs:
compose=(docker compose -f e2e/bub/compose.yaml -f e2e/bub/compose.oceanbase.yaml)
"${compose[@]}" up --detach --wait --wait-timeout 660 oceanbase
container_id=$("${compose[@]}" ps -q oceanbase)
# Full schemas and indexes exceed the slim image's default 1 GB maximum.
docker exec "$container_id" obclient -h127.0.0.1 -P2881 -uroot@sys \
-e "ALTER SYSTEM SET datafile_maxsize='8G'; ALTER SYSTEM SET datafile_next='512M'; ALTER SYSTEM SET datafile_size='4G';"
datafile_ready=false
for config_attempt in $(seq 1 30); do
docker exec "$container_id" obclient -h127.0.0.1 -P2881 -uroot@sys --batch --skip-column-names \
-e "SHOW PARAMETERS WHERE name IN ('datafile_size', 'datafile_maxsize', 'datafile_next');" \
> "$RUNNER_TEMP/acceptance-datafile.tsv"
if awk '
$5 == "datafile_size" && $7 == "4G" { size = 1 }
$5 == "datafile_maxsize" && $7 == "8G" { maximum = 1 }
$5 == "datafile_next" && $7 == "512M" { increment = 1 }
END { exit !(size && maximum && increment) }
' "$RUNNER_TEMP/acceptance-datafile.tsv"; then
datafile_ready=true
break
fi
sleep 2
done
cat "$RUNNER_TEMP/acceptance-datafile.tsv"
if [ "$datafile_ready" != true ]; then
echo 'OceanBase datafile configuration did not become effective within one minute.' >&2
exit 1
fi
docker exec "$container_id" obclient -h127.0.0.1 -P2881 -uroot@test -ppowercontext-e2e \
-Dpowercontext -e 'SET GLOBAL recyclebin=OFF;'
oceanbase_ip=$(docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_id")
test -n "$oceanbase_ip"
export POWERCONTEXT_TEST_OCEANBASE_URL="mysql+aoceanbase://root%40test:powercontext-e2e@${oceanbase_ip}:2881/powercontext?charset=utf8mb4"
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/transport/operations.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"contractSha256": "c4571e4bd59833451e3091ee604e836d185643b0b0d9fa08dceb8eea43060cbf",
"contractSha256": "5e0de479c92d57c2286fe04f3ee2c8e3321798f35412fc237920c105df48ed2d",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 与当前 master 的冲突解决指引(DIRTY 状态)

本 PR 相对 base 666b043a 的桌面契约清单是一致的:head openapi/powercontext.yaml(LF 归一化)sha256 = 5e0de479...,与签入的两处清单一致,已独立复算。master 自 merge-base 以来的变化里与本 PR 相交的只有三处:operations.json/operations.ts 的 SHA 行(#1988 已把 master 侧刷成 c4571e4b)和 desktop/scripts/generate.mjs——后者只是把漂移报错文案改成带修复命令,wanted 操作集没有变化。

因此:

  • openapi/powercontext.yaml 是纯增量(+158),与 master 合并后摘要仍为 5e0de479...;
  • 两处清单的 SHA 冲突应保留本 PR 侧的 5e0de479...(master 侧的 c4571e4b 是旧契约),无需重新生成;
  • api.d.ts 与 _generated/*.py master 侧未动,会自动合并;
  • 合并后建议跑一次 node desktop/scripts/generate.mjs --check 终验。

其余核对结论:topic_memory_recovery.py 的锁序、租约甄别( MySQL repeatable-read 下加锁重读)、retried_at 幂等回放、stale 冲突拒绝均成立;e2e-harness 的 OB datafile 配置带 30 次自校验门控,配错会显式失败,无静默风险。本机 71 个相关测试通过(SQLite 路径)。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢详细核对。已 rebase 到最新 master,保留合并后契约的 5e0de479… 摘要;两处清单一致,node desktop/scripts/generate.mjs --check 已通过。

"operations": {
"get_liveness": {
"method": "GET",
Expand Down
198 changes: 198 additions & 0 deletions desktop/ui/src/generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,66 @@ export interface paths {
patch?: never;
trace?: never;
};
"/v1/topic-memory/processing/status": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Inspect Topic Memory processing and its current terminal block
* @description Return the current Cursor, Source head, retained target and durable blocked-window diagnostics. An active final attempt remains pending because it may still finish. Legacy blocks receive their first observation timestamp; an unavailable historical error code is null. This operation never grants new processing allowance.
*/
post: operations["get_topic_memory_processing_status"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/topic-memory/processing/blocks/get": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Get a retained Topic Memory processing block
* @description Return one exact block, including its original diagnostics and any accepted retry, after recovery.
*/
post: operations["get_topic_memory_processing_block"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/topic-memory/processing/retry": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Explicitly retry one observed Topic Memory block
* @description Require scope.admin and the exact block_id returned by status. Atomically retain the block's failure and usage evidence, grant one fresh bounded allowance and request asynchronous processing without advancing the Cursor or discarding the target or Sources. Repeating a block_id returns its original acceptance and cannot reset a later attempt. An unretried block that no longer owns the frontier returns topic_memory_block_conflict. Ordinary flushes, status reads, restarts and model changes never reset allowance.
*/
post: operations["retry_topic_memory_processing"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/topic-memory/search": {
parameters: {
query?: never;
Expand Down Expand Up @@ -3761,6 +3821,51 @@ export interface components {
*/
hold_codes: string[];
};
TopicMemoryProcessingStatusRequest: {
scope_id: string;
};
GetTopicMemoryProcessingBlockRequest: {
scope_id: string;
block_id: string;
};
RetryTopicMemoryProcessingRequest: {
scope_id: string;
block_id: string;
};
TopicMemoryProcessingBlock: {
block_id: string;
attempt_id: string;
scope_id: string;
binding_name: string;
source_after: number;
source_through: number;
target_source_through: number;
attempts: number;
requests: number;
tokens: number;
error_code: string;
last_error_code: string | null;
/** Format: date-time */
blocked_at: string;
/** Format: date-time */
retried_at: string | null;
retry_request_generation: number | null;
};
/** @enum {string} */
TopicMemoryProcessingState: "idle" | "pending" | "blocked";
/** @enum {string} */
TopicMemoryProcessingRetryStatus: "accepted";
TopicMemoryProcessingStatus: {
state: components["schemas"]["TopicMemoryProcessingState"];
source_cursor: number;
source_head: number;
target_source_through: number | null;
block: components["schemas"]["TopicMemoryProcessingBlock"] | null;
};
RetryTopicMemoryProcessingResponse: {
status: components["schemas"]["TopicMemoryProcessingRetryStatus"];
block: components["schemas"]["TopicMemoryProcessingBlock"];
};
FlushTopicMemoryRequest: {
scope_id: string;
};
Expand Down Expand Up @@ -6500,6 +6605,99 @@ export interface operations {
503: components["responses"]["Unavailable"];
};
};
get_topic_memory_processing_status: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["TopicMemoryProcessingStatusRequest"];
};
};
responses: {
/** @description Current durable processing state. */
200: {
headers: {
"X-PowerContext-Request-ID": components["headers"]["RequestId"];
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["TopicMemoryProcessingStatus"];
};
};
401: components["responses"]["Unauthorized"];
403: components["responses"]["Forbidden"];
422: components["responses"]["InvalidRequest"];
500: components["responses"]["InternalError"];
503: components["responses"]["Unavailable"];
};
};
get_topic_memory_processing_block: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["GetTopicMemoryProcessingBlockRequest"];
};
};
responses: {
/** @description The exact retained block. */
200: {
headers: {
"X-PowerContext-Request-ID": components["headers"]["RequestId"];
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["TopicMemoryProcessingBlock"];
};
};
401: components["responses"]["Unauthorized"];
403: components["responses"]["Forbidden"];
404: components["responses"]["NotFound"];
422: components["responses"]["InvalidRequest"];
500: components["responses"]["InternalError"];
503: components["responses"]["Unavailable"];
};
};
retry_topic_memory_processing: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RetryTopicMemoryProcessingRequest"];
};
};
responses: {
/** @description One retry was durably accepted, including duplicate acceptance. */
200: {
headers: {
"X-PowerContext-Request-ID": components["headers"]["RequestId"];
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["RetryTopicMemoryProcessingResponse"];
};
};
401: components["responses"]["Unauthorized"];
403: components["responses"]["Forbidden"];
404: components["responses"]["NotFound"];
409: components["responses"]["Conflict"];
422: components["responses"]["InvalidRequest"];
500: components["responses"]["InternalError"];
503: components["responses"]["Unavailable"];
};
};
search_topic_memory: {
parameters: {
query?: never;
Expand Down
2 changes: 1 addition & 1 deletion desktop/ui/src/generated/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

// Generated from openapi/powercontext.yaml. Do not edit.
export const contractSha256 = "c4571e4bd59833451e3091ee604e836d185643b0b0d9fa08dceb8eea43060cbf";
export const contractSha256 = "5e0de479c92d57c2286fe04f3ee2c8e3321798f35412fc237920c105df48ed2d";
export const operations = {
"get_liveness": {
"method": "GET",
Expand Down
9 changes: 5 additions & 4 deletions docs/en/docs/operate/troubleshoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,16 +259,17 @@ so the previous database remains available for recovery:
Because `pc_scopes.parent_scope_id` is self-referential, keep ancestor Scope rows before their descendants in the
exported `pc_scopes` data.
If the source predates the three Skill lifecycle tables (`pc_skill_packages`, `pc_agent_skill_targets`, and
`pc_skill_publications`), the Profile tables, `pc_topic_memory_work_budgets`, `pc_recall_effort_daily`,
`pc_receipt_migration_review`, `pc_context_bootstrap_receipts`, or `pc_decision_observations`, remove the absent
tables from their respective layers.
`pc_skill_publications`), the Profile tables, `pc_topic_memory_work_budgets`, `pc_topic_memory_work_attempts`,
`pc_recall_effort_daily`, `pc_receipt_migration_review`, `pc_context_bootstrap_receipts`, or
`pc_decision_observations`, remove the absent tables from their respective layers.
When a work-budget table exists, restore it together with Cursors so failure allowances survive the migration.
Restore work-attempt records as well to preserve block identities, diagnostics and accepted retry receipts.

Layer 1 contains parents and tables without foreign keys:

```bash
obloader <connection-options> -D <new-database> --csv \
--table 'pc_scopes,pc_source_journal_heads,pc_sources,pc_artifacts,pc_source_cursors,pc_memory_source_windows,pc_artifact_processing_leases,pc_artifact_processing_binding_states,pc_artifact_processing_pending,pc_artifact_processing_auto_wave_targets,pc_artifact_processing_sequences,pc_artifact_processing_intents,pc_topic_memory_processing_targets,pc_artifact_processing_schema,pc_artifact_processing_migration_receipts,pc_topic_memory_work_budgets,pc_topic_memory_retrieval_shape,pc_connector_checkpoints,pc_source_definition_manifests,pc_external_skill_registrations,pc_skill_packages,pc_agent_skill_targets,pc_skill_publications,pc_model_usage_daily,pc_recall_token_daily,pc_recall_effort_daily,pc_receipt_migration_review,pc_decision_observations' \
--table 'pc_scopes,pc_source_journal_heads,pc_sources,pc_artifacts,pc_source_cursors,pc_memory_source_windows,pc_artifact_processing_leases,pc_artifact_processing_binding_states,pc_artifact_processing_pending,pc_artifact_processing_auto_wave_targets,pc_artifact_processing_sequences,pc_artifact_processing_intents,pc_topic_memory_processing_targets,pc_artifact_processing_schema,pc_artifact_processing_migration_receipts,pc_topic_memory_work_budgets,pc_topic_memory_work_attempts,pc_topic_memory_retrieval_shape,pc_connector_checkpoints,pc_source_definition_manifests,pc_external_skill_registrations,pc_skill_packages,pc_agent_skill_targets,pc_skill_publications,pc_model_usage_daily,pc_recall_token_daily,pc_recall_effort_daily,pc_receipt_migration_review,pc_decision_observations' \
-f <export-directory>
```

Expand Down
39 changes: 39 additions & 0 deletions docs/en/docs/workflows/topic-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,45 @@ with a Generation model require a persistent, file-backed database rather than a
[Configure models and full memory](../get-started/configure-models.md) and
[Configuration options](../operate/configuration.md) for the complete settings and deployment constraints.

## Inspect and retry blocked processing

After processing exhausts its allowance, the same Scope's later Sources remain pending. The Supervisor stops launching
Workers for that frontier and periodically checks metadata. Other Scopes can continue. A flush or restart does not
grant more allowance.

Inspect the current Cursor, Source head, target and block with `scope.read` permission:

```bash
powercontext --json topic-memory status --scope-id "$SCOPE_ID"
```

The state is `idle`, `pending`, or `blocked`. A block contains its exact `block_id`, attempted range, retained target,
attempt/request/token counters, terminal `error_code`, `last_error_code`, and UTC `blocked_at`. An active third attempt
remains pending because it can still finish. For legacy budgets, `blocked_at` is the first observation time and an
unknown last error is null.
When a legacy budget has no per-attempt audit record, its range remains the stored cumulative frontier coverage;
the final attempt's selected range cannot be reconstructed from that budget.

Correct the cause, then copy the returned block ID into `BLOCK_ID`. A Scope administrator can explicitly grant one
new bounded allowance and request processing:

```bash
powercontext --json topic-memory retry --scope-id "$SCOPE_ID" --block-id "$BLOCK_ID"
powercontext --json topic-memory block --scope-id "$SCOPE_ID" --block-id "$BLOCK_ID"
```

Retry requires `scope.admin`. Its `accepted` response means the request committed; it does not promise generation
success. These reads require `scope.read`, which the `scope.admin` role does not include. Repeating the same ID returns
the original acceptance even after processing is disabled, and cannot reset a later attempt. An unretried block
that no longer owns the current frontier returns HTTP 409. Cursor, target and Sources are retained; the original block
and its retry timestamp remain readable after progress resumes. Normal flushes and restarts retain their existing
budget behavior. Each explicitly accepted retry grants the same three-attempt and provider ceilings.

The equivalent Python Client SDK methods are `get_topic_memory_processing_status`,
`get_topic_memory_processing_block`, and `retry_topic_memory_processing`. Their HTTP routes are
`POST /v1/topic-memory/processing/status`, `POST /v1/topic-memory/processing/blocks/get`, and
`POST /v1/topic-memory/processing/retry`. These administrative operations are not MCP tools.

## Verify a real topic

Use the [Quick Start Topic Memory check](../get-started/quickstart.md#4-verify-topic-memory-with-ordinary-conversation)
Expand Down
Loading
Loading