Skip to content
Merged
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
2 changes: 2 additions & 0 deletions desktop/scripts/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ for (const item of Object.values(contract.paths))
if (wanted.includes(op?.operationId)) collectSchemas(op);
function rustType(schema) {
if (schema.$ref) return schema.$ref.split("/").at(-1);
// A single-member allOf is how the contract annotates a nullable or described reference.
if (schema.allOf?.length === 1) return rustType(schema.allOf[0]);
switch (schema.type) {
case "string":
return "String";
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": "e43c7034b6930688c350c68af5e7bd6083b123783b92d0a50eebbebd45aa7da7",
"contractSha256": "dae5feab227a6c16a8199455a27c82b444c9c17b43bec7de0fca4790c7bed14f",
"operations": {
"get_liveness": {
"method": "GET",
Expand Down
12 changes: 12 additions & 0 deletions desktop/src-tauri/src/transport/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,18 @@ pub struct ArtifactRevision {
pub r#sources: Vec<SourceTypeReference>,
pub r#artifacts: Vec<ArtifactReference>,
pub r#content_digest: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub r#created_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub r#created_by: Option<AccessPrincipal>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub r#restored_from_revision: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub r#reason: Option<String>,
}

#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)]
Expand Down
34 changes: 32 additions & 2 deletions desktop/ui/src/generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1915,7 +1915,7 @@ export interface paths {
get: operations["get_artifact"];
/**
* Replace the current Artifact head
* @description Commit a complete next revision when If-Match identifies the current head. Replacing a Prompt requires current scope.admin authority, including switching to Auto and restoring an earlier revision. Artifact ownership does not authorize Prompt replacement after Scope role revocation. Topic Memory replacement also requires scope.admin and preserves independently versioned tags. Complete title, summary, and detail text replaces the head without semantic generation.
* @description Commit a complete next revision when If-Match identifies the current head. Replacing a Prompt requires current scope.admin authority, including switching to Auto and restoring an earlier revision. Artifact ownership does not authorize Prompt replacement after Scope role revocation. Topic Memory replacement also requires scope.admin and preserves independently versioned tags. Complete title, summary, and detail text replaces the head without semantic generation. Profile replacement uses the same conditional write. To roll back Profile, Prompt, Experience, Skill, Handoff, or Topic Memory, set restored_from_revision and reason. The server checks that the submitted canonical content matches that revision and differs from the current head, then commits a new revision. Atomic Memory replacement rejects both fields; restore Atomic Memory through its restoration flow.
*/
put: operations["replace_artifact"];
post?: never;
Expand Down Expand Up @@ -2493,6 +2493,10 @@ export interface components {
};
ReplaceProfileArtifactRequest: {
content: components["schemas"]["ProfileWriteContent"];
/** @description Historical Profile revision restored by this replacement. Must match content.restored_from_revision when both are set. */
restored_from_revision?: number | null;
/** @description Required when restored_from_revision or content.restored_from_revision is set. */
reason?: string | null;
};
ActivateHandoffRequest: {
scope_id: string;
Expand Down Expand Up @@ -2524,6 +2528,11 @@ export interface components {
published_at?: string | null;
/** @description Family-provided number of direct Source inputs when available. */
source_count?: number | null;
/** Format: date-time */
created_at?: string | null;
created_by?: components["schemas"]["AccessPrincipal"] | null;
restored_from_revision?: number | null;
reason?: string | null;
};
ArtifactCreated: {
scope_id: string;
Expand Down Expand Up @@ -2552,6 +2561,17 @@ export interface components {
sources: components["schemas"]["SourceTypeReference"][];
artifacts: components["schemas"]["ArtifactReference"][];
content_digest: string;
/**
* Format: date-time
* @description When this revision was committed. Absent for revisions stored before this field existed.
*/
created_at?: string | null;
/** @description Principal that committed a rollback. Ordinary revisions omit it. */
created_by?: components["schemas"]["AccessPrincipal"] | null;
/** @description Historical revision restored by this rollback. */
restored_from_revision?: number | null;
/** @description Reason recorded for a rollback. */
reason?: string | null;
};
ArtifactReference: {
family: string;
Expand Down Expand Up @@ -4442,6 +4462,8 @@ export interface components {
};
ReplaceTopicMemoryArtifactRequest: {
content: components["schemas"]["TopicMemoryWriteContent"];
restored_from_revision?: number | null;
reason?: string | null;
};
CreatePromptArtifactRequest: {
/**
Expand Down Expand Up @@ -4583,6 +4605,8 @@ export interface components {
ReplaceArtifactRequest: components["schemas"]["ReplaceAtomicMemoryArtifactRequest"] | components["schemas"]["ReplaceTopicMemoryArtifactRequest"] | components["schemas"]["ReplaceMemoryArtifactRequest"] | components["schemas"]["ReplaceExperienceArtifactRequest"] | components["schemas"]["ReplaceSkillArtifactRequest"] | components["schemas"]["ReplaceHandoffArtifactRequest"] | components["schemas"]["ReplacePromptArtifactRequest"] | components["schemas"]["ReplaceProfileArtifactRequest"];
ReplacePromptArtifactRequest: {
content: components["schemas"]["PromptContent"];
restored_from_revision?: number | null;
reason?: string | null;
};
ListArtifactRevisionsRequest: {
/** @default 50 */
Expand Down Expand Up @@ -4683,12 +4707,18 @@ export interface components {
};
ReplaceExperienceArtifactRequest: {
content: components["schemas"]["ExperienceProposal"];
restored_from_revision?: number | null;
reason?: string | null;
};
ReplaceSkillArtifactRequest: {
content: components["schemas"]["SkillProposal"];
restored_from_revision?: number | null;
reason?: string | null;
};
ReplaceHandoffArtifactRequest: {
content: components["schemas"]["HandoffContent"];
restored_from_revision?: number | null;
reason?: string | null;
};
SourceRecord: {
/** @description Server-owned identity attestation when this Source contains an enforced-mode Handoff Receipt. */
Expand Down Expand Up @@ -8485,7 +8515,7 @@ export interface operations {
};
path: {
scope_id: string;
family: "memory" | "atomic-memory" | "experience" | "skill" | "handoff" | "prompt" | "topic-memory";
family: "memory" | "atomic-memory" | "experience" | "skill" | "handoff" | "prompt" | "topic-memory" | "profile";
artifact_id: string;
};
cookie?: never;
Expand Down
2 changes: 1 addition & 1 deletion desktop/ui/src/generated/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export type ArtifactDreamingOperationCapability = { operation: DreamOperation, o
export type ArtifactFamilyAccessCapability = { family: string, enabled: boolean, share_unit: string, actions: Array<AccessAction>, grantable_roles: Array<AccessRole>, };
export type ArtifactReadFamily = "memory" | "atomic-memory" | "experience" | "skill" | "handoff" | "profile" | "prompt" | "topic-memory";
export type ArtifactReference = { family: string, artifact_id: string, revision: number, };
export type ArtifactRevision = { scope_id: string, family: ArtifactReadFamily, artifact_id: string, revision: number, content: Record<string, unknown>, sources: Array<SourceTypeReference>, artifacts: Array<ArtifactReference>, content_digest: string, };
export type ArtifactRevision = { scope_id: string, family: ArtifactReadFamily, artifact_id: string, revision: number, content: Record<string, unknown>, sources: Array<SourceTypeReference>, artifacts: Array<ArtifactReference>, content_digest: string, created_at?: string | null, created_by?: AccessPrincipal | null, restored_from_revision?: number | null, reason?: string | null, };
export type AtomicMemoryRecord = { artifact: ArtifactReference, kind: string, text: string, state: AtomicMemoryState, state_version: number, merged_into_id: string | null, };
export type AtomicMemorySearchHit = { memory: AtomicMemoryRecord, score: number, matched_by: Array<string>, };
export type AtomicMemorySearchMode = "text" | "vector" | "hybrid";
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 = "e43c7034b6930688c350c68af5e7bd6083b123783b92d0a50eebbebd45aa7da7";
export const contractSha256 = "dae5feab227a6c16a8199455a27c82b444c9c17b43bec7de0fca4790c7bed14f";
export const operations = {
"get_liveness": {
"method": "GET",
Expand Down
2 changes: 1 addition & 1 deletion docs/en/development/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The Dashboard is a content viewer for personal use and demonstrations, disabled

A user may open the Dashboard to check an agreement, find an experience relevant to a similar problem, or resume interrupted work. The page needs to make the current scope clear, help them find the relevant record, and provide access to source material when they need to check it.

The core experience for 1.0 is reading saved content. Memories, experiences, skills and handoffs can exist independently. A scope containing only memories should work normally, and an empty scope should still have a clear entry point and page structure. Existing tools and APIs handle generation, review and saving. Reading their results should not require generation configuration.
The core experience for 1.0 is reading saved content. Existing pages also list revision history, compare two revisions, and roll an exact stored revision forward through the same conditional replace the API already uses. Each Atomic Memory shows its own revisions and comparison read-only. Restoring an Atomic Memory belongs to its restoration flow, which previews every affected memory before confirmation. Memories, experiences, skills and handoffs can exist independently. A scope containing only memories should work normally, and an empty scope should still have a clear entry point and page structure. Existing tools and APIs handle generation, review and saving. Reading their results should not require generation configuration.

Scope names, summaries and saved content supply the business topic. A payment service, customer interviews and personal research can use the same interface without separate navigation for each domain. Before adding a section, explain which task it helps the user complete and whether the available data supports what it claims.

Expand Down
12 changes: 8 additions & 4 deletions docs/en/rfcs/1468_scope_owned_prompt_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,15 @@ Rollback is deliberately not a history rewrite and does not require a third endp

1. read the old exact revision;
2. read the current head and ETag;
3. replace the current head with the old revision's `content` and the current ETag; and
3. replace the current head with the old revision's `content`, the current ETag, `restored_from_revision`, and a
non-blank `reason`; and
4. receive a new revision whose content digest matches the restored revision.

For example, restoring revision 2 while revision 5 is current creates revision 6. Revisions 2 through 5 remain
readable. Generic request audit identifies the actor and request; equality with the restored content is visible through
the content digest. The Server never moves the head pointer backward.
readable. The new revision stores the source revision, the reason, and the authenticated principal. Every new revision
stores `created_at`. Equality with the restored content is visible through the content digest. The Server never moves
the head pointer backward. Omitting both rollback fields remains an ordinary replace. Naming a source without a valid
reason, naming the current head, or submitting content that does not match that source returns 422.

# Reference-level explanation

Expand Down Expand Up @@ -815,7 +818,8 @@ as every update.
- **Draft, review, and activation workflows:** duplicate Candidate/Review concepts and are not needed for the first
administrative vertical slice.
- **A dedicated rollback endpoint:** adds an action whose safe semantics are already expressed by exact read plus
conditional replacement.
conditional replacement. The replace request may name the source revision and a reason; it does not add a route or
a permission.

Doing nothing leaves operational prompts fixed at deployment composition and forces customers to fork Runtime code or
run separate deployments for different prompt behavior.
Expand Down
15 changes: 9 additions & 6 deletions docs/en/rfcs/1485_profile_artifact.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,17 +428,20 @@ ordinal=0, ETag/If-Match, and lineage rules. Create uses artifact_id=profile and
Automatic commits use the same Family validation and repository but cite actual evidence rather than inventing a
manual system Source.

Rollback needs no new API: GET a historical Revision, GET the current Head ETag, then PUT the complete Markdown with
optional `restored_from_revision` in ProfileWriteContent. The server verifies that the historical Revision exists
and its normalized Markdown matches, then creates a new rollback Revision. It neither moves Head back to an older
Revision nor rewinds the Cursor. Omitting the field is an ordinary manual Replace; Create forbids it. Clients compare
two Revisions themselves, with no new diff or history-list endpoint.
Rollback needs no new API: GET a historical Revision, GET the current Head ETag, then PUT the complete Markdown.
`restored_from_revision` may appear beside `content` and inside ProfileWriteContent; when both are set they are the
same integer. A source revision requires a non-blank `reason` of at most 1024 characters. The server verifies that the
historical Revision exists and its normalized Markdown matches, then creates a new rollback Revision that records the
source, the reason, and the authenticated principal. It neither moves Head back to an older Revision nor rewinds the
Cursor. Omitting both source fields is an ordinary manual Replace; Create forbids the source field. Clients compare
two Revisions themselves. The Dashboard renders that comparison; there is no separate diff endpoint.

## Persistence inventory and example rows

BLOB examples below show decoded JSON. Foreign keys and lineage use complete identities. Timestamps and digests
are readable examples; omitted default columns are still written according to existing definitions. Existing tables
have no DDL changes; only Profile BLOB schemas and code branches are extended.
keep their existing constraints. Artifact rows gain nullable `created_at`, `created_by`, `restored_from_revision`, and
`rollback_reason`. Profile BLOB schemas and code branches are extended as specified below.

```mermaid
erDiagram
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ New Handoff writes use a Scope singleton, while detail links can select historic
- Distinguish absent content, read errors, historical content, and failed downloads.
- Preserve collection return context and recover exact reading destinations after authentication.
- Preserve existing Prompt and Topic Memory routes, layouts, search, and pagination without duplicate entry points.
- Exclude profile editing, rollback, regeneration, review, and processing policy management.
- Exclude profile editing, regeneration, review, and processing policy management. History, comparison, and rollback of one exact stored revision stay on the existing page and reuse conditional replace.
- Exclude an all-artifacts browser, cross-Scope aggregation, batch downloads, PDF, public sharing, imports, and a new
machine-readable Agent handoff contract.

Expand Down Expand Up @@ -320,8 +320,8 @@ public selection and digest contract would broaden the feature, so use a Dashboa

Allowlisted relative destinations survive refreshes and copied links; browser history and Referer cannot reliably
distinguish normal navigation, direct entry, and authentication. Returning to exact detail after login preserves an
explicit download action. Profile editing, generation, and rollback require conditional writes, review, and conflict
recovery and remain available through existing tools and APIs.
explicit download action. Profile editing, generation, and review remain available through existing tools and APIs.
Rolling back one stored revision reuses conditional replace on the page that already shows that revision.

# Prior art

Expand All @@ -340,6 +340,6 @@ aggregation, Profile editing, and batch reports require separate designs.

# Future possibilities

Future work can add conditional Profile editing, historical comparisons, and explicitly named latest-Scope report
Future work can add conditional Profile editing and explicitly named latest-Scope report
downloads. Improvements to existing Prompt and Topic Memory pages should address demonstrated gaps separately while
retaining Scope permissions and exact references.
2 changes: 1 addition & 1 deletion docs/zh/development/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Dashboard 是 PowerContext 中供个人使用和演示的内容查看器,默

用户进入 Dashboard 时,可能想确认某项约定,查找解决过类似问题的经验,或继续一项中断的工作。页面需要让用户确定正在查看哪个范围,找到相关记录,并在需要时核对原始材料。

1.0 的核心是阅读已有内容。记忆、经验、技能和交接可以分别存在:只有记忆的范围也应当能够正常使用;没有内容的范围仍有明确的入口和页面结构。生成、审核和保存由现有工具与接口完成,阅读这些结果不应要求用户先配置生成能力。
1.0 的核心是阅读已有内容。现有页面同时列出修订历史、比较两个修订,并通过接口已有的条件替换把某一份已保存修订写成新的当前版本。每条原子记忆可以查看和比较自己的修订,但只读。恢复原子记忆走它自己的恢复流程,确认前会预览所有受影响的记忆。记忆、经验、技能和交接可以分别存在:只有记忆的范围也应当能够正常使用;没有内容的范围仍有明确的入口和页面结构。生成、审核和保存由现有工具与接口完成,阅读这些结果不应要求用户先配置生成能力。

页面的业务主题来自范围名称、摘要和保存的内容。支付服务、客户访谈或个人研究可以使用同一套界面,不需要为每种业务另造导航。新增区块时,应先说明它帮助用户完成哪项工作,以及现有数据能否支持它表达的含义。

Expand Down
Loading
Loading